WKView should stop using endGestureWithEvent
[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 "FrameLoadState.h"
32 #import "NativeWebWheelEvent.h"
33 #import "ViewGestureControllerMessages.h"
34 #import "ViewGestureGeometryCollectorMessages.h"
35 #import "ViewSnapshotStore.h"
36 #import "WebBackForwardList.h"
37 #import "WebPageGroup.h"
38 #import "WebPageMessages.h"
39 #import "WebPageProxy.h"
40 #import "WebPreferences.h"
41 #import "WebProcessProxy.h"
42 #import <Cocoa/Cocoa.h>
43 #import <WebCore/IOSurface.h>
44 #import <WebCore/QuartzCoreSPI.h>
45 #import <WebCore/WebActionDisablingCALayerDelegate.h>
46
47 using namespace WebCore;
48
49 static const double minMagnification = 1;
50 static const double maxMagnification = 3;
51
52 static const double minElasticMagnification = 0.75;
53 static const double maxElasticMagnification = 4;
54
55 static const double zoomOutBoost = 1.6;
56 static const double zoomOutResistance = 0.10;
57
58 static const float smartMagnificationElementPadding = 0.05;
59 static const float smartMagnificationPanScrollThreshold = 100;
60
61 static const double swipeOverlayShadowOpacity = 0.66;
62 static const double swipeOverlayShadowRadius = 3;
63
64 static const CGFloat minimumHorizontalSwipeDistance = 15;
65 static const float minimumScrollEventRatioForSwipe = 0.5;
66
67 static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5;
68 static const std::chrono::seconds swipeSnapshotRemovalWatchdogDuration = 5_s;
69 static const std::chrono::seconds swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration = 3_s;
70 static const std::chrono::milliseconds swipeSnapshotRemovalActiveLoadMonitoringInterval = 250_ms;
71
72 @interface WKSwipeCancellationTracker : NSObject {
73 @private
74     BOOL _isCancelled;
75 }
76
77 @property (nonatomic) BOOL isCancelled;
78
79 @end
80
81 @implementation WKSwipeCancellationTracker
82 @synthesize isCancelled=_isCancelled;
83 @end
84
85 namespace WebKit {
86
87 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
88     : m_webPageProxy(webPageProxy)
89     , m_swipeWatchdogTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
90     , m_swipeActiveLoadMonitoringTimer(RunLoop::main(), this, &ViewGestureController::activeLoadMonitoringTimerFired)
91     , m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
92 {
93     m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this);
94 }
95
96 ViewGestureController::~ViewGestureController()
97 {
98     if (m_swipeCancellationTracker)
99         [m_swipeCancellationTracker setIsCancelled:YES];
100
101     if (m_activeGestureType == ViewGestureType::Swipe)
102         removeSwipeSnapshot();
103
104     if (m_didMoveSwipeSnapshotCallback) {
105         Block_release(m_didMoveSwipeSnapshotCallback);
106         m_didMoveSwipeSnapshotCallback = nullptr;
107     }
108
109     m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID());
110 }
111
112 static double resistanceForDelta(double deltaScale, double currentScale)
113 {
114     // Zoom out with slight acceleration, until we reach minimum scale.
115     if (deltaScale < 0 && currentScale > minMagnification)
116         return zoomOutBoost;
117
118     // Zoom in with no acceleration, until we reach maximum scale.
119     if (deltaScale > 0 && currentScale < maxMagnification)
120         return 1;
121
122     // Outside of the extremes, resist further scaling.
123     double limit = currentScale < minMagnification ? minMagnification : maxMagnification;
124     double scaleDistance = fabs(limit - currentScale);
125     double scalePercent = std::min(std::max(scaleDistance / limit, 0.), 1.);
126     double resistance = zoomOutResistance + scalePercent * (0.01 - zoomOutResistance);
127
128     return resistance;
129 }
130
131 FloatPoint ViewGestureController::scaledMagnificationOrigin(FloatPoint origin, double scale)
132 {
133     FloatPoint scaledMagnificationOrigin(origin);
134     scaledMagnificationOrigin.moveBy(m_visibleContentRect.location());
135     float magnificationOriginScale = 1 - (scale / m_webPageProxy.pageScaleFactor());
136     scaledMagnificationOrigin.scale(magnificationOriginScale, magnificationOriginScale);
137     return scaledMagnificationOrigin;
138 }
139
140 void ViewGestureController::didCollectGeometryForMagnificationGesture(FloatRect visibleContentRect, bool frameHandlesMagnificationGesture)
141 {
142     m_activeGestureType = ViewGestureType::Magnification;
143     m_visibleContentRect = visibleContentRect;
144     m_visibleContentRectIsValid = true;
145     m_frameHandlesMagnificationGesture = frameHandlesMagnificationGesture;
146 }
147
148 void ViewGestureController::handleMagnificationGesture(double scale, FloatPoint origin)
149 {
150     ASSERT(m_activeGestureType == ViewGestureType::None || m_activeGestureType == ViewGestureType::Magnification);
151
152     if (m_activeGestureType == ViewGestureType::None) {
153         // FIXME: We drop the first frame of the gesture on the floor, because we don't have the visible content bounds yet.
154         m_magnification = m_webPageProxy.pageScaleFactor();
155         m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForMagnificationGesture(), m_webPageProxy.pageID());
156         m_lastMagnificationGestureWasSmartMagnification = false;
157
158         return;
159     }
160
161     // We're still waiting for the DidCollectGeometry callback.
162     if (!m_visibleContentRectIsValid)
163         return;
164
165     m_activeGestureType = ViewGestureType::Magnification;
166
167     double scaleWithResistance = resistanceForDelta(scale, m_magnification) * scale;
168
169     m_magnification += m_magnification * scaleWithResistance;
170     m_magnification = std::min(std::max(m_magnification, minElasticMagnification), maxElasticMagnification);
171
172     m_magnificationOrigin = origin;
173
174     if (m_frameHandlesMagnificationGesture)
175         m_webPageProxy.scalePage(m_magnification, roundedIntPoint(origin));
176     else
177         m_webPageProxy.drawingArea()->adjustTransientZoom(m_magnification, scaledMagnificationOrigin(origin, m_magnification));
178 }
179
180 void ViewGestureController::endMagnificationGesture()
181 {
182     if (m_activeGestureType != ViewGestureType::Magnification)
183         return;
184
185     double newMagnification = std::min(std::max(m_magnification, minMagnification), maxMagnification);
186
187     if (m_frameHandlesMagnificationGesture)
188         m_webPageProxy.scalePage(newMagnification, roundedIntPoint(m_magnificationOrigin));
189     else {
190         if (auto drawingArea = m_webPageProxy.drawingArea())
191             drawingArea->commitTransientZoom(newMagnification, scaledMagnificationOrigin(m_magnificationOrigin, newMagnification));
192     }
193
194     m_activeGestureType = ViewGestureType::None;
195     m_visibleContentRectIsValid = false;
196 }
197
198 void ViewGestureController::handleSmartMagnificationGesture(FloatPoint origin)
199 {
200     if (m_activeGestureType != ViewGestureType::None)
201         return;
202
203     m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForSmartMagnificationGesture(origin), m_webPageProxy.pageID());
204 }
205
206 static float maximumRectangleComponentDelta(FloatRect a, FloatRect b)
207 {
208     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()))));
209 }
210
211 void ViewGestureController::didCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect renderRect, FloatRect visibleContentRect, bool isReplacedElement, double viewportMinimumScale, double viewportMaximumScale)
212 {
213     double currentScaleFactor = m_webPageProxy.pageScaleFactor();
214
215     FloatRect unscaledTargetRect = renderRect;
216
217     // If there was no usable element under the cursor, we'll scale towards the cursor instead.
218     if (unscaledTargetRect.isEmpty())
219         unscaledTargetRect.setLocation(origin);
220
221     unscaledTargetRect.scale(1 / currentScaleFactor);
222     unscaledTargetRect.inflateX(unscaledTargetRect.width() * smartMagnificationElementPadding);
223     unscaledTargetRect.inflateY(unscaledTargetRect.height() * smartMagnificationElementPadding);
224
225     double targetMagnification = visibleContentRect.width() / unscaledTargetRect.width();
226
227     // For replaced elements like images, we want to fit the whole element
228     // in the view, so scale it down enough to make both dimensions fit if possible.
229     if (isReplacedElement)
230         targetMagnification = std::min(targetMagnification, static_cast<double>(visibleContentRect.height() / unscaledTargetRect.height()));
231
232     targetMagnification = std::min(std::max(targetMagnification, minMagnification), maxMagnification);
233
234     // Allow panning between elements via double-tap while magnified, unless the target rect is
235     // similar to the last one or the mouse has not moved, in which case we'll zoom all the way out.
236     if (currentScaleFactor > 1 && m_lastMagnificationGestureWasSmartMagnification) {
237         if (maximumRectangleComponentDelta(m_lastSmartMagnificationUnscaledTargetRect, unscaledTargetRect) < smartMagnificationPanScrollThreshold)
238             targetMagnification = 1;
239
240         if (m_lastSmartMagnificationOrigin == origin)
241             targetMagnification = 1;
242     }
243
244     FloatRect targetRect(unscaledTargetRect);
245     targetRect.scale(targetMagnification);
246     FloatPoint targetOrigin(visibleContentRect.center());
247     targetOrigin.moveBy(-targetRect.center());
248
249     m_webPageProxy.drawingArea()->adjustTransientZoom(m_webPageProxy.pageScaleFactor(), scaledMagnificationOrigin(FloatPoint(), m_webPageProxy.pageScaleFactor()));
250     m_webPageProxy.drawingArea()->commitTransientZoom(targetMagnification, targetOrigin);
251
252     m_lastSmartMagnificationUnscaledTargetRect = unscaledTargetRect;
253     m_lastSmartMagnificationOrigin = origin;
254
255     m_lastMagnificationGestureWasSmartMagnification = true;
256 }
257
258 bool ViewGestureController::scrollEventCanBecomeSwipe(NSEvent *event, ViewGestureController::SwipeDirection& potentialSwipeDirection)
259 {
260     if (event.phase != NSEventPhaseBegan)
261         return false;
262
263     if (!event.hasPreciseScrollingDeltas)
264         return false;
265
266     if (![NSEvent isSwipeTrackingFromScrollEventsEnabled])
267         return false;
268
269     if (fabs(event.scrollingDeltaX) <= fabs(event.scrollingDeltaY))
270         return false;
271
272     bool isPinnedToLeft = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToLeftSide();
273     bool isPinnedToRight = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToRightSide();
274
275     bool willSwipeLeft = event.scrollingDeltaX > 0 && isPinnedToLeft && m_webPageProxy.backForwardList().backItem();
276     bool willSwipeRight = event.scrollingDeltaX < 0 && isPinnedToRight && m_webPageProxy.backForwardList().forwardItem();
277     if (!willSwipeLeft && !willSwipeRight)
278         return false;
279
280     potentialSwipeDirection = willSwipeLeft ? ViewGestureController::SwipeDirection::Back : ViewGestureController::SwipeDirection::Forward;
281
282     return true;
283 }
284
285 bool ViewGestureController::deltaIsSufficientToBeginSwipe(NSEvent *event)
286 {
287     if (m_pendingSwipeReason != PendingSwipeReason::InsufficientMagnitude)
288         return false;
289
290     m_cumulativeDeltaForPendingSwipe += FloatSize(event.scrollingDeltaX, event.scrollingDeltaY);
291
292     // If the cumulative delta is ever "too vertical", we will stop tracking this
293     // as a potential swipe until we get another "begin" event.
294     if (fabs(m_cumulativeDeltaForPendingSwipe.height()) >= fabs(m_cumulativeDeltaForPendingSwipe.width()) * minimumScrollEventRatioForSwipe) {
295         m_pendingSwipeReason = PendingSwipeReason::None;
296         return false;
297     }
298
299     if (fabs(m_cumulativeDeltaForPendingSwipe.width()) < minimumHorizontalSwipeDistance)
300         return false;
301
302     return true;
303 }
304
305 void ViewGestureController::setDidMoveSwipeSnapshotCallback(void(^callback)(CGRect))
306 {
307     if (m_didMoveSwipeSnapshotCallback)
308         Block_release(m_didMoveSwipeSnapshotCallback);
309     m_didMoveSwipeSnapshotCallback = Block_copy(callback);
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             trackSwipeGesture(event, m_pendingSwipeDirection);
322             return true;
323         }
324     }
325
326     if (m_activeGestureType != ViewGestureType::None)
327         return false;
328
329     SwipeDirection direction;
330     if (!scrollEventCanBecomeSwipe(event, direction))
331         return false;
332
333     if (!m_shouldIgnorePinnedState && m_webPageProxy.willHandleHorizontalScrollEvents()) {
334         m_pendingSwipeReason = PendingSwipeReason::WebCoreMayScroll;
335         m_pendingSwipeDirection = direction;
336         return false;
337     }
338
339     if (!deltaIsSufficientToBeginSwipe(event)) {
340         m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
341         m_pendingSwipeDirection = direction;
342         return true;
343     }
344
345     trackSwipeGesture(event, direction);
346
347     return true;
348 }
349
350 void ViewGestureController::wheelEventWasNotHandledByWebCore(NSEvent *event)
351 {
352     if (m_pendingSwipeReason != PendingSwipeReason::WebCoreMayScroll)
353         return;
354
355     m_pendingSwipeReason = PendingSwipeReason::None;
356
357     SwipeDirection direction;
358     if (!scrollEventCanBecomeSwipe(event, direction))
359         return;
360
361     if (!deltaIsSufficientToBeginSwipe(event)) {
362         m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
363         return;
364     }
365
366     trackSwipeGesture(event, m_pendingSwipeDirection);
367 }
368
369 void ViewGestureController::trackSwipeGesture(NSEvent *event, SwipeDirection direction)
370 {
371     ASSERT(m_activeGestureType == ViewGestureType::None);
372     m_pendingSwipeReason = PendingSwipeReason::None;
373
374     m_webPageProxy.recordNavigationSnapshot();
375
376     CGFloat maxProgress = (direction == SwipeDirection::Back) ? 1 : 0;
377     CGFloat minProgress = (direction == SwipeDirection::Forward) ? -1 : 0;
378     RefPtr<WebBackForwardListItem> targetItem = (direction == SwipeDirection::Back) ? m_webPageProxy.backForwardList().backItem() : m_webPageProxy.backForwardList().forwardItem();
379     if (!targetItem)
380         return;
381     
382     __block bool swipeCancelled = false;
383
384     ASSERT(!m_swipeCancellationTracker);
385     RetainPtr<WKSwipeCancellationTracker> swipeCancellationTracker = adoptNS([[WKSwipeCancellationTracker alloc] init]);
386     m_swipeCancellationTracker = swipeCancellationTracker;
387
388     [event trackSwipeEventWithOptions:0 dampenAmountThresholdMin:minProgress max:maxProgress usingHandler:^(CGFloat progress, NSEventPhase phase, BOOL isComplete, BOOL *stop) {
389         if ([swipeCancellationTracker isCancelled]) {
390             *stop = YES;
391             return;
392         }
393         if (phase == NSEventPhaseBegan)
394             this->beginSwipeGesture(targetItem.get(), direction);
395         CGFloat clampedProgress = std::min(std::max(progress, minProgress), maxProgress);
396         this->handleSwipeGesture(targetItem.get(), clampedProgress, direction);
397         if (phase == NSEventPhaseCancelled)
398             swipeCancelled = true;
399         if (isComplete)
400             this->endSwipeGesture(targetItem.get(), swipeCancelled);
401     }];
402 }
403
404 FloatRect ViewGestureController::windowRelativeBoundsForCustomSwipeViews() const
405 {
406     FloatRect swipeArea;
407     for (const auto& view : m_customSwipeViews)
408         swipeArea.unite([view convertRect:[view bounds] toView:nil]);
409     swipeArea.setHeight(swipeArea.height() - m_customSwipeViewsTopContentInset);
410     return swipeArea;
411 }
412
413 static CALayer *leastCommonAncestorLayer(const Vector<RetainPtr<CALayer>>& layers)
414 {
415     Vector<Vector<CALayer *>> liveLayerPathsFromRoot(layers.size());
416
417     size_t shortestPathLength = std::numeric_limits<size_t>::max();
418
419     for (size_t layerIndex = 0; layerIndex < layers.size(); layerIndex++) {
420         CALayer *parent = [layers[layerIndex] superlayer];
421         while (parent) {
422             liveLayerPathsFromRoot[layerIndex].insert(0, parent);
423             shortestPathLength = std::min(shortestPathLength, liveLayerPathsFromRoot[layerIndex].size());
424             parent = parent.superlayer;
425         }
426     }
427
428     for (size_t pathIndex = 0; pathIndex < shortestPathLength; pathIndex++) {
429         CALayer *firstPathLayer = liveLayerPathsFromRoot[0][pathIndex];
430         for (size_t layerIndex = 1; layerIndex < layers.size(); layerIndex++) {
431             if (liveLayerPathsFromRoot[layerIndex][pathIndex] != firstPathLayer)
432                 return firstPathLayer;
433         }
434     }
435
436     return liveLayerPathsFromRoot[0][shortestPathLength];
437 }
438
439 CALayer *ViewGestureController::determineSnapshotLayerParent() const
440 {
441     if (m_currentSwipeLiveLayers.size() == 1)
442         return [m_currentSwipeLiveLayers[0] superlayer];
443
444     // We insert our snapshot into the first shared superlayer of the custom views' layer, above the frontmost or below the bottommost layer.
445     return leastCommonAncestorLayer(m_currentSwipeLiveLayers);
446 }
447
448 CALayer *ViewGestureController::determineLayerAdjacentToSnapshotForParent(SwipeDirection direction, CALayer *snapshotLayerParent) const
449 {
450     // If we have custom swiping views, we assume that the views were passed to us in back-to-front z-order.
451     CALayer *layerAdjacentToSnapshot = direction == SwipeDirection::Back ? m_currentSwipeLiveLayers.first().get() : m_currentSwipeLiveLayers.last().get();
452
453     if (m_currentSwipeLiveLayers.size() == 1)
454         return layerAdjacentToSnapshot;
455
456     // 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.
457     while (snapshotLayerParent != layerAdjacentToSnapshot.superlayer)
458         layerAdjacentToSnapshot = layerAdjacentToSnapshot.superlayer;
459     return layerAdjacentToSnapshot;
460 }
461
462 bool ViewGestureController::shouldUseSnapshotForSize(ViewSnapshot& snapshot, FloatSize swipeLayerSize, float topContentInset)
463 {
464     float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
465     if (snapshot.deviceScaleFactor() != deviceScaleFactor)
466         return false;
467
468     FloatSize unobscuredSwipeLayerSizeInDeviceCoordinates = swipeLayerSize - FloatSize(0, topContentInset);
469     unobscuredSwipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
470     if (snapshot.size() != unobscuredSwipeLayerSizeInDeviceCoordinates)
471         return false;
472
473     return true;
474 }
475
476 static bool layerGeometryFlippedToRoot(CALayer *layer)
477 {
478     bool flipped = false;
479     CALayer *parent = layer;
480     while (parent) {
481         if (parent.isGeometryFlipped)
482             flipped = !flipped;
483         parent = parent.superlayer;
484     }
485     return flipped;
486 }
487
488 void ViewGestureController::applyDebuggingPropertiesToSwipeViews()
489 {
490     CAFilter* filter = [CAFilter filterWithType:kCAFilterColorInvert];
491     [m_swipeLayer setFilters:@[ filter ]];
492     [m_swipeLayer setBackgroundColor:[NSColor blueColor].CGColor];
493     [m_swipeLayer setBorderColor:[NSColor yellowColor].CGColor];
494     [m_swipeLayer setBorderWidth:4];
495
496     [m_swipeSnapshotLayer setBackgroundColor:[NSColor greenColor].CGColor];
497     [m_swipeSnapshotLayer setBorderColor:[NSColor redColor].CGColor];
498     [m_swipeSnapshotLayer setBorderWidth:2];
499 }
500
501 void ViewGestureController::beginSwipeGesture(WebBackForwardListItem* targetItem, SwipeDirection direction)
502 {
503     ASSERT(m_currentSwipeLiveLayers.isEmpty());
504
505     m_webPageProxy.navigationGestureDidBegin();
506
507     m_activeGestureType = ViewGestureType::Swipe;
508     m_swipeInProgress = true;
509
510     CALayer *rootContentLayer = m_webPageProxy.acceleratedCompositingRootLayer();
511
512     m_swipeLayer = adoptNS([[CALayer alloc] init]);
513     m_swipeSnapshotLayer = adoptNS([[CALayer alloc] init]);
514     m_currentSwipeCustomViewBounds = windowRelativeBoundsForCustomSwipeViews();
515
516     FloatRect swipeArea;
517     float topContentInset = 0;
518     if (!m_customSwipeViews.isEmpty()) {
519         topContentInset = m_customSwipeViewsTopContentInset;
520         swipeArea = m_currentSwipeCustomViewBounds;
521         swipeArea.expand(0, topContentInset);
522
523         for (const auto& view : m_customSwipeViews) {
524             CALayer *layer = [view layer];
525             ASSERT(layer);
526             m_currentSwipeLiveLayers.append(layer);
527         }
528     } else {
529         swipeArea = FloatRect(FloatPoint(), m_webPageProxy.viewSize());
530         topContentInset = m_webPageProxy.topContentInset();
531         m_currentSwipeLiveLayers.append(rootContentLayer);
532     }
533
534     CALayer *snapshotLayerParent = determineSnapshotLayerParent();
535     bool geometryIsFlippedToRoot = layerGeometryFlippedToRoot(snapshotLayerParent);
536
537     RetainPtr<CGColorRef> backgroundColor = CGColorGetConstantColor(kCGColorWhite);
538     if (ViewSnapshot* snapshot = targetItem->snapshot()) {
539         if (shouldUseSnapshotForSize(*snapshot, swipeArea.size(), topContentInset))
540             [m_swipeSnapshotLayer setContents:snapshot->asLayerContents()];
541
542         Color coreColor = snapshot->backgroundColor();
543         if (coreColor.isValid())
544             backgroundColor = cachedCGColor(coreColor, ColorSpaceDeviceRGB);
545 #if USE_IOSURFACE_VIEW_SNAPSHOTS
546         m_currentSwipeSnapshot = snapshot;
547 #endif
548     }
549
550     [m_swipeLayer setBackgroundColor:backgroundColor.get()];
551     [m_swipeLayer setAnchorPoint:CGPointZero];
552     [m_swipeLayer setFrame:swipeArea];
553     [m_swipeLayer setName:@"Gesture Swipe Root Layer"];
554     [m_swipeLayer setGeometryFlipped:geometryIsFlippedToRoot];
555     [m_swipeLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
556
557     [m_swipeSnapshotLayer setContentsGravity:kCAGravityTopLeft];
558     [m_swipeSnapshotLayer setContentsScale:m_webPageProxy.deviceScaleFactor()];
559     [m_swipeSnapshotLayer setAnchorPoint:CGPointZero];
560     [m_swipeSnapshotLayer setFrame:CGRectMake(0, 0, swipeArea.width(), swipeArea.height() - topContentInset)];
561     [m_swipeSnapshotLayer setName:@"Gesture Swipe Snapshot Layer"];
562     [m_swipeSnapshotLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
563
564     [m_swipeLayer addSublayer:m_swipeSnapshotLayer.get()];
565
566     if (m_webPageProxy.preferences().viewGestureDebuggingEnabled())
567         applyDebuggingPropertiesToSwipeViews();
568
569     // We don't know enough about the custom views' hierarchy to apply a shadow.
570     if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap && m_customSwipeViews.isEmpty()) {
571         if (direction == SwipeDirection::Back) {
572             float topContentInset = m_webPageProxy.topContentInset();
573             FloatRect shadowRect(FloatPoint(0, topContentInset), m_webPageProxy.viewSize() - FloatSize(0, topContentInset));
574             RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowRect, 0));
575             [rootContentLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
576             [rootContentLayer setShadowOpacity:swipeOverlayShadowOpacity];
577             [rootContentLayer setShadowRadius:swipeOverlayShadowRadius];
578             [rootContentLayer setShadowPath:shadowPath.get()];
579         } else {
580             RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect([m_swipeLayer bounds], 0));
581             [m_swipeLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
582             [m_swipeLayer setShadowOpacity:swipeOverlayShadowOpacity];
583             [m_swipeLayer setShadowRadius:swipeOverlayShadowRadius];
584             [m_swipeLayer setShadowPath:shadowPath.get()];
585         }
586     }
587
588     CALayer *layerAdjacentToSnapshot = determineLayerAdjacentToSnapshotForParent(direction, snapshotLayerParent);
589     if (direction == SwipeDirection::Back)
590         [snapshotLayerParent insertSublayer:m_swipeLayer.get() below:layerAdjacentToSnapshot];
591     else
592         [snapshotLayerParent insertSublayer:m_swipeLayer.get() above:layerAdjacentToSnapshot];
593 }
594
595 void ViewGestureController::handleSwipeGesture(WebBackForwardListItem* targetItem, double progress, SwipeDirection direction)
596 {
597     ASSERT(m_activeGestureType == ViewGestureType::Swipe);
598
599     if (!m_webPageProxy.drawingArea())
600         return;
601
602     double width;
603     if (!m_customSwipeViews.isEmpty())
604         width = m_currentSwipeCustomViewBounds.width();
605     else
606         width = m_webPageProxy.drawingArea()->size().width();
607
608     double swipingLayerOffset = floor(width * progress);
609
610     if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
611         if (direction == SwipeDirection::Forward) {
612             [m_swipeLayer setTransform:CATransform3DMakeTranslation(width + swipingLayerOffset, 0, 0)];
613             didMoveSwipeSnapshotLayer();
614         }
615     } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
616         [m_swipeLayer setTransform:CATransform3DMakeTranslation((direction == SwipeDirection::Back ? -width : width) + swipingLayerOffset, 0, 0)];
617
618     for (const auto& layer : m_currentSwipeLiveLayers) {
619         if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
620             if (direction == SwipeDirection::Back)
621                 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
622         } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
623             [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
624     }
625 }
626
627 void ViewGestureController::didMoveSwipeSnapshotLayer()
628 {
629     if (!m_didMoveSwipeSnapshotCallback)
630         return;
631
632     m_didMoveSwipeSnapshotCallback(m_webPageProxy.boundsOfLayerInLayerBackedWindowCoordinates(m_swipeLayer.get()));
633 }
634
635 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, bool cancelled)
636 {
637     ASSERT(m_activeGestureType == ViewGestureType::Swipe);
638
639     m_swipeCancellationTracker = nullptr;
640
641     m_swipeInProgress = false;
642
643     CALayer *rootLayer = m_webPageProxy.acceleratedCompositingRootLayer();
644
645     [rootLayer setShadowOpacity:0];
646     [rootLayer setShadowRadius:0];
647
648     if (cancelled) {
649         removeSwipeSnapshot();
650         m_webPageProxy.navigationGestureDidEnd(false, *targetItem);
651         return;
652     }
653
654     uint64_t renderTreeSize = 0;
655     if (ViewSnapshot* snapshot = targetItem->snapshot())
656         renderTreeSize = snapshot->renderTreeSize();
657
658     m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::SetRenderTreeSizeNotificationThreshold(renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction), m_webPageProxy.pageID());
659
660     m_swipeWaitingForVisuallyNonEmptyLayout = true;
661     m_swipeWaitingForRenderTreeSizeThreshold = true;
662
663     m_webPageProxy.navigationGestureDidEnd(true, *targetItem);
664     m_webPageProxy.goToBackForwardItem(targetItem);
665
666     // FIXME: Like on iOS, we should ensure that even if one of the timeouts fires,
667     // we never show the old page content, instead showing white (or the snapshot background color).
668     m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
669
670     if (ViewSnapshot* snapshot = targetItem->snapshot())
671         m_backgroundColorForCurrentSnapshot = snapshot->backgroundColor();
672 }
673
674 void ViewGestureController::didHitRenderTreeSizeThreshold()
675 {
676     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
677         return;
678
679     m_swipeWaitingForRenderTreeSizeThreshold = false;
680
681     if (!m_swipeWaitingForVisuallyNonEmptyLayout) {
682         // FIXME: Ideally we would call removeSwipeSnapshotAfterRepaint() here, but sometimes
683         // scroll position isn't done restoring until didFinishLoadForFrame, so we flash the wrong content.
684     }
685 }
686
687 void ViewGestureController::didFirstVisuallyNonEmptyLayoutForMainFrame()
688 {
689     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
690         return;
691
692     m_swipeWaitingForVisuallyNonEmptyLayout = false;
693
694     if (!m_swipeWaitingForRenderTreeSizeThreshold) {
695         // FIXME: Ideally we would call removeSwipeSnapshotAfterRepaint() here, but sometimes
696         // scroll position isn't done restoring until didFinishLoadForFrame, so we flash the wrong content.
697     } else {
698         m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.startOneShot(swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration.count());
699         m_swipeWatchdogTimer.stop();
700     }
701 }
702
703 void ViewGestureController::didFinishLoadForMainFrame()
704 {
705     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
706         return;
707
708     if (m_webPageProxy.pageLoadState().isLoading()) {
709         m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval);
710         return;
711     }
712
713     removeSwipeSnapshotAfterRepaint();
714 }
715
716 void ViewGestureController::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type)
717 {
718     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
719         return;
720
721     if (type != SameDocumentNavigationSessionStateReplace && type != SameDocumentNavigationSessionStatePop)
722         return;
723
724     m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval);
725 }
726
727 void ViewGestureController::activeLoadMonitoringTimerFired()
728 {
729     if (m_webPageProxy.pageLoadState().isLoading())
730         return;
731
732     removeSwipeSnapshotAfterRepaint();
733 }
734
735 void ViewGestureController::swipeSnapshotWatchdogTimerFired()
736 {
737     removeSwipeSnapshotAfterRepaint();
738 }
739
740 void ViewGestureController::removeSwipeSnapshotAfterRepaint()
741 {
742     m_swipeActiveLoadMonitoringTimer.stop();
743
744     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
745         return;
746
747     if (m_swipeWaitingForRepaint)
748         return;
749
750     m_swipeWaitingForRepaint = true;
751
752     WebPageProxy* webPageProxy = &m_webPageProxy;
753     m_webPageProxy.forceRepaint(VoidCallback::create([webPageProxy] (CallbackBase::Error error) {
754         webPageProxy->removeNavigationGestureSnapshot();
755     }));
756 }
757
758 void ViewGestureController::removeSwipeSnapshot()
759 {
760     m_swipeWaitingForRepaint = false;
761
762     m_swipeWatchdogTimer.stop();
763     m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.stop();
764
765     if (m_activeGestureType != ViewGestureType::Swipe)
766         return;
767
768 #if USE_IOSURFACE_VIEW_SNAPSHOTS
769     if (m_currentSwipeSnapshot && m_currentSwipeSnapshot->surface())
770         m_currentSwipeSnapshot->surface()->setIsVolatile(true);
771     m_currentSwipeSnapshot = nullptr;
772 #endif
773
774     for (const auto& layer : m_currentSwipeLiveLayers)
775         [layer setTransform:CATransform3DIdentity];
776
777     [m_swipeSnapshotLayer removeFromSuperlayer];
778     m_swipeSnapshotLayer = nullptr;
779
780     [m_swipeLayer removeFromSuperlayer];
781     m_swipeLayer = nullptr;
782
783     m_currentSwipeLiveLayers.clear();
784
785     m_activeGestureType = ViewGestureType::None;
786
787     m_webPageProxy.navigationGestureSnapshotWasRemoved();
788
789     m_backgroundColorForCurrentSnapshot = Color();
790 }
791
792 double ViewGestureController::magnification() const
793 {
794     if (m_activeGestureType == ViewGestureType::Magnification)
795         return m_magnification;
796
797     return m_webPageProxy.pageScaleFactor();
798 }
799
800 } // namespace WebKit
801
802 #endif // !PLATFORM(IOS)