2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #import "ViewGestureController.h"
31 #import "NativeWebWheelEvent.h"
32 #import "WebPageGroup.h"
33 #import "ViewGestureControllerMessages.h"
34 #import "ViewGestureGeometryCollectorMessages.h"
35 #import "ViewSnapshotStore.h"
36 #import "WebBackForwardList.h"
37 #import "WebPageMessages.h"
38 #import "WebPageProxy.h"
39 #import "WebPreferences.h"
40 #import "WebProcessProxy.h"
41 #import <Cocoa/Cocoa.h>
42 #import <QuartzCore/QuartzCore.h>
43 #import <WebCore/IOSurface.h>
44 #import <WebCore/WebActionDisablingCALayerDelegate.h>
46 #if defined(__has_include) && __has_include(<QuartzCore/QuartzCorePrivate.h>)
47 #import <QuartzCore/QuartzCorePrivate.h>
49 @interface CAFilter : NSObject <NSCopying, NSMutableCopying, NSCoding>
53 @interface CAFilter (Details)
54 + (CAFilter *)filterWithType:(NSString *)type;
57 extern NSString * const kCAFilterColorInvert;
59 using namespace WebCore;
61 static const double minMagnification = 1;
62 static const double maxMagnification = 3;
64 static const double minElasticMagnification = 0.75;
65 static const double maxElasticMagnification = 4;
67 static const double zoomOutBoost = 1.6;
68 static const double zoomOutResistance = 0.10;
70 static const float smartMagnificationElementPadding = 0.05;
71 static const float smartMagnificationPanScrollThreshold = 100;
73 static const double swipeOverlayShadowOpacity = 0.66;
74 static const double swipeOverlayShadowRadius = 3;
76 static const CGFloat minimumHorizontalSwipeDistance = 15;
77 static const float minimumScrollEventRatioForSwipe = 0.5;
79 static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5;
80 static const std::chrono::seconds swipeSnapshotRemovalWatchdogDuration = 5_s;
81 static const std::chrono::seconds swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration = 3_s;
83 @interface WKSwipeCancellationTracker : NSObject {
88 @property (nonatomic) BOOL isCancelled;
92 @implementation WKSwipeCancellationTracker
93 @synthesize isCancelled=_isCancelled;
98 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
99 : m_webPageProxy(webPageProxy)
100 , m_activeGestureType(ViewGestureType::None)
101 , m_swipeWatchdogTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
102 , m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
103 , m_lastMagnificationGestureWasSmartMagnification(false)
104 , m_visibleContentRectIsValid(false)
105 , m_frameHandlesMagnificationGesture(false)
106 , m_swipeTransitionStyle(SwipeTransitionStyle::Overlap)
107 , m_customSwipeViewsTopContentInset(0)
108 , m_pendingSwipeReason(PendingSwipeReason::None)
109 , m_didMoveSwipeSnapshotCallback(nullptr)
110 , m_shouldIgnorePinnedState(false)
111 , m_swipeWaitingForVisuallyNonEmptyLayout(false)
112 , m_swipeWaitingForRenderTreeSizeThreshold(false)
113 , m_swipeWaitingForRepaint(false)
114 , m_swipeInProgress(false)
116 m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this);
119 ViewGestureController::~ViewGestureController()
121 if (m_swipeCancellationTracker)
122 [m_swipeCancellationTracker setIsCancelled:YES];
124 if (m_activeGestureType == ViewGestureType::Swipe)
125 removeSwipeSnapshot();
127 if (m_didMoveSwipeSnapshotCallback) {
128 Block_release(m_didMoveSwipeSnapshotCallback);
129 m_didMoveSwipeSnapshotCallback = nullptr;
132 m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID());
135 static double resistanceForDelta(double deltaScale, double currentScale)
137 // Zoom out with slight acceleration, until we reach minimum scale.
138 if (deltaScale < 0 && currentScale > minMagnification)
141 // Zoom in with no acceleration, until we reach maximum scale.
142 if (deltaScale > 0 && currentScale < maxMagnification)
145 // Outside of the extremes, resist further scaling.
146 double limit = currentScale < minMagnification ? minMagnification : maxMagnification;
147 double scaleDistance = fabs(limit - currentScale);
148 double scalePercent = std::min(std::max(scaleDistance / limit, 0.), 1.);
149 double resistance = zoomOutResistance + scalePercent * (0.01 - zoomOutResistance);
154 FloatPoint ViewGestureController::scaledMagnificationOrigin(FloatPoint origin, double scale)
156 FloatPoint scaledMagnificationOrigin(origin);
157 scaledMagnificationOrigin.moveBy(m_visibleContentRect.location());
158 float magnificationOriginScale = 1 - (scale / m_webPageProxy.pageScaleFactor());
159 scaledMagnificationOrigin.scale(magnificationOriginScale, magnificationOriginScale);
160 return scaledMagnificationOrigin;
163 void ViewGestureController::didCollectGeometryForMagnificationGesture(FloatRect visibleContentRect, bool frameHandlesMagnificationGesture)
165 m_activeGestureType = ViewGestureType::Magnification;
166 m_visibleContentRect = visibleContentRect;
167 m_visibleContentRectIsValid = true;
168 m_frameHandlesMagnificationGesture = frameHandlesMagnificationGesture;
171 void ViewGestureController::handleMagnificationGesture(double scale, FloatPoint origin)
173 ASSERT(m_activeGestureType == ViewGestureType::None || m_activeGestureType == ViewGestureType::Magnification);
175 if (m_activeGestureType == ViewGestureType::None) {
176 // FIXME: We drop the first frame of the gesture on the floor, because we don't have the visible content bounds yet.
177 m_magnification = m_webPageProxy.pageScaleFactor();
178 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForMagnificationGesture(), m_webPageProxy.pageID());
179 m_lastMagnificationGestureWasSmartMagnification = false;
184 // We're still waiting for the DidCollectGeometry callback.
185 if (!m_visibleContentRectIsValid)
188 m_activeGestureType = ViewGestureType::Magnification;
190 double scaleWithResistance = resistanceForDelta(scale, m_magnification) * scale;
192 m_magnification += m_magnification * scaleWithResistance;
193 m_magnification = std::min(std::max(m_magnification, minElasticMagnification), maxElasticMagnification);
195 m_magnificationOrigin = origin;
197 if (m_frameHandlesMagnificationGesture)
198 m_webPageProxy.scalePage(m_magnification, roundedIntPoint(origin));
200 m_webPageProxy.drawingArea()->adjustTransientZoom(m_magnification, scaledMagnificationOrigin(origin, m_magnification));
203 void ViewGestureController::endMagnificationGesture()
205 ASSERT(m_activeGestureType == ViewGestureType::Magnification);
207 double newMagnification = std::min(std::max(m_magnification, minMagnification), maxMagnification);
209 if (m_frameHandlesMagnificationGesture)
210 m_webPageProxy.scalePage(newMagnification, roundedIntPoint(m_magnificationOrigin));
212 m_webPageProxy.drawingArea()->commitTransientZoom(newMagnification, scaledMagnificationOrigin(m_magnificationOrigin, newMagnification));
214 m_activeGestureType = ViewGestureType::None;
217 void ViewGestureController::handleSmartMagnificationGesture(FloatPoint origin)
219 if (m_activeGestureType != ViewGestureType::None)
222 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForSmartMagnificationGesture(origin), m_webPageProxy.pageID());
225 static float maximumRectangleComponentDelta(FloatRect a, FloatRect b)
227 return std::max(fabs(a.x() - b.x()), std::max(fabs(a.y() - b.y()), std::max(fabs(a.width() - b.width()), fabs(a.height() - b.height()))));
230 void ViewGestureController::didCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect renderRect, FloatRect visibleContentRect, bool isReplacedElement, double viewportMinimumScale, double viewportMaximumScale)
232 double currentScaleFactor = m_webPageProxy.pageScaleFactor();
234 FloatRect unscaledTargetRect = renderRect;
236 // If there was no usable element under the cursor, we'll scale towards the cursor instead.
237 if (unscaledTargetRect.isEmpty())
238 unscaledTargetRect.setLocation(origin);
240 unscaledTargetRect.scale(1 / currentScaleFactor);
241 unscaledTargetRect.inflateX(unscaledTargetRect.width() * smartMagnificationElementPadding);
242 unscaledTargetRect.inflateY(unscaledTargetRect.height() * smartMagnificationElementPadding);
244 double targetMagnification = visibleContentRect.width() / unscaledTargetRect.width();
246 // For replaced elements like images, we want to fit the whole element
247 // in the view, so scale it down enough to make both dimensions fit if possible.
248 if (isReplacedElement)
249 targetMagnification = std::min(targetMagnification, static_cast<double>(visibleContentRect.height() / unscaledTargetRect.height()));
251 targetMagnification = std::min(std::max(targetMagnification, minMagnification), maxMagnification);
253 // Allow panning between elements via double-tap while magnified, unless the target rect is
254 // similar to the last one or the mouse has not moved, in which case we'll zoom all the way out.
255 if (currentScaleFactor > 1 && m_lastMagnificationGestureWasSmartMagnification) {
256 if (maximumRectangleComponentDelta(m_lastSmartMagnificationUnscaledTargetRect, unscaledTargetRect) < smartMagnificationPanScrollThreshold)
257 targetMagnification = 1;
259 if (m_lastSmartMagnificationOrigin == origin)
260 targetMagnification = 1;
263 FloatRect targetRect(unscaledTargetRect);
264 targetRect.scale(targetMagnification);
265 FloatPoint targetOrigin(visibleContentRect.center());
266 targetOrigin.moveBy(-targetRect.center());
268 m_webPageProxy.drawingArea()->adjustTransientZoom(m_webPageProxy.pageScaleFactor(), scaledMagnificationOrigin(FloatPoint(), m_webPageProxy.pageScaleFactor()));
269 m_webPageProxy.drawingArea()->commitTransientZoom(targetMagnification, targetOrigin);
271 m_lastSmartMagnificationUnscaledTargetRect = unscaledTargetRect;
272 m_lastSmartMagnificationOrigin = origin;
274 m_lastMagnificationGestureWasSmartMagnification = true;
277 bool ViewGestureController::scrollEventCanBecomeSwipe(NSEvent *event, ViewGestureController::SwipeDirection& potentialSwipeDirection)
279 if (event.phase != NSEventPhaseBegan)
282 if (!event.hasPreciseScrollingDeltas)
285 if (![NSEvent isSwipeTrackingFromScrollEventsEnabled])
288 if (fabs(event.scrollingDeltaX) <= fabs(event.scrollingDeltaY))
291 bool isPinnedToLeft = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToLeftSide();
292 bool isPinnedToRight = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToRightSide();
294 bool willSwipeLeft = event.scrollingDeltaX > 0 && isPinnedToLeft && m_webPageProxy.backForwardList().backItem();
295 bool willSwipeRight = event.scrollingDeltaX < 0 && isPinnedToRight && m_webPageProxy.backForwardList().forwardItem();
296 if (!willSwipeLeft && !willSwipeRight)
299 potentialSwipeDirection = willSwipeLeft ? ViewGestureController::SwipeDirection::Left : ViewGestureController::SwipeDirection::Right;
304 bool ViewGestureController::deltaIsSufficientToBeginSwipe(NSEvent *event)
306 if (m_pendingSwipeReason != PendingSwipeReason::InsufficientMagnitude)
309 m_cumulativeDeltaForPendingSwipe += FloatSize(event.scrollingDeltaX, event.scrollingDeltaY);
311 // If the cumulative delta is ever "too vertical", we will stop tracking this
312 // as a potential swipe until we get another "begin" event.
313 if (fabs(m_cumulativeDeltaForPendingSwipe.height()) >= fabs(m_cumulativeDeltaForPendingSwipe.width()) * minimumScrollEventRatioForSwipe) {
314 m_pendingSwipeReason = PendingSwipeReason::None;
318 if (fabs(m_cumulativeDeltaForPendingSwipe.width()) < minimumHorizontalSwipeDistance)
324 void ViewGestureController::setDidMoveSwipeSnapshotCallback(void(^callback)(CGRect))
326 if (m_didMoveSwipeSnapshotCallback)
327 Block_release(m_didMoveSwipeSnapshotCallback);
328 m_didMoveSwipeSnapshotCallback = Block_copy(callback);
331 bool ViewGestureController::handleScrollWheelEvent(NSEvent *event)
333 if (event.phase == NSEventPhaseEnded) {
334 m_cumulativeDeltaForPendingSwipe = FloatSize();
335 m_pendingSwipeReason = PendingSwipeReason::None;
338 if (m_pendingSwipeReason == PendingSwipeReason::InsufficientMagnitude) {
339 if (deltaIsSufficientToBeginSwipe(event)) {
340 trackSwipeGesture(event, m_pendingSwipeDirection);
345 if (m_activeGestureType != ViewGestureType::None)
348 SwipeDirection direction;
349 if (!scrollEventCanBecomeSwipe(event, direction))
352 if (!m_shouldIgnorePinnedState && m_webPageProxy.willHandleHorizontalScrollEvents()) {
353 m_pendingSwipeReason = PendingSwipeReason::WebCoreMayScroll;
354 m_pendingSwipeDirection = direction;
358 if (!deltaIsSufficientToBeginSwipe(event)) {
359 m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
360 m_pendingSwipeDirection = direction;
364 trackSwipeGesture(event, direction);
369 void ViewGestureController::wheelEventWasNotHandledByWebCore(NSEvent *event)
371 if (m_pendingSwipeReason != PendingSwipeReason::WebCoreMayScroll)
374 m_pendingSwipeReason = PendingSwipeReason::None;
376 SwipeDirection direction;
377 if (!scrollEventCanBecomeSwipe(event, direction))
380 if (!deltaIsSufficientToBeginSwipe(event)) {
381 m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
385 trackSwipeGesture(event, m_pendingSwipeDirection);
388 void ViewGestureController::trackSwipeGesture(NSEvent *event, SwipeDirection direction)
390 ASSERT(m_activeGestureType == ViewGestureType::None);
391 m_pendingSwipeReason = PendingSwipeReason::None;
393 m_webPageProxy.recordNavigationSnapshot();
395 CGFloat maxProgress = (direction == SwipeDirection::Left) ? 1 : 0;
396 CGFloat minProgress = (direction == SwipeDirection::Right) ? -1 : 0;
397 RefPtr<WebBackForwardListItem> targetItem = (direction == SwipeDirection::Left) ? m_webPageProxy.backForwardList().backItem() : m_webPageProxy.backForwardList().forwardItem();
398 __block bool swipeCancelled = false;
400 ASSERT(!m_swipeCancellationTracker);
401 RetainPtr<WKSwipeCancellationTracker> swipeCancellationTracker = adoptNS([[WKSwipeCancellationTracker alloc] init]);
402 m_swipeCancellationTracker = swipeCancellationTracker;
404 [event trackSwipeEventWithOptions:0 dampenAmountThresholdMin:minProgress max:maxProgress usingHandler:^(CGFloat progress, NSEventPhase phase, BOOL isComplete, BOOL *stop) {
405 if ([swipeCancellationTracker isCancelled]) {
409 if (phase == NSEventPhaseBegan)
410 this->beginSwipeGesture(targetItem.get(), direction);
411 CGFloat clampedProgress = std::min(std::max(progress, minProgress), maxProgress);
412 this->handleSwipeGesture(targetItem.get(), clampedProgress, direction);
413 if (phase == NSEventPhaseCancelled)
414 swipeCancelled = true;
416 this->endSwipeGesture(targetItem.get(), swipeCancelled);
420 FloatRect ViewGestureController::windowRelativeBoundsForCustomSwipeViews() const
423 for (const auto& view : m_customSwipeViews)
424 swipeArea.unite([view convertRect:[view bounds] toView:nil]);
425 swipeArea.setHeight(swipeArea.height() - m_customSwipeViewsTopContentInset);
429 static CALayer *leastCommonAncestorLayer(const Vector<RetainPtr<CALayer>>& layers)
431 Vector<Vector<CALayer *>> liveLayerPathsFromRoot(layers.size());
433 size_t shortestPathLength = std::numeric_limits<size_t>::max();
435 for (size_t layerIndex = 0; layerIndex < layers.size(); layerIndex++) {
436 CALayer *parent = [layers[layerIndex] superlayer];
438 liveLayerPathsFromRoot[layerIndex].insert(0, parent);
439 shortestPathLength = std::min(shortestPathLength, liveLayerPathsFromRoot[layerIndex].size());
440 parent = parent.superlayer;
444 for (size_t pathIndex = 0; pathIndex < shortestPathLength; pathIndex++) {
445 CALayer *firstPathLayer = liveLayerPathsFromRoot[0][pathIndex];
446 for (size_t layerIndex = 1; layerIndex < layers.size(); layerIndex++) {
447 if (liveLayerPathsFromRoot[layerIndex][pathIndex] != firstPathLayer)
448 return firstPathLayer;
452 return liveLayerPathsFromRoot[0][shortestPathLength];
455 CALayer *ViewGestureController::determineSnapshotLayerParent() const
457 if (m_currentSwipeLiveLayers.size() == 1)
458 return [m_currentSwipeLiveLayers[0] superlayer];
460 // We insert our snapshot into the first shared superlayer of the custom views' layer, above the frontmost or below the bottommost layer.
461 return leastCommonAncestorLayer(m_currentSwipeLiveLayers);
464 CALayer *ViewGestureController::determineLayerAdjacentToSnapshotForParent(SwipeDirection direction, CALayer *snapshotLayerParent) const
466 // If we have custom swiping views, we assume that the views were passed to us in back-to-front z-order.
467 CALayer *layerAdjacentToSnapshot = direction == SwipeDirection::Left ? m_currentSwipeLiveLayers.first().get() : m_currentSwipeLiveLayers.last().get();
469 if (m_currentSwipeLiveLayers.size() == 1)
470 return layerAdjacentToSnapshot;
472 // If the layers are not all siblings, find the child of the layer we're going to insert the snapshot into which has the frontmost/bottommost layer as a child.
473 while (snapshotLayerParent != layerAdjacentToSnapshot.superlayer)
474 layerAdjacentToSnapshot = layerAdjacentToSnapshot.superlayer;
475 return layerAdjacentToSnapshot;
478 bool ViewGestureController::shouldUseSnapshotForSize(ViewSnapshot& snapshot, FloatSize swipeLayerSize, float topContentInset)
480 float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
481 if (snapshot.deviceScaleFactor() != deviceScaleFactor)
484 FloatSize unobscuredSwipeLayerSizeInDeviceCoordinates = swipeLayerSize - FloatSize(0, topContentInset);
485 unobscuredSwipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
486 if (snapshot.size() != unobscuredSwipeLayerSizeInDeviceCoordinates)
492 static bool layerGeometryFlippedToRoot(CALayer *layer)
494 bool flipped = false;
495 CALayer *parent = layer;
497 if (parent.isGeometryFlipped)
499 parent = parent.superlayer;
504 void ViewGestureController::applyDebuggingPropertiesToSwipeViews()
506 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
507 CAFilter* filter = [CAFilter filterWithType:kCAFilterColorInvert];
508 [m_swipeLayer setFilters:@[ filter ]];
510 [m_swipeLayer setBackgroundColor:[NSColor blueColor].CGColor];
511 [m_swipeLayer setBorderColor:[NSColor yellowColor].CGColor];
512 [m_swipeLayer setBorderWidth:4];
514 [m_swipeSnapshotLayer setBackgroundColor:[NSColor greenColor].CGColor];
515 [m_swipeSnapshotLayer setBorderColor:[NSColor redColor].CGColor];
516 [m_swipeSnapshotLayer setBorderWidth:2];
519 void ViewGestureController::beginSwipeGesture(WebBackForwardListItem* targetItem, SwipeDirection direction)
521 ASSERT(m_currentSwipeLiveLayers.isEmpty());
523 m_webPageProxy.navigationGestureDidBegin();
525 m_activeGestureType = ViewGestureType::Swipe;
526 m_swipeInProgress = true;
528 CALayer *rootContentLayer = m_webPageProxy.acceleratedCompositingRootLayer();
530 m_swipeLayer = adoptNS([[CALayer alloc] init]);
531 m_swipeSnapshotLayer = adoptNS([[CALayer alloc] init]);
532 m_currentSwipeCustomViewBounds = windowRelativeBoundsForCustomSwipeViews();
535 float topContentInset = 0;
536 if (!m_customSwipeViews.isEmpty()) {
537 topContentInset = m_customSwipeViewsTopContentInset;
538 swipeArea = m_currentSwipeCustomViewBounds;
539 swipeArea.expand(0, topContentInset);
541 for (const auto& view : m_customSwipeViews) {
542 CALayer *layer = [view layer];
544 m_currentSwipeLiveLayers.append(layer);
547 swipeArea = FloatRect(FloatPoint(), m_webPageProxy.viewSize());
548 topContentInset = m_webPageProxy.topContentInset();
549 m_currentSwipeLiveLayers.append(rootContentLayer);
552 CALayer *snapshotLayerParent = determineSnapshotLayerParent();
553 bool geometryIsFlippedToRoot = layerGeometryFlippedToRoot(snapshotLayerParent);
555 RetainPtr<CGColorRef> backgroundColor = CGColorGetConstantColor(kCGColorWhite);
556 if (ViewSnapshot* snapshot = targetItem->snapshot()) {
557 if (shouldUseSnapshotForSize(*snapshot, swipeArea.size(), topContentInset))
558 [m_swipeSnapshotLayer setContents:snapshot->asLayerContents()];
560 Color coreColor = snapshot->backgroundColor();
561 if (coreColor.isValid())
562 backgroundColor = cachedCGColor(coreColor, ColorSpaceDeviceRGB);
563 #if USE_IOSURFACE_VIEW_SNAPSHOTS
564 m_currentSwipeSnapshotSurface = snapshot->surface();
568 [m_swipeLayer setBackgroundColor:backgroundColor.get()];
569 [m_swipeLayer setAnchorPoint:CGPointZero];
570 [m_swipeLayer setFrame:swipeArea];
571 [m_swipeLayer setName:@"Gesture Swipe Root Layer"];
572 [m_swipeLayer setGeometryFlipped:geometryIsFlippedToRoot];
573 [m_swipeLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
575 [m_swipeSnapshotLayer setContentsGravity:kCAGravityTopLeft];
576 [m_swipeSnapshotLayer setContentsScale:m_webPageProxy.deviceScaleFactor()];
577 [m_swipeSnapshotLayer setAnchorPoint:CGPointZero];
578 [m_swipeSnapshotLayer setFrame:CGRectMake(0, 0, swipeArea.width(), swipeArea.height() - topContentInset)];
579 [m_swipeSnapshotLayer setName:@"Gesture Swipe Snapshot Layer"];
580 [m_swipeSnapshotLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
582 [m_swipeLayer addSublayer:m_swipeSnapshotLayer.get()];
584 if (m_webPageProxy.preferences().viewGestureDebuggingEnabled())
585 applyDebuggingPropertiesToSwipeViews();
587 // We don't know enough about the custom views' hierarchy to apply a shadow.
588 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap && m_customSwipeViews.isEmpty()) {
589 if (direction == SwipeDirection::Left) {
590 float topContentInset = m_webPageProxy.topContentInset();
591 FloatRect shadowRect(FloatPoint(0, topContentInset), m_webPageProxy.viewSize() - FloatSize(0, topContentInset));
592 RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowRect, 0));
593 [rootContentLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
594 [rootContentLayer setShadowOpacity:swipeOverlayShadowOpacity];
595 [rootContentLayer setShadowRadius:swipeOverlayShadowRadius];
596 [rootContentLayer setShadowPath:shadowPath.get()];
598 RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect([m_swipeLayer bounds], 0));
599 [m_swipeLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
600 [m_swipeLayer setShadowOpacity:swipeOverlayShadowOpacity];
601 [m_swipeLayer setShadowRadius:swipeOverlayShadowRadius];
602 [m_swipeLayer setShadowPath:shadowPath.get()];
606 CALayer *layerAdjacentToSnapshot = determineLayerAdjacentToSnapshotForParent(direction, snapshotLayerParent);
607 if (direction == SwipeDirection::Left)
608 [snapshotLayerParent insertSublayer:m_swipeLayer.get() below:layerAdjacentToSnapshot];
610 [snapshotLayerParent insertSublayer:m_swipeLayer.get() above:layerAdjacentToSnapshot];
613 void ViewGestureController::handleSwipeGesture(WebBackForwardListItem* targetItem, double progress, SwipeDirection direction)
615 ASSERT(m_activeGestureType == ViewGestureType::Swipe);
617 if (!m_webPageProxy.drawingArea())
621 if (!m_customSwipeViews.isEmpty())
622 width = m_currentSwipeCustomViewBounds.width();
624 width = m_webPageProxy.drawingArea()->size().width();
626 double swipingLayerOffset = floor(width * progress);
628 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
629 if (direction == SwipeDirection::Right) {
630 [m_swipeLayer setTransform:CATransform3DMakeTranslation(width + swipingLayerOffset, 0, 0)];
631 didMoveSwipeSnapshotLayer();
633 } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
634 [m_swipeLayer setTransform:CATransform3DMakeTranslation((direction == SwipeDirection::Left ? -width : width) + swipingLayerOffset, 0, 0)];
636 for (const auto& layer : m_currentSwipeLiveLayers) {
637 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
638 if (direction == SwipeDirection::Left)
639 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
640 } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
641 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
645 void ViewGestureController::didMoveSwipeSnapshotLayer()
647 if (!m_didMoveSwipeSnapshotCallback)
650 m_didMoveSwipeSnapshotCallback(m_webPageProxy.boundsOfLayerInLayerBackedWindowCoordinates(m_swipeLayer.get()));
653 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, bool cancelled)
655 ASSERT(m_activeGestureType == ViewGestureType::Swipe);
657 m_swipeCancellationTracker = nullptr;
659 m_swipeInProgress = false;
661 CALayer *rootLayer = m_webPageProxy.acceleratedCompositingRootLayer();
663 [rootLayer setShadowOpacity:0];
664 [rootLayer setShadowRadius:0];
667 removeSwipeSnapshot();
668 m_webPageProxy.navigationGestureDidEnd(false, *targetItem);
672 uint64_t renderTreeSize = 0;
673 if (ViewSnapshot* snapshot = targetItem->snapshot())
674 renderTreeSize = snapshot->renderTreeSize();
676 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::SetRenderTreeSizeNotificationThreshold(renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction), m_webPageProxy.pageID());
678 m_swipeWaitingForVisuallyNonEmptyLayout = true;
679 m_swipeWaitingForRenderTreeSizeThreshold = true;
681 m_webPageProxy.navigationGestureDidEnd(true, *targetItem);
682 m_webPageProxy.goToBackForwardItem(targetItem);
684 m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
687 void ViewGestureController::didHitRenderTreeSizeThreshold()
689 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
692 m_swipeWaitingForRenderTreeSizeThreshold = false;
694 if (!m_swipeWaitingForVisuallyNonEmptyLayout)
695 removeSwipeSnapshotAfterRepaint();
698 void ViewGestureController::didFirstVisuallyNonEmptyLayoutForMainFrame()
700 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
703 m_swipeWaitingForVisuallyNonEmptyLayout = false;
705 if (!m_swipeWaitingForRenderTreeSizeThreshold)
706 removeSwipeSnapshotAfterRepaint();
708 m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.startOneShot(swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration.count());
709 m_swipeWatchdogTimer.stop();
713 void ViewGestureController::didFinishLoadForMainFrame()
715 removeSwipeSnapshotAfterRepaint();
718 void ViewGestureController::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type)
720 if (type != SameDocumentNavigationSessionStateReplace && type != SameDocumentNavigationSessionStatePop)
723 removeSwipeSnapshotAfterRepaint();
726 void ViewGestureController::swipeSnapshotWatchdogTimerFired()
728 removeSwipeSnapshotAfterRepaint();
731 void ViewGestureController::removeSwipeSnapshotAfterRepaint()
733 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
736 if (m_swipeWaitingForRepaint)
739 m_swipeWaitingForRepaint = true;
741 WebPageProxy* webPageProxy = &m_webPageProxy;
742 m_webPageProxy.forceRepaint(VoidCallback::create([webPageProxy] (CallbackBase::Error error) {
743 webPageProxy->removeNavigationGestureSnapshot();
747 void ViewGestureController::removeSwipeSnapshot()
749 m_swipeWaitingForRepaint = false;
751 m_swipeWatchdogTimer.stop();
752 m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.stop();
754 if (m_activeGestureType != ViewGestureType::Swipe)
757 #if USE_IOSURFACE_VIEW_SNAPSHOTS
758 if (m_currentSwipeSnapshotSurface)
759 m_currentSwipeSnapshotSurface->setIsVolatile(true);
760 m_currentSwipeSnapshotSurface = nullptr;
763 for (const auto& layer : m_currentSwipeLiveLayers)
764 [layer setTransform:CATransform3DIdentity];
766 [m_swipeSnapshotLayer removeFromSuperlayer];
767 m_swipeSnapshotLayer = nullptr;
769 [m_swipeLayer removeFromSuperlayer];
770 m_swipeLayer = nullptr;
772 m_currentSwipeLiveLayers.clear();
774 m_activeGestureType = ViewGestureType::None;
776 m_webPageProxy.navigationGestureSnapshotWasRemoved();
779 void ViewGestureController::endActiveGesture()
781 if (m_activeGestureType == ViewGestureType::Magnification) {
782 endMagnificationGesture();
783 m_visibleContentRectIsValid = false;
787 double ViewGestureController::magnification() const
789 if (m_activeGestureType == ViewGestureType::Magnification)
790 return m_magnification;
792 return m_webPageProxy.pageScaleFactor();
795 } // namespace WebKit
797 #endif // !PLATFORM(IOS)