f570c729be2481d7dcfe8edca83cd9ce52115e99
[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 "WebBackForwardList.h"
35 #import "WebPageGroup.h"
36 #import "WebPageMessages.h"
37 #import "WebPageProxy.h"
38 #import "WebProcessProxy.h"
39 #import <QuartzCore/QuartzCorePrivate.h>
40 #import <UIKit/UIScreenEdgePanGestureRecognizer.h>
41 #import <UIKit/UIViewControllerTransitioning_Private.h>
42 #import <UIKit/UIWebTouchEventsGestureRecognizer.h>
43 #import <UIKit/_UINavigationInteractiveTransition.h>
44 #import <UIKit/_UINavigationParallaxTransition.h>
45 #import <WebCore/IOSurface.h>
46 #import <wtf/NeverDestroyed.h>
47
48 using namespace WebCore;
49
50 @interface WKSwipeTransitionController : NSObject <_UINavigationInteractiveTransitionBaseDelegate>
51 - (instancetype)initWithViewGestureController:(WebKit::ViewGestureController*)gestureController gestureRecognizerView:(UIView *)gestureRecognizerView;
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 - (WebKit::ViewGestureController::SwipeDirection)directionForTransition:(_UINavigationInteractiveTransitionBase *)transition
92 {
93     return transition == _backTransitionController ? WebKit::ViewGestureController::SwipeDirection::Left : WebKit::ViewGestureController::SwipeDirection::Right;
94 }
95
96 - (void)startInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition
97 {
98     _gestureController->beginSwipeGesture(transition, [self directionForTransition:transition]);
99 }
100
101 - (BOOL)shouldBeginInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition
102 {
103     return _gestureController->canSwipeInDirection([self directionForTransition:transition]);
104 }
105
106 - (BOOL)interactiveTransition:(_UINavigationInteractiveTransitionBase *)transition gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
107 {
108     return [otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]];
109 }
110
111 - (BOOL)interactiveTransition:(_UINavigationInteractiveTransitionBase *)transition gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
112 {
113     return YES;
114 }
115
116 - (UIPanGestureRecognizer *)gestureRecognizerForInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition WithTarget:(id)target action:(SEL)action
117 {
118     UIScreenEdgePanGestureRecognizer *recognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:target action:action];
119     switch ([self directionForTransition:transition]) {
120     case WebKit::ViewGestureController::SwipeDirection::Left:
121         [recognizer setEdges:UIRectEdgeLeft];
122         break;
123     case WebKit::ViewGestureController::SwipeDirection::Right:
124         [recognizer setEdges:UIRectEdgeRight];
125         break;
126     }
127     return [recognizer autorelease];
128 }
129
130 @end
131
132 namespace WebKit {
133
134 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
135     : m_webPageProxy(webPageProxy)
136     , m_activeGestureType(ViewGestureType::None)
137     , m_swipeWatchdogTimer(this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
138     , m_snapshotRemovalTargetRenderTreeSize(0)
139     , m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit(false)
140 {
141     viewGestureControllersForAllPages().add(webPageProxy.pageID(), this);
142 }
143
144 ViewGestureController::~ViewGestureController()
145 {
146     viewGestureControllersForAllPages().remove(m_webPageProxy.pageID());
147 }
148
149 void ViewGestureController::installSwipeHandler(UIView *gestureRecognizerView, UIView *swipingView)
150 {
151     ASSERT(!m_swipeInteractiveTransitionDelegate);
152     m_swipeInteractiveTransitionDelegate = adoptNS([[WKSwipeTransitionController alloc] initWithViewGestureController:this gestureRecognizerView:gestureRecognizerView]);
153     m_liveSwipeView = swipingView;
154 }
155
156 void ViewGestureController::beginSwipeGesture(_UINavigationInteractiveTransitionBase *transition, SwipeDirection direction)
157 {
158     if (m_activeGestureType != ViewGestureType::None)
159         return;
160
161     m_webPageProxy.navigationGestureDidBegin();
162
163     ViewSnapshotStore::shared().recordSnapshot(m_webPageProxy);
164
165     WebKit::WebBackForwardListItem* targetItem = direction == SwipeDirection::Left ? m_webPageProxy.backForwardList().backItem() : m_webPageProxy.backForwardList().forwardItem();
166
167     CGRect liveSwipeViewFrame = [m_liveSwipeView frame];
168
169     RetainPtr<UIViewController> snapshotViewController = adoptNS([[UIViewController alloc] init]);
170     m_snapshotView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
171
172     RetainPtr<UIColor> backgroundColor = [UIColor whiteColor];
173     if (ViewSnapshot* snapshot = targetItem->snapshot()) {
174         float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
175         FloatSize swipeLayerSizeInDeviceCoordinates(liveSwipeViewFrame.size);
176         swipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
177         if (snapshot->hasImage() && snapshot->size() == swipeLayerSizeInDeviceCoordinates && deviceScaleFactor == snapshot->deviceScaleFactor())
178             [m_snapshotView layer].contents = snapshot->asLayerContents();
179         Color coreColor = snapshot->backgroundColor();
180         if (coreColor.isValid())
181             backgroundColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(coreColor, ColorSpaceDeviceRGB)]);
182     }
183
184     [m_snapshotView setBackgroundColor:backgroundColor.get()];
185     [m_snapshotView layer].contentsGravity = kCAGravityTopLeft;
186     [m_snapshotView layer].contentsScale = m_liveSwipeView.window.screen.scale;
187     [snapshotViewController setView:m_snapshotView.get()];
188
189     m_transitionContainerView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
190     m_liveSwipeViewClippingView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
191     [m_liveSwipeViewClippingView setClipsToBounds:YES];
192
193     [m_liveSwipeView.superview insertSubview:m_transitionContainerView.get() belowSubview:m_liveSwipeView];
194     [m_liveSwipeViewClippingView addSubview:m_liveSwipeView];
195     [m_transitionContainerView addSubview:m_liveSwipeViewClippingView.get()];
196
197     RetainPtr<UIViewController> targettedViewController = adoptNS([[UIViewController alloc] init]);
198     [targettedViewController setView:m_liveSwipeViewClippingView.get()];
199
200     UINavigationControllerOperation transitionOperation = direction == SwipeDirection::Left ? UINavigationControllerOperationPop : UINavigationControllerOperationPush;
201     RetainPtr<_UINavigationParallaxTransition> animationController = adoptNS([[_UINavigationParallaxTransition alloc] initWithCurrentOperation:transitionOperation]);
202
203     RetainPtr<_UIViewControllerOneToOneTransitionContext> transitionContext = adoptNS([[_UIViewControllerOneToOneTransitionContext alloc] init]);
204     [transitionContext _setFromViewController:targettedViewController.get()];
205     [transitionContext _setToViewController:snapshotViewController.get()];
206     [transitionContext _setContainerView:m_transitionContainerView.get()];
207     [transitionContext _setFromStartFrame:liveSwipeViewFrame];
208     [transitionContext _setToEndFrame:liveSwipeViewFrame];
209     [transitionContext _setToStartFrame:CGRectZero];
210     [transitionContext _setFromEndFrame:CGRectZero];
211     [transitionContext _setAnimator:animationController.get()];
212     [transitionContext _setInteractor:transition];
213     [transitionContext _setTransitionIsInFlight:YES];
214     [transitionContext _setInteractiveUpdateHandler:^(BOOL finish, CGFloat percent, BOOL transitionCompleted, _UIViewControllerTransitionContext *) {
215         if (finish)
216             m_webPageProxy.navigationGestureWillEnd(transitionCompleted, *targetItem);
217     }];
218     [transitionContext _setCompletionHandler:^(_UIViewControllerTransitionContext *context, BOOL didComplete) { endSwipeGesture(targetItem, context, !didComplete); }];
219     [transitionContext _setInteractiveUpdateHandler:^(BOOL, CGFloat, BOOL, _UIViewControllerTransitionContext *) { }];
220
221     [transition setAnimationController:animationController.get()];
222     [transition startInteractiveTransition:transitionContext.get()];
223
224     m_activeGestureType = ViewGestureType::Swipe;
225 }
226
227 bool ViewGestureController::canSwipeInDirection(SwipeDirection direction)
228 {
229     if (direction == SwipeDirection::Left)
230         return !!m_webPageProxy.backForwardList().backItem();
231     return !!m_webPageProxy.backForwardList().forwardItem();
232 }
233
234 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, _UIViewControllerTransitionContext *context, bool cancelled)
235 {
236     [context _setTransitionIsInFlight:NO];
237     [context _setInteractor:nil];
238     [context _setAnimator:nil];
239     
240     [[m_transitionContainerView superview] insertSubview:m_snapshotView.get() aboveSubview:m_transitionContainerView.get()];
241     [[m_transitionContainerView superview] insertSubview:m_liveSwipeView aboveSubview:m_transitionContainerView.get()];
242     [m_liveSwipeViewClippingView removeFromSuperview];
243     m_liveSwipeViewClippingView = nullptr;
244     [m_transitionContainerView removeFromSuperview];
245     m_transitionContainerView = nullptr;
246     
247     if (cancelled) {
248         removeSwipeSnapshot();
249         m_webPageProxy.navigationGestureDidEnd(false, *targetItem);
250         return;
251     }
252
253     m_snapshotRemovalTargetRenderTreeSize = 0;
254     if (ViewSnapshot* snapshot = targetItem->snapshot())
255         m_snapshotRemovalTargetRenderTreeSize = snapshot->renderTreeSize() * swipeSnapshotRemovalRenderTreeSizeTargetFraction;
256
257     // We don't want to replace the current back-forward item's snapshot
258     // like we normally would when going back or forward, because we are
259     // displaying the destination item's snapshot.
260     ViewSnapshotStore::shared().disableSnapshotting();
261
262     m_webPageProxy.navigationGestureDidEnd(true, *targetItem);
263     m_webPageProxy.goToBackForwardItem(targetItem);
264     ViewSnapshotStore::shared().enableSnapshotting();
265
266     uint64_t pageID = m_webPageProxy.pageID();
267     m_webPageProxy.drawingArea()->dispatchAfterEnsuringDrawing([pageID] (CallbackBase::Error error) {
268         auto gestureControllerIter = viewGestureControllersForAllPages().find(pageID);
269         if (gestureControllerIter != viewGestureControllersForAllPages().end())
270             gestureControllerIter->value->willCommitPostSwipeTransitionLayerTree(error == CallbackBase::Error::None);
271     });
272
273     m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
274 }
275
276 void ViewGestureController::willCommitPostSwipeTransitionLayerTree(bool successful)
277 {
278     if (m_activeGestureType != ViewGestureType::Swipe)
279         return;
280
281     if (!successful) {
282         removeSwipeSnapshot();
283         return;
284     }
285
286     m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit = true;
287 }
288
289 void ViewGestureController::setRenderTreeSize(uint64_t renderTreeSize)
290 {
291     if (m_activeGestureType != ViewGestureType::Swipe)
292         return;
293
294     if (!m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit)
295         return;
296
297     if (!m_snapshotRemovalTargetRenderTreeSize || renderTreeSize > m_snapshotRemovalTargetRenderTreeSize)
298         removeSwipeSnapshot();
299 }
300
301 void ViewGestureController::swipeSnapshotWatchdogTimerFired(Timer<ViewGestureController>*)
302 {
303     removeSwipeSnapshot();
304 }
305
306 void ViewGestureController::removeSwipeSnapshot()
307 {
308     m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit = false;
309
310     m_swipeWatchdogTimer.stop();
311
312     if (m_activeGestureType != ViewGestureType::Swipe)
313         return;
314     
315 #if USE(IOSURFACE)
316     if (m_currentSwipeSnapshotSurface)
317         m_currentSwipeSnapshotSurface->setIsVolatile(true);
318     m_currentSwipeSnapshotSurface = nullptr;
319 #endif
320     
321     [m_snapshotView removeFromSuperview];
322     m_snapshotView = nullptr;
323     
324     m_snapshotRemovalTargetRenderTreeSize = 0;
325     m_activeGestureType = ViewGestureType::None;
326
327     m_webPageProxy.navigationGestureSnapshotWasRemoved();
328 }
329
330 } // namespace WebKit
331
332 #endif // PLATFORM(IOS)