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