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

Side by Side 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: Created 4 years, 9 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 unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/base/test/ui_controls.h" 5 #include "ui/base/test/ui_controls.h"
6 6
7 #import <Cocoa/Cocoa.h> 7 #import <Cocoa/Cocoa.h>
8 #include <vector> 8 #include <vector>
9 9
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/callback.h" 11 #include "base/callback.h"
12 #include "base/mac/scoped_nsautorelease_pool.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/mac/scoped_objc_class_swizzler.h"
12 #include "base/message_loop/message_loop.h" 15 #include "base/message_loop/message_loop.h"
16 #include "base/thread_task_runner_handle.h"
13 #include "ui/events/keycodes/keyboard_code_conversion_mac.h" 17 #include "ui/events/keycodes/keyboard_code_conversion_mac.h"
14 #import "ui/events/test/cocoa_test_event_utils.h" 18 #import "ui/events/test/cocoa_test_event_utils.h"
19 #include "ui/gfx/mac/coordinate_conversion.h"
20 #include "ui/gfx/screen.h"
15 21
16 // Implementation details: We use [NSApplication sendEvent:] instead 22 // Implementation details: We use [NSApplication sendEvent:] instead
17 // of [NSApplication postEvent:atStart:] so that the event gets sent 23 // of [NSApplication postEvent:atStart:] so that the event gets sent
18 // immediately. This lets us run the post-event task right 24 // immediately. This lets us run the post-event task right
19 // immediately as well. Unfortunately I cannot subclass NSEvent (it's 25 // immediately as well. Unfortunately I cannot subclass NSEvent (it's
20 // probably a class cluster) to allow other easy answers. For 26 // probably a class cluster) to allow other easy answers. For
21 // example, if I could subclass NSEvent, I could run the Task in it's 27 // example, if I could subclass NSEvent, I could run the Task in it's
22 // dealloc routine (which necessarily happens after the event is 28 // dealloc routine (which necessarily happens after the event is
23 // dispatched). Unlike Linux, Mac does not have message loop 29 // dispatched). Unlike Linux, Mac does not have message loop
24 // observer/notification. Unlike windows, I cannot post non-events 30 // observer/notification. Unlike windows, I cannot post non-events
(...skipping 18 matching lines...) Expand all
43 // be used, so that we don't need to poll the event queue time to time. 49 // be used, so that we don't need to poll the event queue time to time.
44 50
45 using cocoa_test_event_utils::SynthesizeKeyEvent; 51 using cocoa_test_event_utils::SynthesizeKeyEvent;
46 using cocoa_test_event_utils::TimeIntervalSinceSystemStartup; 52 using cocoa_test_event_utils::TimeIntervalSinceSystemStartup;
47 53
48 namespace { 54 namespace {
49 55
50 // Stores the current mouse location on the screen. So that we can use it 56 // Stores the current mouse location on the screen. So that we can use it
51 // when firing keyboard and mouse click events. 57 // when firing keyboard and mouse click events.
52 NSPoint g_mouse_location = { 0, 0 }; 58 NSPoint g_mouse_location = { 0, 0 };
59 bool g_mouse_button_down[3] = { false, false, false };
tapted 2016/03/01 08:11:58 comment (mention that it's indexed by ui_controls:
themblsha 2016/03/09 17:40:22 Done.
53 60
54 bool g_ui_controls_enabled = false; 61 bool g_ui_controls_enabled = false;
55 62
56 // Creates the proper sequence of autoreleased key events for a key down + up. 63 // Creates the proper sequence of autoreleased key events for a key down + up.
57 void SynthesizeKeyEventsSequence(NSWindow* window, 64 void SynthesizeKeyEventsSequence(NSWindow* window,
58 ui::KeyboardCode keycode, 65 ui::KeyboardCode keycode,
59 bool control, 66 bool control,
60 bool shift, 67 bool shift,
61 bool alt, 68 bool alt,
62 bool command, 69 bool command,
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 return window; 172 return window;
166 } 173 }
167 174
168 // Note that -[NSApplication orderedWindows] won't include NSPanels. If a test 175 // Note that -[NSApplication orderedWindows] won't include NSPanels. If a test
169 // uses those, it will need to handle that itself. 176 // uses those, it will need to handle that itself.
170 return nil; 177 return nil;
171 } 178 }
172 179
173 } // namespace 180 } // namespace
174 181
182 namespace {
183 int kEventDeletedTaskRunnerKey;
tapted 2016/03/01 08:11:58 can be merged with the namespace above. needs a co
themblsha 2016/03/09 17:40:22 Done.
184 } // namespace
185
186 @interface EventDeletedTaskRunner : NSObject {
tapted 2016/03/01 08:11:58 What's the reason why EventQueueWatcher couldn't s
themblsha 2016/03/01 18:41:53 I'm spawning these on a separate thread, as the ma
187 @private
188 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
189 base::Closure finish_closure_;
190 }
191 - (id)initWithClosure:(const base::Closure&)task;
192 @end
193
194 @implementation EventDeletedTaskRunner
195 - (id)initWithClosure:(const base::Closure&)task {
196 if (self = [super init]) {
197 task_runner_ = base::ThreadTaskRunnerHandle::Get();
198 finish_closure_ = task;
199 }
200 return self;
201 }
202
203 - (void)dealloc {
204 task_runner_->PostTask(FROM_HERE, finish_closure_);
205 [super dealloc];
206 }
207 @end
208
209 // Donates testing implementations of NSEvent methods.
210 @interface FakeNSEventTestingDonor : NSObject
211 @end
212
213 @implementation FakeNSEventTestingDonor
214 + (NSPoint)mouseLocation {
215 return g_mouse_location;
216 }
217
218 + (NSUInteger)pressedMouseButtons {
219 NSUInteger result = 0;
220 const int buttons[3] = {
221 ui_controls::LEFT, ui_controls::RIGHT, ui_controls::MIDDLE};
222 for (unsigned int i = 0; i < arraysize(buttons); ++i) {
223 if (g_mouse_button_down[buttons[i]])
224 result |= (1 << i);
225 }
226 return result;
227 }
228 @end
229
230 namespace {
231 class ScopedNSEventSwizzler {
232 public:
233 static void Install() {
234 static ScopedNSEventSwizzler* swizzler = nullptr;
235 if (!swizzler) {
236 swizzler = new ScopedNSEventSwizzler();
237 }
238 }
239
240 protected:
241 ScopedNSEventSwizzler()
242 : mouse_location_swizzler_(new base::mac::ScopedObjCClassSwizzler(
243 [NSEvent class],
244 [FakeNSEventTestingDonor class],
245 @selector(mouseLocation))),
246 pressed_mouse_buttons_swizzler_(new base::mac::ScopedObjCClassSwizzler(
247 [NSEvent class],
248 [FakeNSEventTestingDonor class],
249 @selector(pressedMouseButtons))) {
250 }
251
252 private:
253 scoped_ptr<base::mac::ScopedObjCClassSwizzler> mouse_location_swizzler_;
254 scoped_ptr<base::mac::ScopedObjCClassSwizzler>
255 pressed_mouse_buttons_swizzler_;
256 };
257 } // namespace
258
175 namespace ui_controls { 259 namespace ui_controls {
176 260
177 void EnableUIControls() { 261 void EnableUIControls() {
178 g_ui_controls_enabled = true; 262 g_ui_controls_enabled = true;
179 } 263 }
180 264
181 bool SendKeyPress(gfx::NativeWindow window, 265 bool SendKeyPress(gfx::NativeWindow window,
182 ui::KeyboardCode key, 266 ui::KeyboardCode key,
183 bool control, 267 bool control,
184 bool shift, 268 bool shift,
(...skipping 10 matching lines...) Expand all
195 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, 279 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
196 ui::KeyboardCode key, 280 ui::KeyboardCode key,
197 bool control, 281 bool control,
198 bool shift, 282 bool shift,
199 bool alt, 283 bool alt,
200 bool command, 284 bool command,
201 const base::Closure& task) { 285 const base::Closure& task) {
202 CHECK(g_ui_controls_enabled); 286 CHECK(g_ui_controls_enabled);
203 DCHECK(base::MessageLoopForUI::IsCurrent()); 287 DCHECK(base::MessageLoopForUI::IsCurrent());
204 288
289 // we want to destroy the autoreleased event ASAP
tapted 2016/03/01 08:11:58 Comments should all be complete sentences. Capital
themblsha 2016/03/09 17:40:22 Done.
290 base::mac::ScopedNSAutoreleasePool pool;
291
205 std::vector<NSEvent*> events; 292 std::vector<NSEvent*> events;
206 SynthesizeKeyEventsSequence( 293 SynthesizeKeyEventsSequence(
207 window, key, control, shift, alt, command, &events); 294 window, key, control, shift, alt, command, &events);
208 295
296 if (!task.is_null()) {
297 base::scoped_nsobject<EventDeletedTaskRunner> task_runner =
298 [[EventDeletedTaskRunner alloc] initWithClosure:task];
299
300 // event will be deleted when the last event is deleted
301 for (std::vector<NSEvent*>::iterator iter = events.begin();
tapted 2016/03/01 08:11:58 for (NSEvent* event : events)
302 iter != events.end(); ++iter)
303 objc_setAssociatedObject(*iter, &kEventDeletedTaskRunnerKey,
304 task_runner.get(), OBJC_ASSOCIATION_RETAIN);
305 }
306
209 // TODO(suzhe): Using [NSApplication postEvent:atStart:] here causes 307 // TODO(suzhe): Using [NSApplication postEvent:atStart:] here causes
210 // BrowserKeyEventsTest.CommandKeyEvents to fail. See http://crbug.com/49270 308 // BrowserKeyEventsTest.CommandKeyEvents to fail. See http://crbug.com/49270
211 // But using [NSApplication sendEvent:] should be safe for keyboard events, 309 // But using [NSApplication sendEvent:] should be safe for keyboard events,
212 // because until now, no code wants to retrieve the next event when handling 310 // because until now, no code wants to retrieve the next event when handling
213 // a keyboard event. 311 // a keyboard event.
214 for (std::vector<NSEvent*>::iterator iter = events.begin(); 312 for (std::vector<NSEvent*>::iterator iter = events.begin();
215 iter != events.end(); ++iter) 313 iter != events.end(); ++iter)
216 [[NSApplication sharedApplication] sendEvent:*iter]; 314 [[NSApplication sharedApplication] sendEvent:*iter];
217 315
218 if (!task.is_null()) {
219 base::MessageLoop::current()->PostTask(
220 FROM_HERE, base::Bind(&EventQueueWatcher, task));
221 }
222
223 return true; 316 return true;
224 } 317 }
225 318
226 bool SendMouseMove(long x, long y) { 319 bool SendMouseMove(long x, long y) {
227 CHECK(g_ui_controls_enabled); 320 CHECK(g_ui_controls_enabled);
228 return SendMouseMoveNotifyWhenDone(x, y, base::Closure()); 321 return SendMouseMoveNotifyWhenDone(x, y, base::Closure());
229 } 322 }
230 323
231 // Input position is in screen coordinates. However, NSMouseMoved 324 // Input position is in screen coordinates. However, NSMouseMoved
232 // events require them window-relative, so we adjust. We *DO* flip 325 // events require them window-relative, so we adjust. We *DO* flip
233 // the coordinate space, so input events can be the same for all 326 // the coordinate space, so input events can be the same for all
234 // platforms. E.g. (0,0) is upper-left. 327 // platforms. E.g. (0,0) is upper-left.
235 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { 328 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) {
236 CHECK(g_ui_controls_enabled); 329 CHECK(g_ui_controls_enabled);
237 CGFloat screenHeight = 330 g_mouse_location = gfx::ScreenPointToNSPoint(gfx::Point(x, y)); // flip!
238 [[[NSScreen screens] firstObject] frame].size.height; 331 ScopedNSEventSwizzler::Install();
239 g_mouse_location = NSMakePoint(x, screenHeight - y); // flip!
240 332
241 NSWindow* window = WindowAtCurrentMouseLocation(); 333 NSWindow* window = WindowAtCurrentMouseLocation();
242 334
243 NSPoint pointInWindow = g_mouse_location; 335 NSPoint pointInWindow = g_mouse_location;
244 if (window) 336 if (window)
245 pointInWindow = [window convertScreenToBase:pointInWindow]; 337 pointInWindow = [window convertScreenToBase:pointInWindow];
246 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); 338 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup();
247 339
248 NSEvent* event = 340 NSEventType etype = NSMouseMoved;
tapted 2016/03/01 08:11:58 etype -> event_type
themblsha 2016/03/09 17:40:22 Done.
249 [NSEvent mouseEventWithType:NSMouseMoved 341 if (g_mouse_button_down[LEFT]) {
250 location:pointInWindow 342 etype = NSLeftMouseDragged;
251 modifierFlags:0 343 } else if (g_mouse_button_down[RIGHT]) {
252 timestamp:timestamp 344 etype = NSRightMouseDragged;
253 windowNumber:[window windowNumber] 345 } else if (g_mouse_button_down[MIDDLE]) {
254 context:nil 346 etype = NSOtherMouseDragged;
255 eventNumber:0 347 }
256 clickCount:0 348
257 pressure:0.0]; 349 // we want to destroy the autoreleased event ASAP
258 [[NSApplication sharedApplication] postEvent:event atStart:NO]; 350 base::mac::ScopedNSAutoreleasePool pool;
351
352 NSEvent* event = [NSEvent mouseEventWithType:etype
353 location:pointInWindow
354 modifierFlags:0
355 timestamp:timestamp
356 windowNumber:[window windowNumber]
357 context:nil
358 eventNumber:0
359 clickCount:0
360 pressure:0.0];
259 361
260 if (!task.is_null()) { 362 if (!task.is_null()) {
261 base::MessageLoop::current()->PostTask( 363 base::scoped_nsobject<EventDeletedTaskRunner> task_runner =
262 FROM_HERE, base::Bind(&EventQueueWatcher, task)); 364 [[EventDeletedTaskRunner alloc] initWithClosure:task];
365 objc_setAssociatedObject(event, &kEventDeletedTaskRunnerKey,
366 task_runner.get(), OBJC_ASSOCIATION_RETAIN);
263 } 367 }
264 368
369 [[NSApplication sharedApplication] postEvent:event atStart:NO];
370
265 return true; 371 return true;
266 } 372 }
267 373
268 bool SendMouseEvents(MouseButton type, int state) { 374 bool SendMouseEvents(MouseButton type, int state) {
269 CHECK(g_ui_controls_enabled); 375 CHECK(g_ui_controls_enabled);
270 return SendMouseEventsNotifyWhenDone(type, state, base::Closure()); 376 return SendMouseEventsNotifyWhenDone(type, state, base::Closure());
271 } 377 }
272 378
273 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, 379 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state,
274 const base::Closure& task) { 380 const base::Closure& task) {
275 CHECK(g_ui_controls_enabled); 381 CHECK(g_ui_controls_enabled);
276 // On windows it appears state can be (UP|DOWN). It is unclear if 382 // On windows it appears state can be (UP|DOWN). It is unclear if
277 // that'll happen here but prepare for it just in case. 383 // that'll happen here but prepare for it just in case.
278 if (state == (UP|DOWN)) { 384 if (state == (UP|DOWN)) {
279 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && 385 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) &&
280 SendMouseEventsNotifyWhenDone(type, UP, task)); 386 SendMouseEventsNotifyWhenDone(type, UP, task));
281 } 387 }
282 NSEventType etype = NSLeftMouseDown; 388 NSEventType etype = NSLeftMouseDown;
283 if (type == LEFT) { 389 if (type == LEFT) {
284 if (state == UP) { 390 if (state == UP) {
285 etype = NSLeftMouseUp; 391 etype = NSLeftMouseUp;
392 g_mouse_button_down[LEFT] = false;
286 } else { 393 } else {
287 etype = NSLeftMouseDown; 394 etype = NSLeftMouseDown;
395 g_mouse_button_down[LEFT] = true;
288 } 396 }
289 } else if (type == MIDDLE) { 397 } else if (type == MIDDLE) {
290 if (state == UP) { 398 if (state == UP) {
291 etype = NSOtherMouseUp; 399 etype = NSOtherMouseUp;
400 g_mouse_button_down[MIDDLE] = false;
292 } else { 401 } else {
293 etype = NSOtherMouseDown; 402 etype = NSOtherMouseDown;
403 g_mouse_button_down[MIDDLE] = true;
294 } 404 }
295 } else if (type == RIGHT) { 405 } else if (type == RIGHT) {
296 if (state == UP) { 406 if (state == UP) {
297 etype = NSRightMouseUp; 407 etype = NSRightMouseUp;
408 g_mouse_button_down[RIGHT] = false;
298 } else { 409 } else {
299 etype = NSRightMouseDown; 410 etype = NSRightMouseDown;
411 g_mouse_button_down[RIGHT] = true;
tapted 2016/03/01 08:11:58 these can all be collapsed into g_mouse_button_do
themblsha 2016/03/09 17:40:22 Done.
300 } 412 }
301 } else { 413 } else {
302 return false; 414 return false;
303 } 415 }
304 NSWindow* window = WindowAtCurrentMouseLocation(); 416 NSWindow* window = WindowAtCurrentMouseLocation();
305 NSPoint pointInWindow = g_mouse_location; 417 NSPoint pointInWindow = g_mouse_location;
306 if (window) 418 if (window)
307 pointInWindow = [window convertScreenToBase:pointInWindow]; 419 pointInWindow = [window convertScreenToBase:pointInWindow];
308 420
421 ScopedNSEventSwizzler::Install();
422
423 // we want to destroy the autoreleased event ASAP
424 base::mac::ScopedNSAutoreleasePool pool;
425
309 NSEvent* event = 426 NSEvent* event =
310 [NSEvent mouseEventWithType:etype 427 [NSEvent mouseEventWithType:etype
311 location:pointInWindow 428 location:pointInWindow
312 modifierFlags:0 429 modifierFlags:0
313 timestamp:TimeIntervalSinceSystemStartup() 430 timestamp:TimeIntervalSinceSystemStartup()
314 windowNumber:[window windowNumber] 431 windowNumber:[window windowNumber]
315 context:nil 432 context:nil
316 eventNumber:0 433 eventNumber:0
317 clickCount:1 434 clickCount:1
318 pressure:(state == DOWN ? 1.0 : 0.0 )]; 435 pressure:(state == DOWN ? 1.0 : 0.0 )];
319 [[NSApplication sharedApplication] postEvent:event atStart:NO];
320 436
321 if (!task.is_null()) { 437 if (!task.is_null()) {
322 base::MessageLoop::current()->PostTask( 438 base::scoped_nsobject<EventDeletedTaskRunner> task_runner =
323 FROM_HERE, base::Bind(&EventQueueWatcher, task)); 439 [[EventDeletedTaskRunner alloc] initWithClosure:task];
440 objc_setAssociatedObject(event, &kEventDeletedTaskRunnerKey,
441 task_runner.get(), OBJC_ASSOCIATION_RETAIN);
324 } 442 }
325 443
444 [[NSApplication sharedApplication] postEvent:event atStart:NO];
445
326 return true; 446 return true;
327 } 447 }
328 448
329 bool SendMouseClick(MouseButton type) { 449 bool SendMouseClick(MouseButton type) {
330 CHECK(g_ui_controls_enabled); 450 CHECK(g_ui_controls_enabled);
331 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, base::Closure()); 451 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, base::Closure());
332 } 452 }
333 453
334 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) { 454 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) {
335 base::MessageLoop::current()->PostTask( 455 base::MessageLoop::current()->PostTask(
336 FROM_HERE, base::Bind(&EventQueueWatcher, closure)); 456 FROM_HERE, base::Bind(&EventQueueWatcher, closure));
337 } 457 }
338 458
339 bool IsFullKeyboardAccessEnabled() { 459 bool IsFullKeyboardAccessEnabled() {
340 return [NSApp isFullKeyboardAccessEnabled]; 460 return [NSApp isFullKeyboardAccessEnabled];
341 } 461 }
342 462
343 } // namespace ui_controls 463 } // namespace ui_controls
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698