2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "ViewGestureController.h"
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>
50 using namespace WebCore;
52 @interface WKSwipeTransitionController : NSObject <_UINavigationInteractiveTransitionBaseDelegate>
53 - (instancetype)initWithViewGestureController:(WebKit::ViewGestureController*)gestureController gestureRecognizerView:(UIView *)gestureRecognizerView;
56 @interface _UIViewControllerTransitionContext (WKDetails)
57 @property (nonatomic, copy, setter=_setInteractiveUpdateHandler:) void (^_interactiveUpdateHandler)(BOOL interactionIsOver, CGFloat percentComplete, BOOL transitionCompleted, _UIViewControllerTransitionContext *);
60 @implementation WKSwipeTransitionController
62 WebKit::ViewGestureController *_gestureController;
63 RetainPtr<_UINavigationInteractiveTransitionBase> _backTransitionController;
64 RetainPtr<_UINavigationInteractiveTransitionBase> _forwardTransitionController;
67 static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5;
68 static const std::chrono::seconds swipeSnapshotRemovalWatchdogDuration = 3_s;
70 // The key in this map is the associated page ID.
71 static HashMap<uint64_t, WebKit::ViewGestureController*>& viewGestureControllersForAllPages()
73 static NeverDestroyed<HashMap<uint64_t, WebKit::ViewGestureController*>> viewGestureControllers;
74 return viewGestureControllers.get();
77 - (instancetype)initWithViewGestureController:(WebKit::ViewGestureController*)gestureController gestureRecognizerView:(UIView *)gestureRecognizerView
81 _gestureController = gestureController;
83 _backTransitionController = adoptNS([_UINavigationInteractiveTransitionBase alloc]);
84 _backTransitionController = [_backTransitionController initWithGestureRecognizerView:gestureRecognizerView animator:nil delegate:self];
86 _forwardTransitionController = adoptNS([_UINavigationInteractiveTransitionBase alloc]);
87 _forwardTransitionController = [_forwardTransitionController initWithGestureRecognizerView:gestureRecognizerView animator:nil delegate:self];
88 [_forwardTransitionController setShouldReverseTranslation:YES];
93 - (WebKit::ViewGestureController::SwipeDirection)directionForTransition:(_UINavigationInteractiveTransitionBase *)transition
95 return transition == _backTransitionController ? WebKit::ViewGestureController::SwipeDirection::Left : WebKit::ViewGestureController::SwipeDirection::Right;
98 - (void)startInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition
100 _gestureController->beginSwipeGesture(transition, [self directionForTransition:transition]);
103 - (BOOL)shouldBeginInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition
105 return _gestureController->canSwipeInDirection([self directionForTransition:transition]);
108 - (BOOL)interactiveTransition:(_UINavigationInteractiveTransitionBase *)transition gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
110 return [otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]];
113 - (BOOL)interactiveTransition:(_UINavigationInteractiveTransitionBase *)transition gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
118 - (UIPanGestureRecognizer *)gestureRecognizerForInteractiveTransition:(_UINavigationInteractiveTransitionBase *)transition WithTarget:(id)target action:(SEL)action
120 UIScreenEdgePanGestureRecognizer *recognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:target action:action];
121 switch ([self directionForTransition:transition]) {
122 case WebKit::ViewGestureController::SwipeDirection::Left:
123 [recognizer setEdges:UIRectEdgeLeft];
125 case WebKit::ViewGestureController::SwipeDirection::Right:
126 [recognizer setEdges:UIRectEdgeRight];
129 return [recognizer autorelease];
136 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
137 : m_webPageProxy(webPageProxy)
138 , m_activeGestureType(ViewGestureType::None)
139 , m_swipeWatchdogTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired)
140 , m_snapshotRemovalTargetRenderTreeSize(0)
141 , m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit(false)
143 viewGestureControllersForAllPages().add(webPageProxy.pageID(), this);
146 ViewGestureController::~ViewGestureController()
148 viewGestureControllersForAllPages().remove(m_webPageProxy.pageID());
151 void ViewGestureController::setAlternateBackForwardListSourceView(WKWebView *view)
153 m_alternateBackForwardListSourceView = view;
156 void ViewGestureController::installSwipeHandler(UIView *gestureRecognizerView, UIView *swipingView)
158 ASSERT(!m_swipeInteractiveTransitionDelegate);
159 m_swipeInteractiveTransitionDelegate = adoptNS([[WKSwipeTransitionController alloc] initWithViewGestureController:this gestureRecognizerView:gestureRecognizerView]);
160 m_liveSwipeView = swipingView;
163 void ViewGestureController::beginSwipeGesture(_UINavigationInteractiveTransitionBase *transition, SwipeDirection direction)
165 if (m_activeGestureType != ViewGestureType::None)
168 m_webPageProxy.recordNavigationSnapshot();
170 m_webPageProxyForBackForwardListForCurrentSwipe = m_alternateBackForwardListSourceView.get() ? m_alternateBackForwardListSourceView.get()->_page : &m_webPageProxy;
171 m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureDidBegin();
173 auto& backForwardList = m_webPageProxyForBackForwardListForCurrentSwipe->backForwardList();
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());
180 WebBackForwardListItem* targetItem = direction == SwipeDirection::Left ? backForwardList.backItem() : backForwardList.forwardItem();
182 CGRect liveSwipeViewFrame = [m_liveSwipeView frame];
184 RetainPtr<UIViewController> snapshotViewController = adoptNS([[UIViewController alloc] init]);
185 m_snapshotView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
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)]);
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()];
204 m_transitionContainerView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
205 m_liveSwipeViewClippingView = adoptNS([[UIView alloc] initWithFrame:liveSwipeViewFrame]);
206 [m_liveSwipeViewClippingView setClipsToBounds:YES];
208 [m_liveSwipeView.superview insertSubview:m_transitionContainerView.get() belowSubview:m_liveSwipeView];
209 [m_liveSwipeViewClippingView addSubview:m_liveSwipeView];
210 [m_transitionContainerView addSubview:m_liveSwipeViewClippingView.get()];
212 RetainPtr<UIViewController> targettedViewController = adoptNS([[UIViewController alloc] init]);
213 [targettedViewController setView:m_liveSwipeViewClippingView.get()];
215 UINavigationControllerOperation transitionOperation = direction == SwipeDirection::Left ? UINavigationControllerOperationPop : UINavigationControllerOperationPush;
216 RetainPtr<_UINavigationParallaxTransition> animationController = adoptNS([[_UINavigationParallaxTransition alloc] initWithCurrentOperation:transitionOperation]);
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 *) {
231 m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureWillEnd(transitionCompleted, *targetItem);
233 [transitionContext _setCompletionHandler:^(_UIViewControllerTransitionContext *context, BOOL didComplete) { endSwipeGesture(targetItem, context, !didComplete); }];
234 [transitionContext _setInteractiveUpdateHandler:^(BOOL, CGFloat, BOOL, _UIViewControllerTransitionContext *) { }];
236 [transition setAnimationController:animationController.get()];
237 [transition startInteractiveTransition:transitionContext.get()];
239 m_activeGestureType = ViewGestureType::Swipe;
242 bool ViewGestureController::canSwipeInDirection(SwipeDirection direction)
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();
250 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, _UIViewControllerTransitionContext *context, bool cancelled)
252 [context _setTransitionIsInFlight:NO];
253 [context _setInteractor:nil];
254 [context _setAnimator:nil];
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;
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);
271 m_snapshotRemovalTargetRenderTreeSize = 0;
272 if (ViewSnapshot* snapshot = targetItem->snapshot())
273 m_snapshotRemovalTargetRenderTreeSize = snapshot->renderTreeSize() * swipeSnapshotRemovalRenderTreeSizeTargetFraction;
275 m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureDidEnd(true, *targetItem);
276 m_webPageProxyForBackForwardListForCurrentSwipe->goToBackForwardItem(targetItem);
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);
285 m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count());
288 void ViewGestureController::willCommitPostSwipeTransitionLayerTree(bool successful)
290 if (m_activeGestureType != ViewGestureType::Swipe)
294 removeSwipeSnapshot();
298 m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit = true;
301 void ViewGestureController::setRenderTreeSize(uint64_t renderTreeSize)
303 if (m_activeGestureType != ViewGestureType::Swipe)
306 if (!m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit)
309 if (!m_snapshotRemovalTargetRenderTreeSize || renderTreeSize > m_snapshotRemovalTargetRenderTreeSize)
310 removeSwipeSnapshot();
313 void ViewGestureController::swipeSnapshotWatchdogTimerFired()
315 removeSwipeSnapshot();
318 void ViewGestureController::removeSwipeSnapshot()
320 m_shouldRemoveSnapshotWhenTargetRenderTreeSizeHit = false;
322 m_swipeWatchdogTimer.stop();
324 if (m_activeGestureType != ViewGestureType::Swipe)
328 if (m_currentSwipeSnapshotSurface)
329 m_currentSwipeSnapshotSurface->setIsVolatile(true);
330 m_currentSwipeSnapshotSurface = nullptr;
333 [m_snapshotView removeFromSuperview];
334 m_snapshotView = nullptr;
336 m_snapshotRemovalTargetRenderTreeSize = 0;
337 m_activeGestureType = ViewGestureType::None;
339 m_webPageProxyForBackForwardListForCurrentSwipe->navigationGestureSnapshotWasRemoved();
340 m_webPageProxyForBackForwardListForCurrentSwipe = nullptr;
343 } // namespace WebKit
345 #endif // PLATFORM(IOS)