Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(494)

Unified Diff: ios/web/web_state/ui/crw_web_controller.mm

Issue 2627093003: Reuse context menu in StaticHTMLViewController (Closed)
Patch Set: feedback Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/web/web_state/ui/crw_web_controller.mm
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 0786c951cc97174e75b826167bc78b2134735ec1..ee3d0d122224a803df08cefc3ec9ae68fba82b52 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -37,7 +37,6 @@
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
-#include "components/url_formatter/url_formatter.h"
#import "ios/net/http_response_headers_util.h"
#import "ios/net/nsurlrequest_util.h"
#include "ios/web/history_state_util.h"
@@ -73,6 +72,7 @@
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#include "ios/web/public/web_state/page_display_state.h"
#import "ios/web/public/web_state/ui/crw_content_view.h"
+#import "ios/web/public/web_state/ui/crw_context_menu_delegate.h"
#import "ios/web/public/web_state/ui/crw_native_content.h"
#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
#import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
@@ -87,6 +87,7 @@
#import "ios/web/web_state/js/crw_js_post_request_loader.h"
#import "ios/web/web_state/js/crw_js_window_id_manager.h"
#import "ios/web/web_state/page_viewport_state.h"
+#import "ios/web/web_state/ui/crw_context_menu_controller.h"
#import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/ui/crw_web_controller_container_view.h"
@@ -217,14 +218,6 @@ enum ExternalURLRequestStatus {
NUM_EXTERNAL_URL_REQUEST_STATUS
};
-// Cancels touch events for the given gesture recognizer.
-void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
- if (gesture_recognizer.enabled) {
- gesture_recognizer.enabled = NO;
- gesture_recognizer.enabled = YES;
- }
-}
-
// Utility function for getting the source of NSErrors received by WKWebViews.
WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
DCHECK(error);
@@ -284,7 +277,8 @@ NSError* WKWebViewErrorWithSource(NSError* error, WKWebViewErrorSource source) {
}
@end
-@interface CRWWebController ()<CRWNativeContentDelegate,
+@interface CRWWebController ()<CRWContextMenuDelegate,
+ CRWNativeContentDelegate,
CRWSSLStatusUpdaterDataSource,
CRWSSLStatusUpdaterDelegate,
CRWWebControllerContainerViewDelegate,
@@ -363,15 +357,8 @@ NSError* WKWebViewErrorWithSource(NSError* error, WKWebViewErrorSource source) {
// The touch tracking recognizer allowing us to decide if a navigation is
// started by the user.
base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
- // Long press recognizer that allows showing context menus.
- base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
- // DOM element information for the point where the user made the last touch.
- // Can be nil if has not been calculated yet. Precalculation is necessary
- // because retreiving DOM element relies on async API so element info can not
- // be built on demand. May contain the following keys: @"href", @"src",
- // @"title", @"referrerPolicy". All values are strings. Used for showing
- // context menus.
- base::scoped_nsobject<NSDictionary> _DOMElementForLastTouch;
+ // The controller that tracks long press and check context menu trigger.
+ base::scoped_nsobject<CRWContextMenuController> _contextMenuController;
// Whether a click is in progress.
BOOL _clickInProgress;
// Data on the recorded last user interaction.
@@ -735,22 +722,6 @@ typedef void (^ViewportStateCompletion)(const web::PageViewportState*);
// Sets scroll offset value for webview scroll view from |scrollState|.
- (void)applyWebViewScrollOffsetFromScrollState:
(const web::PageScrollState&)scrollState;
-// Asynchronously fetches full width of the rendered web page.
-- (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
-// Asynchronously fetches information about DOM element for the given point (in
-// UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
-// for element format description.
-- (void)fetchDOMElementAtPoint:(CGPoint)point
- completionHandler:(void (^)(NSDictionary*))handler;
-// Extracts context menu information from the given DOM element.
-- (web::ContextMenuParams)contextMenuParamsForElement:(NSDictionary*)element;
-// Sets the value of |_DOMElementForLastTouch|.
-- (void)setDOMElementForLastTouch:(NSDictionary*)element;
-// Called when the window has determined there was a long-press and context menu
-// must be shown.
-- (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
-// Cancels all touch events in the web view (long presses, tapping, scrolling).
-- (void)cancelAllTouches;
// Returns the referrer for the current page.
- (web::Referrer)currentReferrer;
// Asynchronously returns the referrer policy for the current page.
@@ -989,14 +960,6 @@ namespace {
NSString* const kReferrerHeaderName = @"Referer"; // [sic]
-// The long press detection duration must be shorter than the UIWebView's
-// long click gesture recognizer's minimum duration. That is 0.55s.
-// If our detection duration is shorter, our gesture recognizer will fire
-// first, and if it fails the long click gesture (processed simultaneously)
-// still is able to complete.
-const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
-const CGFloat kLongPressMoveDeltaPixels = 10.0;
-
// The duration of the period following a screen touch during which the user is
// still considered to be interacting with the page.
const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
@@ -1304,48 +1267,6 @@ const NSTimeInterval kSnapshotOverlayTransition = 0.5;
_webStateImpl = nullptr;
}
-- (void)setDOMElementForLastTouch:(NSDictionary*)element {
- _DOMElementForLastTouch.reset([element copy]);
-}
-
-- (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
- // We don't want ongoing notification that the long press is held.
- if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
- return;
-
- if (![_DOMElementForLastTouch count])
- return;
-
- web::ContextMenuParams params =
- [self contextMenuParamsForElement:_DOMElementForLastTouch.get()];
- params.view.reset([_webView retain]);
- params.location = [gestureRecognizer locationInView:_webView];
- if (self.webStateImpl->HandleContextMenu(params)) {
- // Cancelling all touches has the intended side effect of suppressing the
- // system's context menu.
- [self cancelAllTouches];
- }
-}
-
-- (void)cancelAllTouches {
- // Disable web view scrolling.
- CancelTouches(self.webScrollView.panGestureRecognizer);
-
- // All user gestures are handled by a subview of web view scroll view
- // (WKContentView).
- for (UIView* subview in self.webScrollView.subviews) {
- for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
- CancelTouches(recognizer);
- }
- }
-
- // Just disabling/enabling the gesture recognizers is not enough to suppress
- // the click handlers on the JS side. This JS performs the function of
- // suppressing these handlers on the JS side.
- NSString* suppressNextClick = @"__gCrWeb.suppressNextClick()";
- [self executeJavaScript:suppressNextClick completionHandler:nil];
-}
-
// TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
// it as part of WebDelegate delegate API such that a default image is returned
// immediately.
@@ -1583,29 +1504,6 @@ const NSTimeInterval kSnapshotOverlayTransition = 0.5;
[_windowIDJSManager inject];
}
-// Set the specified recognizer to take priority over any recognizers in the
-// view that have a description containing the specified text fragment.
-+ (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
- inView:(UIView*)view
- containingDescription:(NSString*)fragment {
- for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
- if (iRecognizer != recognizer) {
- NSString* description = [iRecognizer description];
- if ([description rangeOfString:fragment].length) {
- [iRecognizer requireGestureRecognizerToFail:recognizer];
- // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
- // is possible for |iRecognizer| to outlive |recognizer| and end up with
- // a dangling pointer. Add a retaining associative reference to ensure
- // that the lifetimes work out.
- // Note that normally using the value as the key wouldn't make any
- // sense, but here it's fine since nothing needs to look up the value.
- objc_setAssociatedObject(view, recognizer, recognizer,
- OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- }
- }
-}
-
- (CRWWebController*)createChildWebController {
CRWWebController* result = [self.delegate webPageOrderedOpen];
DCHECK(!result || result.sessionController.openedByDOM);
@@ -3606,53 +3504,6 @@ const NSTimeInterval kSnapshotOverlayTransition = 0.5;
}
#pragma mark -
-#pragma mark UIGestureRecognizerDelegate
-
-- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
- shouldRecognizeSimultaneouslyWithGestureRecognizer:
- (UIGestureRecognizer*)otherGestureRecognizer {
- // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
- // other recognizers.
- return YES;
-}
-
-- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
- shouldReceiveTouch:(UITouch*)touch {
- // Expect only _contextMenuRecognizer.
- DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
-
- // This is custom long press gesture recognizer. By the time the gesture is
- // recognized the web controller needs to know if there is a link under the
- // touch. If there a link, the web controller will reject system's context
- // menu and show another one. If for some reason context menu info is not
- // fetched - system context menu will be shown.
- [self setDOMElementForLastTouch:nil];
- base::WeakNSObject<CRWWebController> weakSelf(self);
- [self fetchDOMElementAtPoint:[touch locationInView:_webView]
- completionHandler:^(NSDictionary* element) {
- [weakSelf setDOMElementForLastTouch:element];
- }];
- return YES;
-}
-
-- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
- // Expect only _contextMenuRecognizer.
- DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
- if (!_webView) {
- // Show the context menu iff currently displaying a web view.
- // Do nothing for native views.
- return NO;
- }
-
- // Fetching is considered as successful even if |_DOMElementForLastTouch| is
- // empty. However if |_DOMElementForLastTouch| is empty then custom context
- // menu should not be shown.
- UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
- !!_DOMElementForLastTouch);
- return [_DOMElementForLastTouch count];
-}
-
-#pragma mark -
#pragma mark CRWRequestTrackerDelegate
- (BOOL)isForStaticFileRequests {
@@ -4316,82 +4167,6 @@ const NSTimeInterval kSnapshotOverlayTransition = 0.5;
}
#pragma mark -
-#pragma mark Web Page Features
-
-- (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
- if (!_webView) {
- handler(0);
- return;
- }
-
- [self executeJavaScript:@"__gCrWeb.getPageWidth();"
- completionHandler:^(id pageWidth, NSError*) {
- handler([base::mac::ObjCCastStrict<NSNumber>(pageWidth) floatValue]);
- }];
-}
-
-- (void)fetchDOMElementAtPoint:(CGPoint)point
- completionHandler:(void (^)(NSDictionary*))handler {
- DCHECK(handler);
- // Convert point into web page's coordinate system (which may be scaled and/or
- // scrolled).
- CGPoint scrollOffset = self.scrollPosition;
- CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
- base::WeakNSObject<CRWWebController> weakSelf(self);
- [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
- CGFloat scale = pageWidth / webViewContentWidth;
- CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
- (point.y + scrollOffset.y) * scale);
- NSString* const kGetElementScript =
- [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
- localPoint.x, localPoint.y];
- [weakSelf executeJavaScript:kGetElementScript
- completionHandler:^(id element, NSError*) {
- handler(base::mac::ObjCCastStrict<NSDictionary>(element));
- }];
- }];
-}
-
-- (web::ContextMenuParams)contextMenuParamsForElement:(NSDictionary*)element {
- web::ContextMenuParams params;
- NSString* title = nil;
- NSString* href = element[@"href"];
- if (href) {
- params.link_url = GURL(base::SysNSStringToUTF8(href));
- if (params.link_url.SchemeIs(url::kJavaScriptScheme)) {
- title = @"JavaScript";
- } else {
- base::string16 URLText = url_formatter::FormatUrl(params.link_url);
- title = base::SysUTF16ToNSString(URLText);
- }
- }
- NSString* src = element[@"src"];
- if (src) {
- params.src_url = GURL(base::SysNSStringToUTF8(src));
- if (!title)
- title = [[src copy] autorelease];
- if ([title hasPrefix:base::SysUTF8ToNSString(url::kDataScheme)])
- title = nil;
- }
- NSString* titleAttribute = element[@"title"];
- if (titleAttribute)
- title = titleAttribute;
- if (title) {
- params.menu_title.reset([title copy]);
- }
- NSString* referrerPolicy = element[@"referrerPolicy"];
- if (referrerPolicy) {
- params.referrer_policy =
- web::ReferrerPolicyFromString(base::SysNSStringToUTF8(referrerPolicy));
- }
- NSString* innerText = element[@"innerText"];
- if ([innerText length] > 0) {
- params.link_text.reset([innerText copy]);
- }
- return params;
-}
-
-#pragma mark -
#pragma mark Fullscreen
- (CGRect)visibleFrame {
@@ -4674,44 +4449,10 @@ const NSTimeInterval kSnapshotOverlayTransition = 0.5;
requireGestureRecognizerToFail:swipeRecognizer];
}
- // On iOS 4.x, there are two gesture recognizers on the UIWebView
- // subclasses,
- // that have a minimum tap threshold of 0.12s and 0.75s.
- //
- // My theory is that the shorter threshold recognizer performs the link
- // highlight (grey highlight around links when it is tapped and held) while
- // the longer threshold one pops up the context menu.
- //
- // To override the context menu, this recognizer needs to react faster than
- // the 0.75s one. The below gesture recognizer is initialized with a
- // detection duration a little lower than that (see
- // kLongPressDurationSeconds). It also points the delegate to this class
- // that
- // allows simultaneously operate along with the other recognizers.
- _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
- initWithTarget:self
- action:@selector(showContextMenu:)]);
- [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
- [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
- [_contextMenuRecognizer setDelegate:self];
- [_webView addGestureRecognizer:_contextMenuRecognizer];
- // Certain system gesture handlers are known to conflict with our context
- // menu handler, causing extra events to fire when the context menu is
- // active.
-
- // A number of solutions have been investigated. The lowest-risk solution
- // appears to be to recurse through the web controller's recognizers,
- // looking
- // for fingerprints of the recognizers known to cause problems, which are
- // then
- // de-prioritized (below our own long click handler).
- // Hunting for description fragments of system recognizers is undeniably
- // brittle for future versions of iOS. If it does break the context menu
- // events may leak (regressing b/5310177), but the app will otherwise work.
- [CRWWebController requireGestureRecognizerToFail:_contextMenuRecognizer
- inView:_webView
- containingDescription:
- @"action=_highlightLongPressRecognized:"];
+ _contextMenuController.reset([[CRWContextMenuController alloc]
+ initWithWebView:_webView
+ injectionEvaluator:self
+ delegate:self]);
// Add all additional gesture recognizers to the web view.
for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
@@ -4731,6 +4472,8 @@ const NSTimeInterval kSnapshotOverlayTransition = 0.5;
}
- (WKWebView*)webViewWithConfiguration:(WKWebViewConfiguration*)config {
+ // Do not attach the context menu controller immediately as the JavaScript
+ // delegate must be specified.
return web::BuildWKWebView(CGRectZero, config,
self.webStateImpl->GetBrowserState(),
[self useDesktopUserAgent]);
@@ -5365,6 +5108,29 @@ const NSTimeInterval kSnapshotOverlayTransition = 0.5;
}
#pragma mark -
+#pragma mark CRWWebContextMenuControllerDelegate methods
+
+- (BOOL)webView:(WKWebView*)webView
+ handleContextMenu:(const web::ContextMenuParams&)params {
+ DCHECK(webView == _webView);
+ if (_isBeingDestroyed) {
+ return NO;
+ }
+ return self.webStateImpl->HandleContextMenu(params);
+}
+
+#pragma mark -
+#pragma mark CRWNativeContentDelegate methods
+
+- (BOOL)nativeContent:(id)content
+ handleContextMenu:(const web::ContextMenuParams&)params {
+ if (_isBeingDestroyed) {
+ return NO;
+ }
+ return self.webStateImpl->HandleContextMenu(params);
+}
+
+#pragma mark -
#pragma mark KVO Observation
- (void)observeValueForKeyPath:(NSString*)keyPath
« no previous file with comments | « ios/web/web_state/ui/crw_context_menu_controller.mm ('k') | ios/web/web_state/web_view_internal_creation_util.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698