Skip to content

Commit

Permalink
Use CompactContextMenuPresenter to show context menus for focused ele…
Browse files Browse the repository at this point in the history
…ments with datalist

https://bugs.webkit.org/show_bug.cgi?id=264038
rdar://117809656

Reviewed by Richard Robinson and Megan Gardner.

Deploy `CompactContextMenuPresenter` in `WKDataListSuggestionsDropdown`, to support compact context
menu presentation without relying on UIKit SPI. Note that this (in theory) means we no longer need
logic to manually specify preferred edge insets for the suggestions menu, since we now lay out the
hidden native `UIButton` over the bounds of the focused datalist element, which UIKit will
automatically avoid obscuring when presenting or updating the menu.

* Source/WebKit/Platform/spi/ios/UIKitSPI.h:

Remove SPI declarations that are no longer necessary.

* Source/WebKit/UIProcess/ios/CompactContextMenuPresenter.h:
* Source/WebKit/UIProcess/ios/CompactContextMenuPresenter.mm:
(WebKit::CompactContextMenuPresenter::updateVisibleMenu):
* Source/WebKit/UIProcess/ios/WKContentViewInteraction.h:
* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _createTargetedContextMenuHintPreviewForFocusedElement:]):
(-[WKContentView _createTargetedContextMenuHintPreviewForFocusedElement]): Deleted.

Add support for an option to create a targeted context menu hint that's positioned against the left
or right edge of the input, provided there's sufficient space to fit the 250pt (by default) menu.
When the view is RTL, note that we'll prefer to position the menu at the the right edge of the
element, fall back to the left edge if there's not enough horizonal space, and finally fall back to
the element rect itself if there's no space on either side.

* Source/WebKit/UIProcess/ios/WebDataListSuggestionsDropdownIOS.mm:
(-[WKDataListSuggestionsDropdown _showSuggestions]):
(-[WKDataListSuggestionsDropdown _removeContextMenuInteraction]):
(-[WKDataListSuggestionsDropdown contextMenuInteraction:configuration:highlightPreviewForItemWithIdentifier:]):
(-[WKDataListSuggestionsDropdown _preferredEdgeInsetsForSuggestionsMenu]): Deleted.
(-[WKDataListSuggestionsDropdown _contextMenuInteraction:styleForMenuWithConfiguration:]): Deleted.
* Source/WebKit/UIProcess/ios/forms/WKFormSelectPicker.mm:
(-[WKSelectPicker contextMenuInteraction:configuration:highlightPreviewForItemWithIdentifier:]):

Canonical link: https://commits.webkit.org/270373@main
  • Loading branch information
whsieh committed Nov 8, 2023
1 parent c84d7c4 commit f1278a1
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 49 deletions.
10 changes: 0 additions & 10 deletions Source/WebKit/Platform/spi/ios/UIKitSPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -978,16 +978,6 @@ typedef NS_OPTIONS(NSInteger, UIWKDocumentRequestFlags) {
@property (nonatomic, strong) UIImage *image;
@end

typedef NS_ENUM(NSUInteger, _UIContextMenuLayout) {
_UIContextMenuLayoutCompactMenu = 3,
};

@interface _UIContextMenuStyle : NSObject <NSCopying>
@property (nonatomic) _UIContextMenuLayout preferredLayout;
@property (nonatomic) UIEdgeInsets preferredEdgeInsets;
+ (instancetype)defaultStyle;
@end

#if USE(UICONTEXTMENU)

@interface UIContextMenuInteraction ()
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/UIProcess/ios/CompactContextMenuPresenter.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class CompactContextMenuPresenter {
void present(CGPoint locationInRootView);
void dismiss();

void updateVisibleMenu(UIMenu *(^)(UIMenu *));

UIContextMenuInteraction *interaction() const;

private:
Expand Down
5 changes: 5 additions & 0 deletions Source/WebKit/UIProcess/ios/CompactContextMenuPresenter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ - (void)contextMenuInteraction:(UIContextMenuInteraction *)interaction willEndFo
[[m_button contextMenuInteraction] dismissMenu];
}

void CompactContextMenuPresenter::updateVisibleMenu(UIMenu *(^updateBlock)(UIMenu *))
{
[interaction() updateVisibleMenuWithBlock:updateBlock];
}

} // namespace WebKit

#endif // USE(UICONTEXTMENU)
7 changes: 6 additions & 1 deletion Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ enum class InputViewUpdateDeferralSource : uint8_t {
ChangingFocusedElement = 1 << 2,
};

enum class TargetedPreviewPositioning : uint8_t {
Default,
LeadingOrTrailingEdge,
};

using InputViewUpdateDeferralSources = OptionSet<InputViewUpdateDeferralSource>;

struct WKSelectionDrawingInfo {
Expand Down Expand Up @@ -799,7 +804,7 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW)

- (void)presentContextMenu:(UIContextMenuInteraction *)contextMenuInteraction atLocation:(CGPoint)location;

- (UITargetedPreview *)_createTargetedContextMenuHintPreviewForFocusedElement;
- (UITargetedPreview *)_createTargetedContextMenuHintPreviewForFocusedElement:(WebKit::TargetedPreviewPositioning)positioning;
- (UITargetedPreview *)_createTargetedContextMenuHintPreviewIfPossible;
- (void)_removeContextMenuHintContainerIfPossible;
- (void)_targetedPreviewContainerDidRemoveLastSubview:(WKTargetedPreviewContainer *)containerView;
Expand Down
36 changes: 34 additions & 2 deletions Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9735,7 +9735,7 @@ - (BOOL)supportsImagePaste
return adoptNS([[UITargetedPreview alloc] initWithView:snapshotView.get() parameters:parameters.get() target:target.get()]);
}

- (UITargetedPreview *)_createTargetedContextMenuHintPreviewForFocusedElement
- (UITargetedPreview *)_createTargetedContextMenuHintPreviewForFocusedElement:(WebKit::TargetedPreviewPositioning)positioning
{
auto backgroundColor = [&]() -> UIColor * {
switch (_focusedElementInformation.elementType) {
Expand All @@ -9749,7 +9749,39 @@ - (UITargetedPreview *)_createTargetedContextMenuHintPreviewForFocusedElement
}
}();

auto targetedPreview = createFallbackTargetedPreview(self, self.containerForContextMenuHintPreviews, _focusedElementInformation.interactionRect, backgroundColor);
auto previewRect = _focusedElementInformation.interactionRect;
if (positioning == WebKit::TargetedPreviewPositioning::LeadingOrTrailingEdge) {
static constexpr auto defaultMenuWidth = 250;
auto unobscuredRect = WebCore::IntRect { self.unobscuredContentRect };
std::optional<int> previewOffsetX;

auto leftEdge = previewRect.x() - defaultMenuWidth;
bool hasSpaceAfterRightEdge = previewRect.maxX() + defaultMenuWidth <= unobscuredRect.maxX();
bool hasSpaceBeforeLeftEdge = leftEdge > unobscuredRect.x();

switch (self.effectiveUserInterfaceLayoutDirection) {
case UIUserInterfaceLayoutDirectionLeftToRight:
if (hasSpaceAfterRightEdge)
previewOffsetX = previewRect.maxX();
else if (hasSpaceBeforeLeftEdge)
previewOffsetX = leftEdge;
break;
case UIUserInterfaceLayoutDirectionRightToLeft:
if (hasSpaceBeforeLeftEdge)
previewOffsetX = leftEdge;
else if (hasSpaceAfterRightEdge)
previewOffsetX = previewRect.maxX();
break;
}

if (previewOffsetX) {
static constexpr auto additionalOffsetToDockPresentedMenuToEdge = 20;
previewRect.setX(additionalOffsetToDockPresentedMenuToEdge + *previewOffsetX);
previewRect.setWidth(1);
}
}

auto targetedPreview = createFallbackTargetedPreview(self, self.containerForContextMenuHintPreviews, previewRect, backgroundColor);

[self _updateTargetedPreviewScrollViewUsingContainerScrollingNodeID:_focusedElementInformation.containerScrollingNodeID];

Expand Down
58 changes: 23 additions & 35 deletions Source/WebKit/UIProcess/ios/WebDataListSuggestionsDropdownIOS.mm
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

#if ENABLE(DATALIST_ELEMENT) && PLATFORM(IOS_FAMILY)

#import "UIKitSPI.h"
#import "CompactContextMenuPresenter.h"
#import "WKContentView.h"
#import "WKContentViewInteraction.h"
#import "WKFormPeripheral.h"
Expand Down Expand Up @@ -408,7 +408,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
@implementation WKDataListSuggestionsDropdown {
#if USE(UICONTEXTMENU)
RetainPtr<NSArray<UIMenuElement *>> _suggestionsMenuElements;
RetainPtr<UIContextMenuInteraction> _suggestionsContextMenuInteraction;
std::unique_ptr<WebKit::CompactContextMenuPresenter> _suggestionsContextMenuPresenter;
#endif
}

Expand Down Expand Up @@ -467,22 +467,31 @@ - (void)_showSuggestions
#if USE(UICONTEXTMENU)
[self _updateSuggestionsMenuElements];

if (!_suggestionsContextMenuInteraction) {
_suggestionsContextMenuInteraction = adoptNS([[UIContextMenuInteraction alloc] initWithDelegate:self]);
[self.view addInteraction:_suggestionsContextMenuInteraction.get()];

if (!_suggestionsContextMenuPresenter) {
_suggestionsContextMenuPresenter = makeUnique<WebKit::CompactContextMenuPresenter>(self.view, self);
[self.view doAfterEditorStateUpdateAfterFocusingElement:[weakSelf = WeakObjCPtr<WKDataListSuggestionsDropdown>(self)] {
auto strongSelf = weakSelf.get();
if (!strongSelf)
return;

auto view = [strongSelf view];
[view presentContextMenu:strongSelf->_suggestionsContextMenuInteraction.get() atLocation:[view lastInteractionLocation]];
if (strongSelf->_suggestionsContextMenuPresenter) {
strongSelf->_suggestionsContextMenuPresenter->present([&] {
RetainPtr contentView = [strongSelf view];
auto elementRect = [contentView focusedElementInformation].interactionRect;
if (elementRect.isEmpty()) {
elementRect = WebCore::IntRect {
WebCore::IntPoint([contentView lastInteractionLocation]),
WebCore::IntSize { }
};
}
return elementRect;
}());
}
}];
} else {
[_suggestionsContextMenuInteraction updateVisibleMenuWithBlock:[&](UIMenu *visibleMenu) -> UIMenu * {
_suggestionsContextMenuPresenter->updateVisibleMenu(^UIMenu *(UIMenu *visibleMenu) {
return [visibleMenu menuByReplacingChildren:_suggestionsMenuElements.get()];
}];
});
}
#endif
}
Expand Down Expand Up @@ -515,11 +524,11 @@ - (void)_updateSuggestionsMenuElements

- (void)_removeContextMenuInteraction
{
if (!_suggestionsContextMenuInteraction)
if (!_suggestionsContextMenuPresenter)
return;

[self.view removeInteraction:_suggestionsContextMenuInteraction.get()];
_suggestionsContextMenuInteraction = nil;
_suggestionsContextMenuPresenter->dismiss();
_suggestionsContextMenuPresenter = nullptr;
[self.view _removeContextMenuHintContainerIfPossible];
[self.view.webView _didDismissContextMenu];
}
Expand All @@ -541,32 +550,11 @@ - (void)_suggestionsMenuDidDismiss
[self _removeContextMenuInteraction];
}

- (UIEdgeInsets)_preferredEdgeInsetsForSuggestionsMenu
{
CGRect windowBounds = self.view.textEffectsWindow.bounds;
CGRect elementFrameInWindowCoordinates = [self.view convertRect:self.view.focusedElementInformation.interactionRect toView:nil];

if (CGRectGetMidY(elementFrameInWindowCoordinates) > CGRectGetMidY(windowBounds))
return UIEdgeInsetsMake(0, 0, CGRectGetMaxY(windowBounds) - CGRectGetMinY(elementFrameInWindowCoordinates), 0);

// Use MinY rather than MaxY to account for the hint preview.
return UIEdgeInsetsMake(CGRectGetMinY(elementFrameInWindowCoordinates), 0, 0, 0);
}

#pragma mark UIContextMenuInteractionDelegate

- (UITargetedPreview *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configuration:(UIContextMenuConfiguration *)configuration highlightPreviewForItemWithIdentifier:(id<NSCopying>)identifier
{
return [self.view _createTargetedContextMenuHintPreviewForFocusedElement];
}

- (_UIContextMenuStyle *)_contextMenuInteraction:(UIContextMenuInteraction *)interaction styleForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration
{
_UIContextMenuStyle *style = [_UIContextMenuStyle defaultStyle];
style.preferredLayout = _UIContextMenuLayoutCompactMenu;
style.preferredEdgeInsets = [self _preferredEdgeInsetsForSuggestionsMenu];

return style;
return [self.view _createTargetedContextMenuHintPreviewForFocusedElement:WebKit::TargetedPreviewPositioning::LeadingOrTrailingEdge];
}

- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location
Expand Down
2 changes: 1 addition & 1 deletion Source/WebKit/UIProcess/ios/forms/WKFormSelectPicker.mm
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ - (UIAction *)actionForOptionIndex:(NSInteger)optionIndex

- (UITargetedPreview *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configuration:(UIContextMenuConfiguration *)configuration highlightPreviewForItemWithIdentifier:(id<NSCopying>)identifier
{
return [_view _createTargetedContextMenuHintPreviewForFocusedElement];
return [_view _createTargetedContextMenuHintPreviewForFocusedElement:WebKit::TargetedPreviewPositioning::Default];
}

- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location
Expand Down

0 comments on commit f1278a1

Please sign in to comment.