Move URL from WebCore to WTF
[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_FAMILY) && 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 <pal/spi/cf/CFNetworkSPI.h>
49 #import <pal/spi/cocoa/NSStringSPI.h>
50 #import <pal/spi/cocoa/QuartzCoreSPI.h>
51 #import <pal/spi/cocoa/URLFormattingSPI.h>
52 #import <wtf/SoftLinking.h>
53 #import <wtf/cocoa/NSURLExtras.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     _fullscreenViewController = nil;
756 }
757
758 - (void)close
759 {
760     [self _exitFullscreenImmediately];
761     self._webView = nil;
762 }
763
764 - (void)webViewDidRemoveFromSuperviewWhileInFullscreen
765 {
766     if (_fullScreenState == InFullScreen && self._webView.window != _window.get())
767         [self _exitFullscreenImmediately];
768 }
769
770 - (void)videoControlsManagerDidChange
771 {
772     if (_fullscreenViewController)
773         [_fullscreenViewController videoControlsManagerDidChange];
774 }
775
776 - (void)placeholderWillMoveToSuperview:(UIView *)superview
777 {
778     if (superview)
779         return;
780
781     dispatch_async(dispatch_get_main_queue(), ^{
782         if ([_webViewPlaceholder superview] == nil && [_webViewPlaceholder parent] == self)
783             [self close];
784     });
785 }
786
787 #pragma mark -
788 #pragma mark UIGestureRecognizerDelegate
789
790 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
791 {
792     return YES;
793 }
794
795 #pragma mark -
796 #pragma mark UIViewControllerTransitioningDelegate
797
798 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
799 {
800     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
801     [animationController setViewController:presented];
802     [animationController setInitialFrame:_initialFrame];
803     [animationController setFinalFrame:_finalFrame];
804     [animationController setAnimatingIn:YES];
805     return animationController.autorelease();
806 }
807
808 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
809 {
810     CGRect initialFrame = _initialFrame;
811     CGRect finalFrame = _finalFrame;
812
813     // Because we're not calling "requestExitFullscreen()" at the beginning of an interactive animation,
814     // the _initialFrame and _finalFrame values are left over from when we entered fullscreen.
815     if (_inInteractiveDismiss)
816         std::swap(initialFrame, finalFrame);
817
818     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
819     [animationController setViewController:dismissed];
820     [animationController setInitialFrame:initialFrame];
821     [animationController setFinalFrame:finalFrame];
822     [animationController setAnimatingIn:NO];
823     return animationController.autorelease();
824 }
825
826 - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
827 {
828     if (!_inInteractiveDismiss)
829         return nil;
830
831     if (![animator isKindOfClass:[WKFullscreenAnimationController class]])
832         return nil;
833
834     if (!_interactiveDismissTransitionCoordinator) {
835         CGPoint anchor = CGPointZero;
836         if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
837             anchor = [_interactivePanDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
838         else if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
839             anchor = [_interactivePinchDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
840         _interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:anchor]);
841     }
842
843     return _interactiveDismissTransitionCoordinator.get();
844 }
845
846 #pragma mark -
847 #pragma mark Internal Interface
848
849 - (void)_exitFullscreenImmediately
850 {
851     if (![self isFullScreen])
852         return;
853
854     auto* manager = self._manager;
855     if (manager)
856         manager->requestExitFullScreen();
857     [self exitFullScreen];
858     _fullScreenState = ExitingFullScreen;
859     [self _completedExitFullScreen];
860     RetainPtr<WKWebView> webView = self._webView;
861     _webViewPlaceholder.get().parent = nil;
862     replaceViewWithView(_webViewPlaceholder.get(), webView.get());
863     if (auto* page = [webView _page])
864         page->setSuppressVisibilityUpdates(false);
865     if (manager) {
866         manager->didExitFullScreen();
867         manager->setAnimatingFullScreen(false);
868     }
869     _webViewPlaceholder = nil;
870 }
871
872 - (void)_invalidateEVOrganizationName
873 {
874     _EVOrganizationName = nil;
875     _EVOrganizationNameIsValid = NO;
876 }
877
878 - (BOOL)_isSecure
879 {
880     return self._webView.hasOnlySecureContent;
881 }
882
883 - (SecTrustRef)_serverTrust
884 {
885     return self._webView.serverTrust;
886 }
887
888 - (NSString *)_EVOrganizationName
889 {
890     if (!self._isSecure)
891         return nil;
892
893     if (_EVOrganizationNameIsValid)
894         return _EVOrganizationName.get();
895
896     ASSERT(!_EVOrganizationName.get());
897     _EVOrganizationNameIsValid = YES;
898
899     SecTrustRef trust = [self _serverTrust];
900     if (!trust)
901         return nil;
902
903     NSDictionary *infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
904     // If SecTrustCopyInfo returned NULL then it's likely that the SecTrustRef has not been evaluated
905     // and the only way to get the information we need is to call SecTrustEvaluate ourselves.
906     if (!infoDictionary) {
907         SecTrustResultType result = kSecTrustResultProceed;
908
909         // FIXME: This is deprecated <rdar://problem/45894288>.
910 #pragma clang diagnostic push
911 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
912         OSStatus err = SecTrustEvaluate(trust, &result);
913 #pragma clang diagnostic pop
914
915         if (err == noErr)
916             infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
917         if (!infoDictionary)
918             return nil;
919     }
920
921     // Make sure that the EV certificate is valid against our certificate chain.
922     id hasEV = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
923     if (![hasEV isKindOfClass:[NSValue class]] || ![hasEV boolValue])
924         return nil;
925
926     // Make sure that we could contact revocation server and it is still valid.
927     id isNotRevoked = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoRevocationKey];
928     if (![isNotRevoked isKindOfClass:[NSValue class]] || ![isNotRevoked boolValue])
929         return nil;
930
931     _EVOrganizationName = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoCompanyNameKey];
932     return _EVOrganizationName.get();
933 }
934
935 - (void)_updateLocationInfo
936 {
937     NSURL* url = self._webView._committedURL;
938
939     NSString *EVOrganizationName = [self _EVOrganizationName];
940     BOOL showsEVOrganizationName = [EVOrganizationName length] > 0;
941
942     NSString *domain = nil;
943
944 #if HAVE(URL_FORMATTING)
945     domain = [url _lp_simplifiedDisplayString];
946 #else
947     if (LinkPresentationLibrary())
948         domain = [url _lp_simplifiedDisplayString];
949     else
950         domain = WTF::userVisibleString(url);
951 #endif
952
953     NSString *text = nil;
954     if ([[url scheme] caseInsensitiveCompare:@"data"] == NSOrderedSame)
955         text = @"data:";
956     else if (showsEVOrganizationName)
957         text = EVOrganizationName;
958     else
959         text = domain;
960
961     [_fullscreenViewController setLocation:text];
962 }
963
964 - (WebFullScreenManagerProxy*)_manager
965 {
966     if (auto* page = [self._webView _page])
967         return page->fullScreenManager();
968     return nullptr;
969 }
970
971 - (void)_startToDismissFullscreenChanged:(id)sender
972 {
973     if (_inInteractiveDismiss)
974         return;
975     _inInteractiveDismiss = true;
976     [self _dismissFullscreenViewController];
977 }
978
979 - (void)_dismissFullscreenViewController
980 {
981     [_fullscreenViewController setAnimating:YES];
982     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
983         if (![self._webView _page])
984             return;
985
986         if (_interactiveDismissTransitionCoordinator.get().animator.context.transitionWasCancelled)
987             [_fullscreenViewController setAnimating:NO];
988         else
989             [self _completedExitFullScreen];
990         
991         _interactiveDismissTransitionCoordinator = nil;
992     }];
993 }
994
995 - (void)_interactiveDismissChanged:(id)sender
996 {
997     if (!_inInteractiveDismiss)
998         return;
999
1000     auto pinchState = [_interactivePinchDismissGestureRecognizer state];
1001     if (pinchState > UIGestureRecognizerStatePossible && pinchState <= UIGestureRecognizerStateEnded)
1002         return;
1003
1004     CGPoint translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
1005     CGPoint velocity = [_interactivePanDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
1006     CGFloat progress = translation.y / (_fullscreenViewController.get().view.bounds.size.height / 2);
1007     progress = std::min(1., std::max(0., progress));
1008
1009     if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
1010         _inInteractiveDismiss = false;
1011
1012         if (progress > 0.25 || (progress > 0 && velocity.y > 5))
1013             [self requestExitFullScreen];
1014         else
1015             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
1016         return;
1017     }
1018
1019     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withTranslation:CGSizeMake(translation.x, translation.y)];
1020 }
1021
1022 - (void)_interactivePinchDismissChanged:(id)sender
1023 {
1024     if (!_inInteractiveDismiss && _interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan) {
1025         [self _startToDismissFullscreenChanged:sender];
1026         return;
1027     }
1028
1029     CGFloat scale = [_interactivePinchDismissGestureRecognizer scale];
1030     CGFloat velocity = [_interactivePinchDismissGestureRecognizer velocity];
1031     CGFloat progress = std::min(1., std::max(0., 1 - scale));
1032
1033     CGPoint translation = CGPointZero;
1034     auto panState = [_interactivePanDismissGestureRecognizer state];
1035     if (panState > UIGestureRecognizerStatePossible && panState <= UIGestureRecognizerStateEnded)
1036         translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
1037
1038     if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
1039         _inInteractiveDismiss = false;
1040         if ((progress > 0.05 && velocity < 0.) || velocity < -2.5)
1041             [self requestExitFullScreen];
1042         else
1043             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
1044         return;
1045     }
1046
1047     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withScale:scale andTranslation:CGSizeMake(translation.x, translation.y)];
1048 }
1049
1050 @end
1051
1052 #endif // PLATFORM(IOS_FAMILY) && ENABLE(FULLSCREEN_API)