Random crashes on the Web Thread due to Timers firing on the wrong thread in the...
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / ViewGestureControllerMac.mm
1 /*
2  * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #import "config.h"
27 #import "ViewGestureController.h"
28
29 #if PLATFORM(MAC)
30
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>
45
46 #if defined(__has_include) && __has_include(<QuartzCore/QuartzCorePrivate.h>)
47 #import <QuartzCore/QuartzCorePrivate.h>
48 #else
49 @interface CAFilter : NSObject <NSCopying, NSMutableCopying, NSCoding>
50 @end
51 #endif
52
53 @interface CAFilter (Details)
54 + (CAFilter *)filterWithType:(NSString *)type;
55 @end
56
57 extern NSString * const kCAFilterColorInvert;
58
59 using namespace WebCore;
60
61 static const double minMagnification = 1;
62 static const double maxMagnification = 3;
63
64 static const double minElasticMagnification = 0.75;
65 static const double maxElasticMagnification = 4;
66
67 static const double zoomOutBoost = 1.6;
68 static const double zoomOutResistance = 0.10;
69
70 static const float smartMagnificationElementPadding = 0.05;
71 static const float smartMagnificationPanScrollThreshold = 100;
72
73 static const double swipeOverlayShadowOpacity = 0.66;
74 static const double swipeOverlayShadowRadius = 3;
75
76 static const CGFloat minimumHorizontalSwipeDistance = 15;
77 static const float minimumScrollEventRatioForSwipe = 0.5;
78
79 static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5;
80 static const std::chrono::seconds swipeSnapshotRemovalWatchdogDuration = 3_s;
81
82 @interface WKSwipeCancellationTracker : NSObject {
83 @private
84     BOOL _isCancelled;
85 }
86
87 @property (nonatomic) BOOL isCancelled;
88
89 @end
90
91 @implementation WKSwipeCancellationTracker
92 @synthesize isCancelled=_isCancelled;
93 @end
94
95 namespace WebKit {
96
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)
108 {
109     m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this);
110 }
111
112 ViewGestureController::~ViewGestureController()
113 {
114     if (m_swipeCancellationTracker)
115         [m_swipeCancellationTracker setIsCancelled:YES];
116
117     if (m_activeGestureType == ViewGestureType::Swipe)
118         removeSwipeSnapshot();
119
120     m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID());
121 }
122
123 static double resistanceForDelta(double deltaScale, double currentScale)
124 {
125     // Zoom out with slight acceleration, until we reach minimum scale.
126     if (deltaScale < 0 && currentScale > minMagnification)
127         return zoomOutBoost;
128
129     // Zoom in with no acceleration, until we reach maximum scale.
130     if (deltaScale > 0 && currentScale < maxMagnification)
131         return 1;
132
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);
138
139     return resistance;
140 }
141
142 FloatPoint ViewGestureController::scaledMagnificationOrigin(FloatPoint origin, double scale)
143 {
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;
149 }
150
151 void ViewGestureController::didCollectGeometryForMagnificationGesture(FloatRect visibleContentRect, bool frameHandlesMagnificationGesture)
152 {
153     m_activeGestureType = ViewGestureType::Magnification;
154     m_visibleContentRect = visibleContentRect;
155     m_visibleContentRectIsValid = true;
156     m_frameHandlesMagnificationGesture = frameHandlesMagnificationGesture;
157 }
158
159 void ViewGestureController::handleMagnificationGesture(double scale, FloatPoint origin)
160 {
161     ASSERT(m_activeGestureType == ViewGestureType::None || m_activeGestureType == ViewGestureType::Magnification);
162
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;
168
169         return;
170     }
171
172     // We're still waiting for the DidCollectGeometry callback.
173     if (!m_visibleContentRectIsValid)
174         return;
175
176     m_activeGestureType = ViewGestureType::Magnification;
177
178     double scaleWithResistance = resistanceForDelta(scale, m_magnification) * scale;
179
180     m_magnification += m_magnification * scaleWithResistance;
181     m_magnification = std::min(std::max(m_magnification, minElasticMagnification), maxElasticMagnification);
182
183     m_magnificationOrigin = origin;
184
185     if (m_frameHandlesMagnificationGesture)
186         m_webPageProxy.scalePage(m_magnification, roundedIntPoint(origin));
187     else
188         m_webPageProxy.drawingArea()->adjustTransientZoom(m_magnification, scaledMagnificationOrigin(origin, m_magnification));
189 }
190
191 void ViewGestureController::endMagnificationGesture()
192 {
193     ASSERT(m_activeGestureType == ViewGestureType::Magnification);
194
195     double newMagnification = std::min(std::max(m_magnification, minMagnification), maxMagnification);
196
197     if (m_frameHandlesMagnificationGesture)
198         m_webPageProxy.scalePage(newMagnification, roundedIntPoint(m_magnificationOrigin));
199     else
200         m_webPageProxy.drawingArea()->commitTransientZoom(newMagnification, scaledMagnificationOrigin(m_magnificationOrigin, newMagnification));
201
202     m_activeGestureType = ViewGestureType::None;
203 }
204
205 void ViewGestureController::handleSmartMagnificationGesture(FloatPoint origin)
206 {
207     if (m_activeGestureType != ViewGestureType::None)
208         return;
209
210     m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForSmartMagnificationGesture(origin), m_webPageProxy.pageID());
211 }
212
213 static float maximumRectangleComponentDelta(FloatRect a, FloatRect b)
214 {
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()))));
216 }
217
218 void ViewGestureController::didCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect renderRect, FloatRect visibleContentRect, bool isReplacedElement, double viewportMinimumScale, double viewportMaximumScale)
219 {
220     double currentScaleFactor = m_webPageProxy.pageScaleFactor();
221
222     FloatRect unscaledTargetRect = renderRect;
223
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);
227
228     unscaledTargetRect.scale(1 / currentScaleFactor);
229     unscaledTargetRect.inflateX(unscaledTargetRect.width() * smartMagnificationElementPadding);
230     unscaledTargetRect.inflateY(unscaledTargetRect.height() * smartMagnificationElementPadding);
231
232     double targetMagnification = visibleContentRect.width() / unscaledTargetRect.width();
233
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()));
238
239     targetMagnification = std::min(std::max(targetMagnification, minMagnification), maxMagnification);
240
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;
246
247         if (m_lastSmartMagnificationOrigin == origin)
248             targetMagnification = 1;
249     }
250
251     FloatRect targetRect(unscaledTargetRect);
252     targetRect.scale(targetMagnification);
253     FloatPoint targetOrigin(visibleContentRect.center());
254     targetOrigin.moveBy(-targetRect.center());
255
256     m_webPageProxy.drawingArea()->adjustTransientZoom(m_webPageProxy.pageScaleFactor(), scaledMagnificationOrigin(FloatPoint(), m_webPageProxy.pageScaleFactor()));
257     m_webPageProxy.drawingArea()->commitTransientZoom(targetMagnification, targetOrigin);
258
259     m_lastSmartMagnificationUnscaledTargetRect = unscaledTargetRect;
260     m_lastSmartMagnificationOrigin = origin;
261
262     m_lastMagnificationGestureWasSmartMagnification = true;
263 }
264
265 bool ViewGestureController::scrollEventCanBecomeSwipe(NSEvent *event, ViewGestureController::SwipeDirection& potentialSwipeDirection)
266 {
267     if (event.phase != NSEventPhaseBegan)
268         return false;
269
270     if (!event.hasPreciseScrollingDeltas)
271         return false;
272
273     if (![NSEvent isSwipeTrackingFromScrollEventsEnabled])
274         return false;
275
276     if (fabs(event.scrollingDeltaX) <= fabs(event.scrollingDeltaY))
277         return false;
278
279     bool isPinnedToLeft = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToLeftSide();
280     bool isPinnedToRight = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToRightSide();
281
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)
285         return false;
286
287     potentialSwipeDirection = willSwipeLeft ? ViewGestureController::SwipeDirection::Left : ViewGestureController::SwipeDirection::Right;
288
289     return true;
290 }
291
292 bool ViewGestureController::deltaIsSufficientToBeginSwipe(NSEvent *event)
293 {
294     if (m_pendingSwipeReason != PendingSwipeReason::InsufficientMagnitude)
295         return false;
296
297     m_cumulativeDeltaForPendingSwipe += FloatSize(event.scrollingDeltaX, event.scrollingDeltaY);
298
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;
303         return false;
304     }
305
306     if (fabs(m_cumulativeDeltaForPendingSwipe.width()) < minimumHorizontalSwipeDistance)
307         return false;
308
309     return true;
310 }
311
312 bool ViewGestureController::handleScrollWheelEvent(NSEvent *event)
313 {
314     if (event.phase == NSEventPhaseEnded) {
315         m_cumulativeDeltaForPendingSwipe = FloatSize();
316         m_pendingSwipeReason = PendingSwipeReason::None;
317     }
318
319     if (m_pendingSwipeReason == PendingSwipeReason::InsufficientMagnitude) {
320         if (deltaIsSufficientToBeginSwipe(event)) {
321             m_pendingSwipeReason = PendingSwipeReason::None;
322             trackSwipeGesture(event, m_pendingSwipeDirection);
323             return true;
324         }
325     }
326
327     if (m_activeGestureType != ViewGestureType::None)
328         return false;
329
330     SwipeDirection direction;
331     if (!scrollEventCanBecomeSwipe(event, direction))
332         return false;
333
334     if (!m_shouldIgnorePinnedState && m_webPageProxy.willHandleHorizontalScrollEvents()) {
335         m_pendingSwipeReason = PendingSwipeReason::WebCoreMayScroll;
336         m_pendingSwipeDirection = direction;
337         return false;
338     }
339
340     m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
341     if (!deltaIsSufficientToBeginSwipe(event)) {
342         m_pendingSwipeDirection = direction;
343         return true;
344     }
345
346     trackSwipeGesture(event, direction);
347
348     return true;
349 }
350
351 void ViewGestureController::wheelEventWasNotHandledByWebCore(NSEvent *event)
352 {
353     if (m_pendingSwipeReason != PendingSwipeReason::WebCoreMayScroll)
354         return;
355
356     m_pendingSwipeReason = PendingSwipeReason::None;
357
358     SwipeDirection direction;
359     if (!scrollEventCanBecomeSwipe(event, direction))
360         return;
361
362     if (!deltaIsSufficientToBeginSwipe(event)) {
363         m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
364         return;
365     }
366
367     trackSwipeGesture(event, m_pendingSwipeDirection);
368 }
369
370 void ViewGestureController::trackSwipeGesture(NSEvent *event, SwipeDirection direction)
371 {
372     m_webPageProxy.recordNavigationSnapshot();
373
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;
378
379     ASSERT(!m_swipeCancellationTracker);
380     RetainPtr<WKSwipeCancellationTracker> swipeCancellationTracker = adoptNS([[WKSwipeCancellationTracker alloc] init]);
381     m_swipeCancellationTracker = swipeCancellationTracker;
382
383     [event trackSwipeEventWithOptions:0 dampenAmountThresholdMin:minProgress max:maxProgress usingHandler:^(CGFloat progress, NSEventPhase phase, BOOL isComplete, BOOL *stop) {
384         if ([swipeCancellationTracker isCancelled]) {
385             *stop = YES;
386             return;
387         }
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;
394         if (isComplete)
395             this->endSwipeGesture(targetItem.get(), swipeCancelled);
396     }];
397 }
398
399 FloatRect ViewGestureController::windowRelativeBoundsForCustomSwipeViews() const
400 {
401     FloatRect swipeArea;
402     for (const auto& view : m_customSwipeViews)
403         swipeArea.unite([view convertRect:[view bounds] toView:nil]);
404     swipeArea.setHeight(swipeArea.height() - m_customSwipeViewsTopContentInset);
405     return swipeArea;
406 }
407
408 static CALayer *leastCommonAncestorLayer(const Vector<RetainPtr<CALayer>>& layers)
409 {
410     Vector<Vector<CALayer *>> liveLayerPathsFromRoot(layers.size());
411
412     size_t shortestPathLength = std::numeric_limits<size_t>::max();
413
414     for (size_t layerIndex = 0; layerIndex < layers.size(); layerIndex++) {
415         CALayer *parent = [layers[layerIndex] superlayer];
416         while (parent) {
417             liveLayerPathsFromRoot[layerIndex].insert(0, parent);
418             shortestPathLength = std::min(shortestPathLength, liveLayerPathsFromRoot[layerIndex].size());
419             parent = parent.superlayer;
420         }
421     }
422
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;
428         }
429     }
430
431     return liveLayerPathsFromRoot[0][shortestPathLength];
432 }
433
434 CALayer *ViewGestureController::determineSnapshotLayerParent() const
435 {
436     if (m_currentSwipeLiveLayers.size() == 1)
437         return [m_currentSwipeLiveLayers[0] superlayer];
438
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);
441 }
442
443 CALayer *ViewGestureController::determineLayerAdjacentToSnapshotForParent(SwipeDirection direction, CALayer *snapshotLayerParent) const
444 {
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();
447
448     if (m_currentSwipeLiveLayers.size() == 1)
449         return layerAdjacentToSnapshot;
450
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;
455 }
456
457 bool ViewGestureController::shouldUseSnapshotForSize(ViewSnapshot& snapshot, FloatSize swipeLayerSize, float topContentInset)
458 {
459     float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
460     if (snapshot.deviceScaleFactor() != deviceScaleFactor)
461         return false;
462
463     FloatSize unobscuredSwipeLayerSizeInDeviceCoordinates = swipeLayerSize - FloatSize(0, topContentInset);
464     unobscuredSwipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
465     if (snapshot.size() != unobscuredSwipeLayerSizeInDeviceCoordinates)
466         return false;
467
468     return true;
469 }
470
471 static bool layerGeometryFlippedToRoot(CALayer *layer)
472 {
473     bool flipped = false;
474     CALayer *parent = layer;
475     while (parent) {
476         if (parent.isGeometryFlipped)
477             flipped = !flipped;
478         parent = parent.superlayer;
479     }
480     return flipped;
481 }
482
483 void ViewGestureController::applyDebuggingPropertiesToSwipeViews()
484 {
485 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
486     CAFilter* filter = [CAFilter filterWithType:kCAFilterColorInvert];
487     [m_swipeLayer setFilters:@[ filter ]];
488 #endif
489     [m_swipeLayer setBackgroundColor:[NSColor blueColor].CGColor];
490     [m_swipeLayer setBorderColor:[NSColor yellowColor].CGColor];
491     [m_swipeLayer setBorderWidth:4];
492
493     [m_swipeSnapshotLayer setBackgroundColor:[NSColor greenColor].CGColor];
494     [m_swipeSnapshotLayer setBorderColor:[NSColor redColor].CGColor];
495     [m_swipeSnapshotLayer setBorderWidth:2];
496 }
497
498 void ViewGestureController::beginSwipeGesture(WebBackForwardListItem* targetItem, SwipeDirection direction)
499 {
500     ASSERT(m_currentSwipeLiveLayers.isEmpty());
501
502     m_webPageProxy.navigationGestureDidBegin();
503
504     m_activeGestureType = ViewGestureType::Swipe;
505
506     CALayer *rootContentLayer = m_webPageProxy.acceleratedCompositingRootLayer();
507
508     m_swipeLayer = adoptNS([[CALayer alloc] init]);
509     m_swipeSnapshotLayer = adoptNS([[CALayer alloc] init]);
510     m_currentSwipeCustomViewBounds = windowRelativeBoundsForCustomSwipeViews();
511
512     FloatRect swipeArea;
513     float topContentInset = 0;
514     if (!m_customSwipeViews.isEmpty()) {
515         topContentInset = m_customSwipeViewsTopContentInset;
516         swipeArea = m_currentSwipeCustomViewBounds;
517         swipeArea.expand(0, topContentInset);
518
519         for (const auto& view : m_customSwipeViews) {
520             CALayer *layer = [view layer];
521             ASSERT(layer);
522             m_currentSwipeLiveLayers.append(layer);
523         }
524     } else {
525         swipeArea = FloatRect(FloatPoint(), m_webPageProxy.viewSize());
526         topContentInset = m_webPageProxy.topContentInset();
527         m_currentSwipeLiveLayers.append(rootContentLayer);
528     }
529
530     CALayer *snapshotLayerParent = determineSnapshotLayerParent();
531     bool geometryIsFlippedToRoot = layerGeometryFlippedToRoot(snapshotLayerParent);
532
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()];
537
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();
543 #endif
544     }
545
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]];
552
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]];
559
560     [m_swipeLayer addSublayer:m_swipeSnapshotLayer.get()];
561
562     if (m_webPageProxy.preferences().viewGestureDebuggingEnabled())
563         applyDebuggingPropertiesToSwipeViews();
564
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()];
575         } else {
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()];
581         }
582     }
583
584     CALayer *layerAdjacentToSnapshot = determineLayerAdjacentToSnapshotForParent(direction, snapshotLayerParent);
585     if (direction == SwipeDirection::Left)
586         [snapshotLayerParent insertSublayer:m_swipeLayer.get() below:layerAdjacentToSnapshot];
587     else
588         [snapshotLayerParent insertSublayer:m_swipeLayer.get() above:layerAdjacentToSnapshot];
589 }
590
591 void ViewGestureController::handleSwipeGesture(WebBackForwardListItem* targetItem, double progress, SwipeDirection direction)
592 {
593     ASSERT(m_activeGestureType == ViewGestureType::Swipe);
594
595     if (!m_webPageProxy.drawingArea())
596         return;
597
598     double width;
599     if (!m_customSwipeViews.isEmpty())
600         width = m_currentSwipeCustomViewBounds.width();
601     else
602         width = m_webPageProxy.drawingArea()->size().width();
603
604     double swipingLayerOffset = floor(width * progress);
605
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)];
611
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)];
618     }
619 }
620
621 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, bool cancelled)
622 {
623     ASSERT(m_activeGestureType == ViewGestureType::Swipe);
624
625     m_swipeCancellationTracker = nullptr;
626
627     CALayer *rootLayer = m_webPageProxy.acceleratedCompositingRootLayer();
628
629     [rootLayer setShadowOpacity:0];
630     [rootLayer setShadowRadius:0];
631
632     if (cancelled) {
633         removeSwipeSnapshot();
634         m_webPageProxy.navigationGestureDidEnd(false, *targetItem);
635         return;
636     }
637
638     uint64_t renderTreeSize = 0;
639     if (ViewSnapshot* snapshot = targetItem->snapshot())
640         renderTreeSize = snapshot->renderTreeSize();
641
642     m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::SetRenderTreeSizeNotificationThreshold(renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction), m_webPageProxy.pageID());
643
644     m_webPageProxy.navigationGestureDidEnd(true, *targetItem);
645     m_webPageProxy.goToBackForwardItem(targetItem);
646
647     if (!renderTreeSize) {
648         removeSwipeSnapshot();
649         return;
650     }
651
652     m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
653 }
654
655 void ViewGestureController::didHitRenderTreeSizeThreshold()
656 {
657     removeSwipeSnapshot();
658 }
659
660 void ViewGestureController::swipeSnapshotWatchdogTimerFired()
661 {
662     removeSwipeSnapshot();
663 }
664
665 void ViewGestureController::removeSwipeSnapshot()
666 {
667     m_swipeWatchdogTimer.stop();
668
669     if (m_activeGestureType != ViewGestureType::Swipe)
670         return;
671
672 #if USE_IOSURFACE_VIEW_SNAPSHOTS
673     if (m_currentSwipeSnapshotSurface)
674         m_currentSwipeSnapshotSurface->setIsVolatile(true);
675     m_currentSwipeSnapshotSurface = nullptr;
676 #endif
677
678     for (const auto& layer : m_currentSwipeLiveLayers)
679         [layer setTransform:CATransform3DIdentity];
680
681     [m_swipeSnapshotLayer removeFromSuperlayer];
682     m_swipeSnapshotLayer = nullptr;
683
684     [m_swipeLayer removeFromSuperlayer];
685     m_swipeLayer = nullptr;
686
687     m_currentSwipeLiveLayers.clear();
688
689     m_activeGestureType = ViewGestureType::None;
690
691     m_webPageProxy.navigationGestureSnapshotWasRemoved();
692 }
693
694 void ViewGestureController::endActiveGesture()
695 {
696     if (m_activeGestureType == ViewGestureType::Magnification) {
697         endMagnificationGesture();
698         m_visibleContentRectIsValid = false;
699     }
700 }
701
702 double ViewGestureController::magnification() const
703 {
704     if (m_activeGestureType == ViewGestureType::Magnification)
705         return m_magnification;
706
707     return m_webPageProxy.pageScaleFactor();
708 }
709
710 } // namespace WebKit
711
712 #endif // !PLATFORM(IOS)