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