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