| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import <Cocoa/Cocoa.h> |
| #include <stddef.h> |
| |
| #import "base/apple/scoped_objc_class_swizzler.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/time/time.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_dispatcher.h" |
| #include "ui/events/event_processor.h" |
| #include "ui/events/event_target.h" |
| #include "ui/events/event_target_iterator.h" |
| #include "ui/events/event_targeter.h" |
| #import "ui/events/test/cocoa_test_event_utils.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/events/types/event_type.h" |
| #include "ui/gfx/mac/coordinate_conversion.h" |
| |
| namespace { |
| |
| // Set (and always cleared) in EmulateSendEvent() to provide an answer for |
| // [NSApp currentEvent]. |
| NSEvent* g_current_event = nil; |
| |
| } // namespace |
| |
| @interface NSEventDonor : NSObject |
| @end |
| |
| @interface NSApplicationDonor : NSObject |
| @end |
| |
| namespace { |
| |
| // Return the current owner of the EventGeneratorDelegate. May be null. |
| ui::test::EventGenerator* GetActiveGenerator(); |
| |
| NSPoint ConvertRootPointToTarget(NSWindow* target, |
| const gfx::Point& point_in_root) { |
| DCHECK(GetActiveGenerator()); |
| gfx::Point point = point_in_root; |
| |
| point -= gfx::ScreenRectFromNSRect(target.frame).OffsetFromOrigin(); |
| return NSMakePoint(point.x(), NSHeight(target.frame) - point.y()); |
| } |
| |
| // Inverse of ui::EventFlagsFromModifiers(). |
| NSUInteger EventFlagsToModifiers(int flags) { |
| NSUInteger modifiers = 0; |
| modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSEventModifierFlagShift : 0; |
| modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSEventModifierFlagControl : 0; |
| modifiers |= (flags & ui::EF_ALT_DOWN) ? NSEventModifierFlagOption : 0; |
| modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSEventModifierFlagCommand : 0; |
| modifiers |= (flags & ui::EF_CAPS_LOCK_ON) ? NSEventModifierFlagCapsLock : 0; |
| // ui::EF_*_MOUSE_BUTTON not handled here. |
| // NSEventModifierFlagFunction, NSEventModifierFlagNumericPad 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::EventType::kKeyPressed: |
| return NSEventTypeKeyDown; |
| case ui::EventType::kKeyReleased: |
| return NSEventTypeKeyUp; |
| case ui::EventType::kMousePressed: |
| return PickMouseEventType(flags, NSEventTypeLeftMouseDown, |
| NSEventTypeRightMouseDown, |
| NSEventTypeOtherMouseDown); |
| case ui::EventType::kMouseReleased: |
| return PickMouseEventType(flags, NSEventTypeLeftMouseUp, |
| NSEventTypeRightMouseUp, |
| NSEventTypeOtherMouseUp); |
| case ui::EventType::kMouseDragged: |
| return PickMouseEventType(flags, NSEventTypeLeftMouseDragged, |
| NSEventTypeRightMouseDragged, |
| NSEventTypeOtherMouseDragged); |
| case ui::EventType::kMouseMoved: |
| return NSEventTypeMouseMoved; |
| case ui::EventType::kMousewheel: |
| return NSEventTypeScrollWheel; |
| case ui::EventType::kMouseEntered: |
| return NSEventTypeMouseEntered; |
| case ui::EventType::kMouseExited: |
| return NSEventTypeMouseExited; |
| case ui::EventType::kScrollFlingStart: |
| return NSEventTypeSwipe; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| // 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) { |
| base::AutoReset<NSEvent*> reset(&g_current_event, event); |
| NSResponder* responder = [window firstResponder]; |
| switch ([event type]) { |
| case NSEventTypeKeyDown: |
| [responder keyDown:event]; |
| return; |
| case NSEventTypeKeyUp: |
| [responder keyUp:event]; |
| return; |
| default: |
| break; |
| } |
| |
| // 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 NSEventTypeLeftMouseDown: |
| [responder mouseDown:event]; |
| break; |
| case NSEventTypeRightMouseDown: |
| [responder rightMouseDown:event]; |
| break; |
| case NSEventTypeOtherMouseDown: |
| [responder otherMouseDown:event]; |
| break; |
| case NSEventTypeLeftMouseUp: |
| [responder mouseUp:event]; |
| break; |
| case NSEventTypeRightMouseUp: |
| [responder rightMouseUp:event]; |
| break; |
| case NSEventTypeOtherMouseUp: |
| [responder otherMouseUp:event]; |
| break; |
| case NSEventTypeLeftMouseDragged: |
| [responder mouseDragged:event]; |
| break; |
| case NSEventTypeRightMouseDragged: |
| [responder rightMouseDragged:event]; |
| break; |
| case NSEventTypeOtherMouseDragged: |
| [responder otherMouseDragged:event]; |
| break; |
| case NSEventTypeMouseMoved: |
| // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that |
| // NSTrackingAreas have been appropriately installed on |responder|. |
| [responder mouseMoved:event]; |
| break; |
| case NSEventTypeScrollWheel: |
| [responder scrollWheel:event]; |
| break; |
| case NSEventTypeMouseEntered: |
| [responder mouseEntered:event]; |
| break; |
| case NSEventTypeMouseExited: |
| [responder mouseExited:event]; |
| 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(); |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| NSEvent* CreateMouseEventInWindow(NSWindow* window, |
| ui::EventType event_type, |
| const gfx::Point& point_in_root, |
| const base::TimeTicks time_stamp, |
| int flags) { |
| NSUInteger click_count = 0; |
| if (event_type == ui::EventType::kMousePressed || |
| event_type == ui::EventType::kMouseReleased) { |
| 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); |
| if (event_type == ui::EventType::kMouseEntered || |
| event_type == ui::EventType::kMouseExited) { |
| return |
| [NSEvent enterExitEventWithType:type |
| location:point |
| modifierFlags:modifiers |
| timestamp:ui::EventTimeStampToSeconds(time_stamp) |
| windowNumber:window.windowNumber |
| context:nil |
| eventNumber:0 |
| trackingNumber:0 |
| userData:nil]; |
| } |
| return [NSEvent mouseEventWithType:type |
| location:point |
| modifierFlags:modifiers |
| timestamp:ui::EventTimeStampToSeconds(time_stamp) |
| windowNumber:window.windowNumber |
| context:nil |
| eventNumber:0 |
| clickCount:click_count |
| pressure:1.0]; |
| } |
| |
| NSEvent* CreateMouseWheelEventInWindow(NSWindow* window, |
| const ui::MouseEvent* mouse_event) { |
| DCHECK_EQ(mouse_event->type(), ui::EventType::kMousewheel); |
| const ui::MouseWheelEvent* mouse_wheel_event = |
| mouse_event->AsMouseWheelEvent(); |
| return cocoa_test_event_utils::TestScrollEvent( |
| ConvertRootPointToTarget(window, mouse_wheel_event->location()), window, |
| mouse_wheel_event->x_offset(), mouse_wheel_event->y_offset(), false, |
| NSEventPhaseNone, NSEventPhaseNone); |
| } |
| |
| // 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::EventHandler, |
| public ui::EventProcessor, |
| public ui::EventTargeter, |
| public ui::test::EventGeneratorDelegate { |
| public: |
| EventGeneratorDelegateMac(ui::test::EventGenerator* owner, |
| gfx::NativeWindow root_window, |
| gfx::NativeWindow target_window); |
| EventGeneratorDelegateMac(const EventGeneratorDelegateMac&) = delete; |
| EventGeneratorDelegateMac& operator=(const EventGeneratorDelegateMac&) = |
| delete; |
| ~EventGeneratorDelegateMac() override; |
| |
| static EventGeneratorDelegateMac* instance() { return instance_; } |
| |
| NSEvent* OriginalCurrentEvent(id receiver, SEL selector) { |
| return swizzle_current_event_->InvokeOriginal<NSEvent*>(receiver, selector); |
| } |
| |
| NSWindow* target_window() { return target_window_; } |
| ui::test::EventGenerator* owner() { return owner_; } |
| |
| // Overridden from ui::EventTarget: |
| bool CanAcceptEvent(const ui::Event& event) override { return true; } |
| ui::EventTarget* GetParentTarget() override { return nullptr; } |
| std::unique_ptr<ui::EventTargetIterator> GetChildIterator() const override; |
| ui::EventTargeter* GetEventTargeter() override { return this; } |
| |
| // Overridden from ui::EventHandler: |
| void OnMouseEvent(ui::MouseEvent* event) override; |
| void OnKeyEvent(ui::KeyEvent* event) override; |
| void OnTouchEvent(ui::TouchEvent* event) override; |
| void OnScrollEvent(ui::ScrollEvent* event) override; |
| |
| // Overridden from ui::EventSource: |
| ui::EventSink* GetEventSink() override { return this; } |
| |
| // Overridden from ui::EventProcessor: |
| ui::EventTarget* GetRootForEvent(ui::Event* event) override { return this; } |
| ui::EventTargeter* GetDefaultEventTargeter() override { |
| return this->GetEventTargeter(); |
| } |
| |
| // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor): |
| bool CanDispatchToTarget(EventTarget* target) override { return true; } |
| |
| // Overridden from ui::EventTargeter: |
| ui::EventTarget* FindTargetForEvent(ui::EventTarget* root, |
| ui::Event* event) override { |
| return root; |
| } |
| ui::EventTarget* FindNextBestTarget(ui::EventTarget* previous_target, |
| ui::Event* event) override { |
| return nullptr; |
| } |
| |
| // Overridden from ui::test::EventGeneratorDelegate: |
| ui::EventTarget* GetTargetAt(const gfx::Point& location) override { |
| return this; |
| } |
| void SetTargetWindow(gfx::NativeWindow target_window) override { |
| target_window_ = target_window.GetNativeNSWindow(); |
| } |
| ui::EventSource* GetEventSource(ui::EventTarget* target) override { |
| return this; |
| } |
| gfx::Point CenterOfTarget(const ui::EventTarget* target) const override; |
| gfx::Point CenterOfWindow(gfx::NativeWindow window) const override; |
| |
| void ConvertPointFromTarget(const ui::EventTarget* target, |
| gfx::Point* point) const override {} |
| void ConvertPointToTarget(const ui::EventTarget* target, |
| gfx::Point* point) const override {} |
| void ConvertPointFromWindow(gfx::NativeWindow window, |
| gfx::Point* point) const override {} |
| void ConvertPointFromHost(const ui::EventTarget* hosted_target, |
| gfx::Point* point) const override {} |
| |
| protected: |
| // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor) |
| [[nodiscard]] ui::EventDispatchDetails PreDispatchEvent( |
| ui::EventTarget* target, |
| ui::Event* event) override; |
| |
| private: |
| static EventGeneratorDelegateMac* instance_; |
| |
| raw_ptr<ui::test::EventGenerator> owner_; |
| NSWindow* __strong target_window_; |
| std::unique_ptr<base::apple::ScopedObjCClassSwizzler> swizzle_pressed_; |
| std::unique_ptr<base::apple::ScopedObjCClassSwizzler> swizzle_location_; |
| std::unique_ptr<base::apple::ScopedObjCClassSwizzler> swizzle_current_event_; |
| NSMenu* __strong fake_menu_; |
| |
| // Mac always sends trackpad scroll events between begin/end phase event |
| // markers. If |in_trackpad_scroll| is false, a phase begin event is sent |
| // before any trackpad scroll update. |
| bool in_trackpad_scroll = false; |
| |
| // Timestamp on the last scroll update, used to simulate scroll momentum. |
| base::TimeTicks last_scroll_timestamp_; |
| }; |
| |
| // static |
| EventGeneratorDelegateMac* EventGeneratorDelegateMac::instance_ = nullptr; |
| |
| EventGeneratorDelegateMac::EventGeneratorDelegateMac( |
| ui::test::EventGenerator* owner, |
| gfx::NativeWindow root_window, |
| gfx::NativeWindow target_window) |
| : owner_(owner) { |
| DCHECK(!instance_); |
| instance_ = this; |
| SetTargetHandler(this); |
| // Install a fake "edit" menu. |
| fake_menu_ = [[NSMenu alloc] initWithTitle:@"Edit"]; |
| struct FakeMenuItem { |
| NSString* title; |
| SEL action; |
| NSString* key_equivalent; |
| }; |
| const auto kFakeMenuItems = std::to_array<FakeMenuItem>({ |
| {@"Undo", @selector(undo:), @"z"}, |
| {@"Redo", @selector(redo:), @"Z"}, |
| {@"Copy", @selector(copy:), @"c"}, |
| {@"Cut", @selector(cut:), @"x"}, |
| {@"Paste", @selector(paste:), @"v"}, |
| {@"Select All", @selector(selectAll:), @"a"}, |
| }); |
| for (size_t i = 0; i < kFakeMenuItems.size(); ++i) { |
| [fake_menu_ insertItemWithTitle:kFakeMenuItems[i].title |
| action:kFakeMenuItems[i].action |
| keyEquivalent:kFakeMenuItems[i].key_equivalent |
| atIndex:i]; |
| } |
| |
| // Mac doesn't use a |root_window|. Assume that if a single-argument |
| // constructor was used, it should be the actual |target_window|. |
| // TODO(tluk) fix use of the API so this doesn't have to be assumed. |
| // (crbug.com/1071628) |
| if (!target_window) { |
| target_window = root_window; |
| } |
| |
| swizzle_pressed_.reset(); |
| swizzle_location_.reset(); |
| swizzle_current_event_.reset(); |
| |
| SetTargetWindow(target_window); |
| |
| // Normally, edit menu items have a `nil` target. This results in -[NSMenu |
| // performKeyEquivalent:] relying on -[NSApplication targetForAction:to:from:] |
| // to find a target starting at the first responder of the key window. Since |
| // non-interactive tests have no key window, that won't work. So set (or |
| // clear) the target explicitly on all menu items. |
| [fake_menu_.itemArray |
| makeObjectsPerformSelector:@selector(setTarget:) |
| withObject:[target_window.GetNativeNSWindow() |
| firstResponder]]; |
| |
| if (owner_) { |
| swizzle_pressed_ = std::make_unique<base::apple::ScopedObjCClassSwizzler>( |
| [NSEvent class], [NSEventDonor class], @selector(pressedMouseButtons)); |
| swizzle_location_ = std::make_unique<base::apple::ScopedObjCClassSwizzler>( |
| [NSEvent class], [NSEventDonor class], @selector(mouseLocation)); |
| swizzle_current_event_ = |
| std::make_unique<base::apple::ScopedObjCClassSwizzler>( |
| [NSApplication class], [NSApplicationDonor class], |
| @selector(currentEvent)); |
| } |
| } |
| |
| EventGeneratorDelegateMac::~EventGeneratorDelegateMac() { |
| DCHECK_EQ(instance_, this); |
| instance_ = nullptr; |
| } |
| |
| std::unique_ptr<ui::EventTargetIterator> |
| EventGeneratorDelegateMac::GetChildIterator() const { |
| // Return nullptr to dispatch all events to the result of GetRootTarget(). |
| return nullptr; |
| } |
| |
| void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) { |
| NSEvent* ns_event = |
| event->type() == ui::EventType::kMousewheel |
| ? CreateMouseWheelEventInWindow(target_window_, event) |
| : CreateMouseEventInWindow(target_window_, event->type(), |
| event->location(), event->time_stamp(), |
| event->flags()); |
| |
| using Target = ui::test::EventGenerator::Target; |
| switch (owner_->target()) { |
| case Target::APPLICATION: |
| [NSApp sendEvent:ns_event]; |
| break; |
| case Target::WINDOW: |
| [target_window_ sendEvent:ns_event]; |
| break; |
| case Target::WIDGET: |
| EmulateSendEvent(target_window_, ns_event); |
| break; |
| } |
| } |
| |
| void EventGeneratorDelegateMac::OnKeyEvent(ui::KeyEvent* event) { |
| NSUInteger modifiers = EventFlagsToModifiers(event->flags()); |
| NSEvent* ns_event = cocoa_test_event_utils::SynthesizeKeyEvent( |
| target_window_, event->type() == ui::EventType::kKeyPressed, |
| event->key_code(), modifiers, |
| event->is_char() ? event->GetDomKey() : ui::DomKey(ui::DomKey::NONE)); |
| |
| using Target = ui::test::EventGenerator::Target; |
| switch (owner_->target()) { |
| case Target::APPLICATION: |
| [NSApp sendEvent:ns_event]; |
| break; |
| case Target::WINDOW: |
| // -[NSApp sendEvent:] sends -performKeyEquivalent: if Command or Control |
| // modifiers are pressed. Emulate that behavior. |
| if (ns_event.type == NSEventTypeKeyDown && |
| (ns_event.modifierFlags & |
| (NSEventModifierFlagControl | NSEventModifierFlagCommand)) && |
| [target_window_ performKeyEquivalent:ns_event]) { |
| break; // Handled by performKeyEquivalent:. |
| } |
| |
| [target_window_ sendEvent:ns_event]; |
| break; |
| case Target::WIDGET: |
| if ([fake_menu_ performKeyEquivalent:ns_event]) { |
| return; |
| } |
| |
| EmulateSendEvent(target_window_, ns_event); |
| break; |
| } |
| } |
| |
| void EventGeneratorDelegateMac::OnTouchEvent(ui::TouchEvent* event) { |
| NOTREACHED() << "Touchscreen events not supported on Chrome Mac."; |
| } |
| |
| void EventGeneratorDelegateMac::OnScrollEvent(ui::ScrollEvent* event) { |
| // Ignore FLING_CANCEL. Cocoa provides a continuous stream of events during a |
| // fling. For now, this method simulates a momentum stream using a single |
| // update with a momentum phase (plus begin/end phase events), triggered when |
| // the EventGenerator requests a FLING_START. |
| if (event->type() == ui::EventType::kScrollFlingCancel) { |
| return; |
| } |
| |
| NSPoint location = |
| ConvertRootPointToTarget(target_window_, event->location()); |
| |
| // MAY_BEGIN/END comes from the EventGenerator for trackpad rests. |
| if (event->momentum_phase() == ui::EventMomentumPhase::MAY_BEGIN || |
| event->momentum_phase() == ui::EventMomentumPhase::END) { |
| DCHECK_EQ(0, event->x_offset()); |
| DCHECK_EQ(0, event->y_offset()); |
| NSEventPhase phase = |
| event->momentum_phase() == ui::EventMomentumPhase::MAY_BEGIN |
| ? NSEventPhaseMayBegin |
| : NSEventPhaseCancelled; |
| |
| NSEvent* rest = cocoa_test_event_utils::TestScrollEvent( |
| location, target_window_, 0, 0, true, phase, NSEventPhaseNone); |
| EmulateSendEvent(target_window_, rest); |
| |
| // Allow the next ScrollSequence to skip the "begin". |
| in_trackpad_scroll = phase == NSEventPhaseMayBegin; |
| return; |
| } |
| |
| NSEventPhase event_phase = NSEventPhaseBegan; |
| NSEventPhase momentum_phase = NSEventPhaseNone; |
| |
| // Treat FLING_START as the beginning of a momentum phase. |
| if (event->type() == ui::EventType::kScrollFlingStart) { |
| DCHECK(in_trackpad_scroll); |
| // First end the non-momentum phase. |
| NSEvent* end = cocoa_test_event_utils::TestScrollEvent( |
| location, target_window_, 0, 0, true, NSEventPhaseEnded, |
| NSEventPhaseNone); |
| EmulateSendEvent(target_window_, end); |
| in_trackpad_scroll = false; |
| |
| // Assume a zero time delta means no fling. Just end the event phase. |
| if (event->time_stamp() == last_scroll_timestamp_) { |
| return; |
| } |
| |
| // Otherwise, switch phases for the "fling". |
| std::swap(event_phase, momentum_phase); |
| } |
| |
| // Send a begin for the current event phase, unless it's already in progress. |
| if (!in_trackpad_scroll) { |
| NSEvent* begin = cocoa_test_event_utils::TestScrollEvent( |
| location, target_window_, 0, 0, true, event_phase, momentum_phase); |
| EmulateSendEvent(target_window_, begin); |
| in_trackpad_scroll = true; |
| } |
| |
| if (event->type() == ui::EventType::kScroll) { |
| NSEvent* update = cocoa_test_event_utils::TestScrollEvent( |
| location, target_window_, -event->x_offset(), -event->y_offset(), true, |
| NSEventPhaseChanged, NSEventPhaseNone); |
| EmulateSendEvent(target_window_, update); |
| } else { |
| DCHECK_EQ(event->type(), ui::EventType::kScrollFlingStart); |
| // Mac generates a stream of events. For the purposes of testing, just |
| // generate one. |
| NSEvent* update = cocoa_test_event_utils::TestScrollEvent( |
| location, target_window_, -event->x_offset(), -event->y_offset(), true, |
| NSEventPhaseNone, NSEventPhaseChanged); |
| EmulateSendEvent(target_window_, update); |
| |
| // Never leave the momentum part hanging. |
| NSEvent* end = cocoa_test_event_utils::TestScrollEvent( |
| location, target_window_, 0, 0, true, NSEventPhaseNone, |
| NSEventPhaseEnded); |
| EmulateSendEvent(target_window_, end); |
| in_trackpad_scroll = false; |
| } |
| |
| last_scroll_timestamp_ = event->time_stamp(); |
| } |
| |
| gfx::Point EventGeneratorDelegateMac::CenterOfTarget( |
| const ui::EventTarget* target) const { |
| DCHECK_EQ(target, this); |
| return CenterOfWindow(gfx::NativeWindow(target_window_)); |
| } |
| |
| gfx::Point EventGeneratorDelegateMac::CenterOfWindow( |
| gfx::NativeWindow native_window) const { |
| NSWindow* window = native_window.GetNativeNSWindow(); |
| DCHECK_EQ(window, target_window_); |
| // Assume the window is at the top-left of the coordinate system (even if |
| // AppKit has moved it into the work area) see ConvertRootPointToTarget(). |
| return gfx::Point(NSWidth([window frame]) / 2, NSHeight([window frame]) / 2); |
| } |
| |
| ui::EventDispatchDetails EventGeneratorDelegateMac::PreDispatchEvent( |
| ui::EventTarget* target, |
| ui::Event* event) { |
| // Set the TestScreen's cursor point before mouse event dispatch. The |
| // Screen's value is checked by views controls and other UI components; this |
| // pattern matches aura::WindowEventDispatcher::PreDispatchMouseEvent(). |
| if (event->IsMouseEvent()) { |
| ui::MouseEvent* mouse_event = event->AsMouseEvent(); |
| // Similar to the logic in Aura's |
| // EnvInputStateController::UpdateStateForMouseEvent(), capture change and |
| // synthesized events don't need to update the cursor location. |
| if (mouse_event->type() != ui::EventType::kMouseCaptureChanged && |
| !(mouse_event->flags() & ui::EF_IS_SYNTHESIZED)) { |
| // Update the cursor location on screen. |
| owner_->set_current_screen_location(mouse_event->root_location()); |
| display::Screen::Get()->SetCursorScreenPointForTesting( |
| mouse_event->root_location()); |
| } |
| } |
| return ui::EventDispatchDetails(); |
| } |
| |
| ui::test::EventGenerator* GetActiveGenerator() { |
| return EventGeneratorDelegateMac::instance() |
| ? EventGeneratorDelegateMac::instance()->owner() |
| : nullptr; |
| } |
| |
| } // namespace |
| |
| namespace views::test { |
| |
| std::unique_ptr<ui::test::EventGeneratorDelegate> |
| CreateEventGeneratorDelegateMac(ui::test::EventGenerator* owner, |
| gfx::NativeWindow root_window, |
| gfx::NativeWindow target_window) { |
| return std::make_unique<EventGeneratorDelegateMac>(owner, root_window, |
| target_window); |
| } |
| } // namespace views::test |
| |
| @implementation NSEventDonor |
| |
| // Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the |
| // active generator. |
| + (NSUInteger)pressedMouseButtons { |
| ui::test::EventGenerator* generator = GetActiveGenerator(); |
| if (!generator) { |
| return [NSEventDonor pressedMouseButtons]; // Call original implementation. |
| } |
| |
| int flags = 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; |
| } |
| |
| // Donate +[NSEvent mouseLocation] by retrieving the current position on screen. |
| + (NSPoint)mouseLocation { |
| ui::test::EventGenerator* generator = GetActiveGenerator(); |
| if (!generator) { |
| return [NSEventDonor mouseLocation]; // Call original implementation. |
| } |
| |
| // The location is the point in the root window which, for desktop widgets, is |
| // the widget itself. |
| gfx::Point point_in_root = generator->current_screen_location(); |
| NSWindow* window = EventGeneratorDelegateMac::instance()->target_window(); |
| NSPoint point_in_window = ConvertRootPointToTarget(window, point_in_root); |
| return [window convertPointToScreen:point_in_window]; |
| } |
| |
| @end |
| |
| @implementation NSApplicationDonor |
| |
| - (NSEvent*)currentEvent { |
| if (g_current_event) { |
| return g_current_event; |
| } |
| |
| // Find the original implementation and invoke it. |
| return EventGeneratorDelegateMac::instance()->OriginalCurrentEvent(self, |
| _cmd); |
| } |
| |
| @end |