CRASH at WebKit: WebKit::WebFullScreenManagerProxy::saveScrollPosition
[WebKit-https.git] / Source / WebKit / UIProcess / ios / fullscreen / WKFullScreenWindowControllerIOS.mm
1 /*
2  * Copyright (C) 2017-2018 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
28 #if PLATFORM(IOS) && ENABLE(FULLSCREEN_API)
29 #import "WKFullScreenWindowControllerIOS.h"
30
31 #import "UIKitSPI.h"
32 #import "WKFullScreenViewController.h"
33 #import "WKFullscreenStackView.h"
34 #import "WKWebView.h"
35 #import "WKWebViewInternal.h"
36 #import "WKWebViewPrivate.h"
37 #import "WebFullScreenManagerProxy.h"
38 #import "WebPageProxy.h"
39 #import <Foundation/Foundation.h>
40 #import <Security/SecCertificate.h>
41 #import <Security/SecTrust.h>
42 #import <UIKit/UIVisualEffectView.h>
43 #import <WebCore/FloatRect.h>
44 #import <WebCore/GeometryUtilities.h>
45 #import <WebCore/IntRect.h>
46 #import <WebCore/LocalizedStrings.h>
47 #import <WebCore/ViewportArguments.h>
48 #import <WebCore/WebCoreNSURLExtras.h>
49 #import <pal/spi/cf/CFNetworkSPI.h>
50 #import <pal/spi/cocoa/NSStringSPI.h>
51 #import <pal/spi/cocoa/QuartzCoreSPI.h>
52 #import <pal/spi/cocoa/URLFormattingSPI.h>
53 #import <wtf/SoftLinking.h>
54 #import <wtf/spi/cocoa/SecuritySPI.h>
55
56 using namespace WebKit;
57 using namespace WebCore;
58
59 #if !HAVE(URL_FORMATTING)
60 SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(LinkPresentation)
61 #endif
62
63 namespace WebKit {
64
65 static CGSize sizeExpandedToSize(CGSize initial, CGSize other)
66 {
67     return CGSizeMake(std::max(initial.width, other.width),  std::max(initial.height, other.height));
68 }
69
70 static CGRect safeInlineRect(CGRect initial, CGSize parentSize)
71 {
72     if (initial.origin.y > parentSize.height || initial.origin.y < -initial.size.height || initial.origin.x > parentSize.width || initial.origin.x < -initial.size.width)
73         return CGRectMake(parentSize.width / 2, parentSize.height / 2, 1, 1);
74     return initial;
75 }
76
77 static void replaceViewWithView(UIView *view, UIView *otherView)
78 {
79     [CATransaction begin];
80     [CATransaction setDisableActions:YES];
81     [otherView setFrame:[view frame]];
82     [otherView setAutoresizingMask:[view autoresizingMask]];
83     [[view superview] insertSubview:otherView aboveSubview:view];
84     [view removeFromSuperview];
85     [CATransaction commit];
86 }
87
88 enum FullScreenState : NSInteger {
89     NotInFullScreen,
90     WaitingToEnterFullScreen,
91     EnteringFullScreen,
92     InFullScreen,
93     WaitingToExitFullScreen,
94     ExitingFullScreen,
95 };
96
97 struct WKWebViewState {
98     float _savedTopContentInset = 0.0;
99     CGFloat _savedPageScale = 1;
100     CGFloat _savedViewScale = 1.0;
101     CGFloat _savedZoomScale = 1;
102     UIEdgeInsets _savedEdgeInset = UIEdgeInsetsZero;
103     UIEdgeInsets _savedObscuredInsets = UIEdgeInsetsZero;
104     UIEdgeInsets _savedScrollIndicatorInsets = UIEdgeInsetsZero;
105     CGPoint _savedContentOffset = CGPointZero;
106     CGFloat _savedMinimumZoomScale = 1;
107     CGFloat _savedMaximumZoomScale = 1;
108     BOOL _savedBouncesZoom = NO;
109     BOOL _savedForceAlwaysUserScalable = NO;
110
111     void applyTo(WKWebView* webView)
112     {
113         [webView _setPageScale:_savedPageScale withOrigin:CGPointMake(0, 0)];
114         [webView _setObscuredInsets:_savedObscuredInsets];
115         [[webView scrollView] setContentInset:_savedEdgeInset];
116         [[webView scrollView] setContentOffset:_savedContentOffset];
117         [[webView scrollView] setScrollIndicatorInsets:_savedScrollIndicatorInsets];
118         if (auto* page = webView._page) {
119             page->setTopContentInset(_savedTopContentInset);
120             page->setForceAlwaysUserScalable(_savedForceAlwaysUserScalable);
121         }
122         [webView _setViewScale:_savedViewScale];
123         [[webView scrollView] setZoomScale:_savedZoomScale];
124         webView.scrollView.minimumZoomScale = _savedMinimumZoomScale;
125         webView.scrollView.maximumZoomScale = _savedMaximumZoomScale;
126         webView.scrollView.bouncesZoom = _savedBouncesZoom;
127     }
128     
129     void store(WKWebView* webView)
130     {
131         _savedPageScale = [webView _pageScale];
132         _savedObscuredInsets = [webView _obscuredInsets];
133         _savedEdgeInset = [[webView scrollView] contentInset];
134         _savedContentOffset = [[webView scrollView] contentOffset];
135         _savedScrollIndicatorInsets = [[webView scrollView] scrollIndicatorInsets];
136         if (auto* page = webView._page) {
137             _savedTopContentInset = page->topContentInset();
138             _savedForceAlwaysUserScalable = page->forceAlwaysUserScalable();
139         }
140         _savedViewScale = [webView _viewScale];
141         _savedZoomScale = [[webView scrollView] zoomScale];
142         _savedMinimumZoomScale = webView.scrollView.minimumZoomScale;
143         _savedMaximumZoomScale = webView.scrollView.maximumZoomScale;
144         _savedBouncesZoom = webView.scrollView.bouncesZoom;
145     }
146 };
147
148 } // namespace WebKit
149
150 static const NSTimeInterval kAnimationDuration = 0.2;
151
152 #pragma mark -
153
154 @interface WKFullscreenAnimationController : NSObject <UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning>
155 @property (retain, nonatomic) UIViewController* viewController;
156 @property (nonatomic) CGRect initialFrame;
157 @property (nonatomic) CGRect finalFrame;
158 @property (nonatomic, getter=isAnimatingIn) BOOL animatingIn;
159 @property (readonly, nonatomic) id<UIViewControllerContextTransitioning> context;
160 @end
161
162 @implementation WKFullscreenAnimationController {
163     CGRect _initialMaskViewBounds;
164     CGRect _finalMaskViewBounds;
165     CGAffineTransform _initialAnimatingViewTransform;
166     CGAffineTransform _finalAnimatingViewTransform;
167     CGPoint _initialMaskViewCenter;
168     CGPoint _finalMaskViewCenter;
169     RetainPtr<UIView> _maskView;
170     RetainPtr<UIView> _animatingView;
171     RetainPtr<id<UIViewControllerContextTransitioning>> _context;
172     CGFloat _initialBackgroundAlpha;
173     CGFloat _finalBackgroundAlpha;
174 }
175
176 - (void)dealloc
177 {
178     [_viewController release];
179     [super dealloc];
180 }
181
182 - (id<UIViewControllerContextTransitioning>)context
183 {
184     return _context.get();
185 }
186
187 - (void)_createViewsForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
188 {
189     _maskView = adoptNS([[UIView alloc] init]);
190     [_maskView setBackgroundColor:[UIColor blackColor]];
191     [_maskView setBounds:_initialMaskViewBounds];
192     [_maskView setCenter:_initialMaskViewCenter];
193     [_animatingView setMaskView:_maskView.get()];
194     [_animatingView setTransform:_initialAnimatingViewTransform];
195
196     UIView *containerView = [transitionContext containerView];
197     [containerView addSubview:_animatingView.get()];
198 }
199
200 - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
201 {
202     return kAnimationDuration;
203 }
204
205 - (void)configureInitialAndFinalStatesForTransition:(id<UIViewControllerContextTransitioning>)transitionContext
206 {
207     _context = transitionContext;
208     UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
209     UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
210
211     CGRect inlineFrame = _animatingIn ? _initialFrame : _finalFrame;
212     CGRect fullscreenFrame = _animatingIn ? _finalFrame : _initialFrame;
213     _animatingView = _animatingIn ? toView : fromView;
214
215     CGRect boundsRect = largestRectWithAspectRatioInsideRect(FloatRect(inlineFrame).size().aspectRatio(), fullscreenFrame);
216     boundsRect.origin = CGPointZero;
217
218     _initialMaskViewBounds = _animatingIn ? boundsRect : [_animatingView bounds];
219     _initialMaskViewCenter = CGPointMake(CGRectGetMidX([_animatingView bounds]), CGRectGetMidY([_animatingView bounds]));
220     _finalMaskViewBounds = _animatingIn ? [_animatingView bounds] : boundsRect;
221     _finalMaskViewCenter = CGPointMake(CGRectGetMidX([_animatingView bounds]), CGRectGetMidY([_animatingView bounds]));
222
223     FloatRect scaleRect = smallestRectWithAspectRatioAroundRect(FloatRect(fullscreenFrame).size().aspectRatio(), inlineFrame);
224     CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scaleRect.width() / fullscreenFrame.size.width, scaleRect.height() / fullscreenFrame.size.height);
225     CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(inlineFrame) - CGRectGetMidX(fullscreenFrame), CGRectGetMidY(inlineFrame) - CGRectGetMidY(fullscreenFrame));
226
227     CGAffineTransform finalTransform = CGAffineTransformConcat(scaleTransform, translateTransform);
228     _initialAnimatingViewTransform = _animatingIn ? finalTransform : CGAffineTransformIdentity;
229     _finalAnimatingViewTransform = _animatingIn ? CGAffineTransformIdentity : finalTransform;
230
231     _initialBackgroundAlpha = _animatingIn ? 0 : 1;
232     _finalBackgroundAlpha = _animatingIn ? 1 : 0;
233 }
234
235 - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
236 {
237     [self configureInitialAndFinalStatesForTransition:transitionContext];
238     [self _createViewsForTransitionContext:transitionContext];
239
240     UIWindow *window = [transitionContext containerView].window;
241     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha];
242
243     [UIView animateWithDuration:kAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
244         [self updateWithProgress:1];
245     } completion:^(BOOL finished) {
246         [self animationEnded:![transitionContext transitionWasCancelled]];
247         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
248     }];
249 }
250
251 - (void)animationEnded:(BOOL)transitionCompleted
252 {
253     if (([self isAnimatingIn] && !transitionCompleted) || (![self isAnimatingIn] && transitionCompleted))
254         [_animatingView removeFromSuperview];
255
256     [_animatingView setMaskView:nil];
257     _maskView = nil;
258     _animatingView = nil;
259 }
260
261 - (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext
262 {
263     [self configureInitialAndFinalStatesForTransition:transitionContext];
264     [self _createViewsForTransitionContext:transitionContext];
265 }
266
267 - (void)updateWithProgress:(CGFloat)progress
268 {
269     CGAffineTransform progressTransform = _initialAnimatingViewTransform;
270     progressTransform.a += progress * (_finalAnimatingViewTransform.a - _initialAnimatingViewTransform.a);
271     progressTransform.b += progress * (_finalAnimatingViewTransform.b - _initialAnimatingViewTransform.b);
272     progressTransform.c += progress * (_finalAnimatingViewTransform.c - _initialAnimatingViewTransform.c);
273     progressTransform.d += progress * (_finalAnimatingViewTransform.d - _initialAnimatingViewTransform.d);
274     progressTransform.tx += progress * (_finalAnimatingViewTransform.tx - _initialAnimatingViewTransform.tx);
275     progressTransform.ty += progress * (_finalAnimatingViewTransform.ty - _initialAnimatingViewTransform.ty);
276     [_animatingView setTransform:progressTransform];
277
278     CGRect progressBounds = _initialMaskViewBounds;
279     progressBounds.origin.x += progress * (_finalMaskViewBounds.origin.x - _initialMaskViewBounds.origin.x);
280     progressBounds.origin.y += progress * (_finalMaskViewBounds.origin.y - _initialMaskViewBounds.origin.y);
281     progressBounds.size.width += progress * (_finalMaskViewBounds.size.width - _initialMaskViewBounds.size.width);
282     progressBounds.size.height += progress * (_finalMaskViewBounds.size.height - _initialMaskViewBounds.size.height);
283     [_maskView setBounds:progressBounds];
284
285     CGPoint progressCenter = _initialMaskViewCenter;
286     progressCenter.x += progress * (_finalMaskViewCenter.x - _finalMaskViewCenter.x);
287     progressCenter.y += progress * (_finalMaskViewCenter.y - _finalMaskViewCenter.y);
288     [_maskView setCenter:progressCenter];
289
290     UIWindow *window = [_animatingView window];
291     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
292 }
293
294 - (void)updateWithProgress:(CGFloat)progress scale:(CGFloat)scale translation:(CGSize)translation anchor:(CGPoint)anchor
295 {
296     CGAffineTransform progressTransform = _initialAnimatingViewTransform;
297     progressTransform = CGAffineTransformScale(progressTransform, scale, scale);
298     progressTransform = CGAffineTransformTranslate(progressTransform, translation.width, translation.height);
299     [_animatingView setTransform:progressTransform];
300
301     UIWindow *window = [_animatingView window];
302     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
303 }
304
305 - (void)updateWithProgress:(CGFloat)progress translation:(CGSize)translation anchor:(CGPoint)anchor
306 {
307     CGAffineTransform progressTransform = _initialAnimatingViewTransform;
308     progressTransform.a += progress * (_finalAnimatingViewTransform.a - _initialAnimatingViewTransform.a);
309     progressTransform.b += progress * (_finalAnimatingViewTransform.b - _initialAnimatingViewTransform.b);
310     progressTransform.c += progress * (_finalAnimatingViewTransform.c - _initialAnimatingViewTransform.c);
311     progressTransform.d += progress * (_finalAnimatingViewTransform.d - _initialAnimatingViewTransform.d);
312     progressTransform.tx += _initialAnimatingViewTransform.tx + translation.width;
313     progressTransform.ty += _initialAnimatingViewTransform.ty + translation.height;
314     [_animatingView setTransform:progressTransform];
315
316     UIWindow *window = [_animatingView window];
317     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
318 }
319
320 - (void)end:(BOOL)cancelled {
321     if (cancelled) {
322         [UIView animateWithDuration:kAnimationDuration animations:^{
323             [self updateWithProgress:0];
324         } completion:^(BOOL finished) {
325             [_context cancelInteractiveTransition];
326             [_context completeTransition:NO];
327         }];
328     } else {
329         [UIView animateWithDuration:kAnimationDuration animations:^{
330             [self updateWithProgress:1];
331         } completion:^(BOOL finished) {
332             [_context finishInteractiveTransition];
333             [_context completeTransition:YES];
334         }];
335     }
336 }
337
338 @end
339
340 #pragma mark -
341
342 @interface WKFullScreenInteractiveTransition : NSObject<UIViewControllerInteractiveTransitioning>
343 - (id)initWithAnimator:(WKFullscreenAnimationController *)animator anchor:(CGPoint)point;
344 @property (nonatomic, readonly) WKFullscreenAnimationController *animator;
345 @end
346
347 @implementation WKFullScreenInteractiveTransition {
348     RetainPtr<WKFullscreenAnimationController> _animator;
349     RetainPtr<id<UIViewControllerContextTransitioning>> _context;
350     CGPoint _anchor;
351 }
352
353 - (id)initWithAnimator:(WKFullscreenAnimationController *)animator anchor:(CGPoint)point
354 {
355     if (!(self = [super init]))
356         return nil;
357
358     _animator = animator;
359     _anchor = point;
360     return self;
361 }
362
363 - (WKFullscreenAnimationController *)animator
364 {
365     return _animator.get();
366 }
367
368 - (BOOL)wantsInteractiveStart
369 {
370     return YES;
371 }
372
373 - (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext
374 {
375     _context = transitionContext;
376     [_animator startInteractiveTransition:transitionContext];
377 }
378
379 - (void)updateInteractiveTransition:(CGFloat)progress withTranslation:(CGSize)translation
380 {
381     [_animator updateWithProgress:progress translation:translation anchor:_anchor];
382     [_context updateInteractiveTransition:progress];
383 }
384
385 - (void)updateInteractiveTransition:(CGFloat)progress withScale:(CGFloat)scale andTranslation:(CGSize)translation
386 {
387     [_animator updateWithProgress:progress scale:scale translation:translation anchor:_anchor];
388     [_context updateInteractiveTransition:progress];
389 }
390
391 - (void)cancelInteractiveTransition
392 {
393     [_animator end:YES];
394 }
395
396 - (void)finishInteractiveTransition
397 {
398     [_animator end:NO];
399 }
400 @end
401
402 #pragma mark -
403
404 @interface WKFullScreenWindowController () <UIGestureRecognizerDelegate>
405 @property (weak, nonatomic) WKWebView *_webView; // Cannot be retained, see <rdar://problem/14884666>.
406 - (void)placeholderWillMoveToSuperview:(UIView *)superview;
407 @end
408
409 #pragma mark -
410
411 @interface WKFullScreenPlaceholderView : UIView
412 @property (weak, nonatomic) WKFullScreenWindowController *parent;
413 @end
414
415 @implementation WKFullScreenPlaceholderView
416 - (void)willMoveToSuperview:(UIView *)newSuperview
417 {
418     [super viewWillMoveToSuperview:newSuperview];
419     [self.parent placeholderWillMoveToSuperview:newSuperview];
420 }
421 @end
422
423 #pragma mark -
424
425 @implementation WKFullScreenWindowController {
426     RetainPtr<WKFullScreenPlaceholderView> _webViewPlaceholder;
427
428     FullScreenState _fullScreenState;
429     WKWebViewState _viewState;
430
431     RetainPtr<UIWindow> _window;
432     RetainPtr<UIViewController> _rootViewController;
433
434     RefPtr<WebKit::VoidCallback> _repaintCallback;
435     RetainPtr<UIViewController> _viewControllerForPresentation;
436     RetainPtr<WKFullScreenViewController> _fullscreenViewController;
437     RetainPtr<UISwipeGestureRecognizer> _startDismissGestureRecognizer;
438     RetainPtr<UIPanGestureRecognizer> _interactivePanDismissGestureRecognizer;
439     RetainPtr<UIPinchGestureRecognizer> _interactivePinchDismissGestureRecognizer;
440     RetainPtr<WKFullScreenInteractiveTransition> _interactiveDismissTransitionCoordinator;
441
442     CGRect _initialFrame;
443     CGRect _finalFrame;
444
445     RetainPtr<NSString> _EVOrganizationName;
446     BOOL _EVOrganizationNameIsValid;
447     BOOL _inInteractiveDismiss;
448
449     RetainPtr<id> _notificationListener;
450 }
451
452 #pragma mark -
453 #pragma mark Initialization
454 - (id)initWithWebView:(WKWebView *)webView
455 {
456     if (!(self = [super init]))
457         return nil;
458
459     self._webView = webView;
460
461     return self;
462 }
463
464 - (void)dealloc
465 {
466     [NSObject cancelPreviousPerformRequestsWithTarget:self];
467     [[NSNotificationCenter defaultCenter] removeObserver:self];
468
469     [super dealloc];
470 }
471
472 #pragma mark -
473 #pragma mark Accessors
474
475 - (BOOL)isFullScreen
476 {
477     return _fullScreenState == WaitingToEnterFullScreen
478         || _fullScreenState == EnteringFullScreen
479         || _fullScreenState == InFullScreen;
480 }
481
482 - (UIView *)webViewPlaceholder
483 {
484     return _webViewPlaceholder.get();
485 }
486
487 #pragma mark -
488 #pragma mark External Interface
489
490 - (void)enterFullScreen
491 {
492     if ([self isFullScreen])
493         return;
494
495     RetainPtr<WKWebView> webView = self._webView;
496     auto* page = [webView _page];
497     auto* manager = self._manager;
498     if (!page || !manager)
499         return;
500
501     [self _invalidateEVOrganizationName];
502
503     _fullScreenState = WaitingToEnterFullScreen;
504
505     _window = adoptNS([[UIWindow alloc] init]);
506     [_window setBackgroundColor:[UIColor clearColor]];
507     [_window setWindowLevel:UIWindowLevelNormal - 1];
508     [_window setHidden:NO];
509
510     _rootViewController = [[UIViewController alloc] init];
511     _rootViewController.get().view = [[[UIView alloc] initWithFrame:_window.get().bounds] autorelease];
512     _rootViewController.get().view.backgroundColor = [UIColor clearColor];
513     _rootViewController.get().view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
514     [_rootViewController setModalPresentationStyle:UIModalPresentationCustom];
515     [_rootViewController setTransitioningDelegate:self];
516
517     _window.get().rootViewController = _rootViewController.get();
518
519     _fullscreenViewController = adoptNS([[WKFullScreenViewController alloc] initWithWebView:webView.get()]);
520     [_fullscreenViewController setModalPresentationStyle:UIModalPresentationCustom];
521     [_fullscreenViewController setTransitioningDelegate:self];
522     [_fullscreenViewController setModalPresentationCapturesStatusBarAppearance:YES];
523     [_fullscreenViewController setTarget:self];
524     [_fullscreenViewController setAction:@selector(requestExitFullScreen)];
525     _fullscreenViewController.get().view.frame = _rootViewController.get().view.bounds;
526     [self _updateLocationInfo];
527
528     _startDismissGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(_startToDismissFullscreenChanged:)]);
529     [_startDismissGestureRecognizer setDelegate:self];
530     [_startDismissGestureRecognizer setCancelsTouchesInView:YES];
531     [_startDismissGestureRecognizer setNumberOfTouchesRequired:1];
532     [_startDismissGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
533     [_fullscreenViewController.get().view addGestureRecognizer:_startDismissGestureRecognizer.get()];
534
535     _interactivePanDismissGestureRecognizer = adoptNS([[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_interactiveDismissChanged:)]);
536     [_interactivePanDismissGestureRecognizer setDelegate:self];
537     [_interactivePanDismissGestureRecognizer setCancelsTouchesInView:NO];
538     [_fullscreenViewController.get().view addGestureRecognizer:_interactivePanDismissGestureRecognizer.get()];
539
540     _interactivePinchDismissGestureRecognizer = adoptNS([[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(_interactivePinchDismissChanged:)]);
541     [_interactivePinchDismissGestureRecognizer setDelegate:self];
542     [_interactivePinchDismissGestureRecognizer setCancelsTouchesInView:NO];
543     [_fullscreenViewController.get().view addGestureRecognizer:_interactivePinchDismissGestureRecognizer.get()];
544
545     manager->saveScrollPosition();
546
547     page->setSuppressVisibilityUpdates(true);
548
549     _viewState.store(webView.get());
550
551     _webViewPlaceholder = adoptNS([[WKFullScreenPlaceholderView alloc] init]);
552     [_webViewPlaceholder setParent:self];
553     [[_webViewPlaceholder layer] setName:@"Fullscreen Placeholder View"];
554
555     WKSnapshotConfiguration* config = nil;
556     [webView takeSnapshotWithConfiguration:config completionHandler:^(UIImage * snapshotImage, NSError * error) {
557         RetainPtr<WKWebView> webView = self._webView;
558         auto* page = [self._webView _page];
559         if (!page)
560             return;
561
562         [CATransaction begin];
563         [CATransaction setDisableActions:YES];
564         
565         [[_webViewPlaceholder layer] setContents:(id)[snapshotImage CGImage]];
566         replaceViewWithView(webView.get(), _webViewPlaceholder.get());
567
568         WKWebViewState().applyTo(webView.get());
569         
570         [webView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
571         [webView setFrame:[_window bounds]];
572         [webView _overrideLayoutParametersWithMinimumLayoutSize:[_window bounds].size maximumUnobscuredSizeOverride:[_window bounds].size];
573         [_window insertSubview:webView.get() atIndex:0];
574         [webView setNeedsLayout];
575         [webView layoutIfNeeded];
576         
577         if (auto* manager = self._manager)
578             manager->setAnimatingFullScreen(true);
579
580         ViewportArguments arguments { ViewportArguments::CSSDeviceAdaptation };
581         arguments.zoom = 1;
582         arguments.minZoom = 1;
583         arguments.maxZoom = 1;
584         arguments.userZoom = 1;
585         page->setOverrideViewportArguments(arguments);
586
587         _repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
588             _repaintCallback = nullptr;
589             if (auto* manager = [protectedSelf _manager]) {
590                 manager->willEnterFullScreen();
591                 return;
592             }
593
594             ASSERT_NOT_REACHED();
595             [self _exitFullscreenImmediately];
596         });
597         page->forceRepaint(_repaintCallback.copyRef());
598
599         [CATransaction commit];
600     }];
601 }
602
603 - (void)beganEnterFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
604 {
605     if (_fullScreenState != WaitingToEnterFullScreen)
606         return;
607     _fullScreenState = EnteringFullScreen;
608
609     _initialFrame = initialFrame;
610     _finalFrame = finalFrame;
611     
612     _initialFrame.size = sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
613     _finalFrame.size = sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
614     _initialFrame = safeInlineRect(_initialFrame, [_rootViewController view].frame.size);
615
616     [CATransaction begin];
617     [CATransaction setDisableActions:YES];
618
619     RetainPtr<WKWebView> webView = self._webView;
620     [webView removeFromSuperview];
621
622     [_window setWindowLevel:UIWindowLevelNormal];
623     [_window makeKeyAndVisible];
624     [_fullscreenViewController setPrefersStatusBarHidden:NO];
625     [_fullscreenViewController showUI];
626
627     [CATransaction commit];
628
629     [_rootViewController presentViewController:_fullscreenViewController.get() animated:YES completion:^{
630         _fullScreenState = InFullScreen;
631
632         auto* page = [self._webView _page];
633         auto* manager = self._manager;
634         if (page && manager) {
635             [self._webView becomeFirstResponder];
636             manager->didEnterFullScreen();
637             manager->setAnimatingFullScreen(false);
638             page->setSuppressVisibilityUpdates(false);
639             return;
640         }
641
642         ASSERT_NOT_REACHED();
643         [self _exitFullscreenImmediately];
644     }];
645 }
646
647 - (void)requestExitFullScreen
648 {
649     if (auto* manager = self._manager) {
650         manager->requestExitFullScreen();
651         return;
652     }
653
654     ASSERT_NOT_REACHED();
655     [self _exitFullscreenImmediately];
656 }
657
658 - (void)exitFullScreen
659 {
660     if (!self.isFullScreen)
661         return;
662     _fullScreenState = WaitingToExitFullScreen;
663
664     if (auto* manager = self._manager) {
665         manager->setAnimatingFullScreen(true);
666         manager->willExitFullScreen();
667         return;
668     }
669
670     ASSERT_NOT_REACHED();
671     [self _exitFullscreenImmediately];
672 }
673
674 - (void)beganExitFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
675 {
676     if (_fullScreenState != WaitingToExitFullScreen)
677         return;
678     _fullScreenState = ExitingFullScreen;
679
680     _initialFrame = initialFrame;
681     _finalFrame = finalFrame;
682     
683     _initialFrame.size = sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
684     _finalFrame.size = sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
685     _finalFrame = safeInlineRect(_finalFrame, [_rootViewController view].frame.size);
686
687     if (auto* page = [self._webView _page])
688         page->setSuppressVisibilityUpdates(true);
689
690     [_fullscreenViewController setPrefersStatusBarHidden:NO];
691
692     if (_interactiveDismissTransitionCoordinator) {
693         [_interactiveDismissTransitionCoordinator finishInteractiveTransition];
694         _interactiveDismissTransitionCoordinator = nil;
695         return;
696     }
697
698     [self _dismissFullscreenViewController];
699 }
700
701 - (void)_completedExitFullScreen
702 {
703     if (_fullScreenState != ExitingFullScreen)
704         return;
705     _fullScreenState = NotInFullScreen;
706
707     [CATransaction begin];
708     [CATransaction setDisableActions:YES];
709
710     RetainPtr<WKWebView> webView = self._webView;
711     [[_webViewPlaceholder superview] insertSubview:webView.get() belowSubview:_webViewPlaceholder.get()];
712     [webView setFrame:[_webViewPlaceholder frame]];
713     [webView setAutoresizingMask:[_webViewPlaceholder autoresizingMask]];
714
715     [[webView window] makeKeyAndVisible];
716     [webView becomeFirstResponder];
717
718     _viewState.applyTo(webView.get());
719     if (auto* page = [webView _page])
720         page->setOverrideViewportArguments(std::nullopt);
721
722     [webView setNeedsLayout];
723     [webView layoutIfNeeded];
724
725     [CATransaction commit];
726
727     [_window setHidden:YES];
728     _window = nil;
729
730     if (auto* manager = self._manager) {
731         manager->setAnimatingFullScreen(false);
732         manager->didExitFullScreen();
733     }
734
735     if (_repaintCallback) {
736         _repaintCallback->invalidate(WebKit::CallbackBase::Error::OwnerWasInvalidated);
737         ASSERT(!_repaintCallback);
738     }
739
740     _repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
741         _repaintCallback = nullptr;
742         _webViewPlaceholder.get().parent = nil;
743         [_webViewPlaceholder removeFromSuperview];
744
745         if (auto* page = [self._webView _page])
746             page->setSuppressVisibilityUpdates(false);
747     });
748
749     if (auto* page = [self._webView _page])
750         page->forceRepaint(_repaintCallback.copyRef());
751     else
752         _repaintCallback->performCallback();
753
754     [_fullscreenViewController setPrefersStatusBarHidden:YES];
755 }
756
757 - (void)close
758 {
759     [self _exitFullscreenImmediately];
760     self._webView = nil;
761 }
762
763 - (void)webViewDidRemoveFromSuperviewWhileInFullscreen
764 {
765     if (_fullScreenState == InFullScreen && self._webView.window != _window.get())
766         [self _exitFullscreenImmediately];
767 }
768
769 - (void)videoControlsManagerDidChange
770 {
771     if (_fullscreenViewController)
772         [_fullscreenViewController videoControlsManagerDidChange];
773 }
774
775 - (void)placeholderWillMoveToSuperview:(UIView *)superview
776 {
777     if (superview)
778         return;
779
780     dispatch_async(dispatch_get_main_queue(), ^{
781         if ([_webViewPlaceholder superview] == nil && [_webViewPlaceholder parent] == self)
782             [self close];
783     });
784 }
785
786 #pragma mark -
787 #pragma mark UIGestureRecognizerDelegate
788
789 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
790 {
791     return YES;
792 }
793
794 #pragma mark -
795 #pragma mark UIViewControllerTransitioningDelegate
796
797 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
798 {
799     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
800     [animationController setViewController:presented];
801     [animationController setInitialFrame:_initialFrame];
802     [animationController setFinalFrame:_finalFrame];
803     [animationController setAnimatingIn:YES];
804     return animationController.autorelease();
805 }
806
807 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
808 {
809     CGRect initialFrame = _initialFrame;
810     CGRect finalFrame = _finalFrame;
811
812     // Because we're not calling "requestExitFullscreen()" at the beginning of an interactive animation,
813     // the _initialFrame and _finalFrame values are left over from when we entered fullscreen.
814     if (_inInteractiveDismiss)
815         std::swap(initialFrame, finalFrame);
816
817     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
818     [animationController setViewController:dismissed];
819     [animationController setInitialFrame:initialFrame];
820     [animationController setFinalFrame:finalFrame];
821     [animationController setAnimatingIn:NO];
822     return animationController.autorelease();
823 }
824
825 - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
826 {
827     if (!_inInteractiveDismiss)
828         return nil;
829
830     if (![animator isKindOfClass:[WKFullscreenAnimationController class]])
831         return nil;
832
833     if (!_interactiveDismissTransitionCoordinator) {
834         CGPoint anchor = CGPointZero;
835         if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
836             anchor = [_interactivePanDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
837         else if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
838             anchor = [_interactivePinchDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
839         _interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:anchor]);
840     }
841
842     return _interactiveDismissTransitionCoordinator.get();
843 }
844
845 #pragma mark -
846 #pragma mark Internal Interface
847
848 - (void)_exitFullscreenImmediately
849 {
850     if (![self isFullScreen])
851         return;
852
853     auto* manager = self._manager;
854     if (manager)
855         manager->requestExitFullScreen();
856     [self exitFullScreen];
857     _fullScreenState = ExitingFullScreen;
858     [self _completedExitFullScreen];
859     RetainPtr<WKWebView> webView = self._webView;
860     _webViewPlaceholder.get().parent = nil;
861     replaceViewWithView(_webViewPlaceholder.get(), webView.get());
862     if (auto* page = [webView _page])
863         page->setSuppressVisibilityUpdates(false);
864     if (manager) {
865         manager->didExitFullScreen();
866         manager->setAnimatingFullScreen(false);
867     }
868     _webViewPlaceholder = nil;
869 }
870
871 - (void)_invalidateEVOrganizationName
872 {
873     _EVOrganizationName = nil;
874     _EVOrganizationNameIsValid = NO;
875 }
876
877 - (BOOL)_isSecure
878 {
879     return self._webView.hasOnlySecureContent;
880 }
881
882 - (SecTrustRef)_serverTrust
883 {
884     return self._webView.serverTrust;
885 }
886
887 - (NSString *)_EVOrganizationName
888 {
889     if (!self._isSecure)
890         return nil;
891
892     if (_EVOrganizationNameIsValid)
893         return _EVOrganizationName.get();
894
895     ASSERT(!_EVOrganizationName.get());
896     _EVOrganizationNameIsValid = YES;
897
898     SecTrustRef trust = [self _serverTrust];
899     if (!trust)
900         return nil;
901
902     NSDictionary *infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
903     // If SecTrustCopyInfo returned NULL then it's likely that the SecTrustRef has not been evaluated
904     // and the only way to get the information we need is to call SecTrustEvaluate ourselves.
905     if (!infoDictionary) {
906         SecTrustResultType result = kSecTrustResultProceed;
907         OSStatus err = SecTrustEvaluate(trust, &result);
908         if (err == noErr)
909             infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
910         if (!infoDictionary)
911             return nil;
912     }
913
914     // Make sure that the EV certificate is valid against our certificate chain.
915     id hasEV = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
916     if (![hasEV isKindOfClass:[NSValue class]] || ![hasEV boolValue])
917         return nil;
918
919     // Make sure that we could contact revocation server and it is still valid.
920     id isNotRevoked = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoRevocationKey];
921     if (![isNotRevoked isKindOfClass:[NSValue class]] || ![isNotRevoked boolValue])
922         return nil;
923
924     _EVOrganizationName = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoCompanyNameKey];
925     return _EVOrganizationName.get();
926 }
927
928 - (void)_updateLocationInfo
929 {
930     NSURL* url = self._webView._committedURL;
931
932     NSString *EVOrganizationName = [self _EVOrganizationName];
933     BOOL showsEVOrganizationName = [EVOrganizationName length] > 0;
934
935     NSString *domain = nil;
936
937 #if HAVE(URL_FORMATTING)
938     domain = [url _lp_simplifiedDisplayString];
939 #else
940     if (LinkPresentationLibrary())
941         domain = [url _lp_simplifiedDisplayString];
942     else
943         domain = userVisibleString(url);
944 #endif
945
946     NSString *text = nil;
947     if ([[url scheme] caseInsensitiveCompare:@"data"] == NSOrderedSame)
948         text = @"data:";
949     else if (showsEVOrganizationName)
950         text = EVOrganizationName;
951     else
952         text = domain;
953
954     [_fullscreenViewController setLocation:text];
955 }
956
957 - (WebFullScreenManagerProxy*)_manager
958 {
959     if (auto* page = [self._webView _page])
960         return page->fullScreenManager();
961     return nullptr;
962 }
963
964 - (void)_startToDismissFullscreenChanged:(id)sender
965 {
966     if (_inInteractiveDismiss)
967         return;
968     _inInteractiveDismiss = true;
969     [self _dismissFullscreenViewController];
970 }
971
972 - (void)_dismissFullscreenViewController
973 {
974     [_fullscreenViewController setAnimating:YES];
975     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
976         if (![self._webView _page])
977             return;
978
979
980         if (_interactiveDismissTransitionCoordinator.get().animator.context.transitionWasCancelled) {
981             [_fullscreenViewController setAnimating:NO];
982             return;
983         }
984
985         _interactiveDismissTransitionCoordinator = nil;
986
987         [self _completedExitFullScreen];
988     }];
989 }
990
991 - (void)_interactiveDismissChanged:(id)sender
992 {
993     if (!_inInteractiveDismiss)
994         return;
995
996     auto pinchState = [_interactivePinchDismissGestureRecognizer state];
997     if (pinchState > UIGestureRecognizerStatePossible && pinchState <= UIGestureRecognizerStateEnded)
998         return;
999
1000     CGPoint translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
1001     CGPoint velocity = [_interactivePanDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
1002     CGFloat progress = translation.y / (_fullscreenViewController.get().view.bounds.size.height / 2);
1003     progress = std::min(1., std::max(0., progress));
1004
1005     if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
1006         _inInteractiveDismiss = false;
1007
1008         if (progress > 0.25 || (progress > 0 && velocity.y > 5))
1009             [self requestExitFullScreen];
1010         else
1011             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
1012         return;
1013     }
1014
1015     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withTranslation:CGSizeMake(translation.x, translation.y)];
1016 }
1017
1018 - (void)_interactivePinchDismissChanged:(id)sender
1019 {
1020     if (!_inInteractiveDismiss && _interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan) {
1021         [self _startToDismissFullscreenChanged:sender];
1022         return;
1023     }
1024
1025     CGFloat scale = [_interactivePinchDismissGestureRecognizer scale];
1026     CGFloat velocity = [_interactivePinchDismissGestureRecognizer velocity];
1027     CGFloat progress = std::min(1., std::max(0., 1 - scale));
1028
1029     CGPoint translation = CGPointZero;
1030     auto panState = [_interactivePanDismissGestureRecognizer state];
1031     if (panState > UIGestureRecognizerStatePossible && panState <= UIGestureRecognizerStateEnded)
1032         translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
1033
1034     if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
1035         _inInteractiveDismiss = false;
1036         if ((progress > 0.05 && velocity < 0.) || velocity < -2.5)
1037             [self requestExitFullScreen];
1038         else
1039             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
1040         return;
1041     }
1042
1043     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withScale:scale andTranslation:CGSizeMake(translation.x, translation.y)];
1044 }
1045
1046 @end
1047
1048 #endif // PLATFORM(IOS) && ENABLE(FULLSCREEN_API)