435b01a03171fee5432a068d313d439d85edbab1
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / ViewGestureControllerIOS.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(IOS)
30
31 #import "UIKitSPI.h"
32 #import "ViewGestureControllerMessages.h"
33 #import "ViewGestureGeometryCollectorMessages.h"
34 #import "ViewSnapshotStore.h"
35 #import "WKBackForwardListItemInternal.h"
36 #import "WKWebViewInternal.h"
37 #import "WebBackForwardList.h"
38 #import "WebPageGroup.h"
39 #import "WebPageMessages.h"
40 #import "WebPageProxy.h"
41 #import "WebProcessProxy.h"
42 #import <UIKit/UIScreenEdgePanGestureRecognizer.h>
43 #import <WebCore/IOSurface.h>
44 #import <WebCore/QuartzCoreSPI.h>
45 #import <wtf/NeverDestroyed.h>
46
47 using namespace WebCore;
48
49 @interface WKSwipeTransitionController : NSObject <_UINavigationInteractiveTransitionBaseDelegate>
50 - (instancetype)initWithViewGestureController:(WebKit::ViewGestureController*)gestureController gestureRecognizerView:(UIView *)gestureRecognizerView;
51 - (void)invalidate;
52 @end
53
54 @interface _UIViewControllerTransitionContext (WKDetails)
55 @property (nonatomic, copy, setter=_setInteractiveUpdateHandler:)  void (^_interactiveUpdateHandler)(BOOL interactionIsOver, CGFloat percentComplete, BOOL transitionCompleted, _UIViewControllerTransitionContext *);
56 @end
57
58 @implementation WKSwipeTransitionController
59 {
60     WebKit::ViewGestureController *_gestureController;
61     RetainPtr<_UINavigationInteractiveTransitionBase> _backTransitionController;
62     RetainPtr<_UINavigationInteractiveTransitionBase> _forwardTransitionController;
63 }
64
65 static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5;
66 static const std::chrono::seconds swipeSnapshotRemovalWatchdogDuration = 3_s;
67
68 // The key in this map is the associated page ID.
69 static HashMap<uint64_t, WebKit::ViewGestureController*>& viewGestureControllersForAllPages()
70 {
71     static NeverDestroyed<HashMap<uint64_t, WebKit::ViewGestureController*>> viewGestureControllers;
72     return viewGestureControllers.get();
73 }
74
75 - (instancetype)initWithViewGestureController:(WebKit::ViewGestureController*)gestureController gestureRecognizerView:(UIView *)gestureRecognizerView
76 {
77     self = [super init];
78     if (self) {
79         _gestureController = gestureController;
80
81         _backTransitionController = adoptNS([_UINavigationInteractiveTransitionBase alloc]);
82         _backTransitionController = [_backTransitionController initWithGestureRecognizerView:gestureRecognizerView animator:nil delegate:self];
83         
84         _forwardTransitionController = adoptNS([_UINavigationInteractiveTransitionBase alloc]);
85         _forwardTransitionController = [_forwardTransitionController initWithGestureRecognizerView:gestureRecognizerView animator:nil delegate:self];
86         [_forwardTransitionController setShouldReverseTranslation:YES];
87     }
88     return self;
89 }
90
91 - (void)invalidate
92 {
93     _gestureController = nullptr;
94 }
95
96 - (WebKit::ViewGestureController::SwipeDirection)directionForTransition:(_UINavigationInteractiveTransitionBase *)transition
97 {
98     return transition == _backTransitionController ? WebKit::ViewGestureController::SwipeDirection::Left : WebKit::ViewGestureController::SwipeDirection::Right;
99 }
100
101 - (void)startInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition
102 {
103     _gestureController->beginSwipeGesture(transition, [self directionForTransition:transition]);
104 }
105
106 - (BOOL)shouldBeginInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition
107 {
108     return _gestureController->canSwipeInDirection([self directionForTransition:transition]);
109 }
110
111 - (BOOL)interactiveTransition:(_UINavigationInteractiveTransitionBase *)transition gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
112 {
113     return [otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]];
114 }
115
116 - (BOOL)interactiveTransition:(_UINavigationInteractiveTransitionBase *)transition gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
117 {
118     return YES;
119 }
120
121 - (UIPanGestureRecognizer *)gestureRecognizerForInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition WithTarget:(id)target action:(SEL)action
122 {
123     UIScreenEdgePanGestureRecognizer *recognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:target action:action];
124     switch ([self directionForTransition:transition]) {
125     case WebKit::ViewGestureController::SwipeDirection::Left:
126         [recognizer setEdges:UIRectEdgeLeft];
127         break;
128     case WebKit::ViewGestureController::SwipeDirection::Right:
129         [recognizer setEdges:UIRectEdgeRight];
130         break;
131     }
132     return [recognizer autorelease];
133 }
134
135 @end
136
137 namespace WebKit {
138
139 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
140     : m_webPageProxy(webPageProxy)
141     , m_activeGestureType(ViewGestureType::None)
142     , m_swipeWatchdogTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
143     , m_snapshotRemovalTargetRenderTreeSize(0)
144     , m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit(false)
145     , m_gesturePendingSnapshotRemoval(0)
146 {
147     viewGestureControllersForAllPages().add(webPageProxy.pageID(), this);
148 }
149
150 ViewGestureController::~ViewGestureController()
151 {
152     [m_swipeTransitionContext _setTransitionIsInFlight:NO];
153     [m_swipeTransitionContext _setInteractor:nil];
154     [m_swipeTransitionContext _setAnimator:nil];
155     [m_swipeInteractiveTransitionDelegate invalidate];
156     viewGestureControllersForAllPages().remove(m_webPageProxy.pageID());
157 }
158
159 void ViewGestureController::setAlternateBackForwardListSourceView(WKWebView *view)
160 {
161     m_alternateBackForwardListSourceView = view;
162 }
163
164 void ViewGestureController::installSwipeHandler(UIView *gestureRecognizerView, UIView *swipingView)
165 {
166     ASSERT(!m_swipeInteractiveTransitionDelegate);
167     m_swipeInteractiveTransitionDelegate = adoptNS([[WKSwipeTransitionController alloc] initWithViewGestureController:this gestureRecognizerView:gestureRecognizerView]);
168     m_liveSwipeView = swipingView;
169 }
170
171 void ViewGestureController::beginSwipeGesture(_UINavigationInteractiveTransitionBase *transition, SwipeDirection direction)
172 {
173     if (m_activeGestureType != ViewGestureType::None)
174         return;
175
176     m_webPageProxy.recordNavigationSnapshot();
177
178     m_webPageProxyForBackForwardListForCurrentSwipe = m_alternateBackForwardListSourceView.get() ? m_alternateBackForwardListSourceView.get()->_page : &m_webPageProxy;
179     m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureDidBegin();
180
181     auto& backForwardList = m_webPageProxyForBackForwardListForCurrentSwipe->backForwardList();
182
183     // Copy the snapshot from this view to the one that owns the back forward list, so that
184     // swiping forward will have the correct snapshot.
185     if (m_webPageProxyForBackForwardListForCurrentSwipe != &m_webPageProxy)
186         backForwardList.currentItem()->setSnapshot(m_webPageProxy.backForwardList().currentItem()->snapshot());
187
188     RefPtr<WebBackForwardListItem> targetItem = direction == SwipeDirection::Left ? backForwardList.backItem() : backForwardList.forwardItem();
189
190     CGRect liveSwipeViewFrame = [m_liveSwipeView frame];
191
192     RetainPtr<UIViewController> snapshotViewController = adoptNS([[UIViewController alloc] init]);
193     m_snapshotView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
194
195     RetainPtr<UIColor> backgroundColor = [UIColor whiteColor];
196     if (ViewSnapshot* snapshot = targetItem->snapshot()) {
197         float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
198         FloatSize swipeLayerSizeInDeviceCoordinates(liveSwipeViewFrame.size);
199         swipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
200         if (snapshot->hasImage() && snapshot->size() == swipeLayerSizeInDeviceCoordinates && deviceScaleFactor == snapshot->deviceScaleFactor())
201             [m_snapshotView layer].contents = snapshot->asLayerContents();
202         Color coreColor = snapshot->backgroundColor();
203         if (coreColor.isValid())
204             backgroundColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(coreColor, ColorSpaceDeviceRGB)]);
205     }
206
207     [m_snapshotView setBackgroundColor:backgroundColor.get()];
208     [m_snapshotView layer].contentsGravity = kCAGravityTopLeft;
209     [m_snapshotView layer].contentsScale = m_liveSwipeView.window.screen.scale;
210     [snapshotViewController setView:m_snapshotView.get()];
211
212     m_transitionContainerView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
213     m_liveSwipeViewClippingView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
214     [m_liveSwipeViewClippingView setClipsToBounds:YES];
215
216     [m_liveSwipeView.superview insertSubview:m_transitionContainerView.get() belowSubview:m_liveSwipeView];
217     [m_transitionContainerView addSubview:m_liveSwipeViewClippingView.get()];
218     [m_liveSwipeViewClippingView addSubview:m_liveSwipeView];
219
220     RetainPtr<UIViewController> targettedViewController = adoptNS([[UIViewController alloc] init]);
221     [targettedViewController setView:m_liveSwipeViewClippingView.get()];
222
223     UINavigationControllerOperation transitionOperation = direction == SwipeDirection::Left ? UINavigationControllerOperationPop : UINavigationControllerOperationPush;
224     RetainPtr<_UINavigationParallaxTransition> animationController = adoptNS([[_UINavigationParallaxTransition alloc] initWithCurrentOperation:transitionOperation]);
225
226     m_swipeTransitionContext = adoptNS([[_UIViewControllerOneToOneTransitionContext alloc] init]);
227     [m_swipeTransitionContext _setFromViewController:targettedViewController.get()];
228     [m_swipeTransitionContext _setToViewController:snapshotViewController.get()];
229     [m_swipeTransitionContext _setContainerView:m_transitionContainerView.get()];
230     [m_swipeTransitionContext _setFromStartFrame:liveSwipeViewFrame];
231     [m_swipeTransitionContext _setToEndFrame:liveSwipeViewFrame];
232     [m_swipeTransitionContext _setToStartFrame:CGRectZero];
233     [m_swipeTransitionContext _setFromEndFrame:CGRectZero];
234     [m_swipeTransitionContext _setAnimator:animationController.get()];
235     [m_swipeTransitionContext _setInteractor:transition];
236     [m_swipeTransitionContext _setTransitionIsInFlight:YES];
237     [m_swipeTransitionContext _setInteractiveUpdateHandler:^(BOOL finish, CGFloat percent, BOOL transitionCompleted, _UIViewControllerTransitionContext *) {
238         if (finish)
239             m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureWillEnd(transitionCompleted, *targetItem);
240     }];
241     uint64_t pageID = m_webPageProxy.pageID();
242     [m_swipeTransitionContext _setCompletionHandler:^(_UIViewControllerTransitionContext *context, BOOL didComplete) {
243         auto gestureControllerIter = viewGestureControllersForAllPages().find(pageID);
244         if (gestureControllerIter != viewGestureControllersForAllPages().end())
245             gestureControllerIter->value->endSwipeGesture(targetItem.get(), context, !didComplete);
246     }];
247     [m_swipeTransitionContext _setInteractiveUpdateHandler:^(BOOL, CGFloat, BOOL, _UIViewControllerTransitionContext *) { }];
248
249     [transition setAnimationController:animationController.get()];
250     [transition startInteractiveTransition:m_swipeTransitionContext.get()];
251
252     m_activeGestureType = ViewGestureType::Swipe;
253 }
254
255 bool ViewGestureController::canSwipeInDirection(SwipeDirection direction)
256 {
257     auto& backForwardList = m_alternateBackForwardListSourceView.get() ? m_alternateBackForwardListSourceView.get()->_page->backForwardList() : m_webPageProxy.backForwardList();
258     if (direction == SwipeDirection::Left)
259         return !!backForwardList.backItem();
260     return !!backForwardList.forwardItem();
261 }
262
263 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, _UIViewControllerTransitionContext *context, bool cancelled)
264 {
265     [context _setTransitionIsInFlight:NO];
266     [context _setInteractor:nil];
267     [context _setAnimator:nil];
268     
269     [[m_transitionContainerView superview] insertSubview:m_snapshotView.get() aboveSubview:m_transitionContainerView.get()];
270     [[m_transitionContainerView superview] insertSubview:m_liveSwipeView aboveSubview:m_transitionContainerView.get()];
271     [m_liveSwipeViewClippingView removeFromSuperview];
272     m_liveSwipeViewClippingView = nullptr;
273     [m_transitionContainerView removeFromSuperview];
274     m_transitionContainerView = nullptr;
275
276     if (cancelled) {
277         // removeSwipeSnapshot will clear m_webPageProxyForBackForwardListForCurrentSwipe, so hold on to it here.
278         RefPtr<WebPageProxy> webPageProxyForBackForwardListForCurrentSwipe = m_webPageProxyForBackForwardListForCurrentSwipe;
279         removeSwipeSnapshot();
280         webPageProxyForBackForwardListForCurrentSwipe->navigationGestureDidEnd(false, *targetItem);
281         return;
282     }
283
284     m_snapshotRemovalTargetRenderTreeSize = 0;
285     if (ViewSnapshot* snapshot = targetItem->snapshot())
286         m_snapshotRemovalTargetRenderTreeSize = snapshot->renderTreeSize() * swipeSnapshotRemovalRenderTreeSizeTargetFraction;
287
288     m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureDidEnd(true, *targetItem);
289     m_webPageProxyForBackForwardListForCurrentSwipe->goToBackForwardItem(targetItem);
290
291     if (auto drawingArea = m_webPageProxy.drawingArea()) {
292         uint64_t pageID = m_webPageProxy.pageID();
293         uint64_t gesturePendingSnapshotRemoval = m_gesturePendingSnapshotRemoval;
294         drawingArea->dispatchAfterEnsuringDrawing([pageID, gesturePendingSnapshotRemoval] (CallbackBase::Error error) {
295             auto gestureControllerIter = viewGestureControllersForAllPages().find(pageID);
296             if (gestureControllerIter != viewGestureControllersForAllPages().end() && gestureControllerIter->value->m_gesturePendingSnapshotRemoval == gesturePendingSnapshotRemoval)
297                 gestureControllerIter->value->willCommitPostSwipeTransitionLayerTree(error == CallbackBase::Error::None);
298         });
299     } else {
300         removeSwipeSnapshot();
301         return;
302     }
303
304     m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
305 }
306
307 void ViewGestureController::willCommitPostSwipeTransitionLayerTree(bool successful)
308 {
309     if (m_activeGestureType != ViewGestureType::Swipe)
310         return;
311
312     if (!successful) {
313         removeSwipeSnapshot();
314         return;
315     }
316
317     m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit = true;
318 }
319
320 void ViewGestureController::setRenderTreeSize(uint64_t renderTreeSize)
321 {
322     if (m_activeGestureType != ViewGestureType::Swipe)
323         return;
324
325     if (!m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit)
326         return;
327
328     if (!m_snapshotRemovalTargetRenderTreeSize || renderTreeSize > m_snapshotRemovalTargetRenderTreeSize)
329         removeSwipeSnapshot();
330 }
331
332 void ViewGestureController::swipeSnapshotWatchdogTimerFired()
333 {
334     removeSwipeSnapshot();
335 }
336
337 void ViewGestureController::removeSwipeSnapshot()
338 {
339     m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit = false;
340
341     m_swipeWatchdogTimer.stop();
342
343     if (m_activeGestureType != ViewGestureType::Swipe)
344         return;
345     
346     ++m_gesturePendingSnapshotRemoval;
347     
348     [m_snapshotView removeFromSuperview];
349     m_snapshotView = nullptr;
350     
351     m_snapshotRemovalTargetRenderTreeSize = 0;
352     m_activeGestureType = ViewGestureType::None;
353
354     m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureSnapshotWasRemoved();
355     m_webPageProxyForBackForwardListForCurrentSwipe = nullptr;
356
357     m_swipeTransitionContext = nullptr;
358 }
359
360 } // namespace WebKit
361
362 #endif // PLATFORM(IOS)