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