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