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

Unified Diff: ui/base/test/ui_controls_mac.mm

Issue 1747803003: MacViews: Implement Tab Dragging (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Removed debug logging, use CGEvents for drag-n-drop. Created 4 years, 10 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/base/test/ui_controls_mac.mm
diff --git a/ui/base/test/ui_controls_mac.mm b/ui/base/test/ui_controls_mac.mm
index 15cdb7963b019d11df0e749c13239fb6a40faafe..c9a47faad8d40141743f2a05148b322e341c5b19 100644
--- a/ui/base/test/ui_controls_mac.mm
+++ b/ui/base/test/ui_controls_mac.mm
@@ -9,9 +9,18 @@
#include "base/bind.h"
#include "base/callback.h"
+#include "base/debug/stack_trace.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/mac/scoped_nsobject.h"
tapted 2016/03/04 06:33:08 the objc headers should be #imported
themblsha 2016/03/09 17:40:22 Done.
+#include "base/mac/scoped_objc_class_swizzler.h"
#include "base/message_loop/message_loop.h"
+#include "base/thread_task_runner_handle.h"
+#include "content/public/browser/browser_thread.h"
#include "ui/events/keycodes/keyboard_code_conversion_mac.h"
#import "ui/events/test/cocoa_test_event_utils.h"
+#include "ui/gfx/mac/coordinate_conversion.h"
+#include "ui/gfx/screen.h"
// Implementation details: We use [NSApplication sendEvent:] instead
// of [NSApplication postEvent:atStart:] so that the event gets sent
@@ -47,9 +56,14 @@ using cocoa_test_event_utils::TimeIntervalSinceSystemStartup;
namespace {
+// When enabled, all simulated mouse events will be posted to
+// the WindowServer, and the actual mouse will move on the screen.
+bool g_use_cgevents = false;
+
// Stores the current mouse location on the screen. So that we can use it
// when firing keyboard and mouse click events.
NSPoint g_mouse_location = { 0, 0 };
+bool g_mouse_button_down[3] = { false, false, false };
bool g_ui_controls_enabled = false;
@@ -121,6 +135,12 @@ void SynthesizeKeyEventsSequence(NSWindow* window,
}
}
+void RunTaskInTaskRunner(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ const base::Closure& task) {
+ task_runner->PostTask(FROM_HERE, task);
+}
+
// A helper function to watch for the event queue. The specific task will be
// fired when there is no more event in the queue.
void EventQueueWatcher(const base::Closure& task) {
@@ -172,6 +192,177 @@ NSWindow* WindowAtCurrentMouseLocation() {
} // namespace
+// Since CGEvents take some time to reach -[NSApplication sendEvent:] we use
+// this class to wait for the events to start being processed before sending
+// finish notifications.
+class EventMonitor {
+ public:
+ void NotifyWhenEventIsProcessed(NSEvent* event, const base::Closure& task) {
+ dispatch_sync(queue_, ^{
+ tasks_.emplace_back(Task(event, task));
+ });
+ }
+
+ void ProcessingEvent(NSEvent* event) {
+ dispatch_sync(queue_, ^{
+ auto it = std::find_if(
+ tasks_.begin(), tasks_.end(),
+ [&event](const Task& task) { return task.MatchesEvent(event); });
+ if (it != tasks_.end()) {
+ it->Run();
+ tasks_.erase(it);
+ }
+ });
+ }
+
+ static EventMonitor* Instance() {
+ static EventMonitor* monitor = nullptr;
+ if (!monitor) {
+ monitor = new EventMonitor();
+ }
+ return monitor;
+ }
+
+ private:
+ class Task {
+ public:
+ Task(NSEvent* event, const base::Closure& task)
+ : task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ event_([event retain]),
+ finish_closure_(task) {}
+
+ bool MatchesEvent(NSEvent* event) const {
+ // When moving the window using BridgedNativeWidget::RunMoveLoop the
+ // locationInWindow can be a little inconsistent with what we expect.
+ return [event_ type] == [event type] /*&&
+ CGPointEqualToPoint([event_ locationInWindow],
+ [event locationInWindow]) */;
+ }
+
+ void Run() {
+ // We get here before the event is actually processed. Run the
+ // EventQueueWatcher on the main thread in order to wait for all events to
+ // finish processing.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &EventQueueWatcher,
+ base::Bind(&RunTaskInTaskRunner, task_runner_, finish_closure_)));
+ }
+
+ private:
+ // Events could be spawned on background threads. Be sure to invoke the
+ // |finish_closure_| on an appropriate thread.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ base::scoped_nsobject<NSEvent> event_;
+ base::Closure finish_closure_;
+ };
+
+ EventMonitor()
+ : queue_(dispatch_queue_create("ui_controls_mac.EventMonitor",
+ DISPATCH_QUEUE_SERIAL)),
+ send_event_swizzler_(
+ new base::mac::ScopedObjCClassSwizzler([NSApplication class],
+ @selector(sendEvent:),
+ @selector(cr_sendEvent:))) {
+ }
+
+ std::vector<Task> tasks_;
+
+ // synchronizes access to the |tasks_| in case we spawn the events on a
+ // different thread
+ dispatch_queue_t queue_ = 0;
+
+ scoped_ptr<base::mac::ScopedObjCClassSwizzler>
+ send_event_swizzler_;
+};
+
+// Donates testing implementations of NSApplication methods. We can't simply
+// use -[NSEvent addLocalMonitorForEventsMatchingMask:handler:], as other event
+// monitors could have precedence, and they could filter the events before we
+// can see them. But since nobody swizzles -[NSApplication sendEvent:] we should
+// be safe.
+@interface NSApplication(TestingDonor)
+@end
+
+@implementation NSApplication(TestingDonor)
+- (void)cr_sendEvent:(NSEvent*)event {
+ // Invoke the finish handler before the event is processed, since we can get
+ // stuck in BridgedNativeWidget::RunMoveLoop and would never see the event
+ // otherwise.
+ EventMonitor::Instance()->ProcessingEvent(event);
+
+ [self cr_sendEvent:event];
+}
+@end
+
+// Donates testing implementations of NSEvent methods.
+@interface FakeNSEventTestingDonor : NSObject
+@end
+
+@implementation FakeNSEventTestingDonor
++ (NSPoint)mouseLocation {
+ return g_mouse_location;
+}
+
++ (NSUInteger)pressedMouseButtons {
+ NSUInteger result = 0;
+ const int buttons[3] = {
+ ui_controls::LEFT, ui_controls::RIGHT, ui_controls::MIDDLE};
+ for (unsigned int i = 0; i < arraysize(buttons); ++i) {
+ if (g_mouse_button_down[buttons[i]])
+ result |= (1 << i);
+ }
+ return result;
+}
+@end
+
+// Donates testing implementations of NSWindow methods.
+@interface FakeNSWindowTestingDonor : NSObject
+@end
+
+@implementation FakeNSWindowTestingDonor
+- (NSPoint)mouseLocationOutsideOfEventStream {
+ NSWindow* window = base::mac::ObjCCastStrict<NSWindow>(self);
+ return [window convertScreenToBase:g_mouse_location];
+}
+@end
+
+namespace {
+class NSEventSwizzler {
+ public:
+ static void Install() {
+ static NSEventSwizzler* swizzler = nullptr;
+ if (!swizzler) {
+ swizzler = new NSEventSwizzler();
+ }
+ }
+
+ protected:
+ NSEventSwizzler()
+ : mouse_location_swizzler_(new base::mac::ScopedObjCClassSwizzler(
+ [NSEvent class],
+ [FakeNSEventTestingDonor class],
+ @selector(mouseLocation))),
+ pressed_mouse_buttons_swizzler_(new base::mac::ScopedObjCClassSwizzler(
+ [NSEvent class],
+ [FakeNSEventTestingDonor class],
+ @selector(pressedMouseButtons))),
+ mouse_location_outside_of_event_stream_swizzler_(
+ new base::mac::ScopedObjCClassSwizzler(
+ [NSWindow class],
+ [FakeNSWindowTestingDonor class],
+ @selector(mouseLocationOutsideOfEventStream))) {}
+
+ private:
+ scoped_ptr<base::mac::ScopedObjCClassSwizzler> mouse_location_swizzler_;
+ scoped_ptr<base::mac::ScopedObjCClassSwizzler>
+ pressed_mouse_buttons_swizzler_;
+ scoped_ptr<base::mac::ScopedObjCClassSwizzler>
+ mouse_location_outside_of_event_stream_swizzler_;
+};
+} // namespace
+
namespace ui_controls {
void EnableUIControls() {
@@ -202,6 +393,9 @@ bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
CHECK(g_ui_controls_enabled);
DCHECK(base::MessageLoopForUI::IsCurrent());
+ // we want to destroy the autoreleased event ASAP
+ base::mac::ScopedNSAutoreleasePool pool;
+
std::vector<NSEvent*> events;
SynthesizeKeyEventsSequence(
window, key, control, shift, alt, command, &events);
@@ -234,9 +428,8 @@ bool SendMouseMove(long x, long y) {
// platforms. E.g. (0,0) is upper-left.
bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) {
CHECK(g_ui_controls_enabled);
- CGFloat screenHeight =
- [[[NSScreen screens] firstObject] frame].size.height;
- g_mouse_location = NSMakePoint(x, screenHeight - y); // flip!
+ g_mouse_location = gfx::ScreenPointToNSPoint(gfx::Point(x, y)); // flip!
+ NSEventSwizzler::Install();
NSWindow* window = WindowAtCurrentMouseLocation();
@@ -245,21 +438,40 @@ bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) {
pointInWindow = [window convertScreenToBase:pointInWindow];
NSTimeInterval timestamp = TimeIntervalSinceSystemStartup();
+ NSEventType etype = NSMouseMoved;
+ if (g_mouse_button_down[LEFT]) {
+ etype = NSLeftMouseDragged;
+ } else if (g_mouse_button_down[RIGHT]) {
+ etype = NSRightMouseDragged;
+ } else if (g_mouse_button_down[MIDDLE]) {
+ etype = NSOtherMouseDragged;
+ }
+
+ // we want to destroy the autoreleased event ASAP
+ base::mac::ScopedNSAutoreleasePool pool;
+
NSEvent* event =
- [NSEvent mouseEventWithType:NSMouseMoved
+ [NSEvent mouseEventWithType:etype
location:pointInWindow
modifierFlags:0
timestamp:timestamp
windowNumber:[window windowNumber]
context:nil
eventNumber:0
- clickCount:0
- pressure:0.0];
- [[NSApplication sharedApplication] postEvent:event atStart:NO];
+ clickCount:(etype == NSMouseMoved ? 0 : 1)
+ pressure:(etype == NSMouseMoved ? 0.0 : 1.0)];
if (!task.is_null()) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE, base::Bind(&EventQueueWatcher, task));
+ EventMonitor::Instance()->NotifyWhenEventIsProcessed(event, task);
+ }
+
+ gfx::Point gp = gfx::ScreenPointFromNSPoint(g_mouse_location);
+ CGWarpMouseCursorPosition(CGPointMake(gp.x(), gp.y()));
+
+ if (g_use_cgevents) {
+ CGEventPost(kCGSessionEventTap, [event CGEvent]);
+ } else {
+ [[NSApplication sharedApplication] postEvent:event atStart:NO];
}
return true;
@@ -283,20 +495,26 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state,
if (type == LEFT) {
if (state == UP) {
etype = NSLeftMouseUp;
+ g_mouse_button_down[LEFT] = false;
} else {
etype = NSLeftMouseDown;
+ g_mouse_button_down[LEFT] = true;
}
} else if (type == MIDDLE) {
if (state == UP) {
etype = NSOtherMouseUp;
+ g_mouse_button_down[MIDDLE] = false;
} else {
etype = NSOtherMouseDown;
+ g_mouse_button_down[MIDDLE] = true;
}
} else if (type == RIGHT) {
if (state == UP) {
etype = NSRightMouseUp;
+ g_mouse_button_down[RIGHT] = false;
} else {
etype = NSRightMouseDown;
+ g_mouse_button_down[RIGHT] = true;
}
} else {
return false;
@@ -306,6 +524,11 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state,
if (window)
pointInWindow = [window convertScreenToBase:pointInWindow];
+ NSEventSwizzler::Install();
+
+ // we want to destroy the autoreleased event ASAP
+ base::mac::ScopedNSAutoreleasePool pool;
+
NSEvent* event =
[NSEvent mouseEventWithType:etype
location:pointInWindow
@@ -316,11 +539,15 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state,
eventNumber:0
clickCount:1
pressure:(state == DOWN ? 1.0 : 0.0 )];
- [[NSApplication sharedApplication] postEvent:event atStart:NO];
if (!task.is_null()) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE, base::Bind(&EventQueueWatcher, task));
+ EventMonitor::Instance()->NotifyWhenEventIsProcessed(event, task);
+ }
+
+ if (g_use_cgevents) {
+ CGEventPost(kCGSessionEventTap, [event CGEvent]);
+ } else {
+ [[NSApplication sharedApplication] postEvent:event atStart:NO];
}
return true;
@@ -340,4 +567,12 @@ bool IsFullKeyboardAccessEnabled() {
return [NSApp isFullKeyboardAccessEnabled];
}
+void SetSendMouseEventsAsCGEvents(bool enable_cgevents) {
+ g_use_cgevents = enable_cgevents;
+}
+
+bool SendMouseEventsAsCGEvents() {
+ return g_use_cgevents;
+}
+
} // namespace ui_controls

Powered by Google App Engine
This is Rietveld 408576698