Skip to content

Commit

Permalink
REGRESSION (UI-side compositing): Unable to scroll to top of page aft…
Browse files Browse the repository at this point in the history
…er double tapping trackpad causes page to scroll down

https://bugs.webkit.org/show_bug.cgi?id=266529
rdar://116419609

Reviewed by Tim Horton.

The smart magnify gesture (two-finger double tap on the trackpad) results in some combination of
scrolling and zooming. `RemoteLayerTreeDrawingAreaProxyMac::commitTransientZoom()` uses a
CAAnimation to animate to the final state, but the completion handler of that animation put the
transform directly onto the `layerForPageScale`.

That's OK if the gesture results in a scale change; that transform will get replaced by one that
comes from the web process. However, if a gesture just resulted in a scroll with no scale change,
we never get a new transform from the web process, so we leave that transform on
`layerForPageScale`. This transform has a translation baked in, so this causes a permanent scroll
offset.

So we have to stop shoving the transform onto this layer. This revealed an issue where there was a
jump when the animation was removed. Fixing this just requires two things:

1. Delay removing the transient zoom animations until we know the web process has committed the new
   scale and scroll position (via a `callAfterNextPresentationUpdate()`).

2. Override any scroll position changes that come from the web process before this by adding a
   CAAnimation onto the scrolled contents layer which overrides the `position` property. If we
   don't do this, scroll position changes cause a flash of offset content. This requires tracking
   m_pageScrollingLayerID, but that information was already in the transaction for banner hookup.

* LayoutTests/view-gestures/smart-magnify/double-tap-zoom-scroll-above-top-expected.html: Added.
* LayoutTests/view-gestures/smart-magnify/double-tap-zoom-scroll-above-top.html: Added.
* Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeDrawingAreaProxyMac.h:
* Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeDrawingAreaProxyMac.mm:
(WebKit::RemoteLayerTreeDrawingAreaProxyMac::didCommitLayerTree):
(WebKit::fillFowardsAnimationWithKeyPath):
(WebKit::transientZoomTransformOverrideAnimation):
(WebKit::RemoteLayerTreeDrawingAreaProxyMac::applyTransientZoomToLayer):
(WebKit::RemoteLayerTreeDrawingAreaProxyMac::removeTransientZoomFromLayer):
(WebKit::RemoteLayerTreeDrawingAreaProxyMac::commitTransientZoom):
(WebKit::RemoteLayerTreeDrawingAreaProxyMac::sendCommitTransientZoom):

Canonical link: https://commits.webkit.org/272229@main
  • Loading branch information
smfr committed Dec 18, 2023
1 parent cc7eb61 commit 3a5a2dc
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 2000px;
margin: 0;
}
.target {
margin-top: 400px;
width: 100%;
height: 1200px;
background-color: silver;
}

.top-stripe {
width: 100%;
height: 20px;
background-color: green;
}
</style>
</head>
<body>
<div class="top-stripe"></div>
<div class="target"></div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 2000px;
margin: 0;
}
.target {
margin-top: 400px;
width: 100%;
height: 1200px;
background-color: silver;
}

.top-stripe {
width: 100%;
height: 20px;
background-color: green;
}
</style>
<script src="../../resources/ui-helper.js"></script>
<script>
if (window.testRunner)
testRunner.waitUntilDone();

async function doSmartMagnify()
{
await UIHelper.setWebViewAllowsMagnification(true);
await UIHelper.smartMagnifyAt(400, 500);

window.scrollTo(0, 0);
await UIHelper.ensurePresentationUpdate();

if (window.testRunner)
testRunner.notifyDone();
}

window.addEventListener('load', () => {
doSmartMagnify();
}, false);
</script>
</head>
<body>
<div class="top-stripe"></div>
<div class="target"></div>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ friend class RemoteScrollingCoordinatorProxyMac;

void updateZoomTransactionID();
WebCore::PlatformLayerIdentifier pageScalingLayerID() { return m_pageScalingLayerID; }
WebCore::PlatformLayerIdentifier pageScrollingLayerID() { return m_pageScrollingLayerID; }

private:
WebCore::DelegatedScrollingMode delegatedScrollingMode() const override;
std::unique_ptr<RemoteScrollingCoordinatorProxy> createScrollingCoordinatorProxy() const override;
Expand Down Expand Up @@ -93,7 +95,10 @@ friend class RemoteScrollingCoordinatorProxyMac;
std::optional<DisplayLinkObserverID> m_displayRefreshObserverID;
std::optional<DisplayLinkObserverID> m_fullSpeedUpdateObserverID;
std::unique_ptr<RemoteLayerTreeDisplayLinkClient> m_displayLinkClient;

WebCore::PlatformLayerIdentifier m_pageScalingLayerID;
WebCore::PlatformLayerIdentifier m_pageScrollingLayerID;

bool m_usesOverlayScrollbars { false };

std::optional<TransactionID> m_transactionIDAfterEndingTransientZoom;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
namespace WebKit {
using namespace WebCore;

static NSString * const transientAnimationKey = @"wkTransientZoomScale";
static NSString * const transientZoomAnimationKey = @"wkTransientZoomScale";
static NSString * const transientZoomCommitAnimationKey = @"wkTransientZoomCommit";
static NSString * const transientZoomScrollPositionOverrideAnimationKey = @"wkScrollPositionOverride";

class RemoteLayerTreeDisplayLinkClient final : public DisplayLink::Client {
public:
Expand Down Expand Up @@ -172,6 +174,7 @@ explicit RemoteLayerTreeDisplayLinkClient(WebPageProxyIdentifier pageID)
void RemoteLayerTreeDrawingAreaProxyMac::didCommitLayerTree(IPC::Connection&, const RemoteLayerTreeTransaction& transaction, const RemoteScrollingCoordinatorTransaction&)
{
m_pageScalingLayerID = transaction.pageScalingLayerID();
m_pageScrollingLayerID = transaction.scrolledContentsLayerID();

if (m_transientZoomScale)
applyTransientZoomToLayer();
Expand Down Expand Up @@ -199,15 +202,22 @@ explicit RemoteLayerTreeDisplayLinkClient(WebPageProxyIdentifier pageID)
layoutBannerLayers(transaction);
}

static RetainPtr<CABasicAnimation> transientZoomTransformOverrideAnimation(const TransformationMatrix& transform)
static RetainPtr<CABasicAnimation> fillFowardsAnimationWithKeyPath(NSString *keyPath)
{
RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"transform"];
RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:keyPath];
[animation setDuration:std::numeric_limits<double>::max()];
[animation setFillMode:kCAFillModeForwards];
[animation setAdditive:NO];
[animation setRemovedOnCompletion:false];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];

return animation;
}

static RetainPtr<CABasicAnimation> transientZoomTransformOverrideAnimation(const TransformationMatrix& transform)
{
auto animation = fillFowardsAnimationWithKeyPath(@"transform");

NSValue *transformValue = [NSValue valueWithCATransform3D:transform];
[animation setFromValue:transformValue];
[animation setToValue:transformValue];
Expand All @@ -230,8 +240,8 @@ explicit RemoteLayerTreeDisplayLinkClient(WebPageProxyIdentifier pageID)

BEGIN_BLOCK_OBJC_EXCEPTIONS
auto animationWithScale = transientZoomTransformOverrideAnimation(transform);
[layerForPageScale removeAnimationForKey:transientAnimationKey];
[layerForPageScale addAnimation:animationWithScale.get() forKey:transientAnimationKey];
[layerForPageScale removeAnimationForKey:transientZoomAnimationKey];
[layerForPageScale addAnimation:animationWithScale.get() forKey:transientZoomAnimationKey];
END_BLOCK_OBJC_EXCEPTIONS
}

Expand All @@ -242,7 +252,7 @@ explicit RemoteLayerTreeDisplayLinkClient(WebPageProxyIdentifier pageID)
return;

BEGIN_BLOCK_OBJC_EXCEPTIONS
[layerForPageScale removeAnimationForKey:transientAnimationKey];
[layerForPageScale removeAnimationForKey:transientZoomAnimationKey];
END_BLOCK_OBJC_EXCEPTIONS
}

Expand All @@ -269,16 +279,16 @@ explicit RemoteLayerTreeDisplayLinkClient(WebPageProxyIdentifier pageID)
IntSize scaledTotalContentsSize = roundedIntSize(m_webPageProxy->scrollingCoordinatorProxy()->totalContentsSize());
scaledTotalContentsSize.scale(scale / m_webPageProxy->scrollingCoordinatorProxy()->mainFrameScaleFactor());

LOG_WITH_STREAM(Scrolling, stream << "RemoteLayerTreeDrawingAreaProxyMac::commitTransientZoom constrainScrollPositionForOverhang - constrainedOrigin: " << constrainedOrigin << " visibleContentRect: " << visibleContentRect << " scaledTotalContentsSize: " << scaledTotalContentsSize << "scrollOrigin :" << m_webPageProxy->scrollingCoordinatorProxy()->scrollOrigin() << "headerHeight :" << m_webPageProxy->scrollingCoordinatorProxy()->headerHeight() << " footerHeight : " << m_webPageProxy->scrollingCoordinatorProxy()->footerHeight());
LOG_WITH_STREAM(ViewGestures, stream << "RemoteLayerTreeDrawingAreaProxyMac::commitTransientZoom constrainScrollPositionForOverhang - constrainedOrigin: " << constrainedOrigin << " visibleContentRect: " << visibleContentRect << " scaledTotalContentsSize: " << scaledTotalContentsSize << " scrollOrigin:" << m_webPageProxy->scrollingCoordinatorProxy()->scrollOrigin() << " headerHeight:" << m_webPageProxy->scrollingCoordinatorProxy()->headerHeight() << " footerHeight: " << m_webPageProxy->scrollingCoordinatorProxy()->footerHeight());

// 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.
constrainedOrigin = ScrollableArea::constrainScrollPositionForOverhang(roundedIntRect(visibleContentRect), scaledTotalContentsSize, roundedIntPoint(constrainedOrigin), m_webPageProxy->scrollingCoordinatorProxy()->scrollOrigin(), m_webPageProxy->scrollingCoordinatorProxy()->headerHeight(), m_webPageProxy->scrollingCoordinatorProxy()->footerHeight());
constrainedOrigin.moveBy(-visibleContentRect.location());
constrainedOrigin = -constrainedOrigin;

LOG_WITH_STREAM(Scrolling, stream << "RemoteLayerTreeDrawingAreaProxyMac::commitTransientZoom - origin " << origin << " constrained to " << constrainedOrigin << ", scale " << scale);
LOG_WITH_STREAM(ViewGestures, stream << "RemoteLayerTreeDrawingAreaProxyMac::commitTransientZoom - origin " << origin << " constrained to " << constrainedOrigin << ", scale " << scale);

auto transientZoomScale = std::exchange(m_transientZoomScale, { });
auto transientZoomOrigin = std::exchange(m_transientZoomOrigin, { });
Expand All @@ -297,34 +307,54 @@ explicit RemoteLayerTreeDisplayLinkClient(WebPageProxyIdentifier pageID)
transform.scale(scale);

BEGIN_BLOCK_OBJC_EXCEPTIONS

[CATransaction begin];

CALayer *layerForPageScale = remoteLayerTreeHost().layerForID(m_pageScalingLayerID);
RetainPtr<CABasicAnimation> renderViewAnimationCA = DrawingArea::transientZoomSnapAnimationForKeyPath("transform"_s);
auto renderViewAnimationCA = DrawingArea::transientZoomSnapAnimationForKeyPath("transform"_s);
NSValue *transformValue = [NSValue valueWithCATransform3D:transform];
[renderViewAnimationCA setToValue:transformValue];

CALayer *layerForPageScrolling = remoteLayerTreeHost().layerForID(m_pageScrollingLayerID);
auto scrollPositionOverrideAnimation = fillFowardsAnimationWithKeyPath(@"position");
NSValue *pointValue = [NSValue valueWithPoint:NSPointFromCGPoint(layerForPageScrolling.position)];
[scrollPositionOverrideAnimation setFromValue:pointValue];
[scrollPositionOverrideAnimation setToValue:pointValue];
[layerForPageScrolling addAnimation:scrollPositionOverrideAnimation.get() forKey:transientZoomScrollPositionOverrideAnimationKey];

[CATransaction setCompletionBlock:[scale, constrainedOrigin, rootScrollingNodeID, protectedWebPageProxy = this->protectedWebPageProxy()] () {
if (auto* drawingArea = static_cast<RemoteLayerTreeDrawingAreaProxyMac*>(protectedWebPageProxy->drawingArea()))
drawingArea->sendCommitTransientZoom(scale, constrainedOrigin, rootScrollingNodeID);

[CATransaction setCompletionBlock:[scale, constrainedOrigin, transform, rootScrollingNodeID, protectedWebPageProxy = this->protectedWebPageProxy()] () {
if (auto* drawingArea = static_cast<RemoteLayerTreeDrawingAreaProxyMac*>(protectedWebPageProxy->drawingArea())) {
CALayer *layerForPageScale = drawingArea->remoteLayerTreeHost().layerForID(drawingArea->pageScalingLayerID());
if (layerForPageScale) {
layerForPageScale.transform = transform;
[layerForPageScale removeAnimationForKey:transientAnimationKey];
[layerForPageScale removeAnimationForKey:@"transientZoomCommit"];
protectedWebPageProxy->callAfterNextPresentationUpdate([protectedWebPageProxy] {
auto* drawingArea = static_cast<RemoteLayerTreeDrawingAreaProxyMac*>(protectedWebPageProxy->drawingArea());
if (!drawingArea)
return;

BEGIN_BLOCK_OBJC_EXCEPTIONS
if (CALayer *layerForPageScale = drawingArea->remoteLayerTreeHost().layerForID(drawingArea->pageScalingLayerID())) {
[layerForPageScale removeAnimationForKey:transientZoomAnimationKey];
[layerForPageScale removeAnimationForKey:transientZoomCommitAnimationKey];
}

drawingArea->sendCommitTransientZoom(scale, constrainedOrigin, rootScrollingNodeID);
}
if (CALayer *layerForPageScrolling = drawingArea->remoteLayerTreeHost().layerForID(drawingArea->m_pageScrollingLayerID))
[layerForPageScrolling removeAnimationForKey:transientZoomScrollPositionOverrideAnimationKey];

END_BLOCK_OBJC_EXCEPTIONS
});
}];

[layerForPageScale addAnimation:renderViewAnimationCA.get() forKey:@"transientZoomCommit"];
[layerForPageScale addAnimation:renderViewAnimationCA.get() forKey:transientZoomCommitAnimationKey];

[CATransaction commit];

END_BLOCK_OBJC_EXCEPTIONS
}

void RemoteLayerTreeDrawingAreaProxyMac::sendCommitTransientZoom(double scale, FloatPoint origin, WebCore::ScrollingNodeID rootNodeID)
{
updateZoomTransactionID();

sendWithAsyncReply(Messages::DrawingArea::CommitTransientZoom(scale, origin), [rootNodeID, protectedWebPageProxy = this->protectedWebPageProxy()] {
if (auto* scrollingCoordinatorProxy = protectedWebPageProxy->scrollingCoordinatorProxy())
scrollingCoordinatorProxy->removeWheelEventTestCompletionDeferralForReason(rootNodeID, WheelEventTestMonitorDeferReason::CommittingTransientZoom);
Expand Down

0 comments on commit 3a5a2dc

Please sign in to comment.