diff options
author | tengs@chromium.org <tengs@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-10 19:49:20 +0000 |
---|---|---|
committer | tengs@chromium.org <tengs@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-10 19:49:20 +0000 |
commit | 55398d2c62c998c1665451cd1046d41f1238b0b2 (patch) | |
tree | 9b48bc0b9e6643aebfb8739912fcef632482e0ca | |
parent | d70f885988158ff8ff5614943fab8a669174027e (diff) | |
download | chromium_src-55398d2c62c998c1665451cd1046d41f1238b0b2.zip chromium_src-55398d2c62c998c1665451cd1046d41f1238b0b2.tar.gz chromium_src-55398d2c62c998c1665451cd1046d41f1238b0b2.tar.bz2 |
Add autoclick accessibility feature to ash.
BUG=272401
TEST=added unit tests
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=226895
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=227760
Review URL: https://codereview.chromium.org/24191002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@227976 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ash/ash.gyp | 4 | ||||
-rw-r--r-- | ash/autoclick/autoclick_controller.cc | 157 | ||||
-rw-r--r-- | ash/autoclick/autoclick_controller.h | 38 | ||||
-rw-r--r-- | ash/autoclick/autoclick_unittest.cc | 241 | ||||
-rw-r--r-- | ash/shell.cc | 3 | ||||
-rw-r--r-- | ash/shell.h | 7 |
6 files changed, 450 insertions, 0 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index 22b49ca..2049e99 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -67,6 +67,8 @@ 'accelerators/focus_manager_factory.h', 'accelerators/nested_dispatcher_controller.cc', 'accelerators/nested_dispatcher_controller.h', + 'autoclick/autoclick_controller.cc', + 'autoclick/autoclick_controller.h', 'ash_constants.cc', 'ash_constants.h', 'ash_switches.cc', @@ -707,6 +709,7 @@ 'accelerators/accelerator_filter_unittest.cc', 'accelerators/accelerator_table_unittest.cc', 'accelerators/nested_dispatcher_controller_unittest.cc', + 'autoclick/autoclick_unittest.cc', 'desktop_background/desktop_background_controller_unittest.cc', 'desktop_background/wallpaper_resizer_unittest.cc', 'dip_unittest.cc', @@ -822,6 +825,7 @@ ['exclude', 'magnifier/magnification_controller_unittest.cc'], ['exclude', 'wm/workspace/workspace_window_resizer_unittest.cc'], ['exclude', 'wm/sticky_keys_unittest.cc'], + ['exclude', 'autoclick/autoclick_unittest.cc'], ], 'sources': [ '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources/ui_unscaled_resources.rc', diff --git a/ash/autoclick/autoclick_controller.cc b/ash/autoclick/autoclick_controller.cc new file mode 100644 index 0000000..c2804a7 --- /dev/null +++ b/ash/autoclick/autoclick_controller.cc @@ -0,0 +1,157 @@ +// 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/autoclick/autoclick_controller.h" + +#include "ash/shell.h" +#include "ash/wm/coordinate_conversion.h" +#include "base/timer/timer.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/event_handler.h" +#include "ui/gfx/point.h" + +namespace ash { + +namespace { + +// The default wait time between last mouse movement and sending the autoclick. +int kDefaultClickWaitTimeMs = 500; + +} // namespace + +class AutoclickControllerImpl : public AutoclickController, + public ui::EventHandler { + public: + AutoclickControllerImpl(); + virtual ~AutoclickControllerImpl(); + + private: + // AutoclickController overrides: + virtual void SetEnabled(bool enabled) OVERRIDE; + virtual bool IsEnabled() const OVERRIDE; + virtual void SetClickWaitTime(int wait_time_ms) OVERRIDE; + virtual int GetClickWaitTime() const OVERRIDE; + + // ui::EventHandler overrides: + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + + void InitClickTimer(); + + void DoAutoclick(); + + bool enabled_; + int wait_time_ms_; + int mouse_event_flags_; + scoped_ptr<base::Timer> autoclick_timer_; + + DISALLOW_COPY_AND_ASSIGN(AutoclickControllerImpl); +}; + + +AutoclickControllerImpl::AutoclickControllerImpl() + : enabled_(false), + wait_time_ms_(kDefaultClickWaitTimeMs), + mouse_event_flags_(ui::EF_NONE) { + InitClickTimer(); +} + +AutoclickControllerImpl::~AutoclickControllerImpl() { +} + +void AutoclickControllerImpl::SetEnabled(bool enabled) { + if (enabled_ == enabled) + return; + enabled_ = enabled; + + if (enabled_) { + Shell::GetInstance()->AddPreTargetHandler(this); + autoclick_timer_->Stop(); + } else { + Shell::GetInstance()->RemovePreTargetHandler(this); + } +} + +bool AutoclickControllerImpl::IsEnabled() const { + return enabled_; +} + +void AutoclickControllerImpl::SetClickWaitTime(int wait_time_ms) { + wait_time_ms_ = wait_time_ms; + InitClickTimer(); +} + +int AutoclickControllerImpl::GetClickWaitTime() const { + return wait_time_ms_; +} + +void AutoclickControllerImpl::InitClickTimer() { + autoclick_timer_.reset(new base::Timer( + FROM_HERE, + base::TimeDelta::FromMilliseconds(wait_time_ms_), + base::Bind(&AutoclickControllerImpl::DoAutoclick, + base::Unretained(this)), + false)); +} + +void AutoclickControllerImpl::OnMouseEvent(ui::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_MOVED) { + mouse_event_flags_ = event->flags(); + autoclick_timer_->Reset(); + } else if (event->type() == ui::ET_MOUSE_PRESSED) { + autoclick_timer_->Stop(); + } else if (event->type() == ui::ET_MOUSEWHEEL && + autoclick_timer_->IsRunning()) { + autoclick_timer_->Reset(); + } +} + +void AutoclickControllerImpl::OnKeyEvent(ui::KeyEvent* event) { + int modifier_mask = + ui::EF_SHIFT_DOWN | + ui::EF_CONTROL_DOWN | + ui::EF_ALT_DOWN | + ui::EF_COMMAND_DOWN | + ui::EF_EXTENDED; + int new_modifiers = event->flags() & modifier_mask; + mouse_event_flags_ = (mouse_event_flags_ & ~modifier_mask) | new_modifiers; +} + +void AutoclickControllerImpl::OnTouchEvent(ui::TouchEvent* event) { + autoclick_timer_->Stop(); +} + +void AutoclickControllerImpl::DoAutoclick() { + gfx::Point screen_location = + aura::Env::GetInstance()->last_mouse_location(); + aura::RootWindow* root_window = wm::GetRootWindowAt(screen_location); + DCHECK(root_window) << "Root window not found while attempting autoclick."; + + gfx::Point click_location(screen_location); + wm::ConvertPointFromScreen(root_window, &click_location); + root_window->ConvertPointToHost(&click_location); + + ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, + click_location, + click_location, + mouse_event_flags_ | ui::EF_LEFT_MOUSE_BUTTON); + ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, + click_location, + click_location, + mouse_event_flags_ | ui::EF_LEFT_MOUSE_BUTTON); + + root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&press_event); + root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&release_event); +} + +// static. +AutoclickController* AutoclickController::CreateInstance() { + return new AutoclickControllerImpl(); +} + +} // namespace ash diff --git a/ash/autoclick/autoclick_controller.h b/ash/autoclick/autoclick_controller.h new file mode 100644 index 0000000..481660f --- /dev/null +++ b/ash/autoclick/autoclick_controller.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef ASH_AUTOCLICK_AUTOCLICK_CONTROLLER_H +#define ASH_AUTOCLICK_AUTOCLICK_CONTROLLER_H + +namespace ash { + +// Controls the autoclick a11y feature in ash. +// If enabled, we will automatically send a click event a short time after +// the mouse had been at rest. +class AutoclickController { + public: + virtual ~AutoclickController() {} + + // Set whether autoclicking is enabled. + virtual void SetEnabled(bool enabled) = 0; + + // Returns true if autoclicking is enabled. + virtual bool IsEnabled() const = 0; + + // Set the time to wait from when the mouse stops moving to when + // the autoclick event is sent. + virtual void SetClickWaitTime(int wait_time_ms) = 0; + + // Returns the wait time in milliseconds. + virtual int GetClickWaitTime() const = 0; + + static AutoclickController* CreateInstance(); + + protected: + AutoclickController() {} +}; + +} // namespace ash + +#endif // ASH_AUTOCLICK_AUTOCLICK_CONTROLLER_H diff --git a/ash/autoclick/autoclick_unittest.cc b/ash/autoclick/autoclick_unittest.cc new file mode 100644 index 0000000..dd462f4 --- /dev/null +++ b/ash/autoclick/autoclick_unittest.cc @@ -0,0 +1,241 @@ +// 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/autoclick/autoclick_controller.h" +#include "ash/shell.h" +#include "ash/test/ash_test_base.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/window.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/event_handler.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace ash { + +class MouseEventCapturer : public ui::EventHandler { + public: + MouseEventCapturer() { Reset(); } + + void Reset() { + events_.clear(); + } + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + if (!(event->flags() & ui::EF_LEFT_MOUSE_BUTTON)) + return; + // Filter out extraneous mouse events like mouse entered, exited, + // capture changed, etc. + ui::EventType type = event->type(); + if (type == ui::ET_MOUSE_MOVED || type == ui::ET_MOUSE_PRESSED || + type == ui::ET_MOUSE_RELEASED) { + events_.push_back(ui::MouseEvent( + event->type(), + event->location(), + event->root_location(), + event->flags())); + // Stop event propagation so we don't click on random stuff that + // might break test assumptions. + 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_; + } + + private: + std::vector<ui::MouseEvent> events_; + + DISALLOW_COPY_AND_ASSIGN(MouseEventCapturer); +}; + +class AutoclickTest : public test::AshTestBase { + public: + AutoclickTest() {} + virtual ~AutoclickTest() {} + + virtual void SetUp() OVERRIDE { + test::AshTestBase::SetUp(); + Shell::GetInstance()->AddPreTargetHandler(&mouse_event_capturer_); + GetAutoclickController()->SetClickWaitTime(0); + + // Move mouse to deterministic location at the start of each test. + GetEventGenerator().MoveMouseTo(10, 10); + } + + virtual void TearDown() OVERRIDE { + Shell::GetInstance()->RemovePreTargetHandler(&mouse_event_capturer_); + test::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() { + mouse_event_capturer_.Reset(); + RunAllPendingInMessageLoop(); + return mouse_event_capturer_.captured_events(); + } + + AutoclickController* GetAutoclickController() { + return Shell::GetInstance()->autoclick_controller(); + } + + 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); + GetEventGenerator().MoveMouseTo(0, 0); + EXPECT_TRUE(GetAutoclickController()->IsEnabled()); + events = WaitForMouseEvents(); + EXPECT_EQ(2u, events.size()); + EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); + EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); + + // We should not get any more clicks until we move the mouse. + events = WaitForMouseEvents(); + EXPECT_EQ(0u, events.size()); + GetEventGenerator().MoveMouseTo(0, 1); + events = WaitForMouseEvents(); + EXPECT_EQ(2u, events.size()); + EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); + EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); + + // Disable autoclick, and we should see the original behaviour. + GetAutoclickController()->SetEnabled(false); + EXPECT_FALSE(GetAutoclickController()->IsEnabled()); + events = WaitForMouseEvents(); + EXPECT_EQ(0u, events.size()); +} + +#if defined(OS_WIN) +// On Windows, we are getting unexpected mouse drag events that +// are breaking this test. See http://crbug.com/303830. +#define MAYBE_MouseMovement \ + DISABLED_MouseMovement +#else +#define MAYBE_MouseMovement \ + MouseMovement +#endif +TEST_F(AutoclickTest, MAYBE_MouseMovement) { + std::vector<ui::MouseEvent> events; + GetAutoclickController()->SetEnabled(true); + + gfx::Point p1(1, 1); + gfx::Point p2(2, 2); + gfx::Point p3(3, 3); + + // 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, SingleKeyModifier) { + GetAutoclickController()->SetEnabled(true); + 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); + 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); + + 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); +} + +#if defined(OS_WIN) +// Multiple displays are not supported on Windows Ash. http://crbug.com/165962 +#define MAYBE_ExtendedDisplay \ + DISABLED_ExtendedDisplay +#else +#define MAYBE_ExtendedDisplay \ + ExtendedDisplay +#endif +TEST_F(AutoclickTest, MAYBE_ExtendedDisplay) { + UpdateDisplay("1280x1024,800x600"); + RunAllPendingInMessageLoop(); + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + EXPECT_EQ(2u, root_windows.size()); + + GetAutoclickController()->SetEnabled(true); + std::vector<ui::MouseEvent> events; + + // Test first root window. + aura::test::EventGenerator generator1(root_windows[0]); + generator1.MoveMouseTo(100, 200); + events = WaitForMouseEvents(); + EXPECT_EQ(2u, events.size()); + EXPECT_EQ(100, events[0].root_location().x()); + EXPECT_EQ(200, events[0].root_location().y()); + + // Test second root window. + aura::test::EventGenerator generator2(root_windows[1]); + generator2.MoveMouseTo(300, 400); + events = WaitForMouseEvents(); + EXPECT_EQ(2u, events.size()); + EXPECT_EQ(300, events[0].root_location().x()); + EXPECT_EQ(400, events[0].root_location().y()); +} + +} // namespace ash diff --git a/ash/shell.cc b/ash/shell.cc index 92a81d5..6c14d66 100644 --- a/ash/shell.cc +++ b/ash/shell.cc @@ -12,6 +12,7 @@ #include "ash/accelerators/focus_manager_factory.h" #include "ash/accelerators/nested_dispatcher_controller.h" #include "ash/ash_switches.h" +#include "ash/autoclick/autoclick_controller.h" #include "ash/caps_lock_delegate.h" #include "ash/desktop_background/desktop_background_controller.h" #include "ash/desktop_background/desktop_background_view.h" @@ -810,6 +811,8 @@ void Shell::Init() { partial_magnification_controller_.reset( new PartialMagnificationController()); + autoclick_controller_.reset(AutoclickController::CreateInstance()); + high_contrast_controller_.reset(new HighContrastController); video_detector_.reset(new VideoDetector); window_cycle_controller_.reset(new WindowCycleController()); diff --git a/ash/shell.h b/ash/shell.h index a72826c..d665add 100644 --- a/ash/shell.h +++ b/ash/shell.h @@ -74,6 +74,7 @@ namespace ash { class AcceleratorController; class AshNativeCursorManager; +class AutoclickController; class CapsLockDelegate; class DesktopBackgroundController; class DisplayController; @@ -369,6 +370,11 @@ class ASH_EXPORT Shell PartialMagnificationController* partial_magnification_controller() { return partial_magnification_controller_.get(); } + + AutoclickController* autoclick_controller() { + return autoclick_controller_.get(); + } + aura::client::ActivationClient* activation_client() { return activation_client_; } @@ -590,6 +596,7 @@ class ASH_EXPORT Shell scoped_ptr<HighContrastController> high_contrast_controller_; scoped_ptr<MagnificationController> magnification_controller_; scoped_ptr<PartialMagnificationController> partial_magnification_controller_; + scoped_ptr<AutoclickController> autoclick_controller_; scoped_ptr<aura::client::FocusClient> focus_client_; scoped_ptr<aura::client::UserActionClient> user_action_client_; aura::client::ActivationClient* activation_client_; |