1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/cocoa/menu_utils.h"
#include <optional>
#import <AppKit/AppKit.h>
#import "base/mac/scoped_sending_event.h"
#import "base/message_loop/message_pump_apple.h"
#include "base/task/current_thread.h"
#include "ui/base/interaction/element_tracker_mac.h"
#include "ui/gfx/mac/coordinate_conversion.h"
namespace ui {
NSEvent* EventForPositioningContextMenu(const gfx::Point& anchor,
NSWindow* window) {
NSPoint location_in_window =
[window convertPointFromScreen:gfx::ScreenPointToNSPoint(anchor)];
return EventForPositioningContextMenuRelativeToWindow(location_in_window,
window);
}
NSEvent* EventForPositioningContextMenuRelativeToWindow(const NSPoint& anchor,
NSWindow* window) {
NSEvent* event = NSApp.currentEvent;
switch (event.type) {
case NSEventTypeLeftMouseDown:
case NSEventTypeLeftMouseUp:
case NSEventTypeRightMouseDown:
case NSEventTypeRightMouseUp:
case NSEventTypeOtherMouseDown:
case NSEventTypeOtherMouseUp:
return event;
default:
break;
}
return [NSEvent mouseEventWithType:NSEventTypeRightMouseDown
location:anchor
modifierFlags:0
timestamp:0
windowNumber:window.windowNumber
context:nil
eventNumber:0
clickCount:1
pressure:0];
}
void ShowContextMenu(NSMenu* menu,
NSEvent* event,
NSView* view,
bool allow_nested_tasks,
ElementContext context) {
// Make sure events can be pumped while the menu is up.
std::optional<
base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop>
allow;
if (allow_nested_tasks) {
allow.emplace();
}
// One of the events that could be pumped is |window.close()|.
// User-initiated event-tracking loops protect against this by
// setting flags in -[CrApplication sendEvent:], but since
// web-content menus are initiated by IPC message the setup has to
// be done manually.
std::optional<base::mac::ScopedSendingEvent> sendingEventScoper;
if (allow_nested_tasks) {
sendingEventScoper.emplace();
}
// Ensure the UI can update while the menu is fading out.
base::ScopedPumpMessagesInPrivateModes pump_private;
if (context) {
ui::ElementTrackerMac::GetInstance()->NotifyMenuWillShow(menu, context);
}
// Show the menu.
[NSMenu popUpContextMenu:menu withEvent:event forView:view];
if (context) {
// We expect to see the following order of events:
//
// - menu will show
// - element shown
// - element activated (optional)
// - element hidden
// - menu completed
//
// However, the OS notification for "element activated" fires *after* the OS
// notification for "element hidden", so Chromium code handling the "element
// hidden" callback responds by doing a post to the main dispatch queue.
// Therefore, because there's already a post on the main dispatch queue,
// this event must be posted to the main dispatch queue as well to ensure
// correct ordering.
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC),
dispatch_get_main_queue(), ^{
ui::ElementTrackerMac::GetInstance()->NotifyMenuDoneShowing(menu);
});
}
}
} // namespace ui
|