Commit 9ca289f6 authored by Gauthier Ambard's avatar Gauthier Ambard Committed by Chromium LUCI CQ

[iOS] Add UIContextMenu support for context menu

This CL adds a new class that could handle using UIContextMenu to
display context menu.
The new class is reusing the existing JavaScript from the current
context menu to find the element on which the long press was done.

For now the class isn't connected and isn't displaying anything.

Bug: 1140387
Change-Id: I33c2f90445af29712b8680da3f69cdda21dd0367
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2578958
Commit-Queue: Gauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarMike Dougherty <michaeldo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#841532}
parent 9926254e
...@@ -189,10 +189,13 @@ class WebClient { ...@@ -189,10 +189,13 @@ class WebClient {
virtual UIView* GetWindowedContainer(); virtual UIView* GetWindowedContainer();
// Enables the logic to handle long press and force // Enables the logic to handle long press and force
// touch. Should return false to use the context menu API. // touch through action sheet. Should return false to use the context menu
// Defaults to return true. // API. Defaults to return true.
virtual bool EnableLongPressAndForceTouchHandling() const; virtual bool EnableLongPressAndForceTouchHandling() const;
// Enables the logic to handle long press context menu with UIContextMenu.
virtual bool EnableLongPressUIContextMenu() const;
// This method is used when the user didn't express any preference for the // This method is used when the user didn't express any preference for the
// version of |url|. Returning true allows to make sure that for |url|, the // version of |url|. Returning true allows to make sure that for |url|, the
// mobile version will be used, unless the user explicitly requested the // mobile version will be used, unless the user explicitly requested the
......
...@@ -120,6 +120,10 @@ bool WebClient::EnableLongPressAndForceTouchHandling() const { ...@@ -120,6 +120,10 @@ bool WebClient::EnableLongPressAndForceTouchHandling() const {
return true; return true;
} }
bool WebClient::EnableLongPressUIContextMenu() const {
return false;
}
bool WebClient::ForceMobileVersionByDefault(const GURL&) { bool WebClient::ForceMobileVersionByDefault(const GURL&) {
return false; return false;
} }
......
...@@ -104,6 +104,8 @@ source_set("crw_context_menu_controller") { ...@@ -104,6 +104,8 @@ source_set("crw_context_menu_controller") {
] ]
sources = [ sources = [
"crw_context_menu_controller.h",
"crw_context_menu_controller.mm",
"crw_context_menu_element_fetcher.h", "crw_context_menu_element_fetcher.h",
"crw_context_menu_element_fetcher.mm", "crw_context_menu_element_fetcher.mm",
"crw_html_element_fetch_request.h", "crw_html_element_fetch_request.h",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_WEB_WEB_STATE_UI_CRW_CONTEXT_MENU_CONTROLLER_H_
#define IOS_WEB_WEB_STATE_UI_CRW_CONTEXT_MENU_CONTROLLER_H_
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
namespace web {
class WebState;
}
// Controller for displaying system Context Menu when the user is long pressing
// on an element. This is working by adding an interaction to the whole web view
// and then only adding focus on the element being long pressed.
API_AVAILABLE(ios(13.0)) @interface CRWContextMenuController : NSObject
- (instancetype)initWithWebView:(WKWebView*)webView
webState:(web::WebState*)webState;
@end
#endif // IOS_WEB_WEB_STATE_UI_CRW_CONTEXT_MENU_CONTROLLER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/web_state/ui/crw_context_menu_controller.h"
#import "ios/web/public/ui/context_menu_params.h"
#import "ios/web/web_state/ui/crw_context_menu_element_fetcher.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const CGFloat kJavaScriptTimeout = 1;
} // namespace
@interface CRWContextMenuController () <UIContextMenuInteractionDelegate>
@property(nonatomic, assign) web::ContextMenuParams params;
// The context menu responsible for the interaction.
@property(nonatomic, strong) UIContextMenuInteraction* contextMenu;
@property(nonatomic, strong) WKWebView* webView;
@property(nonatomic, assign) web::WebState* webState;
@property(nonatomic, strong) CRWContextMenuElementFetcher* elementFetcher;
@end
@implementation CRWContextMenuController
- (instancetype)initWithWebView:(WKWebView*)webView
webState:(web::WebState*)webState {
self = [super init];
if (self) {
_contextMenu = [[UIContextMenuInteraction alloc] initWithDelegate:self];
_webView = webView;
[webView addInteraction:_contextMenu];
_webState = webState;
_elementFetcher =
[[CRWContextMenuElementFetcher alloc] initWithWebView:webView
webState:webState];
}
return self;
}
#pragma mark - UIContextMenuInteractionDelegate
- (UIContextMenuConfiguration*)contextMenuInteraction:
(UIContextMenuInteraction*)interaction
configurationForMenuAtLocation:(CGPoint)location {
CGPoint locationInWebView =
[self.webView.scrollView convertPoint:location fromView:interaction.view];
// While traditionally using dispatch_async would be used here, we have to
// instead use CFRunLoop because dispatch_async blocks the thread. As this
// function is called by iOS when it detects the user's force touch, it is on
// the main thread and we cannot block that. CFRunLoop instead just loops on
// the main thread until the completion block is fired.
__block BOOL isRunLoopNested = NO;
__block BOOL javascriptEvaluationComplete = NO;
__block BOOL isRunLoopComplete = NO;
__weak __typeof(self) weakSelf = self;
[self.elementFetcher
fetchDOMElementAtPoint:locationInWebView
completionHandler:^(const web::ContextMenuParams& params) {
__typeof(self) strongSelf = weakSelf;
javascriptEvaluationComplete = YES;
strongSelf.params = params;
if (isRunLoopNested) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
}];
// Make sure to timeout in case the JavaScript doesn't return in a timely
// manner. While this is executing, the scrolling on the page is frozen.
// Interacting with the page will force this method to return even before any
// of this code is called.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(kJavaScriptTimeout * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
if (!isRunLoopComplete) {
// JavaScript didn't complete. Cancel the JavaScript and
// return.
CFRunLoopStop(CFRunLoopGetCurrent());
__typeof(self) strongSelf = weakSelf;
[strongSelf.elementFetcher cancelFetches];
}
});
// CFRunLoopRun isn't necessary if javascript evaluation is completed by the
// time we reach this line.
if (!javascriptEvaluationComplete) {
isRunLoopNested = YES;
CFRunLoopRun();
isRunLoopNested = NO;
}
isRunLoopComplete = YES;
self.params.location = [self.webView convertPoint:location
fromView:interaction.view];
// TODO(crbug.com/1140387): Present the context menu with the params.
return nil;
}
@end
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
#import "ios/web/security/crw_ssl_status_updater.h" #import "ios/web/security/crw_ssl_status_updater.h"
#import "ios/web/web_state/page_viewport_state.h" #import "ios/web/web_state/page_viewport_state.h"
#import "ios/web/web_state/ui/cookie_blocking_error_logger.h" #import "ios/web/web_state/ui/cookie_blocking_error_logger.h"
#import "ios/web/web_state/ui/crw_context_menu_controller.h"
#import "ios/web/web_state/ui/crw_legacy_context_menu_controller.h" #import "ios/web/web_state/ui/crw_legacy_context_menu_controller.h"
#import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h" #import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
#import "ios/web/web_state/ui/crw_web_controller_container_view.h" #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
...@@ -223,6 +224,10 @@ NSString* const kSessionRestoreScriptMessageName = @"session_restore"; ...@@ -223,6 +224,10 @@ NSString* const kSessionRestoreScriptMessageName = @"session_restore";
// Returns the navigation item for the current page. // Returns the navigation item for the current page.
@property(nonatomic, readonly) web::NavigationItemImpl* currentNavItem; @property(nonatomic, readonly) web::NavigationItemImpl* currentNavItem;
// ContextMenu controller, handling the interactions with the context menu.
@property(nonatomic, strong)
CRWContextMenuController* contextMenuController API_AVAILABLE(ios(13.0));
// Returns the current URL of the web view, and sets |trustLevel| accordingly // Returns the current URL of the web view, and sets |trustLevel| accordingly
// based on the confidence in the verification. // based on the confidence in the verification.
- (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel; - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel;
...@@ -1550,10 +1555,22 @@ typedef void (^ViewportStateCompletion)(const web::PageViewportState*); ...@@ -1550,10 +1555,22 @@ typedef void (^ViewportStateCompletion)(const web::PageViewportState*);
requireGestureRecognizerToFail:swipeRecognizer]; requireGestureRecognizerToFail:swipeRecognizer];
} }
self.UIHandler.contextMenuController = BOOL usingNewContextMenu = NO;
[[CRWLegacyContextMenuController alloc] if (web::GetWebClient()->EnableLongPressUIContextMenu()) {
if (@available(iOS 13, *)) {
usingNewContextMenu = YES;
self.contextMenuController = [[CRWContextMenuController alloc]
initWithWebView:self.webView initWithWebView:self.webView
webState:self.webStateImpl]; webState:self.webStateImpl];
}
}
if (!usingNewContextMenu) {
// Default to legacy implementation.
self.UIHandler.contextMenuController =
[[CRWLegacyContextMenuController alloc]
initWithWebView:self.webView
webState:self.webStateImpl];
}
// WKWebViews with invalid or empty frames have exhibited rendering bugs, so // WKWebViews with invalid or empty frames have exhibited rendering bugs, so
// resize the view to match the container view upon creation. // resize the view to match the container view upon creation.
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
// Delegate for the handler. // Delegate for the handler.
@property(nonatomic, weak) id<CRWWKUIHandlerDelegate> delegate; @property(nonatomic, weak) id<CRWWKUIHandlerDelegate> delegate;
// TODO(crbug.com/1156636): Remove once the new context menus have shipped.
// Context menu controller, to be set when the WebView is created. // Context menu controller, to be set when the WebView is created.
@property(nonatomic, strong) @property(nonatomic, strong)
CRWLegacyContextMenuController* contextMenuController; CRWLegacyContextMenuController* contextMenuController;
......
...@@ -125,9 +125,11 @@ void WKWebViewConfigurationProvider::ResetWithWebViewConfiguration( ...@@ -125,9 +125,11 @@ void WKWebViewConfigurationProvider::ResetWithWebViewConfiguration(
// displayed and also prevents the iOS 13 ContextMenu delegate methods // displayed and also prevents the iOS 13 ContextMenu delegate methods
// from being called. // from being called.
// https://github.com/WebKit/webkit/blob/1233effdb7826a5f03b3cdc0f67d713741e70976/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm#L307 // https://github.com/WebKit/webkit/blob/1233effdb7826a5f03b3cdc0f67d713741e70976/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm#L307
BOOL enable_long_press_action = BOOL disable_long_press_system_actions =
!web::GetWebClient()->EnableLongPressAndForceTouchHandling();
[configuration_ setValue:@(enable_long_press_action) web::GetWebClient()->EnableLongPressAndForceTouchHandling() ||
web::GetWebClient()->EnableLongPressUIContextMenu();
[configuration_ setValue:@(!disable_long_press_system_actions)
forKey:@"longPressActionsEnabled"]; forKey:@"longPressActionsEnabled"];
} @catch (NSException* exception) { } @catch (NSException* exception) {
NOTREACHED() << "Error setting value for longPressActionsEnabled"; NOTREACHED() << "Error setting value for longPressActionsEnabled";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment