WebKit briefly shows wrong webpage after swiping back (gigaom.com, or any site on...
[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     ASSERT(m_activeGestureType == ViewGestureType::Magnification);
183
184     double newMagnification = std::min(std::max(m_magnification, minMagnification), maxMagnification);
185
186     if (m_frameHandlesMagnificationGesture)
187         m_webPageProxy.scalePage(newMagnification, roundedIntPoint(m_magnificationOrigin));
188     else {
189         if (auto drawingArea = m_webPageProxy.drawingArea())
190             drawingArea->commitTransientZoom(newMagnification, scaledMagnificationOrigin(m_magnificationOrigin, newMagnification));
191     }
192
193     m_activeGestureType = ViewGestureType::None;
194 }
195
196 void ViewGestureController::handleSmartMagnificationGesture(FloatPoint origin)
197 {
198     if (m_activeGestureType != ViewGestureType::None)
199         return;
200
201     m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForSmartMagnificationGesture(origin), m_webPageProxy.pageID());
202 }
203
204 static float maximumRectangleComponentDelta(FloatRect a, FloatRect b)
205 {
206     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()))));
207 }
208
209 void ViewGestureController::didCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect renderRect, FloatRect visibleContentRect, bool isReplacedElement, double viewportMinimumScale, double viewportMaximumScale)
210 {
211     double currentScaleFactor = m_webPageProxy.pageScaleFactor();
212
213     FloatRect unscaledTargetRect = renderRect;
214
215     // If there was no usable element under the cursor, we'll scale towards the cursor instead.
216     if (unscaledTargetRect.isEmpty())
217         unscaledTargetRect.setLocation(origin);
218
219     unscaledTargetRect.scale(1 / currentScaleFactor);
220     unscaledTargetRect.inflateX(unscaledTargetRect.width() * smartMagnificationElementPadding);
221     unscaledTargetRect.inflateY(unscaledTargetRect.height() * smartMagnificationElementPadding);
222
223     double targetMagnification = visibleContentRect.width() / unscaledTargetRect.width();
224
225     // For replaced elements like images, we want to fit the whole element
226     // in the view, so scale it down enough to make both dimensions fit if possible.
227     if (isReplacedElement)
228         targetMagnification = std::min(targetMagnification, static_cast<double>(visibleContentRect.height() / unscaledTargetRect.height()));
229
230     targetMagnification = std::min(std::max(targetMagnification, minMagnification), maxMagnification);
231
232     // Allow panning between elements via double-tap while magnified, unless the target rect is
233     // similar to the last one or the mouse has not moved, in which case we'll zoom all the way out.
234     if (currentScaleFactor > 1 && m_lastMagnificationGestureWasSmartMagnification) {
235         if (maximumRectangleComponentDelta(m_lastSmartMagnificationUnscaledTargetRect, unscaledTargetRect) < smartMagnificationPanScrollThreshold)
236             targetMagnification = 1;
237
238         if (m_lastSmartMagnificationOrigin == origin)
239             targetMagnification = 1;
240     }
241
242     FloatRect targetRect(unscaledTargetRect);
243     targetRect.scale(targetMagnification);
244     FloatPoint targetOrigin(visibleContentRect.center());
245     targetOrigin.moveBy(-targetRect.center());
246
247     m_webPageProxy.drawingArea()->adjustTransientZoom(m_webPageProxy.pageScaleFactor(), scaledMagnificationOrigin(FloatPoint(), m_webPageProxy.pageScaleFactor()));
248     m_webPageProxy.drawingArea()->commitTransientZoom(targetMagnification, targetOrigin);
249
250     m_lastSmartMagnificationUnscaledTargetRect = unscaledTargetRect;
251     m_lastSmartMagnificationOrigin = origin;
252
253     m_lastMagnificationGestureWasSmartMagnification = true;
254 }
255
256 bool ViewGestureController::scrollEventCanBecomeSwipe(NSEvent *event, ViewGestureController::SwipeDirection& potentialSwipeDirection)
257 {
258     if (event.phase != NSEventPhaseBegan)
259         return false;
260
261     if (!event.hasPreciseScrollingDeltas)
262         return false;
263
264     if (![NSEvent isSwipeTrackingFromScrollEventsEnabled])
265         return false;
266
267     if (fabs(event.scrollingDeltaX) <= fabs(event.scrollingDeltaY))
268         return false;
269
270     bool isPinnedToLeft = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToLeftSide();
271     bool isPinnedToRight = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToRightSide();
272
273     bool willSwipeLeft = event.scrollingDeltaX > 0 && isPinnedToLeft && m_webPageProxy.backForwardList().backItem();
274     bool willSwipeRight = event.scrollingDeltaX < 0 && isPinnedToRight && m_webPageProxy.backForwardList().forwardItem();
275     if (!willSwipeLeft && !willSwipeRight)
276         return false;
277
278     potentialSwipeDirection = willSwipeLeft ? ViewGestureController::SwipeDirection::Left : ViewGestureController::SwipeDirection::Right;
279
280     return true;
281 }
282
283 bool ViewGestureController::deltaIsSufficientToBeginSwipe(NSEvent *event)
284 {
285     if (m_pendingSwipeReason != PendingSwipeReason::InsufficientMagnitude)
286         return false;
287
288     m_cumulativeDeltaForPendingSwipe += FloatSize(event.scrollingDeltaX, event.scrollingDeltaY);
289
290     // If the cumulative delta is ever "too vertical", we will stop tracking this
291     // as a potential swipe until we get another "begin" event.
292     if (fabs(m_cumulativeDeltaForPendingSwipe.height()) >= fabs(m_cumulativeDeltaForPendingSwipe.width()) * minimumScrollEventRatioForSwipe) {
293         m_pendingSwipeReason = PendingSwipeReason::None;
294         return false;
295     }
296
297     if (fabs(m_cumulativeDeltaForPendingSwipe.width()) < minimumHorizontalSwipeDistance)
298         return false;
299
300     return true;
301 }
302
303 void ViewGestureController::setDidMoveSwipeSnapshotCallback(void(^callback)(CGRect))
304 {
305     if (m_didMoveSwipeSnapshotCallback)
306         Block_release(m_didMoveSwipeSnapshotCallback);
307     m_didMoveSwipeSnapshotCallback = Block_copy(callback);
308 }
309
310 bool ViewGestureController::handleScrollWheelEvent(NSEvent *event)
311 {
312     if (event.phase == NSEventPhaseEnded) {
313         m_cumulativeDeltaForPendingSwipe = FloatSize();
314         m_pendingSwipeReason = PendingSwipeReason::None;
315     }
316
317     if (m_pendingSwipeReason == PendingSwipeReason::InsufficientMagnitude) {
318         if (deltaIsSufficientToBeginSwipe(event)) {
319             trackSwipeGesture(event, m_pendingSwipeDirection);
320             return true;
321         }
322     }
323
324     if (m_activeGestureType != ViewGestureType::None)
325         return false;
326
327     SwipeDirection direction;
328     if (!scrollEventCanBecomeSwipe(event, direction))
329         return false;
330
331     if (!m_shouldIgnorePinnedState && m_webPageProxy.willHandleHorizontalScrollEvents()) {
332         m_pendingSwipeReason = PendingSwipeReason::WebCoreMayScroll;
333         m_pendingSwipeDirection = direction;
334         return false;
335     }
336
337     if (!deltaIsSufficientToBeginSwipe(event)) {
338         m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
339         m_pendingSwipeDirection = direction;
340         return true;
341     }
342
343     trackSwipeGesture(event, direction);
344
345     return true;
346 }
347
348 void ViewGestureController::wheelEventWasNotHandledByWebCore(NSEvent *event)
349 {
350     if (m_pendingSwipeReason != PendingSwipeReason::WebCoreMayScroll)
351         return;
352
353     m_pendingSwipeReason = PendingSwipeReason::None;
354
355     SwipeDirection direction;
356     if (!scrollEventCanBecomeSwipe(event, direction))
357         return;
358
359     if (!deltaIsSufficientToBeginSwipe(event)) {
360         m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude;
361         return;
362     }
363
364     trackSwipeGesture(event, m_pendingSwipeDirection);
365 }
366
367 void ViewGestureController::trackSwipeGesture(NSEvent *event, SwipeDirection direction)
368 {
369     ASSERT(m_activeGestureType == ViewGestureType::None);
370     m_pendingSwipeReason = PendingSwipeReason::None;
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     if (!targetItem)
378         return;
379     
380     __block bool swipeCancelled = false;
381
382     ASSERT(!m_swipeCancellationTracker);
383     RetainPtr<WKSwipeCancellationTracker> swipeCancellationTracker = adoptNS([[WKSwipeCancellationTracker alloc] init]);
384     m_swipeCancellationTracker = swipeCancellationTracker;
385
386     [event trackSwipeEventWithOptions:0 dampenAmountThresholdMin:minProgress max:maxProgress usingHandler:^(CGFloat progress, NSEventPhase phase, BOOL isComplete, BOOL *stop) {
387         if ([swipeCancellationTracker isCancelled]) {
388             *stop = YES;
389             return;
390         }
391         if (phase == NSEventPhaseBegan)
392             this->beginSwipeGesture(targetItem.get(), direction);
393         CGFloat clampedProgress = std::min(std::max(progress, minProgress), maxProgress);
394         this->handleSwipeGesture(targetItem.get(), clampedProgress, direction);
395         if (phase == NSEventPhaseCancelled)
396             swipeCancelled = true;
397         if (isComplete)
398             this->endSwipeGesture(targetItem.get(), swipeCancelled);
399     }];
400 }
401
402 FloatRect ViewGestureController::windowRelativeBoundsForCustomSwipeViews() const
403 {
404     FloatRect swipeArea;
405     for (const auto& view : m_customSwipeViews)
406         swipeArea.unite([view convertRect:[view bounds] toView:nil]);
407     swipeArea.setHeight(swipeArea.height() - m_customSwipeViewsTopContentInset);
408     return swipeArea;
409 }
410
411 static CALayer *leastCommonAncestorLayer(const Vector<RetainPtr<CALayer>>& layers)
412 {
413     Vector<Vector<CALayer *>> liveLayerPathsFromRoot(layers.size());
414
415     size_t shortestPathLength = std::numeric_limits<size_t>::max();
416
417     for (size_t layerIndex = 0; layerIndex < layers.size(); layerIndex++) {
418         CALayer *parent = [layers[layerIndex] superlayer];
419         while (parent) {
420             liveLayerPathsFromRoot[layerIndex].insert(0, parent);
421             shortestPathLength = std::min(shortestPathLength, liveLayerPathsFromRoot[layerIndex].size());
422             parent = parent.superlayer;
423         }
424     }
425
426     for (size_t pathIndex = 0; pathIndex < shortestPathLength; pathIndex++) {
427         CALayer *firstPathLayer = liveLayerPathsFromRoot[0][pathIndex];
428         for (size_t layerIndex = 1; layerIndex < layers.size(); layerIndex++) {
429             if (liveLayerPathsFromRoot[layerIndex][pathIndex] != firstPathLayer)
430                 return firstPathLayer;
431         }
432     }
433
434     return liveLayerPathsFromRoot[0][shortestPathLength];
435 }
436
437 CALayer *ViewGestureController::determineSnapshotLayerParent() const
438 {
439     if (m_currentSwipeLiveLayers.size() == 1)
440         return [m_currentSwipeLiveLayers[0] superlayer];
441
442     // We insert our snapshot into the first shared superlayer of the custom views' layer, above the frontmost or below the bottommost layer.
443     return leastCommonAncestorLayer(m_currentSwipeLiveLayers);
444 }
445
446 CALayer *ViewGestureController::determineLayerAdjacentToSnapshotForParent(SwipeDirection direction, CALayer *snapshotLayerParent) const
447 {
448     // If we have custom swiping views, we assume that the views were passed to us in back-to-front z-order.
449     CALayer *layerAdjacentToSnapshot = direction == SwipeDirection::Left ? m_currentSwipeLiveLayers.first().get() : m_currentSwipeLiveLayers.last().get();
450
451     if (m_currentSwipeLiveLayers.size() == 1)
452         return layerAdjacentToSnapshot;
453
454     // 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.
455     while (snapshotLayerParent != layerAdjacentToSnapshot.superlayer)
456         layerAdjacentToSnapshot = layerAdjacentToSnapshot.superlayer;
457     return layerAdjacentToSnapshot;
458 }
459
460 bool ViewGestureController::shouldUseSnapshotForSize(ViewSnapshot& snapshot, FloatSize swipeLayerSize, float topContentInset)
461 {
462     float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
463     if (snapshot.deviceScaleFactor() != deviceScaleFactor)
464         return false;
465
466     FloatSize unobscuredSwipeLayerSizeInDeviceCoordinates = swipeLayerSize - FloatSize(0, topContentInset);
467     unobscuredSwipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
468     if (snapshot.size() != unobscuredSwipeLayerSizeInDeviceCoordinates)
469         return false;
470
471     return true;
472 }
473
474 static bool layerGeometryFlippedToRoot(CALayer *layer)
475 {
476     bool flipped = false;
477     CALayer *parent = layer;
478     while (parent) {
479         if (parent.isGeometryFlipped)
480             flipped = !flipped;
481         parent = parent.superlayer;
482     }
483     return flipped;
484 }
485
486 void ViewGestureController::applyDebuggingPropertiesToSwipeViews()
487 {
488     CAFilter* filter = [CAFilter filterWithType:kCAFilterColorInvert];
489     [m_swipeLayer setFilters:@[ filter ]];
490     [m_swipeLayer setBackgroundColor:[NSColor blueColor].CGColor];
491     [m_swipeLayer setBorderColor:[NSColor yellowColor].CGColor];
492     [m_swipeLayer setBorderWidth:4];
493
494     [m_swipeSnapshotLayer setBackgroundColor:[NSColor greenColor].CGColor];
495     [m_swipeSnapshotLayer setBorderColor:[NSColor redColor].CGColor];
496     [m_swipeSnapshotLayer setBorderWidth:2];
497 }
498
499 void ViewGestureController::beginSwipeGesture(WebBackForwardListItem* targetItem, SwipeDirection direction)
500 {
501     ASSERT(m_currentSwipeLiveLayers.isEmpty());
502
503     m_webPageProxy.navigationGestureDidBegin();
504
505     m_activeGestureType = ViewGestureType::Swipe;
506     m_swipeInProgress = true;
507
508     CALayer *rootContentLayer = m_webPageProxy.acceleratedCompositingRootLayer();
509
510     m_swipeLayer = adoptNS([[CALayer alloc] init]);
511     m_swipeSnapshotLayer = adoptNS([[CALayer alloc] init]);
512     m_currentSwipeCustomViewBounds = windowRelativeBoundsForCustomSwipeViews();
513
514     FloatRect swipeArea;
515     float topContentInset = 0;
516     if (!m_customSwipeViews.isEmpty()) {
517         topContentInset = m_customSwipeViewsTopContentInset;
518         swipeArea = m_currentSwipeCustomViewBounds;
519         swipeArea.expand(0, topContentInset);
520
521         for (const auto& view : m_customSwipeViews) {
522             CALayer *layer = [view layer];
523             ASSERT(layer);
524             m_currentSwipeLiveLayers.append(layer);
525         }
526     } else {
527         swipeArea = FloatRect(FloatPoint(), m_webPageProxy.viewSize());
528         topContentInset = m_webPageProxy.topContentInset();
529         m_currentSwipeLiveLayers.append(rootContentLayer);
530     }
531
532     CALayer *snapshotLayerParent = determineSnapshotLayerParent();
533     bool geometryIsFlippedToRoot = layerGeometryFlippedToRoot(snapshotLayerParent);
534
535     RetainPtr<CGColorRef> backgroundColor = CGColorGetConstantColor(kCGColorWhite);
536     if (ViewSnapshot* snapshot = targetItem->snapshot()) {
537         if (shouldUseSnapshotForSize(*snapshot, swipeArea.size(), topContentInset))
538             [m_swipeSnapshotLayer setContents:snapshot->asLayerContents()];
539
540         Color coreColor = snapshot->backgroundColor();
541         if (coreColor.isValid())
542             backgroundColor = cachedCGColor(coreColor, ColorSpaceDeviceRGB);
543 #if USE_IOSURFACE_VIEW_SNAPSHOTS
544         m_currentSwipeSnapshot = snapshot;
545 #endif
546     }
547
548     [m_swipeLayer setBackgroundColor:backgroundColor.get()];
549     [m_swipeLayer setAnchorPoint:CGPointZero];
550     [m_swipeLayer setFrame:swipeArea];
551     [m_swipeLayer setName:@"Gesture Swipe Root Layer"];
552     [m_swipeLayer setGeometryFlipped:geometryIsFlippedToRoot];
553     [m_swipeLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
554
555     [m_swipeSnapshotLayer setContentsGravity:kCAGravityTopLeft];
556     [m_swipeSnapshotLayer setContentsScale:m_webPageProxy.deviceScaleFactor()];
557     [m_swipeSnapshotLayer setAnchorPoint:CGPointZero];
558     [m_swipeSnapshotLayer setFrame:CGRectMake(0, 0, swipeArea.width(), swipeArea.height() - topContentInset)];
559     [m_swipeSnapshotLayer setName:@"Gesture Swipe Snapshot Layer"];
560     [m_swipeSnapshotLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
561
562     [m_swipeLayer addSublayer:m_swipeSnapshotLayer.get()];
563
564     if (m_webPageProxy.preferences().viewGestureDebuggingEnabled())
565         applyDebuggingPropertiesToSwipeViews();
566
567     // We don't know enough about the custom views' hierarchy to apply a shadow.
568     if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap && m_customSwipeViews.isEmpty()) {
569         if (direction == SwipeDirection::Left) {
570             float topContentInset = m_webPageProxy.topContentInset();
571             FloatRect shadowRect(FloatPoint(0, topContentInset), m_webPageProxy.viewSize() - FloatSize(0, topContentInset));
572             RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowRect, 0));
573             [rootContentLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
574             [rootContentLayer setShadowOpacity:swipeOverlayShadowOpacity];
575             [rootContentLayer setShadowRadius:swipeOverlayShadowRadius];
576             [rootContentLayer setShadowPath:shadowPath.get()];
577         } else {
578             RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect([m_swipeLayer bounds], 0));
579             [m_swipeLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)];
580             [m_swipeLayer setShadowOpacity:swipeOverlayShadowOpacity];
581             [m_swipeLayer setShadowRadius:swipeOverlayShadowRadius];
582             [m_swipeLayer setShadowPath:shadowPath.get()];
583         }
584     }
585
586     CALayer *layerAdjacentToSnapshot = determineLayerAdjacentToSnapshotForParent(direction, snapshotLayerParent);
587     if (direction == SwipeDirection::Left)
588         [snapshotLayerParent insertSublayer:m_swipeLayer.get() below:layerAdjacentToSnapshot];
589     else
590         [snapshotLayerParent insertSublayer:m_swipeLayer.get() above:layerAdjacentToSnapshot];
591 }
592
593 void ViewGestureController::handleSwipeGesture(WebBackForwardListItem* targetItem, double progress, SwipeDirection direction)
594 {
595     ASSERT(m_activeGestureType == ViewGestureType::Swipe);
596
597     if (!m_webPageProxy.drawingArea())
598         return;
599
600     double width;
601     if (!m_customSwipeViews.isEmpty())
602         width = m_currentSwipeCustomViewBounds.width();
603     else
604         width = m_webPageProxy.drawingArea()->size().width();
605
606     double swipingLayerOffset = floor(width * progress);
607
608     if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
609         if (direction == SwipeDirection::Right) {
610             [m_swipeLayer setTransform:CATransform3DMakeTranslation(width + swipingLayerOffset, 0, 0)];
611             didMoveSwipeSnapshotLayer();
612         }
613     } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
614         [m_swipeLayer setTransform:CATransform3DMakeTranslation((direction == SwipeDirection::Left ? -width : width) + swipingLayerOffset, 0, 0)];
615
616     for (const auto& layer : m_currentSwipeLiveLayers) {
617         if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) {
618             if (direction == SwipeDirection::Left)
619                 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
620         } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push)
621             [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)];
622     }
623 }
624
625 void ViewGestureController::didMoveSwipeSnapshotLayer()
626 {
627     if (!m_didMoveSwipeSnapshotCallback)
628         return;
629
630     m_didMoveSwipeSnapshotCallback(m_webPageProxy.boundsOfLayerInLayerBackedWindowCoordinates(m_swipeLayer.get()));
631 }
632
633 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, bool cancelled)
634 {
635     ASSERT(m_activeGestureType == ViewGestureType::Swipe);
636
637     m_swipeCancellationTracker = nullptr;
638
639     m_swipeInProgress = false;
640
641     CALayer *rootLayer = m_webPageProxy.acceleratedCompositingRootLayer();
642
643     [rootLayer setShadowOpacity:0];
644     [rootLayer setShadowRadius:0];
645
646     if (cancelled) {
647         removeSwipeSnapshot();
648         m_webPageProxy.navigationGestureDidEnd(false, *targetItem);
649         return;
650     }
651
652     uint64_t renderTreeSize = 0;
653     if (ViewSnapshot* snapshot = targetItem->snapshot())
654         renderTreeSize = snapshot->renderTreeSize();
655
656     m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::SetRenderTreeSizeNotificationThreshold(renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction), m_webPageProxy.pageID());
657
658     m_swipeWaitingForVisuallyNonEmptyLayout = true;
659     m_swipeWaitingForRenderTreeSizeThreshold = true;
660
661     m_webPageProxy.navigationGestureDidEnd(true, *targetItem);
662     m_webPageProxy.goToBackForwardItem(targetItem);
663
664     // FIXME: Like on iOS, we should ensure that even if one of the timeouts fires,
665     // we never show the old page content, instead showing white (or the snapshot background color).
666     m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
667
668     if (ViewSnapshot* snapshot = targetItem->snapshot())
669         m_backgroundColorForCurrentSnapshot = snapshot->backgroundColor();
670 }
671
672 void ViewGestureController::didHitRenderTreeSizeThreshold()
673 {
674     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
675         return;
676
677     m_swipeWaitingForRenderTreeSizeThreshold = false;
678
679     if (!m_swipeWaitingForVisuallyNonEmptyLayout) {
680         // FIXME: Ideally we would call removeSwipeSnapshotAfterRepaint() here, but sometimes
681         // scroll position isn't done restoring until didFinishLoadForFrame, so we flash the wrong content.
682     }
683 }
684
685 void ViewGestureController::didFirstVisuallyNonEmptyLayoutForMainFrame()
686 {
687     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
688         return;
689
690     m_swipeWaitingForVisuallyNonEmptyLayout = false;
691
692     if (!m_swipeWaitingForRenderTreeSizeThreshold) {
693         // FIXME: Ideally we would call removeSwipeSnapshotAfterRepaint() here, but sometimes
694         // scroll position isn't done restoring until didFinishLoadForFrame, so we flash the wrong content.
695     } else {
696         m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.startOneShot(swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration.count());
697         m_swipeWatchdogTimer.stop();
698     }
699 }
700
701 void ViewGestureController::didFinishLoadForMainFrame()
702 {
703     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
704         return;
705
706     if (m_webPageProxy.pageLoadState().isLoading()) {
707         m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval);
708         return;
709     }
710
711     removeSwipeSnapshotAfterRepaint();
712 }
713
714 void ViewGestureController::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type)
715 {
716     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
717         return;
718
719     if (type != SameDocumentNavigationSessionStateReplace && type != SameDocumentNavigationSessionStatePop)
720         return;
721
722     m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval);
723 }
724
725 void ViewGestureController::activeLoadMonitoringTimerFired()
726 {
727     if (m_webPageProxy.pageLoadState().isLoading())
728         return;
729
730     removeSwipeSnapshotAfterRepaint();
731 }
732
733 void ViewGestureController::swipeSnapshotWatchdogTimerFired()
734 {
735     removeSwipeSnapshotAfterRepaint();
736 }
737
738 void ViewGestureController::removeSwipeSnapshotAfterRepaint()
739 {
740     m_swipeActiveLoadMonitoringTimer.stop();
741
742     if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress)
743         return;
744
745     if (m_swipeWaitingForRepaint)
746         return;
747
748     m_swipeWaitingForRepaint = true;
749
750     WebPageProxy* webPageProxy = &m_webPageProxy;
751     m_webPageProxy.forceRepaint(VoidCallback::create([webPageProxy] (CallbackBase::Error error) {
752         webPageProxy->removeNavigationGestureSnapshot();
753     }));
754 }
755
756 void ViewGestureController::removeSwipeSnapshot()
757 {
758     m_swipeWaitingForRepaint = false;
759
760     m_swipeWatchdogTimer.stop();
761     m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.stop();
762
763     if (m_activeGestureType != ViewGestureType::Swipe)
764         return;
765
766 #if USE_IOSURFACE_VIEW_SNAPSHOTS
767     if (m_currentSwipeSnapshot && m_currentSwipeSnapshot->surface())
768         m_currentSwipeSnapshot->surface()->setIsVolatile(true);
769     m_currentSwipeSnapshot = nullptr;
770 #endif
771
772     for (const auto& layer : m_currentSwipeLiveLayers)
773         [layer setTransform:CATransform3DIdentity];
774
775     [m_swipeSnapshotLayer removeFromSuperlayer];
776     m_swipeSnapshotLayer = nullptr;
777
778     [m_swipeLayer removeFromSuperlayer];
779     m_swipeLayer = nullptr;
780
781     m_currentSwipeLiveLayers.clear();
782
783     m_activeGestureType = ViewGestureType::None;
784
785     m_webPageProxy.navigationGestureSnapshotWasRemoved();
786
787     m_backgroundColorForCurrentSnapshot = Color();
788 }
789
790 void ViewGestureController::endActiveGesture()
791 {
792     if (m_activeGestureType == ViewGestureType::Magnification) {
793         endMagnificationGesture();
794         m_visibleContentRectIsValid = false;
795     }
796 }
797
798 double ViewGestureController::magnification() const
799 {
800     if (m_activeGestureType == ViewGestureType::Magnification)
801         return m_magnification;
802
803     return m_webPageProxy.pageScaleFactor();
804 }
805
806 } // namespace WebKit
807
808 #endif // !PLATFORM(IOS)