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