summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortengs@chromium.org <tengs@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-10 19:49:20 +0000
committertengs@chromium.org <tengs@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-10 19:49:20 +0000
commit55398d2c62c998c1665451cd1046d41f1238b0b2 (patch)
tree9b48bc0b9e6643aebfb8739912fcef632482e0ca
parentd70f885988158ff8ff5614943fab8a669174027e (diff)
downloadchromium_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.gyp4
-rw-r--r--ash/autoclick/autoclick_controller.cc157
-rw-r--r--ash/autoclick/autoclick_controller.h38
-rw-r--r--ash/autoclick/autoclick_unittest.cc241
-rw-r--r--ash/shell.cc3
-rw-r--r--ash/shell.h7
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_;