Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(256)

Unified Diff: ui/views/test/widget_event_generator_mac.mm

Issue 322893005: MacViews: Add WidgetEventGenerator to abstract platform-specific event generation for tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: self review + related fix for r283070 Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/views/test/widget_event_generator_mac.mm
diff --git a/ui/views/test/widget_event_generator_mac.mm b/ui/views/test/widget_event_generator_mac.mm
new file mode 100644
index 0000000000000000000000000000000000000000..eadc2e8e06e94eab9e8f54cf532e492f7aee07e8
--- /dev/null
+++ b/ui/views/test/widget_event_generator_mac.mm
@@ -0,0 +1,344 @@
+// Copyright 2014 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 "ui/views/test/widget_event_generator.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "ui/events/event_processor.h"
+#include "ui/events/event_target_iterator.h"
+#import "ui/events/test/cocoa_test_event_utils.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// Singleton to provide state for swizzled Objective C methods.
+views::test::WidgetEventGeneratorMac* g_active_generator = NULL;
+
+} // namespace
+
+@interface NSEventDonor : NSObject
+@end
+
+@implementation NSEventDonor
+
+// Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the
+// active generator.
++ (NSUInteger)pressedMouseButtons {
+ int flags = g_active_generator->flags();
+ NSUInteger bitmask = 0;
+ if (flags & ui::EF_LEFT_MOUSE_BUTTON)
+ bitmask |= 1;
+ if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
+ bitmask |= 1 << 1;
+ if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
+ bitmask |= 1 << 2;
+ return bitmask;
+}
+
+@end
+
+namespace views {
+namespace test {
+namespace {
+
+NSPoint ConvertRootPointToTarget(NSWindow* target,
+ const gfx::Point& point_in_root) {
+ // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can
+ // reposition the window on screen and make things flaky. Initially, just
+ // assume that the contentRect of |target| is at the top-left corner of the
+ // screen.
+ NSRect content_rect = [target contentRectForFrameRect:[target frame]];
+ return NSMakePoint(point_in_root.x(),
+ NSHeight(content_rect) - point_in_root.y());
+}
+
+// Inverse of ui::EventFlagsFromModifiers().
+NSUInteger EventFlagsToModifiers(int flags) {
+ NSUInteger modifiers = 0;
+ modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0;
+ modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0;
+ modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0;
+ modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0;
+ modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0;
+ // ui::EF_*_MOUSE_BUTTON not handled here.
+ // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped.
+ return modifiers;
+}
+
+// Picks the corresponding mouse event type for the buttons set in |flags|.
+NSEventType PickMouseEventType(int flags,
+ NSEventType left,
+ NSEventType right,
+ NSEventType other) {
+ if (flags & ui::EF_LEFT_MOUSE_BUTTON)
+ return left;
+ if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
+ return right;
+ return other;
+}
+
+// Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set
+// using the inverse of ui::EventFlagsFromNSEventWithModifiers().
+NSEventType EventTypeToNative(ui::EventType ui_event_type,
+ int flags,
+ NSUInteger* modifiers) {
+ if (modifiers)
+ *modifiers = EventFlagsToModifiers(flags);
+ switch (ui_event_type) {
+ case ui::ET_UNKNOWN:
+ return 0;
+ case ui::ET_KEY_PRESSED:
+ return NSKeyDown;
+ case ui::ET_KEY_RELEASED:
+ return NSKeyUp;
+ case ui::ET_MOUSE_PRESSED:
+ return PickMouseEventType(flags,
+ NSLeftMouseDown,
+ NSRightMouseDown,
+ NSOtherMouseDown);
+ case ui::ET_MOUSE_RELEASED:
+ return PickMouseEventType(flags,
+ NSLeftMouseUp,
+ NSRightMouseUp,
+ NSOtherMouseUp);
+ case ui::ET_MOUSE_DRAGGED:
+ return PickMouseEventType(flags,
+ NSLeftMouseDragged,
+ NSRightMouseDragged,
+ NSOtherMouseDragged);
+ case ui::ET_MOUSE_MOVED:
+ return NSMouseMoved;
+ case ui::ET_MOUSEWHEEL:
+ return NSScrollWheel;
+ case ui::ET_MOUSE_ENTERED:
+ return NSMouseEntered;
+ case ui::ET_MOUSE_EXITED:
+ return NSMouseExited;
+ case ui::ET_SCROLL_FLING_START:
+ return NSEventTypeSwipe;
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+// Emulate the dispatching that would be performed by -[NSWindow sendEvent:].
+// sendEvent is a black box which (among other things) will try to peek at the
+// event queue and can block indefinitely.
+void EmulateSendEvent(NSWindow* window, NSEvent* event) {
+ NSResponder* responder = [window firstResponder];
+ switch ([event type]) {
+ case NSKeyDown:
+ [responder keyDown:event];
+ return;
+ case NSKeyUp:
+ [responder keyUp:event];
+ return;
+ }
+
+ // For mouse events, NSWindow will use -[NSView hitTest:] for the initial
+ // mouseDown, and then keep track of the NSView returned. The toolkit-views
+ // RootView does this too. So, for tests, assume tracking will be done there,
+ // and the NSWindow's contentView is wrapping a views::internal::RootView.
+ responder = [window contentView];
+ switch ([event type]) {
+ case NSLeftMouseDown:
+ [responder mouseDown:event];
+ break;
+ case NSRightMouseDown:
+ [responder rightMouseDown:event];
+ break;
+ case NSOtherMouseDown:
+ [responder otherMouseDown:event];
+ break;
+ case NSLeftMouseUp:
+ [responder mouseUp:event];
+ break;
+ case NSRightMouseUp:
+ [responder rightMouseUp:event];
+ break;
+ case NSOtherMouseUp:
+ [responder otherMouseUp:event];
+ break;
+ case NSLeftMouseDragged:
+ [responder mouseDragged:event];
+ break;
+ case NSRightMouseDragged:
+ [responder rightMouseDragged:event];
+ break;
+ case NSOtherMouseDragged:
+ [responder otherMouseDragged:event];
+ break;
+ case NSMouseMoved:
+ // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that
+ // NSTrackingAreas have been appropriately installed on |responder|.
+ [responder mouseMoved:event];
+ break;
+ case NSScrollWheel:
+ [responder scrollWheel:event];
+ break;
+ case NSMouseEntered:
+ case NSMouseExited:
+ // With the assumptions in NSMouseMoved, it doesn't make sense for the
+ // generator to handle entered/exited separately. It's the responsibility
+ // of views::internal::RootView to convert the moved events into entered
+ // and exited events for the individual views.
+ NOTREACHED();
+ break;
+ case NSEventTypeSwipe:
+ // NSEventTypeSwipe events can't be generated using public interfaces on
+ // NSEvent, so this will need to be handled at a higher level.
+ NOTREACHED();
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void DispatchMouseEventInWindow(NSWindow* window,
+ ui::EventType event_type,
+ const gfx::Point& point_in_root,
+ int flags) {
+ NSUInteger click_count = 0;
+ if (event_type == ui::ET_MOUSE_PRESSED ||
+ event_type == ui::ET_MOUSE_RELEASED) {
+ if (flags & ui::EF_IS_TRIPLE_CLICK)
+ click_count = 3;
+ else if (flags & ui::EF_IS_DOUBLE_CLICK)
+ click_count = 2;
+ else
+ click_count = 1;
+ }
+ NSPoint point = ConvertRootPointToTarget(window, point_in_root);
+ NSUInteger modifiers = 0;
+ NSEventType type = EventTypeToNative(event_type, flags, &modifiers);
+ NSEvent* event = [NSEvent mouseEventWithType:type
+ location:point
+ modifierFlags:modifiers
+ timestamp:0
+ windowNumber:[window windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:click_count
+ pressure:1.0];
+
+ // Typically events go through NSApplication. For tests, dispatch the event
+ // directly to make things more predicatble.
+ EmulateSendEvent(window, event);
+}
+
+// Implementation of ui::test::EventGeneratorDelegate for Mac. Everything
+// defined inline is just a stub. Interesting overrides are defined below the
+// class.
+class EventGeneratorDelegateMac : public ui::EventTarget,
+ public ui::EventSource,
+ public ui::EventProcessor,
+ public ui::EventTargeter,
+ public ui::test::EventGeneratorDelegate {
+ public:
+ EventGeneratorDelegateMac(NSWindow* window);
+ virtual ~EventGeneratorDelegateMac() {}
+
+ // Overridden from ui::EventTarget:
+ virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE { return true; }
+ virtual ui::EventTarget* GetParentTarget() OVERRIDE { return NULL; }
+ virtual scoped_ptr<ui::EventTargetIterator> GetChildIterator() const OVERRIDE;
+ virtual ui::EventTargeter* GetEventTargeter() OVERRIDE {
+ return this;
+ }
+
+ // Overridden from ui::EventHandler (via ui::EventTarget):
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+
+ // Overridden from ui::EventSource:
+ virtual ui::EventProcessor* GetEventProcessor() OVERRIDE { return this; }
+
+ // Overridden from ui::EventProcessor:
+ virtual ui::EventTarget* GetRootTarget() OVERRIDE { return this; }
+
+ // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor):
+ virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE {
+ return true;
+ }
+
+ // Overridden from ui::test::EventGeneratorDelegate:
+ virtual ui::EventTarget* GetTargetAt(const gfx::Point& location) OVERRIDE {
+ return this;
+ }
+ virtual ui::EventSource* GetEventSource(ui::EventTarget* target) OVERRIDE {
+ return this;
+ }
+ virtual gfx::Point CenterOfTarget(
+ const ui::EventTarget* target) const OVERRIDE {
+ return gfx::Point();
+ }
+ virtual void ConvertPointFromTarget(const ui::EventTarget* target,
+ gfx::Point* point) const OVERRIDE {}
+ virtual void ConvertPointToTarget(const ui::EventTarget* target,
+ gfx::Point* point) const OVERRIDE {}
+ virtual void ConvertPointFromHost(const ui::EventTarget* hosted_target,
+ gfx::Point* point) const OVERRIDE {}
+
+ private:
+ NSWindow* window_;
+ ScopedClassSwizzler swizzle_pressed_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventGeneratorDelegateMac);
+};
+
+EventGeneratorDelegateMac::EventGeneratorDelegateMac(NSWindow* window)
+ : window_(window),
+ swizzle_pressed_([NSEvent class],
+ [NSEventDonor class],
+ @selector(pressedMouseButtons)) {
+}
+
+scoped_ptr<ui::EventTargetIterator>
+EventGeneratorDelegateMac::GetChildIterator() const {
+ // Return NULL to dispatch all events to the result of GetRootTarget().
+ return scoped_ptr<ui::EventTargetIterator>();
+}
+
+void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) {
+ DispatchMouseEventInWindow(window_,
+ event->type(),
+ event->location(),
+ event->changed_button_flags());
+}
+
+} // namespace
+
+WidgetEventGenerator::WidgetEventGenerator(Widget* widget)
+ : WidgetEventGeneratorMac(widget->GetNativeWindow()) {
+}
+
+WidgetEventGenerator::WidgetEventGenerator(gfx::NativeView context)
+ : WidgetEventGeneratorMac([context window]) {
+}
+
+WidgetEventGenerator::WidgetEventGenerator(
+ gfx::NativeView context,
+ Widget* window_for_initial_location)
+ : WidgetEventGeneratorMac([context window]) {
+ set_current_location(
+ window_for_initial_location->GetRestoredBounds().CenterPoint());
+}
+
+WidgetEventGenerator::~WidgetEventGenerator() {
+}
+
+WidgetEventGeneratorMac::WidgetEventGeneratorMac(gfx::NativeWindow ns_window)
+ : EventGenerator(new EventGeneratorDelegateMac(ns_window), gfx::Point()) {
+ DCHECK(!g_active_generator);
+ g_active_generator = this;
+}
+
+WidgetEventGeneratorMac::~WidgetEventGeneratorMac() {
+ DCHECK_EQ(this, g_active_generator);
+ g_active_generator = NULL;
+}
+
+} // namespace views
+} // namespace test

Powered by Google App Engine
This is Rietveld 408576698