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 = 3_s;
82 @interface WKSwipeCancellationTracker : NSObject {
87 @property (nonatomic) BOOL isCancelled;
91 @implementation WKSwipeCancellationTracker
92 @synthesize isCancelled=_isCancelled;
97 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
98 : m_webPageProxy(webPageProxy)
99 , m_activeGestureType(ViewGestureType::None)
100 , m_swipeWatchdogTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
101 , m_lastMagnificationGestureWasSmartMagnification(false)
102 , m_visibleContentRectIsValid(false)
103 , m_frameHandlesMagnificationGesture(false)
104 , m_swipeTransitionStyle(SwipeTransitionStyle::Overlap)
105 , m_customSwipeViewsTopContentInset(0)
106 , m_pendingSwipeReason(PendingSwipeReason::None)
107 , m_shouldIgnorePinnedState(false)
109 m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this);
112 ViewGestureController::~ViewGestureController()
114 if (m_swipeCancellationTracker)
115 [m_swipeCancellationTracker setIsCancelled:YES];
117 if (m_activeGestureType == ViewGestureType::Swipe)
118 removeSwipeSnapshot();
120 m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID());
123 static double resistanceForDelta(double deltaScale, double currentScale)
125 // Zoom out with slight acceleration, until we reach minimum scale.
126 if (deltaScale < 0 && currentScale > minMagnification)
129 // Zoom in with no acceleration, until we reach maximum scale.
130 if (deltaScale > 0 && currentScale < maxMagnification)
133 // Outside of the extremes, resist further scaling.
134 double limit = currentScale < minMagnification ? minMagnification : maxMagnification;
135 double scaleDistance = fabs(limit - currentScale);
136 double scalePercent = std::min(std::max(scaleDistance / limit, 0.), 1.);
137 double resistance = zoomOutResistance + scalePercent * (0.01 - zoomOutResistance);
142 FloatPoint ViewGestureController::scaledMagnificationOrigin(FloatPoint origin, double scale)
144 FloatPoint scaledMagnificationOrigin(origin);
145 scaledMagnificationOrigin.moveBy(m_visibleContentRect.location());
146 float magnificationOriginScale = 1 - (scale / m_webPageProxy.pageScaleFactor());
147 scaledMagnificationOrigin.scale(magnificationOriginScale, magnificationOriginScale);
148 return scaledMagnificationOrigin;
151 void ViewGestureController::didCollectGeometryForMagnificationGesture(FloatRect visibleContentRect, bool frameHandlesMagnificationGesture)
153 m_activeGestureType = ViewGestureType::Magnification;
154 m_visibleContentRect = visibleContentRect;
155 m_visibleContentRectIsValid = true;
156 m_frameHandlesMagnificationGesture = frameHandlesMagnificationGesture;
159 void ViewGestureController::handleMagnificationGesture(double scale, FloatPoint origin)
161 ASSERT(m_activeGestureType == ViewGestureType::None || m_activeGestureType == ViewGestureType::Magnification);
163 if (m_activeGestureType == ViewGestureType::None) {
164 // FIXME: We drop the first frame of the gesture on the floor, because we don't have the visible content bounds yet.
165 m_magnification = m_webPageProxy.pageScaleFactor();
166 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForMagnificationGesture(), m_webPageProxy.pageID());
167 m_lastMagnificationGestureWasSmartMagnification = false;
172 // We're still waiting for the DidCollectGeometry callback.
173 if (!m_visibleContentRectIsValid)
176 m_activeGestureType = ViewGestureType::Magnification;
178 double scaleWithResistance = resistanceForDelta(scale, m_magnification) * scale;
180 m_magnification += m_magnification * scaleWithResistance;
181 m_magnification = std::min(std::max(m_magnification, minElasticMagnification), maxElasticMagnification);
183 m_magnificationOrigin = origin;
185 if (m_frameHandlesMagnificationGesture)
186 m_webPageProxy.scalePage(m_magnification, roundedIntPoint(origin));
188 m_webPageProxy.drawingArea()->adjustTransientZoom(m_magnification, scaledMagnificationOrigin(origin, m_magnification));
191 void ViewGestureController::endMagnificationGesture()
193 ASSERT(m_activeGestureType == ViewGestureType::Magnification);
195 double newMagnification = std::min(std::max(m_magnification, minMagnification), maxMagnification);
197 if (m_frameHandlesMagnificationGesture)
198 m_webPageProxy.scalePage(newMagnification, roundedIntPoint(m_magnificationOrigin));
200 m_webPageProxy.drawingArea()->commitTransientZoom(newMagnification, scaledMagnificationOrigin(m_magnificationOrigin, newMagnification));
202 m_activeGestureType = ViewGestureType::None;
205 void ViewGestureController::handleSmartMagnificationGesture(FloatPoint origin)
207 if (m_activeGestureType != ViewGestureType::None)
210 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForSmartMagnificationGesture(origin), m_webPageProxy.pageID());
213 static float maximumRectangleComponentDelta(FloatRect a, FloatRect b)
215 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()))));
218 void ViewGestureController::didCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect renderRect, FloatRect visibleContentRect, bool isReplacedElement, double viewportMinimumScale, double viewportMaximumScale)
220 double currentScaleFactor = m_webPageProxy.pageScaleFactor();
222 FloatRect unscaledTargetRect = renderRect;
224 // If there was no usable element under the cursor, we'll scale towards the cursor instead.
225 if (unscaledTargetRect.isEmpty())
226 unscaledTargetRect.setLocation(origin);
228 unscaledTargetRect.scale(1 / currentScaleFactor);
229 unscaledTargetRect.inflateX(unscaledTargetRect.width() * smartMagnificationElementPadding);
230 unscaledTargetRect.inflateY(unscaledTargetRect.height() * smartMagnificationElementPadding);
232 double targetMagnification = visibleContentRect.width() / unscaledTargetRect.width();
234 // For replaced elements like images, we want to fit the whole element
235 // in the view, so scale it down enough to make both dimensions fit if possible.
236 if (isReplacedElement)
237 targetMagnification = std::min(targetMagnification, static_cast<double>(visibleContentRect.height() / unscaledTargetRect.height()));
239 targetMagnification = std::min(std::max(targetMagnification, minMagnification), maxMagnification);
241 // Allow panning between elements via double-tap while magnified, unless the target rect is
242 // similar to the last one or the mouse has not moved, in which case we'll zoom all the way out.
243 if (currentScaleFactor > 1 && m_lastMagnificationGestureWasSmartMagnification) {
244 if (maximumRectangleComponentDelta(m_lastSmartMagnificationUnscaledTargetRect, unscaledTargetRect) < smartMagnificationPanScrollThreshold)
245 targetMagnification = 1;
247 if (m_lastSmartMagnificationOrigin == origin)
248 targetMagnification = 1;
251 FloatRect targetRect(unscaledTargetRect);
252 targetRect.scale(targetMagnification);
253 FloatPoint targetOrigin(visibleContentRect.center());
254 targetOrigin.moveBy(-targetRect.center());
256 m_webPageProxy.drawingArea()->adjustTransientZoom(m_webPageProxy.pageScaleFactor(), scaledMagnificationOrigin(FloatPoint(), m_webPageProxy.pageScaleFactor()));
257 m_webPageProxy.drawingArea()->commitTransientZoom(targetMagnification, targetOrigin);
259 m_lastSmartMagnificationUnscaledTargetRect = unscaledTargetRect;
260 m_lastSmartMagnificationOrigin = origin;
262 m_lastMagnificationGestureWasSmartMagnification = true;
265 bool ViewGestureController::scrollEventCanBecomeSwipe(NSEvent *event, ViewGestureController::SwipeDirection& potentialSwipeDirection)
267 if (event.phase != NSEventPhaseBegan)
270 if (!event.hasPreciseScrollingDeltas)
273 if (![NSEvent isSwipeTrackingFromScrollEventsEnabled])
276 if (fabs(event.scrollingDeltaX) <= fabs(event.scrollingDeltaY))
279 bool isPinnedToLeft = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToLeftSide();
280 bool isPinnedToRight = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToRightSide();
282 bool willSwipeLeft = event.scrollingDeltaX > 0 && isPinnedToLeft && m_webPageProxy.backForwardList().backItem();
283 bool willSwipeRight = event.scrollingDeltaX < 0 && isPinnedToRight && m_webPageProxy.backForwardList().forwardItem();
284 if (!willSwipeLeft && !willSwipeRight)
287 potentialSwipeDirection = willSwipeLeft ? ViewGestureController::SwipeDirection::Left : ViewGestureController::SwipeDirection::Right;
292 bool ViewGestureController::deltaIsSufficientToBeginSwipe(NSEvent *event)
294 if (m_pendingSwipeReason != PendingSwipeReason::InsufficientMagnitude)
297 m_cumulativeDeltaForPendingSwipe += FloatSize(event.scrollingDeltaX, event.scrollingDeltaY);
299 // If the cumulative delta is ever "too vertical", we will stop tracking this
300 // as a potential swipe until we get another "begin" event.
301 if (fabs(m_cumulativeDeltaForPendingSwipe.height()) >= fabs(m_cumulativeDeltaForPendingSwipe.width()) * minimumScrollEventRatioForSwipe) {
302 m_pendingSwipeReason = PendingSwipeReason::None;
306 if (fabs(m_cumulativeDeltaForPendingSwipe.width()) < minimumHorizontalSwipeDistance)
312 bool ViewGestureController::handleScrollWheelEvent(NSEvent *event)
314 if (event.phase == NSEventPhaseEnded) {
315 m_cumulativeDeltaForPendingSwipe = FloatSize();
316 m_pendingSwipeReason = PendingSwipeReason::None;
319 if (m_pendingSwipeReason == PendingSwipeReason::InsufficientMagnitude) {
320 if (deltaIsSufficientToBeginSwipe(event)) {
321 m_pendingSwipeReason = PendingSwipeReason::None;
322 trackSwipeGesture(event, m_pendingSwipeDirection);
327 if (m_activeGestureType != ViewGestureType::None)
330 SwipeDirection direction;
331 if (!scrollEventCanBecomeSwipe(event, direction))
334 if (!m_shouldIgnorePinnedState && m_webPageProxy.willHandleHorizontalScrollEvents()) {
335 m_pendingSwipeReason = PendingSwipeReason::WebCoreMayScroll;
336 m_pendingSwipeDirection = direction;
340 m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
341 if (!deltaIsSufficientToBeginSwipe(event)) {
342 m_pendingSwipeDirection = direction;
346 trackSwipeGesture(event, direction);
351 void ViewGestureController::wheelEventWasNotHandledByWebCore(NSEvent *event)
353 if (m_pendingSwipeReason != PendingSwipeReason::WebCoreMayScroll)
356 m_pendingSwipeReason = PendingSwipeReason::None;
358 SwipeDirection direction;
359 if (!scrollEventCanBecomeSwipe(event, direction))
362 if (!deltaIsSufficientToBeginSwipe(event)) {
363 m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
367 trackSwipeGesture(event, m_pendingSwipeDirection);
370 void ViewGestureController::trackSwipeGesture(NSEvent *event, SwipeDirection direction)
372 m_webPageProxy.recordNavigationSnapshot();
374 CGFloat maxProgress = (direction == SwipeDirection::Left) ? 1 : 0;
375 CGFloat minProgress = (direction == SwipeDirection::Right) ? -1 : 0;
376 RefPtr<WebBackForwardListItem> targetItem = (direction == SwipeDirection::Left) ? m_webPageProxy.backForwardList().backItem() : m_webPageProxy.backForwardList().forwardItem();
377 __block bool swipeCancelled = false;
379 ASSERT(!m_swipeCancellationTracker);
380 RetainPtr<WKSwipeCancellationTracker> swipeCancellationTracker = adoptNS([[WKSwipeCancellationTracker alloc] init]);
381 m_swipeCancellationTracker = swipeCancellationTracker;
383 [event trackSwipeEventWithOptions:0 dampenAmountThresholdMin:minProgress max:maxProgress usingHandler:^(CGFloat progress, NSEventPhase phase, BOOL isComplete, BOOL *stop) {
384 if ([swipeCancellationTracker isCancelled]) {
388 if (phase == NSEventPhaseBegan)
389 this->beginSwipeGesture(targetItem.get(), direction);
390 CGFloat clampedProgress = std::min(std::max(progress, minProgress), maxProgress);
391 this->handleSwipeGesture(targetItem.get(), clampedProgress, direction);
392 if (phase == NSEventPhaseCancelled)
393 swipeCancelled = true;
395 this->endSwipeGesture(targetItem.get(), swipeCancelled);
399 FloatRect ViewGestureController::windowRelativeBoundsForCustomSwipeViews() const
402 for (const auto& view : m_customSwipeViews)
403 swipeArea.unite([view convertRect:[view bounds] toView:nil]);
404 swipeArea.setHeight(swipeArea.height() - m_customSwipeViewsTopContentInset);
408 static CALayer *leastCommonAncestorLayer(const Vector<RetainPtr<CALayer>>& layers)
410 Vector<Vector<CALayer *>> liveLayerPathsFromRoot(layers.size());
412 size_t shortestPathLength = std::numeric_limits<size_t>::max();
414 for (size_t layerIndex = 0; layerIndex < layers.size(); layerIndex++) {
415 CALayer *parent = [layers[layerIndex] superlayer];
417 liveLayerPathsFromRoot[layerIndex].insert(0, parent);
418 shortestPathLength = std::min(shortestPathLength, liveLayerPathsFromRoot[layerIndex].size());
419 parent = parent.superlayer;
423 for (size_t pathIndex = 0; pathIndex < shortestPathLength; pathIndex++) {
424 CALayer *firstPathLayer = liveLayerPathsFromRoot[0][pathIndex];
425 for (size_t layerIndex = 1; layerIndex < layers.size(); layerIndex++) {
426 if (liveLayerPathsFromRoot[layerIndex][pathIndex] != firstPathLayer)
427 return firstPathLayer;
431 return liveLayerPathsFromRoot[0][shortestPathLength];
434 CALayer *ViewGestureController::determineSnapshotLayerParent() const
436 if (m_currentSwipeLiveLayers.size() == 1)
437 return [m_currentSwipeLiveLayers[0] superlayer];
439 // We insert our snapshot into the first shared superlayer of the custom views' layer, above the frontmost or below the bottommost layer.
440 return leastCommonAncestorLayer(m_currentSwipeLiveLayers);
443 CALayer *ViewGestureController::determineLayerAdjacentToSnapshotForParent(SwipeDirection direction, CALayer *snapshotLayerParent) const
445 // If we have custom swiping views, we assume that the views were passed to us in back-to-front z-order.
446 CALayer *layerAdjacentToSnapshot = direction == SwipeDirection::Left ? m_currentSwipeLiveLayers.first().get() : m_currentSwipeLiveLayers.last().get();
448 if (m_currentSwipeLiveLayers.size() == 1)
449 return layerAdjacentToSnapshot;
451 // 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.
452 while (snapshotLayerParent != layerAdjacentToSnapshot.superlayer)
453 layerAdjacentToSnapshot = layerAdjacentToSnapshot.superlayer;
454 return layerAdjacentToSnapshot;
457 bool ViewGestureController::shouldUseSnapshotForSize(ViewSnapshot& snapshot, FloatSize swipeLayerSize, float topContentInset)
459 float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
460 if (snapshot.deviceScaleFactor() != deviceScaleFactor)
463 FloatSize unobscuredSwipeLayerSizeInDeviceCoordinates = swipeLayerSize - FloatSize(0, topContentInset);
464 unobscuredSwipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
465 if (snapshot.size() != unobscuredSwipeLayerSizeInDeviceCoordinates)
471 static bool layerGeometryFlippedToRoot(CALayer *layer)
473 bool flipped = false;
474 CALayer *parent = layer;
476 if (parent.isGeometryFlipped)
478 parent = parent.superlayer;
483 void ViewGestureController::applyDebuggingPropertiesToSwipeViews()
485 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
486 CAFilter* filter = [CAFilter filterWithType:kCAFilterColorInvert];
487 [m_swipeLayer setFilters:@[ filter ]];
489 [m_swipeLayer setBackgroundColor:[NSColor blueColor].CGColor];
490 [m_swipeLayer setBorderColor:[NSColor yellowColor].CGColor];
491 [m_swipeLayer setBorderWidth:4];
493 [m_swipeSnapshotLayer setBackgroundColor:[NSColor greenColor].CGColor];
494 [m_swipeSnapshotLayer setBorderColor:[NSColor redColor].CGColor];
495 [m_swipeSnapshotLayer setBorderWidth:2];
498 void ViewGestureController::beginSwipeGesture(WebBackForwardListItem* targetItem, SwipeDirection direction)
500 ASSERT(m_currentSwipeLiveLayers.isEmpty());
502 m_webPageProxy.navigationGestureDidBegin();
504 m_activeGestureType = ViewGestureType::Swipe;
506 CALayer *rootContentLayer = m_webPageProxy.acceleratedCompositingRootLayer();
508 m_swipeLayer = adoptNS([[CALayer alloc] init]);
509 m_swipeSnapshotLayer = adoptNS([[CALayer alloc] init]);
510 m_currentSwipeCustomViewBounds = windowRelativeBoundsForCustomSwipeViews();
513 float topContentInset = 0;
514 if (!m_customSwipeViews.isEmpty()) {
515 topContentInset = m_customSwipeViewsTopContentInset;
516 swipeArea = m_currentSwipeCustomViewBounds;
517 swipeArea.expand(0, topContentInset);
519 for (const auto& view : m_customSwipeViews) {
520 CALayer *layer = [view layer];
522 m_currentSwipeLiveLayers.append(layer);
525 swipeArea = FloatRect(FloatPoint(), m_webPageProxy.viewSize());
526 topContentInset = m_webPageProxy.topContentInset();
527 m_currentSwipeLiveLayers.append(rootContentLayer);
530 CALayer *snapshotLayerParent = determineSnapshotLayerParent();
531 bool geometryIsFlippedToRoot = layerGeometryFlippedToRoot(snapshotLayerParent);
533 RetainPtr<CGColorRef> backgroundColor = CGColorGetConstantColor(kCGColorWhite);
534 if (ViewSnapshot* snapshot = targetItem->snapshot()) {
535 if (shouldUseSnapshotForSize(*snapshot, swipeArea.size(), topContentInset))
536 [m_swipeSnapshotLayer setContents:snapshot->asLayerContents()];
538 Color coreColor = snapshot->backgroundColor();
539 if (coreColor.isValid())
540 backgroundColor = cachedCGColor(coreColor, ColorSpaceDeviceRGB);
541 #if USE_IOSURFACE_VIEW_SNAPSHOTS
542 m_currentSwipeSnapshotSurface = snapshot->surface();
546 [m_swipeLayer setBackgroundColor:backgroundColor.get()];
547 [m_swipeLayer setAnchorPoint:CGPointZero];
548 [m_swipeLayer setFrame:swipeArea];
549 [m_swipeLayer setName:@"Gesture Swipe Root Layer"];
550 [m_swipeLayer setGeometryFlipped:geometryIsFlippedToRoot];
551 [m_swipeLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
553 [m_swipeSnapshotLayer setContentsGravity:kCAGravityTopLeft];
554 [m_swipeSnapshotLayer setContentsScale:m_webPageProxy.deviceScaleFactor()];
555 [m_swipeSnapshotLayer setAnchorPoint:CGPointZero];
556 [m_swipeSnapshotLayer setFrame:CGRectMake(0, 0, swipeArea.width(), swipeArea.height() - topContentInset)];
557 [m_swipeSnapshotLayer setName:@"Gesture Swipe Snapshot Layer"];
558 [m_swipeSnapshotLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
560 [m_swipeLayer addSublayer:m_swipeSnapshotLayer.get()];
562 if (m_webPageProxy.preferences().viewGestureDebuggingEnabled())
563 applyDebuggingPropertiesToSwipeViews();
565 // We don't know enough about the custom views' hierarchy to apply a shadow.
566 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap && m_customSwipeViews.isEmpty()) {
567 if (direction == SwipeDirection::Left) {
568 float topContentInset = m_webPageProxy.topContentInset();
569 FloatRect shadowRect(FloatPoint(0, topContentInset), m_webPageProxy.viewSize() - FloatSize(0, topContentInset));
570 RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowRect, 0));
571 [rootContentLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
572 [rootContentLayer setShadowOpacity:swipeOverlayShadowOpacity];
573 [rootContentLayer setShadowRadius:swipeOverlayShadowRadius];
574 [rootContentLayer setShadowPath:shadowPath.get()];
576 RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect([m_swipeLayer bounds], 0));
577 [m_swipeLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
578 [m_swipeLayer setShadowOpacity:swipeOverlayShadowOpacity];
579 [m_swipeLayer setShadowRadius:swipeOverlayShadowRadius];
580 [m_swipeLayer setShadowPath:shadowPath.get()];
584 CALayer *layerAdjacentToSnapshot = determineLayerAdjacentToSnapshotForParent(direction, snapshotLayerParent);
585 if (direction == SwipeDirection::Left)
586 [snapshotLayerParent insertSublayer:m_swipeLayer.get() below:layerAdjacentToSnapshot];
588 [snapshotLayerParent insertSublayer:m_swipeLayer.get() above:layerAdjacentToSnapshot];
591 void ViewGestureController::handleSwipeGesture(WebBackForwardListItem* targetItem, double progress, SwipeDirection direction)
593 ASSERT(m_activeGestureType == ViewGestureType::Swipe);
595 if (!m_webPageProxy.drawingArea())
599 if (!m_customSwipeViews.isEmpty())
600 width = m_currentSwipeCustomViewBounds.width();
602 width = m_webPageProxy.drawingArea()->size().width();
604 double swipingLayerOffset = floor(width * progress);
606 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
607 if (direction == SwipeDirection::Right)
608 [m_swipeLayer setTransform:CATransform3DMakeTranslation(width + swipingLayerOffset, 0, 0)];
609 } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
610 [m_swipeLayer setTransform:CATransform3DMakeTranslation((direction == SwipeDirection::Left ? -width : width) + swipingLayerOffset, 0, 0)];
612 for (const auto& layer : m_currentSwipeLiveLayers) {
613 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
614 if (direction == SwipeDirection::Left)
615 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
616 } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
617 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
621 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, bool cancelled)
623 ASSERT(m_activeGestureType == ViewGestureType::Swipe);
625 m_swipeCancellationTracker = nullptr;
627 CALayer *rootLayer = m_webPageProxy.acceleratedCompositingRootLayer();
629 [rootLayer setShadowOpacity:0];
630 [rootLayer setShadowRadius:0];
633 removeSwipeSnapshot();
634 m_webPageProxy.navigationGestureDidEnd(false, *targetItem);
638 uint64_t renderTreeSize = 0;
639 if (ViewSnapshot* snapshot = targetItem->snapshot())
640 renderTreeSize = snapshot->renderTreeSize();
642 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::SetRenderTreeSizeNotificationThreshold(renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction), m_webPageProxy.pageID());
644 m_webPageProxy.navigationGestureDidEnd(true, *targetItem);
645 m_webPageProxy.goToBackForwardItem(targetItem);
647 if (!renderTreeSize) {
648 removeSwipeSnapshot();
652 m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
655 void ViewGestureController::didHitRenderTreeSizeThreshold()
657 removeSwipeSnapshot();
660 void ViewGestureController::swipeSnapshotWatchdogTimerFired()
662 removeSwipeSnapshot();
665 void ViewGestureController::removeSwipeSnapshot()
667 m_swipeWatchdogTimer.stop();
669 if (m_activeGestureType != ViewGestureType::Swipe)
672 #if USE_IOSURFACE_VIEW_SNAPSHOTS
673 if (m_currentSwipeSnapshotSurface)
674 m_currentSwipeSnapshotSurface->setIsVolatile(true);
675 m_currentSwipeSnapshotSurface = nullptr;
678 for (const auto& layer : m_currentSwipeLiveLayers)
679 [layer setTransform:CATransform3DIdentity];
681 [m_swipeSnapshotLayer removeFromSuperlayer];
682 m_swipeSnapshotLayer = nullptr;
684 [m_swipeLayer removeFromSuperlayer];
685 m_swipeLayer = nullptr;
687 m_currentSwipeLiveLayers.clear();
689 m_activeGestureType = ViewGestureType::None;
691 m_webPageProxy.navigationGestureSnapshotWasRemoved();
694 void ViewGestureController::endActiveGesture()
696 if (m_activeGestureType == ViewGestureType::Magnification) {
697 endMagnificationGesture();
698 m_visibleContentRectIsValid = false;
702 double ViewGestureController::magnification() const
704 if (m_activeGestureType == ViewGestureType::Magnification)
705 return m_magnification;
707 return m_webPageProxy.pageScaleFactor();
710 } // namespace WebKit
712 #endif // !PLATFORM(IOS)