Extend fast-clicking behavior to trigger on elements that have negligible zoom
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 19 Oct 2015 19:53:45 +0000 (19:53 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 19 Oct 2015 19:53:45 +0000 (19:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150248
<rdar://problem/23140069>

Reviewed by Simon Fraser.

Currently, fast-clicking only triggers on pages that have unscalable viewports. To allow more websites to benefit from fast-clicking
behavior, we generalize fast-clicking to also occur when tapping on elements for which double-tap-to-zoom would zoom the element in
or out by an insignificant amount. We define an insignificant amount of zoom to mean that zooming would increase the viewport scale
by less than a configurable threshold, or decrease the viewport scale by more than a configurable threshold. We accomplish this by
temporarily disabling the double tap gesture recognizer for the duration of the tap. This patch refactors some logic used to compute
zoomed viewports to make it possible to predict the change in viewport scale when double tapping to zoom. See the changes in
SmartMagnificationController and ViewGestureGeometryCollector for more details.

There are no new tests, since this patch does not change existing behavior. Instead, this patch adds the machinery needed for zoom-
dependent fast-clicking: by setting the preferences WebKitFastClickingEnabled and WebKitFastClickZoomThreshold, we will be able to tweak
the behavior dynamically.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _didCommitLayerTree:]):
(-[WKWebView _contentZoomScale]):
(-[WKWebView _targetContentZoomScaleForRect:currentScale:fitEntireRect:minimumScale:maximumScale:]):
(-[WKWebView _zoomToRect:withOrigin:fitEntireRect:minimumScale:maximumScale:minimumScrollDistance:]):
(-[WKWebView _viewportIsUserScalable]):
* UIProcess/API/Cocoa/WKWebViewInternal.h:
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/PageClient.h:
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* UIProcess/ios/PageClientImplIOS.h:
* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::disableDoubleTapGesturesUntilTapIsFinishedIfNecessary):
* UIProcess/ios/SmartMagnificationController.h:
* UIProcess/ios/SmartMagnificationController.mm:
(WebKit::SmartMagnificationController::adjustSmartMagnificationTargetRectAndZoomScales):
(WebKit::SmartMagnificationController::didCollectGeometryForSmartMagnificationGesture):
(WebKit::SmartMagnificationController::magnify):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _didGetTapHighlightForRequest:color:quads:topLeftRadius:topRightRadius:bottomLeftRadius:bottomRightRadius:]):
(-[WKContentView _fastClickZoomThreshold]):
(-[WKContentView _allowDoubleTapToZoomForCurrentZoomScale:andTargetZoomScale:]):
(-[WKContentView _disableDoubleTapGesturesUntilTapIsFinishedIfNecessary:allowsDoubleTapZoom:targetRect:isReplaced:minimumScale:maximumScale:]):
(-[WKContentView _highlightLongPressRecognized:]):
(-[WKContentView _endPotentialTapAndEnableDoubleTapGesturesIfNecessary]):
(-[WKContentView _singleTapRecognized:]):
(cancelPotentialTapIfNecessary):
(-[WKContentView _singleTapCommited:]):
(-[WKContentView webSelectionRects]): Deleted.
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::disableDoubleTapGesturesUntilTapIsFinishedIfNecessary):
* WebProcess/WebPage/ViewGestureGeometryCollector.cpp:
(WebKit::ViewGestureGeometryCollector::collectGeometryForSmartMagnificationGesture):
(WebKit::ViewGestureGeometryCollector::computeZoomInformationForNode):
* WebProcess/WebPage/ViewGestureGeometryCollector.h:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::potentialTapAtPosition):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@191309 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit2/UIProcess/API/Cocoa/WKWebViewInternal.h
Source/WebKit2/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit2/UIProcess/PageClient.h
Source/WebKit2/UIProcess/WebPageProxy.h
Source/WebKit2/UIProcess/WebPageProxy.messages.in
Source/WebKit2/UIProcess/ios/PageClientImplIOS.h
Source/WebKit2/UIProcess/ios/PageClientImplIOS.mm
Source/WebKit2/UIProcess/ios/SmartMagnificationController.h
Source/WebKit2/UIProcess/ios/SmartMagnificationController.mm
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit2/UIProcess/ios/WebPageProxyIOS.mm
Source/WebKit2/WebProcess/WebPage/ViewGestureGeometryCollector.cpp
Source/WebKit2/WebProcess/WebPage/ViewGestureGeometryCollector.h
Source/WebKit2/WebProcess/WebPage/ios/WebPageIOS.mm

index ebfd6cb2f548fab6ee4f4c3d3a12dd998a0fdf9b..6af77fb57464e1d85c4780dd6915f300779f0b44 100644 (file)
@@ -1,3 +1,63 @@
+2015-10-17  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Extend fast-clicking behavior to trigger on elements that have negligible zoom
+        https://bugs.webkit.org/show_bug.cgi?id=150248
+        <rdar://problem/23140069>
+
+        Reviewed by Simon Fraser.
+
+        Currently, fast-clicking only triggers on pages that have unscalable viewports. To allow more websites to benefit from fast-clicking
+        behavior, we generalize fast-clicking to also occur when tapping on elements for which double-tap-to-zoom would zoom the element in
+        or out by an insignificant amount. We define an insignificant amount of zoom to mean that zooming would increase the viewport scale
+        by less than a configurable threshold, or decrease the viewport scale by more than a configurable threshold. We accomplish this by
+        temporarily disabling the double tap gesture recognizer for the duration of the tap. This patch refactors some logic used to compute
+        zoomed viewports to make it possible to predict the change in viewport scale when double tapping to zoom. See the changes in
+        SmartMagnificationController and ViewGestureGeometryCollector for more details.
+
+        There are no new tests, since this patch does not change existing behavior. Instead, this patch adds the machinery needed for zoom-
+        dependent fast-clicking: by setting the preferences WebKitFastClickingEnabled and WebKitFastClickZoomThreshold, we will be able to tweak
+        the behavior dynamically.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _didCommitLayerTree:]):
+        (-[WKWebView _contentZoomScale]):
+        (-[WKWebView _targetContentZoomScaleForRect:currentScale:fitEntireRect:minimumScale:maximumScale:]):
+        (-[WKWebView _zoomToRect:withOrigin:fitEntireRect:minimumScale:maximumScale:minimumScrollDistance:]):
+        (-[WKWebView _viewportIsUserScalable]):
+        * UIProcess/API/Cocoa/WKWebViewInternal.h:
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/PageClient.h:
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * UIProcess/ios/PageClientImplIOS.h:
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::disableDoubleTapGesturesUntilTapIsFinishedIfNecessary):
+        * UIProcess/ios/SmartMagnificationController.h:
+        * UIProcess/ios/SmartMagnificationController.mm:
+        (WebKit::SmartMagnificationController::adjustSmartMagnificationTargetRectAndZoomScales):
+        (WebKit::SmartMagnificationController::didCollectGeometryForSmartMagnificationGesture):
+        (WebKit::SmartMagnificationController::magnify):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView _didGetTapHighlightForRequest:color:quads:topLeftRadius:topRightRadius:bottomLeftRadius:bottomRightRadius:]):
+        (-[WKContentView _fastClickZoomThreshold]):
+        (-[WKContentView _allowDoubleTapToZoomForCurrentZoomScale:andTargetZoomScale:]):
+        (-[WKContentView _disableDoubleTapGesturesUntilTapIsFinishedIfNecessary:allowsDoubleTapZoom:targetRect:isReplaced:minimumScale:maximumScale:]):
+        (-[WKContentView _highlightLongPressRecognized:]):
+        (-[WKContentView _endPotentialTapAndEnableDoubleTapGesturesIfNecessary]):
+        (-[WKContentView _singleTapRecognized:]):
+        (cancelPotentialTapIfNecessary):
+        (-[WKContentView _singleTapCommited:]):
+        (-[WKContentView webSelectionRects]): Deleted.
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::disableDoubleTapGesturesUntilTapIsFinishedIfNecessary):
+        * WebProcess/WebPage/ViewGestureGeometryCollector.cpp:
+        (WebKit::ViewGestureGeometryCollector::collectGeometryForSmartMagnificationGesture):
+        (WebKit::ViewGestureGeometryCollector::computeZoomInformationForNode):
+        * WebProcess/WebPage/ViewGestureGeometryCollector.h:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::potentialTapAtPosition):
+
 2015-10-19  Tim Horton  <timothy_horton@apple.com>
 
         WKView being inside WKWebView leads to weird API issues
index 1d9078fd3d8ff6b7dcf585288634dad32a03db70..42be97adccf64be422a5ba1f7b368db15d407320 100644 (file)
@@ -997,7 +997,7 @@ static inline bool areEssentiallyEqualAsFloat(float a, float b)
     if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom])
         [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()];
 
-    [_contentView _setDoubleTapGesturesEnabled:[_scrollView isZoomEnabled] && [_scrollView minimumZoomScale] < [_scrollView maximumZoomScale]];
+    [_contentView _setDoubleTapGesturesEnabled:self._viewportIsUserScalable];
 
     [self _updateScrollViewBackground];
 
@@ -1395,12 +1395,13 @@ static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOff
                         force:YES];
 }
 
-- (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
+- (CGFloat)_contentZoomScale
 {
-    const float maximumScaleFactorDeltaForPanScroll = 0.02;
-
-    double currentScale = contentZoomScale(self);
+    return contentZoomScale(self);
+}
 
+- (CGFloat)_targetContentZoomScaleForRect:(const WebCore::FloatRect&)targetRect currentScale:(double)currentScale fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale
+{
     WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
     double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width();
     double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height();
@@ -1408,7 +1409,16 @@ static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOff
     horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale);
     verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale);
 
-    double targetScale = fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
+    return fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
+}
+
+- (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
+{
+    const float maximumScaleFactorDeltaForPanScroll = 0.02;
+
+    double currentScale = contentZoomScale(self);
+    double targetScale = [self _targetContentZoomScaleForRect:targetRect currentScale:currentScale fitEntireRect:fitEntireRect minimumScale:minimumScale maximumScale:maximumScale];
+
     if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) {
         if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance])
             return true;
@@ -3066,6 +3076,11 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
     return _viewportMetaTagWidth;
 }
 
+- (BOOL)_viewportIsUserScalable
+{
+    return [_scrollView isZoomEnabled] && [_scrollView minimumZoomScale] < [_scrollView maximumZoomScale];
+}
+
 - (_WKWebViewPrintFormatter *)_webViewPrintFormatter
 {
     UIViewPrintFormatter *viewPrintFormatter = self.viewPrintFormatter;
index 5a4b210a96230204e5a66fb9d80995c5a21dc234..a936a6f18a1fa4109ac684bc0a8fcff214e429ff 100644 (file)
@@ -109,6 +109,9 @@ struct PrintInfo;
 - (void)_navigationGestureDidBegin;
 - (void)_navigationGestureDidEnd;
 
+- (CGFloat)_contentZoomScale;
+- (CGFloat)_targetContentZoomScaleForRect:(const WebCore::FloatRect&)targetRect currentScale:(double)currentScale fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale;
+
 @property (nonatomic, readonly) UIEdgeInsets _computedContentInset;
 #else
 @property (nonatomic, setter=_setIgnoresNonWheelEvents:) BOOL _ignoresNonWheelEvents;
index 96f5f9bab01d972dc0693cfbe23f3bbfe48b0d3d..438d6c51d220af585b754283b2a92920219983b5 100644 (file)
@@ -135,6 +135,7 @@ typedef NS_ENUM(NSInteger, _WKImmediateActionType) {
 
 // The viewport meta tag width is negative if the value is not defined.
 @property (nonatomic, readonly) CGFloat _viewportMetaTagWidth;
+@property (nonatomic, readonly) BOOL _viewportIsUserScalable;
 
 @property (nonatomic, readonly) _WKWebViewPrintFormatter *_webViewPrintFormatter;
 
index 375630ab666cbe6a58a85af280db5c9eacd170fd..9becc4042894f0cdc80f625b5e8556351c455dc9 100644 (file)
@@ -294,6 +294,7 @@ public:
     virtual void showPlaybackTargetPicker(bool hasVideo, const WebCore::IntRect& elementRect) = 0;
     virtual void zoomToRect(WebCore::FloatRect, double minimumScale, double maximumScale) = 0;
     virtual void didChangeViewportMetaTagWidth(float) = 0;
+    virtual void disableDoubleTapGesturesUntilTapIsFinishedIfNecessary(uint64_t requestID, bool allowsDoubleTapZoom, const WebCore::FloatRect& targetRect, bool isReplacedElement, double minimumScale, double maximumScale) = 0;
     virtual double minimumZoomScale() const = 0;
     virtual WebCore::FloatRect documentRect() const = 0;
     virtual void overflowScrollViewWillStartPanGesture() = 0;
index 36521752bf995138c7ef43840f960ff1bb6391b2..441209fa091e20aef2a9f0f7ad268e2cdc8cc851 100644 (file)
@@ -509,6 +509,7 @@ public:
     void commitPotentialTapFailed();
     void didNotHandleTapAsClick(const WebCore::IntPoint&);
     void viewportMetaTagWidthDidChange(float width);
+    void disableDoubleTapGesturesUntilTapIsFinishedIfNecessary(uint64_t requestID, bool allowsDoubleTapZoom, const WebCore::FloatRect& targetRect, bool isReplacedElement, double minimumScale, double maximumScale);
     void didFinishDrawingPagesToPDF(const IPC::DataReference&);
     void contentSizeCategoryDidChange(const String& contentSizeCategory);
     void getLookupContextAtPoint(const WebCore::IntPoint&, std::function<void(const String&, CallbackBase::Error)>);
index 31c07c30e8ec7b387ccf9ea85fe4e0cdba669c09..f548e99ad37ed48c09777e104e8835f113eced49 100644 (file)
@@ -188,6 +188,7 @@ messages -> WebPageProxy {
     CommitPotentialTapFailed()
     DidNotHandleTapAsClick(WebCore::IntPoint point)
     ViewportMetaTagWidthDidChange(float width)
+    DisableDoubleTapGesturesUntilTapIsFinishedIfNecessary(uint64_t requestID, bool allowsDoubleTapZoom, WebCore::FloatRect targetRect, bool isReplacedElement, double minimumScale, double maximumScale)
     DidFinishDrawingPagesToPDF(IPC::DataReference pdfData)
 #endif
 #if PLATFORM(GTK)
index 123d367ea208cf0a05e7df9f2c87101087f835ae..9350ca4626f1646d6883755fc629a9dd2a326d86 100644 (file)
@@ -134,6 +134,7 @@ private:
 
     virtual bool handleRunOpenPanel(WebPageProxy*, WebFrameProxy*, WebOpenPanelParameters*, WebOpenPanelResultListenerProxy*) override;
     virtual void didChangeViewportMetaTagWidth(float) override;
+    virtual void disableDoubleTapGesturesUntilTapIsFinishedIfNecessary(uint64_t requestID, bool allowsDoubleTapZoom, const WebCore::FloatRect& targetRect, bool isReplacedElement, double minimumScale, double maximumScale) override;
     virtual double minimumZoomScale() const override;
     virtual WebCore::FloatRect documentRect() const override;
 
index d76f94166e4ff0aa8d918d4a431c7f9a45b6d3dd..47e4680ff3de875c833eb76933f7729612eb84ff 100644 (file)
@@ -259,6 +259,14 @@ void PageClientImpl::didChangeViewportMetaTagWidth(float newWidth)
     [m_webView _setViewportMetaTagWidth:newWidth];
 }
 
+void PageClientImpl::disableDoubleTapGesturesUntilTapIsFinishedIfNecessary(uint64_t requestID, bool allowsDoubleTapZoom, const WebCore::FloatRect& targetRect, bool isReplacedElement, double minimumScale, double maximumScale)
+{
+    if (!m_webView._viewportIsUserScalable)
+        return;
+
+    [m_contentView _disableDoubleTapGesturesUntilTapIsFinishedIfNecessary:requestID allowsDoubleTapZoom:allowsDoubleTapZoom targetRect:targetRect isReplaced:isReplacedElement minimumScale:minimumScale maximumScale:maximumScale];
+}
+
 double PageClientImpl::minimumZoomScale() const
 {
     if (UIScrollView *scroller = [m_webView scrollView])
index 60ca5d4212213dd45143ca8aee56bbef4001b7a2..b765a0a33e738fb5d3901e3982a9d834f4bb4a1e 100644 (file)
@@ -48,6 +48,7 @@ public:
 
     void handleSmartMagnificationGesture(WebCore::FloatPoint origin);
     void handleResetMagnificationGesture(WebCore::FloatPoint origin);
+    void adjustSmartMagnificationTargetRectAndZoomScales(bool addMagnificationPadding, WebCore::FloatRect& targetRect, double& minimumScale, double& maximumScale);
 
 private:
     // IPC::MessageReceiver.
index f6cd16cc70694c7673846d33b8beb5559d1f0537..410b823957fb839100d6a8982986bbc6e73399de 100644 (file)
@@ -78,6 +78,17 @@ void SmartMagnificationController::handleResetMagnificationGesture(FloatPoint or
     [m_contentView _zoomOutWithOrigin:origin];
 }
 
+void SmartMagnificationController::adjustSmartMagnificationTargetRectAndZoomScales(bool addMagnificationPadding, WebCore::FloatRect& targetRect, double& minimumScale, double& maximumScale)
+{
+    if (addMagnificationPadding) {
+        targetRect.inflateX(smartMagnificationElementPadding * targetRect.width());
+        targetRect.inflateY(smartMagnificationElementPadding * targetRect.height());
+    }
+
+    minimumScale = std::max(minimumScale, smartMagnificationMinimumScale);
+    maximumScale = std::min(maximumScale, smartMagnificationMaximumScale);
+}
+
 void SmartMagnificationController::didCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect targetRect, FloatRect visibleContentRect, bool isReplacedElement, double viewportMinimumScale, double viewportMaximumScale)
 {
     if (targetRect.isEmpty()) {
@@ -85,14 +96,9 @@ void SmartMagnificationController::didCollectGeometryForSmartMagnificationGestur
         [m_contentView _zoomOutWithOrigin:origin];
         return;
     }
-
-    if (!isReplacedElement) {
-        targetRect.inflateX(smartMagnificationElementPadding * targetRect.width());
-        targetRect.inflateY(smartMagnificationElementPadding * targetRect.height());
-    }
-
-    double maximumScale = std::min(viewportMaximumScale, smartMagnificationMaximumScale);
-    double minimumScale = std::max(viewportMinimumScale, smartMagnificationMinimumScale);
+    double minimumScale = viewportMinimumScale;
+    double maximumScale = viewportMaximumScale;
+    adjustSmartMagnificationTargetRectAndZoomScales(!isReplacedElement, targetRect, minimumScale, maximumScale);
 
     // FIXME: Check if text selection wants to consume the double tap before we attempt magnification.
 
@@ -118,12 +124,9 @@ void SmartMagnificationController::didCollectGeometryForSmartMagnificationGestur
 
 void SmartMagnificationController::magnify(FloatPoint origin, FloatRect targetRect, FloatRect visibleContentRect, double viewportMinimumScale, double viewportMaximumScale)
 {
-    targetRect.inflateX(smartMagnificationElementPadding * targetRect.width());
-    targetRect.inflateY(smartMagnificationElementPadding * targetRect.height());
-
-    double maximumScale = std::min(viewportMaximumScale, smartMagnificationMaximumScale);
-    double minimumScale = std::max(viewportMinimumScale, smartMagnificationMinimumScale);
-
+    double maximumScale = viewportMaximumScale;
+    double minimumScale = viewportMinimumScale;
+    adjustSmartMagnificationTargetRectAndZoomScales(true, targetRect, minimumScale, maximumScale);
     [m_contentView _zoomToRect:targetRect withOrigin:origin fitEntireRect:NO minimumScale:minimumScale maximumScale:maximumScale minimumScrollDistance:0];
 }
 
index 49b419cdff94603a0f8b66ac8ff338a8f731de5f..f07d84332aa3fdf0c022fec41db7fb1c970a260a 100644 (file)
@@ -126,7 +126,7 @@ struct WKAutoCorrectionData {
 
     id <UITextInputDelegate> _inputDelegate;
 
-    uint64_t _latestTapHighlightID;
+    uint64_t _latestTapID;
     struct TapHighlightInformation {
         WebCore::Color color;
         Vector<WebCore::FloatQuad> quads;
@@ -182,6 +182,7 @@ struct WKAutoCorrectionData {
 - (void)_commitPotentialTapFailed;
 - (void)_didGetTapHighlightForRequest:(uint64_t)requestID color:(const WebCore::Color&)color quads:(const Vector<WebCore::FloatQuad>&)highlightedQuads topLeftRadius:(const WebCore::IntSize&)topLeftRadius topRightRadius:(const WebCore::IntSize&)topRightRadius bottomLeftRadius:(const WebCore::IntSize&)bottomLeftRadius bottomRightRadius:(const WebCore::IntSize&)bottomRightRadius;
 
+- (void)_disableDoubleTapGesturesUntilTapIsFinishedIfNecessary:(uint64_t)requestID allowsDoubleTapZoom:(bool)allowsDoubleTapZoom targetRect:(WebCore::FloatRect)targetRect isReplaced:(BOOL)isReplacedElement minimumScale:(double)minimumScale maximumScale:(double)maximumScale;
 - (void)_startAssistingNode:(const WebKit::AssistedNodeInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode userObject:(NSObject <NSSecureCoding> *)userObject;
 - (void)_stopAssistingNode;
 - (void)_selectionChanged;
index 879a7e63133c2cabb81edd57ca31bd7d7c6470d4..a03488990b8340b9e76ec9889a2df42ba91e7724 100644 (file)
@@ -831,7 +831,7 @@ static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius,
 
 - (void)_didGetTapHighlightForRequest:(uint64_t)requestID color:(const WebCore::Color&)color quads:(const Vector<WebCore::FloatQuad>&)highlightedQuads topLeftRadius:(const WebCore::IntSize&)topLeftRadius topRightRadius:(const WebCore::IntSize&)topRightRadius bottomLeftRadius:(const WebCore::IntSize&)bottomLeftRadius bottomRightRadius:(const WebCore::IntSize&)bottomRightRadius
 {
-    if (!_isTapHighlightIDValid || _latestTapHighlightID != requestID)
+    if (!_isTapHighlightIDValid || _latestTapID != requestID)
         return;
 
     _isTapHighlightIDValid = NO;
@@ -851,6 +851,44 @@ static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius,
     [self _showTapHighlight];
 }
 
+- (CGFloat)_fastClickZoomThreshold
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    if (![defaults boolForKey:@"WebKitFastClickingEnabled"])
+        return 0;
+
+    return [defaults floatForKey:@"WebKitFastClickZoomThreshold"];
+}
+
+- (BOOL)_allowDoubleTapToZoomForCurrentZoomScale:(CGFloat)currentZoomScale andTargetZoomScale:(CGFloat)targetZoomScale
+{
+    CGFloat zoomThreshold = [self _fastClickZoomThreshold];
+    if (!zoomThreshold)
+        return YES;
+
+    CGFloat minimumZoomRatioForDoubleTapToZoomIn = 1 + zoomThreshold;
+    CGFloat maximumZoomRatioForDoubleTapToZoomOut = 1 / minimumZoomRatioForDoubleTapToZoomIn;
+    CGFloat zoomRatio = targetZoomScale / currentZoomScale;
+    return zoomRatio < maximumZoomRatioForDoubleTapToZoomOut || zoomRatio > minimumZoomRatioForDoubleTapToZoomIn;
+}
+
+- (void)_disableDoubleTapGesturesUntilTapIsFinishedIfNecessary:(uint64_t)requestID allowsDoubleTapZoom:(bool)allowsDoubleTapZoom targetRect:(WebCore::FloatRect)targetRect isReplaced:(BOOL)isReplacedElement minimumScale:(double)minimumScale maximumScale:(double)maximumScale
+{
+    if (!_potentialTapInProgress || _latestTapID != requestID)
+        return;
+
+    if (allowsDoubleTapZoom) {
+        // Though the element allows us to zoom in on double tap, we avoid this behavior in favor of fast clicking if the difference in scale is insignificant.
+        _smartMagnificationController->adjustSmartMagnificationTargetRectAndZoomScales(!isReplacedElement, targetRect, minimumScale, maximumScale);
+        CGFloat currentZoomScale = [_webView _contentZoomScale];
+        CGFloat targetZoomScale = [_webView _targetContentZoomScaleForRect:targetRect currentScale:currentZoomScale fitEntireRect:isReplacedElement minimumScale:minimumScale maximumScale:maximumScale];
+        if ([self _allowDoubleTapToZoomForCurrentZoomScale:currentZoomScale andTargetZoomScale:targetZoomScale])
+            return;
+    }
+
+    [self _setDoubleTapGesturesEnabled:NO];
+}
+
 - (void)_cancelLongPressGestureRecognizer
 {
     [_highlightLongPressGestureRecognizer cancel];
@@ -1155,7 +1193,7 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
     case UIGestureRecognizerStateBegan:
         _highlightLongPressCanClick = YES;
         cancelPotentialTapIfNecessary(self);
-        _page->tapHighlightAtPosition([gestureRecognizer startPoint], ++_latestTapHighlightID);
+        _page->tapHighlightAtPosition([gestureRecognizer startPoint], ++_latestTapID);
         _isTapHighlightIDValid = YES;
         break;
     case UIGestureRecognizerStateEnded:
@@ -1190,12 +1228,20 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
     }
 }
 
+- (void)_endPotentialTapAndEnableDoubleTapGesturesIfNecessary
+{
+    if (_webView._viewportIsUserScalable)
+        [self _setDoubleTapGesturesEnabled:YES];
+
+    _potentialTapInProgress = NO;
+}
+
 - (void)_singleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
 {
     ASSERT(gestureRecognizer == _singleTapGestureRecognizer);
     ASSERT(!_potentialTapInProgress);
 
-    _page->potentialTapAtPosition(gestureRecognizer.location, ++_latestTapHighlightID);
+    _page->potentialTapAtPosition(gestureRecognizer.location, ++_latestTapID);
     _potentialTapInProgress = YES;
     _isTapHighlightIDValid = YES;
 }
@@ -1203,7 +1249,7 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
 static void cancelPotentialTapIfNecessary(WKContentView* contentView)
 {
     if (contentView->_potentialTapInProgress) {
-        contentView->_potentialTapInProgress = NO;
+        [contentView _endPotentialTapAndEnableDoubleTapGesturesIfNecessary];
         [contentView _cancelInteraction];
         contentView->_page->cancelPotentialTap();
     }
@@ -1242,7 +1288,7 @@ static void cancelPotentialTapIfNecessary(WKContentView* contentView)
 
     _lastInteractionLocation = gestureRecognizer.location;
 
-    _potentialTapInProgress = NO;
+    [self _endPotentialTapAndEnableDoubleTapGesturesIfNecessary];
 
     if (_hasTapHighlightForPotentialTap) {
         [self _showTapHighlight];
index 0a329d092880ab7bc8f29383e804b1f9290ae649..7d307cfe2495da503233fa249cb3f9e2b6d27f84 100644 (file)
@@ -937,6 +937,11 @@ void WebPageProxy::viewportMetaTagWidthDidChange(float width)
     m_pageClient.didChangeViewportMetaTagWidth(width);
 }
 
+void WebPageProxy::disableDoubleTapGesturesUntilTapIsFinishedIfNecessary(uint64_t requestID, bool allowsDoubleTapZoom, const WebCore::FloatRect& targetRect, bool isReplacedElement, double minimumScale, double maximumScale)
+{
+    m_pageClient.disableDoubleTapGesturesUntilTapIsFinishedIfNecessary(requestID, allowsDoubleTapZoom, targetRect, isReplacedElement, minimumScale, maximumScale);
+}
+
 void WebPageProxy::didFinishDrawingPagesToPDF(const IPC::DataReference& pdfData)
 {
     m_pageClient.didFinishDrawingPagesToPDF(pdfData);
index df7013d2ff6f1c046b237cf3e1be8714c76557a7..02ee9cd4ebb7fb3cd44b075bcc58efaafbe66573 100644 (file)
@@ -86,39 +86,45 @@ void ViewGestureGeometryCollector::collectGeometryForSmartMagnificationGesture(F
     HitTestResult hitTestResult = HitTestResult(originInContentsSpace);
 
     m_webPage.mainFrameView()->renderView()->hitTest(HitTestRequest(), hitTestResult);
+    Node* node = hitTestResult.innerNode();
+    if (!node) {
+        dispatchDidCollectGeometryForSmartMagnificationGesture(FloatPoint(), FloatRect(), FloatRect(), false, 0, 0);
+        return;
+    }
+
+    bool isReplaced;
+    FloatRect renderRect;
+    double viewportMinimumScale;
+    double viewportMaximumScale;
 
-    if (Node* node = hitTestResult.innerNode()) {
-        bool isReplaced;
-        FloatRect renderRect = node->renderRect(&isReplaced);
-
-        if (node->document().isImageDocument()) {
-            if (HTMLImageElement* imageElement = static_cast<ImageDocument&>(node->document()).imageElement()) {
-                if (node != imageElement) {
-                    renderRect = imageElement->renderRect(&isReplaced);
-                    FloatPoint newOrigin = origin;
-                    if (origin.x() < renderRect.x() || origin.x() > renderRect.maxX())
-                        newOrigin.setX(renderRect.x() + renderRect.width() / 2);
-                    if (origin.y() < renderRect.y() || origin.y() > renderRect.maxY())
-                        newOrigin.setY(renderRect.y() + renderRect.height() / 2);
-                    origin = newOrigin;
-                }
-                isReplaced = true;
+    computeZoomInformationForNode(*node, origin, renderRect, isReplaced, viewportMinimumScale, viewportMaximumScale);
+    dispatchDidCollectGeometryForSmartMagnificationGesture(origin, renderRect, visibleContentRect, isReplaced, viewportMinimumScale, viewportMaximumScale);
+}
+
+void ViewGestureGeometryCollector::computeZoomInformationForNode(Node& node, FloatPoint& origin, FloatRect& renderRect, bool& isReplaced, double& viewportMinimumScale, double& viewportMaximumScale)
+{
+    renderRect = node.renderRect(&isReplaced);
+    if (node.document().isImageDocument()) {
+        if (HTMLImageElement* imageElement = static_cast<ImageDocument&>(node.document()).imageElement()) {
+            if (&node != imageElement) {
+                renderRect = imageElement->renderRect(&isReplaced);
+                FloatPoint newOrigin = origin;
+                if (origin.x() < renderRect.x() || origin.x() > renderRect.maxX())
+                    newOrigin.setX(renderRect.x() + renderRect.width() / 2);
+                if (origin.y() < renderRect.y() || origin.y() > renderRect.maxY())
+                    newOrigin.setY(renderRect.y() + renderRect.height() / 2);
+                origin = newOrigin;
             }
+            isReplaced = true;
         }
-
+    }
 #if PLATFORM(MAC)
-        double viewportMinimumScale = 0;
-        double viewportMaximumScale = std::numeric_limits<double>::max();
+    viewportMinimumScale = 0;
+    viewportMaximumScale = std::numeric_limits<double>::max();
 #else
-        double viewportMinimumScale = m_webPage.minimumPageScaleFactor();
-        double viewportMaximumScale = m_webPage.maximumPageScaleFactor();
+    viewportMinimumScale = m_webPage.minimumPageScaleFactor();
+    viewportMaximumScale = m_webPage.maximumPageScaleFactor();
 #endif
-
-        dispatchDidCollectGeometryForSmartMagnificationGesture(origin, renderRect, visibleContentRect, isReplaced, viewportMinimumScale, viewportMaximumScale);
-        return;
-    }
-
-    dispatchDidCollectGeometryForSmartMagnificationGesture(FloatPoint(), FloatRect(), FloatRect(), false, 0, 0);
 }
 
 #if PLATFORM(MAC)
index 7ab01996685d38443285970ee38d6c17814bc661..98d9c97b2c4d133ce19186c916eef40c7ebdbf23 100644 (file)
@@ -32,6 +32,7 @@
 namespace WebCore {
 class FloatPoint;
 class FloatRect;
+class Node;
 }
 
 namespace WebKit {
@@ -44,6 +45,7 @@ public:
     ~ViewGestureGeometryCollector();
 
     void mainFrameDidLayout();
+    void computeZoomInformationForNode(WebCore::Node&, WebCore::FloatPoint& origin, WebCore::FloatRect& renderRect, bool& isReplaced, double& viewportMinimumScale, double& viewportMaximumScale);
 
 private:
     // IPC::MessageReceiver.
index 2dcafe1d3c0345ce4f6c996f20f7d407be78e051..a1b890695216a0c5fa8dd62750abc83ef03c377b 100644 (file)
@@ -662,6 +662,16 @@ void WebPage::potentialTapAtPosition(uint64_t requestID, const WebCore::FloatPoi
 {
     m_potentialTapNode = m_page->mainFrame().nodeRespondingToClickEvents(position, m_potentialTapLocation);
     sendTapHighlightForNodeIfNecessary(requestID, m_potentialTapNode.get());
+    if (m_potentialTapNode) {
+        FloatPoint origin = position;
+        FloatRect renderRect;
+        bool isReplaced;
+        double viewportMinimumScale;
+        double viewportMaximumScale;
+
+        m_viewGestureGeometryCollector.computeZoomInformationForNode(*m_potentialTapNode.get(), origin, renderRect, isReplaced, viewportMinimumScale, viewportMaximumScale);
+        send(Messages::WebPageProxy::DisableDoubleTapGesturesUntilTapIsFinishedIfNecessary(requestID, true, renderRect, isReplaced, viewportMinimumScale, viewportMaximumScale));
+    }
 }
 
 void WebPage::commitPotentialTap(uint64_t lastLayerTreeTransactionId)