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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
content / browser / renderer_host / web_menu_runner_ios.mm [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "content/browser/renderer_host/web_menu_runner_ios.h"
#include "base/strings/sys_string_conversions.h"
@interface UIContextMenuInteraction ()
- (void)_presentMenuAtLocation:(CGPoint)location;
@end
@interface WebMenuRunner () <UIContextMenuInteractionDelegate>
@end
@implementation WebMenuRunner {
// The UIView in which the popup menu will be displayed.
UIView* __weak _view;
// The bounds of the select element from which the menu was triggered.
CGRect _elementBounds;
// The index of the selected menu item.
size_t _selectedIndex;
// A flag set to YES if a menu item was chosen, or NO if the menu was
// dismissed without selecting an item.
BOOL _menuItemWasChosen;
// The native UIMenu object.
UIMenu* __strong _menu;
// Interaction for displaying a popup menu.
UIContextMenuInteraction* __strong _selectContextMenuInteraction;
// Delegate to handle menu select/cancel events.
base::WeakPtr<content::MenuInteractionDelegate> _delegate;
}
- (id)initWithDelegate:(base::WeakPtr<content::MenuInteractionDelegate>)delegate
items:(const std::vector<blink::mojom::MenuItemPtr>&)items
initialIndex:(int)index
fontSize:(CGFloat)fontSize
rightAligned:(BOOL)rightAligned {
if ((self = [super init])) {
_delegate = delegate;
DCHECK_GE(index, 0);
_selectedIndex = static_cast<size_t>(index);
[self createMenu:items];
}
return self;
}
- (void)showMenuInView:(UIView*)view withBounds:(CGRect)bounds {
_view = view;
_elementBounds = bounds;
_selectContextMenuInteraction =
[[UIContextMenuInteraction alloc] initWithDelegate:self];
[_view addInteraction:_selectContextMenuInteraction];
// TODO(crbug.com/40274444): _presentMenuAtLocation is a private API
// which triggers the ContextMenu immediately at a specified location. By
// default, the ContextMenu is only triggered on long press or 3D touch. This
// private API is needed to use because we expect the popup menu to appear
// immediately when the user touches the <select> element area.
[_selectContextMenuInteraction _presentMenuAtLocation:_elementBounds.origin];
}
- (void)dealloc {
[_view removeInteraction:_selectContextMenuInteraction];
}
#pragma mark - UIContextMenuInteractionDelegate
// TODO(crbug.com/40266320): This menu is being shown with unwanted effects.
// Need to find a way to show just the menu without using private API.
- (UIContextMenuConfiguration*)contextMenuInteraction:
(UIContextMenuInteraction*)interaction
configurationForMenuAtLocation:(CGPoint)location {
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu* _Nullable(
NSArray<UIMenuElement*>* _Nonnull suggestedActions) {
return self->_menu;
}];
}
- (UITargetedPreview*)contextMenuInteraction:
(UIContextMenuInteraction*)interaction
configuration:
(UIContextMenuConfiguration*)configuration
highlightPreviewForItemWithIdentifier:(id<NSCopying>)identifier {
UIView* snapshotView = [_view resizableSnapshotViewFromRect:_elementBounds
afterScreenUpdates:NO
withCapInsets:UIEdgeInsetsZero];
UIPreviewTarget* previewTarget = [[UIPreviewTarget alloc]
initWithContainer:_view
center:CGPointMake(CGRectGetMidX(_elementBounds),
CGRectGetMidY(_elementBounds))];
return
[[UITargetedPreview alloc] initWithView:snapshotView
parameters:[[UIPreviewParameters alloc] init]
target:previewTarget];
}
- (void)contextMenuInteraction:(UIContextMenuInteraction*)interaction
willEndForConfiguration:(UIContextMenuConfiguration*)configuration
animator:(id<UIContextMenuInteractionAnimating>)animator {
_menu = nil;
if (!_delegate) {
return;
}
if (_menuItemWasChosen) {
_delegate->OnMenuItemSelected(_selectedIndex);
} else {
_delegate->OnMenuCanceled();
}
}
#pragma mark - Private
// Creates the native UIMenu object using the provided list of menu items.
- (void)createMenu:(const std::vector<blink::mojom::MenuItemPtr>&)items {
NSMutableArray* actions = [NSMutableArray array];
for (size_t i = 0; i < items.size(); ++i) {
UIAction* action = [self addItem:items[i] itemIndex:i];
if (i == _selectedIndex) {
action.state = UIMenuElementStateOn;
}
[actions addObject:action];
}
_menu = [UIMenu menuWithTitle:@""
image:nil
identifier:nil
options:UIMenuOptionsDisplayInline
children:actions];
}
// Worker function used during initialization.
- (UIAction*)addItem:(const blink::mojom::MenuItemPtr&)item
itemIndex:(size_t)index {
NSString* title = base::SysUTF8ToNSString(item->label.value_or(""));
UIAction* itemAction =
[UIAction actionWithTitle:title
image:nil
identifier:nil
handler:^(__kindof UIAction* action) {
[self menuItemSelected:index];
}];
return itemAction;
}
// A callback for the menu controller object to call when an item is selected
// from the menu. This is not called if the menu is dismissed without a
// selection.
- (void)menuItemSelected:(size_t)index {
_menuItemWasChosen = YES;
_selectedIndex = index;
}
@end // WebMenuRunner