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