chromium/ui/views/test/event_generator_delegate_mac.mm

// 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.

#include "base/memory/raw_ptr.h"

#import <Cocoa/Cocoa.h>
#include <stddef.h>

#import "base/apple/scoped_objc_class_swizzler.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::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::GetScreen()->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