WebKit2 View Gestures: Pinching beyond the extremes doesn't animate back to the min/max
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Dec 2013 02:12:31 +0000 (02:12 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Dec 2013 02:12:31 +0000 (02:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=125750

Reviewed by Simon Fraser.

When you let go of a pinch where you exceeded the maximum or minimum scale,
the view should animate back to the limit instead of snapping back.

* UIProcess/mac/ViewGestureController.cpp:
(WebKit::ViewGestureController::endMagnificationGesture):
Unscrolling the scaled origin here is wrong; we should send the same
value for 'origin' for commitTransientZoom that we do for adjustTransientZoom.
The drawing area can deal with transforming that however it needs to call scalePage().

* WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h:
* WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm:
(WebKit::TiledCoreAnimationDrawingArea::beginTransientZoom):
Drive-by use WebPage::mainFrameView().

(WebKit::TiledCoreAnimationDrawingArea::adjustTransientZoom):
Update the shadow layer's bounds, position, and shadow path while zooming,
instead of hiding it.

(WebKit::transientZoomSnapAnimationForKeyPath):
Create a CABasicAnimation that stays applied to its target when it finishes,
lasts for a quarter second, and uses the ease-in-ease-out timing function.

(WebKit::constrainZoomOriginForFrameView):
When we land a transient zoom, there may be overhang areas (linen, in the case of Mac)
visible. During the commit animation, we should move the RenderView layer appropriately
so that the page covers the entire view and there are no overhang areas visible.
Adapt some code from ScrollableArea to constrain the zoom origin to do so.

(WebKit::TiledCoreAnimationDrawingArea::commitTransientZoom):
Animate the RenderView layer's transform, and the shadow layer's bounds, position, and path.
When they complete, apply the zoom via scalePage() and flush the layer tree (otherwise there
can be a flash between removing the animations and the next flush with the new scale).

(WebKit::TiledCoreAnimationDrawingArea::applyTransientZoomToPage):
Move the code to land the transient zoom (via scalePage) and to put the shadow back
in the state WebCore left it out into its own function, so it can be called both from the
animation completion callback and from the short-circuit in the case where we don't need to animate.

* WebCore.exp.in:
Export a few things so WebKit2 can use them.

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

Source/WebCore/ChangeLog
Source/WebCore/WebCore.exp.in
Source/WebCore/platform/KeyedCoding.h
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/mac/ViewGestureController.cpp
Source/WebKit2/WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h
Source/WebKit2/WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm

index eaa03fe..fec8641 100644 (file)
@@ -1,3 +1,13 @@
+2013-12-16  Tim Horton  <timothy_horton@apple.com>
+
+        WebKit2 View Gestures: Pinching beyond the extremes doesn't animate back to the min/max
+        https://bugs.webkit.org/show_bug.cgi?id=125750
+
+        Reviewed by Simon Fraser.
+
+        * WebCore.exp.in:
+        Export a few things so WebKit2 can use them.
+
 2013-12-16  Joseph Pecoraro  <pecoraro@apple.com>
 
         Fix some whitespace issues in inspector code
index 4dbc732..cb5ce73 100644 (file)
@@ -399,6 +399,7 @@ __ZN7WebCore14ScrollableArea24setScrollbarOverlayStyleENS_21ScrollbarOverlayStyl
 __ZN7WebCore14ScrollableArea27notifyScrollPositionChangedERKNS_8IntPointE
 __ZN7WebCore14ScrollableArea28setScrollOffsetFromInternalsERKNS_8IntPointE
 __ZN7WebCore14ScrollableArea30scrollToOffsetWithoutAnimationERKNS_10FloatPointE
+__ZN7WebCore14ScrollableArea34constrainScrollPositionForOverhangERKNS_7IntRectERKNS_7IntSizeERKNS_8IntPointES9_ii
 __ZN7WebCore14ScrollableArea6scrollENS_15ScrollDirectionENS_17ScrollGranularityEf
 __ZN7WebCore14ScrollableAreaC2Ev
 __ZN7WebCore14ScrollableAreaD2Ev
@@ -437,6 +438,7 @@ __ZN7WebCore14cookiesEnabledERKNS_21NetworkStorageSessionERKNS_3URLES5_
 __ZN7WebCore14decodeHostNameEP8NSString
 __ZN7WebCore14encodeHostNameEP8NSString
 __ZN7WebCore14endOfParagraphERKNS_15VisiblePositionENS_27EditingBoundaryCrossingRuleE
+__ZN7WebCore14roundedIntRectERKNS_9FloatRectE
 __ZN7WebCore14setMetadataURLERN3WTF6StringERKS1_S4_
 __ZN7WebCore15AffineTransform5flipYEv
 __ZN7WebCore15AffineTransform5scaleEd
@@ -757,6 +759,9 @@ __ZN7WebCore19ResourceRequestBase24s_defaultTimeoutIntervalE
 __ZN7WebCore19ResourceRequestBase25setDefaultTimeoutIntervalEd
 __ZN7WebCore19ResourceRequestBase50setResponseContentDispositionEncodingFallbackArrayERKN3WTF6StringES4_S4_
 __ZN7WebCore19ResourceRequestBase6setURLERKNS_3URLE
+__ZN7WebCore19PlatformCAAnimation6createEP19CAPropertyAnimation
+__ZN7WebCore19PlatformCAAnimation10setToValueERKNS_20TransformationMatrixE
+__ZN7WebCore19PlatformCAAnimationD1Ev
 __ZN7WebCore19TextResourceDecoder5flushEv
 __ZN7WebCore19TextResourceDecoder6decodeEPKcm
 __ZN7WebCore19TextResourceDecoderC1ERKN3WTF6StringERKNS_12TextEncodingEb
@@ -1232,6 +1237,7 @@ __ZN7WebCore9ClipboardD1Ev
 __ZN7WebCore9DOMWindow30dispatchAllPendingUnloadEventsEv
 __ZN7WebCore9DOMWindow36dispatchAllPendingBeforeUnloadEventsEv
 __ZN7WebCore9FloatRect5scaleEff
+__ZN7WebCore9FloatRect9intersectERKS0_
 __ZN7WebCore9FloatRectC1ERK6CGRect
 __ZN7WebCore9FloatRectC1ERKNS_7IntRectE
 __ZN7WebCore9FloatSizeC1ERK6CGSize
@@ -1393,6 +1399,7 @@ __ZNK7WebCore10RenderText16linesBoundingBoxEv
 __ZNK7WebCore10RenderText9firstRunXEv
 __ZNK7WebCore10RenderText9firstRunYEv
 __ZNK7WebCore10RenderView12documentRectEv
+__ZNK7WebCore10RenderView20unscaledDocumentRectEv
 __ZNK7WebCore10ScrollView12contentsSizeEv
 __ZNK7WebCore10ScrollView12documentViewEv
 __ZNK7WebCore10ScrollView14scrollbarModesERNS_13ScrollbarModeES2_
index 2d6f4d9..c7da9cc 100644 (file)
@@ -35,63 +35,7 @@ protected:
     virtual ~KeyedEncoder() { }
 
 public:
-    virtual void encodeBytes(const String& key, const uint8_t*, size_t) = 0;
-    virtual void encodeBool(const String& key, bool) = 0;
     virtual void encodeUInt32(const String& key, uint32_t) = 0;
-    virtual void encodeInt32(const String& key, int32_t) = 0;
-    virtual void encodeInt64(const String& key, int64_t) = 0;
-    virtual void encodeFloat(const String& key, float) = 0;
-    virtual void encodeDouble(const String& key, double) = 0;
-    virtual void encodeString(const String& key, const String&) = 0;
-
-    template<typename T>
-    void encodeEnum(const String& key, T value)
-    {
-        static_assert(std::is_enum<T>::value, "T must be an enum type");
-
-        encodeInt64(key, static_cast<int64_t>(value));
-    }
-
-    template<typename T, typename F>
-    void encodeObject(const String& key, const T& object, F&& function)
-    {
-        beginObject(key);
-        function(*this, object);
-        endObject();
-    }
-
-    template<typename T, typename F>
-    void encodeConditionalObject(const String& key, const T* object, F&& function)
-    {
-        if (!object)
-            return;
-
-        encodeObject(key, *object, std::forward<F>(function));
-    }
-
-    template<typename T, typename F>
-    void encodeObjects(const String& key, T begin, T end, F&& function)
-    {
-        if (begin == end)
-            return;
-
-        beginArray(key);
-        for (T it = begin; it != end; ++it) {
-            beginArrayElement();
-            function(*this, *it);
-            endArrayElement();
-        }
-        endArray();
-    }
-
-private:
-    virtual void beginObject(const String& key) = 0;
-    virtual void endObject() = 0;
-
-    virtual void beginArray(const String& key) = 0;
-    virtual void beginArrayElement() = 0;
-    virtual void endArrayElement() = 0;
-    virtual void endArray() = 0;
 };
 
 } // namespace WebCore
index bf03e83..140365e 100644 (file)
@@ -1,3 +1,48 @@
+2013-12-16  Tim Horton  <timothy_horton@apple.com>
+
+        WebKit2 View Gestures: Pinching beyond the extremes doesn't animate back to the min/max
+        https://bugs.webkit.org/show_bug.cgi?id=125750
+
+        Reviewed by Simon Fraser.
+
+        When you let go of a pinch where you exceeded the maximum or minimum scale,
+        the view should animate back to the limit instead of snapping back.
+
+        * UIProcess/mac/ViewGestureController.cpp:
+        (WebKit::ViewGestureController::endMagnificationGesture):
+        Unscrolling the scaled origin here is wrong; we should send the same
+        value for 'origin' for commitTransientZoom that we do for adjustTransientZoom.
+        The drawing area can deal with transforming that however it needs to call scalePage().
+
+        * WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h:
+        * WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm:
+        (WebKit::TiledCoreAnimationDrawingArea::beginTransientZoom):
+        Drive-by use WebPage::mainFrameView().
+
+        (WebKit::TiledCoreAnimationDrawingArea::adjustTransientZoom):
+        Update the shadow layer's bounds, position, and shadow path while zooming,
+        instead of hiding it.
+
+        (WebKit::transientZoomSnapAnimationForKeyPath):
+        Create a CABasicAnimation that stays applied to its target when it finishes,
+        lasts for a quarter second, and uses the ease-in-ease-out timing function.
+
+        (WebKit::constrainZoomOriginForFrameView):
+        When we land a transient zoom, there may be overhang areas (linen, in the case of Mac)
+        visible. During the commit animation, we should move the RenderView layer appropriately
+        so that the page covers the entire view and there are no overhang areas visible.
+        Adapt some code from ScrollableArea to constrain the zoom origin to do so.
+
+        (WebKit::TiledCoreAnimationDrawingArea::commitTransientZoom):
+        Animate the RenderView layer's transform, and the shadow layer's bounds, position, and path.
+        When they complete, apply the zoom via scalePage() and flush the layer tree (otherwise there
+        can be a flash between removing the animations and the next flush with the new scale).
+
+        (WebKit::TiledCoreAnimationDrawingArea::applyTransientZoomToPage):
+        Move the code to land the transient zoom (via scalePage) and to put the shadow back
+        in the state WebCore left it out into its own function, so it can be called both from the
+        animation completion callback and from the short-circuit in the case where we don't need to animate.
+
 2013-12-16  Ryuan Choi  <ryuan.choi@samsung.com>
 
         Unreviewed. Fix GTK+ build after r160653.
index 2770dca..db2a587 100644 (file)
@@ -122,13 +122,8 @@ void ViewGestureController::endMagnificationGesture()
 {
     ASSERT(m_activeGestureType == ViewGestureType::Magnification);
 
-    // FIXME: Should rubber-band back when zoomed in or out past the limit.
     double newMagnification = std::min(std::max(m_magnification, minMagnification), maxMagnification);
-
-    FloatPoint scaledOrigin = scaledMagnificationOrigin(m_magnificationOrigin, newMagnification);
-    scaledOrigin.moveBy(-m_visibleContentRect.location());
-
-    m_webPageProxy.drawingArea()->commitTransientZoom(newMagnification, -scaledOrigin);
+    m_webPageProxy.drawingArea()->commitTransientZoom(newMagnification, scaledMagnificationOrigin(m_magnificationOrigin, newMagnification));
 }
 
 void ViewGestureController::endActiveGesture()
index 07c74ca..c85291c 100644 (file)
@@ -107,6 +107,7 @@ private:
     virtual void beginTransientZoom() OVERRIDE;
     virtual void adjustTransientZoom(double scale, WebCore::FloatPoint origin) OVERRIDE;
     virtual void commitTransientZoom(double scale, WebCore::FloatPoint origin) OVERRIDE;
+    void applyTransientZoomToPage(double scale, WebCore::FloatPoint origin);
 
     void updateLayerHostingContext();
 
index a7241fb..70f136b 100644 (file)
@@ -47,6 +47,7 @@
 #import <WebCore/RenderLayerBacking.h>
 #import <WebCore/RenderLayerCompositor.h>
 #import <WebCore/RenderView.h>
+#import <WebCore/ScrollbarTheme.h>
 #import <WebCore/Settings.h>
 #import <WebCore/TiledBacking.h>
 #import <wtf/MainThread.h>
@@ -702,7 +703,7 @@ bool TiledCoreAnimationDrawingArea::shouldUseTiledBackingForFrameView(const Fram
 
 void TiledCoreAnimationDrawingArea::beginTransientZoom()
 {
-    FloatRect visibleContentRect = m_webPage->corePage()->mainFrame().view()->visibleContentRect(ScrollableArea::IncludeScrollbars);
+    FloatRect visibleContentRect = m_webPage->mainFrameView()->visibleContentRect(ScrollableArea::IncludeScrollbars);
     m_webPage->send(Messages::ViewGestureController::DidBeginTransientZoom(visibleContentRect));
 }
 
@@ -715,38 +716,153 @@ void TiledCoreAnimationDrawingArea::adjustTransientZoom(double scale, FloatPoint
     if (!m_rootLayer)
         return;
 
-    RenderView* renderView = m_webPage->corePage()->mainFrame().view()->renderView();
-
     TransformationMatrix transform;
     transform.translate(origin.x(), origin.y());
     transform.scale(scale);
 
+    RenderView* renderView = m_webPage->mainFrameView()->renderView();
     PlatformCALayer* renderViewLayer = static_cast<GraphicsLayerCA*>(renderView->layer()->backing()->graphicsLayer())->platformCALayer();
     renderViewLayer->setTransform(transform);
     renderViewLayer->setAnchorPoint(FloatPoint3D());
     renderViewLayer->setPosition(FloatPoint3D());
 
-    // FIXME: We should transform the shadow layer as well, instead of hiding it.
     PlatformCALayer* shadowLayer = static_cast<GraphicsLayerCA*>(renderView->compositor().layerForContentShadow())->platformCALayer();
-    shadowLayer->setHidden(true);
+
+    FloatRect shadowBounds = FloatRect(FloatPoint(), toFloatSize(renderView->layoutOverflowRect().maxXMaxYCorner()));
+    shadowBounds.scale(scale);
+
+    shadowLayer->setBounds(shadowBounds);
+    shadowLayer->setPosition(origin + shadowBounds.center());
+    shadowLayer->platformLayer().shadowPath = adoptCF(CGPathCreateWithRect(shadowBounds, NULL)).get();
 
     m_transientZoomScale = scale;
 }
 
+static RetainPtr<CABasicAnimation> transientZoomSnapAnimationForKeyPath(String keyPath)
+{
+    const float transientZoomSnapBackDuration = 0.25;
+
+    RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:keyPath];
+    [animation setDuration:transientZoomSnapBackDuration];
+    [animation setFillMode:kCAFillModeForwards];
+    [animation setRemovedOnCompletion:false];
+    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
+
+    return animation;
+}
+
+/*static FloatPoint constrainZoomOriginForFrameView(double scale, FloatPoint origin, FrameView* frameView)
+{
+    // Scaling may have exposed the overhang area, so we need to constrain the final
+    // layer position exactly like scrolling will once it's committed, to ensure that
+    // scrolling doesn't make the view jump; this is borrowed from ScrollableArea.
+    FloatRect visibleContentRect = frameView->visibleContentRect(ScrollableArea::IncludeScrollbars);
+
+    FloatRect zoomContentRect = visibleContentRect;
+    zoomContentRect.moveBy(-origin);
+
+    FloatRect documentRect = frameView->renderView()->unscaledDocumentRect();
+    documentRect.scale(scale);
+    zoomContentRect.intersect(documentRect);
+
+    if (zoomContentRect.size() != visibleContentRect.size()) {
+        zoomContentRect.setSize(visibleContentRect.size());
+        zoomContentRect.intersect(documentRect);
+        if (zoomContentRect.width() < visibleContentRect.width())
+            zoomContentRect.move(-(visibleContentRect.width() - zoomContentRect.width()), 0);
+        if (zoomContentRect.height() < visibleContentRect.height())
+            zoomContentRect.move(0, -(visibleContentRect.height() - zoomContentRect.height()));
+    }
+
+    FloatPoint constrainedOrigin = zoomContentRect.location();
+    constrainedOrigin.moveBy(-visibleContentRect.location());
+
+    return -constrainedOrigin;
+}*/
+
 void TiledCoreAnimationDrawingArea::commitTransientZoom(double scale, FloatPoint origin)
 {
-    RenderView* renderView = m_webPage->corePage()->mainFrame().view()->renderView();
+    FrameView* frameView = m_webPage->mainFrameView();
+    RenderView* renderView = frameView->renderView();
+    PlatformCALayer* renderViewLayer = static_cast<GraphicsLayerCA*>(renderView->layer()->backing()->graphicsLayer())->platformCALayer();
+
+    FloatRect visibleContentRect = frameView->visibleContentRect(ScrollableArea::IncludeScrollbars);
+
+    FloatPoint constrainedOrigin = visibleContentRect.location();
+    constrainedOrigin.moveBy(-origin);
+
+    IntRect documentRect = frameView->renderView()->unscaledDocumentRect();
+    documentRect.scale(scale);
+
+    constrainedOrigin = ScrollableArea::constrainScrollPositionForOverhang(roundedIntRect(visibleContentRect), documentRect.size(), roundedIntPoint(constrainedOrigin), IntPoint(), 0, 0);
+    constrainedOrigin.moveBy(-visibleContentRect.location());
+    constrainedOrigin = -constrainedOrigin;
+
+    if (m_transientZoomScale == scale && roundedIntPoint(origin) == roundedIntPoint(constrainedOrigin)) {
+        // We're already at the right scale and position, so we don't need to animate.
+        applyTransientZoomToPage(scale, origin);
+        return;
+    }
 
-    // If the page scale is already the target scale, scalePage() will short-circuit
-    // and not apply the transform, so we can't depend on it to do so.
     TransformationMatrix transform;
+    transform.translate(constrainedOrigin.x(), constrainedOrigin.y());
     transform.scale(scale);
-    static_cast<GraphicsLayerCA*>(renderView->layer()->backing()->graphicsLayer())->platformCALayer()->setTransform(transform);
 
-    PlatformCALayer* shadowLayer = static_cast<GraphicsLayerCA*>(renderView->compositor().layerForContentShadow())->platformCALayer();
-    shadowLayer->setHidden(false);
+    RetainPtr<CABasicAnimation> renderViewAnimationCA = transientZoomSnapAnimationForKeyPath("transform");
+    RefPtr<PlatformCAAnimation> renderViewAnimation = PlatformCAAnimation::create(renderViewAnimationCA.get());
+    renderViewAnimation->setToValue(transform);
+
+    RetainPtr<CALayer> shadowLayer = static_cast<GraphicsLayerCA*>(renderView->compositor().layerForContentShadow())->platformCALayer()->platformLayer();
+
+    FloatRect shadowBounds = FloatRect(FloatPoint(), toFloatSize(renderView->layoutOverflowRect().maxXMaxYCorner()));
+    shadowBounds.scale(scale);
+    RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowBounds, NULL)).get();
+
+    RetainPtr<CABasicAnimation> shadowBoundsAnimation = transientZoomSnapAnimationForKeyPath("bounds");
+    [shadowBoundsAnimation setToValue:[NSValue valueWithRect:shadowBounds]];
+    RetainPtr<CABasicAnimation> shadowPositionAnimation = transientZoomSnapAnimationForKeyPath("position");
+    [shadowBoundsAnimation setToValue:[NSValue valueWithPoint:constrainedOrigin + shadowBounds.center()]];
+    RetainPtr<CABasicAnimation> shadowPathAnimation = transientZoomSnapAnimationForKeyPath("shadowPath");
+    [shadowBoundsAnimation setToValue:(id)shadowPath.get()];
+
+    [CATransaction begin];
+    [CATransaction setCompletionBlock:^(void) {
+        renderViewLayer->removeAnimationForKey("transientZoomCommit");
+        [shadowLayer removeAllAnimations];
+        applyTransientZoomToPage(scale, origin);
+    }];
+
+    renderViewLayer->addAnimationForKey("transientZoomCommit", renderViewAnimation.get());
+    [shadowLayer addAnimation:shadowBoundsAnimation.get() forKey:@"transientZoomCommitShadowBounds"];
+    [shadowLayer addAnimation:shadowPositionAnimation.get() forKey:@"transientZoomCommitShadowPosition"];
+    [shadowLayer addAnimation:shadowPathAnimation.get() forKey:@"transientZoomCommitShadowPath"];
 
-    m_webPage->scalePage(scale, roundedIntPoint(origin));
+    [CATransaction commit];
+}
+
+void TiledCoreAnimationDrawingArea::applyTransientZoomToPage(double scale, FloatPoint origin)
+{
+    RenderView* renderView = m_webPage->mainFrameView()->renderView();
+    PlatformCALayer* renderViewLayer = static_cast<GraphicsLayerCA*>(renderView->layer()->backing()->graphicsLayer())->platformCALayer();
+
+    TransformationMatrix finalTransform;
+    finalTransform.scale(scale);
+
+    // If the page scale is already the target scale, setPageScaleFactor() will short-circuit
+    // and not apply the transform, so we can't depend on it to do so.
+    renderViewLayer->setTransform(finalTransform);
+
+    PlatformCALayer* shadowLayer = static_cast<GraphicsLayerCA*>(renderView->compositor().layerForContentShadow())->platformCALayer();
+    IntRect overflowRect = renderView->pixelSnappedLayoutOverflowRect();
+    shadowLayer->setBounds(IntRect(IntPoint(), toIntSize(overflowRect.maxXMaxYCorner())));
+    shadowLayer->setPosition(shadowLayer->bounds().center());
+    ScrollbarTheme::theme()->setUpContentShadowLayer(renderView->compositor().layerForContentShadow());
+
+    FloatPoint unscrolledOrigin(origin);
+    FloatRect visibleContentRect = m_webPage->mainFrameView()->visibleContentRect(ScrollableArea::IncludeScrollbars);
+    unscrolledOrigin.moveBy(-visibleContentRect.location());
+    m_webPage->scalePage(scale, roundedIntPoint(-unscrolledOrigin));
+    flushLayers();
 }
 
 } // namespace WebKit