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