Improve coordination for creating UIWindow instances.
[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 #if USE(APPLE_INTERNAL_SDK)
493 #import <WebKitAdditions/WKFullScreenWindowControllerIOSAdditions.mm>
494 #else
495 static RetainPtr<UIWindow> makeWindowFromView(UIView *)
496 {
497     return adoptNS([[UIWindow alloc] init]);
498 }
499 #endif
500
501 - (void)enterFullScreen
502 {
503     if ([self isFullScreen])
504         return;
505
506     RetainPtr<WKWebView> webView = self._webView;
507     auto* page = [webView _page];
508     auto* manager = self._manager;
509     if (!page || !manager)
510         return;
511
512     [self _invalidateEVOrganizationName];
513
514     _fullScreenState = WebKit::WaitingToEnterFullScreen;
515
516     _window = makeWindowFromView(webView.get());
517     [_window setBackgroundColor:[UIColor clearColor]];
518     [_window setWindowLevel:UIWindowLevelNormal - 1];
519     [_window setHidden:NO];
520
521     _rootViewController = [[UIViewController alloc] init];
522     _rootViewController.get().view = [[[UIView alloc] initWithFrame:_window.get().bounds] autorelease];
523     _rootViewController.get().view.backgroundColor = [UIColor clearColor];
524     _rootViewController.get().view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
525     [_rootViewController setModalPresentationStyle:UIModalPresentationCustom];
526     [_rootViewController setTransitioningDelegate:self];
527
528     _window.get().rootViewController = _rootViewController.get();
529
530     _fullscreenViewController = adoptNS([[WKFullScreenViewController alloc] initWithWebView:webView.get()]);
531     [_fullscreenViewController setModalPresentationStyle:UIModalPresentationCustom];
532     [_fullscreenViewController setTransitioningDelegate:self];
533     [_fullscreenViewController setModalPresentationCapturesStatusBarAppearance:YES];
534     [_fullscreenViewController setTarget:self];
535     [_fullscreenViewController setAction:@selector(requestExitFullScreen)];
536     _fullscreenViewController.get().view.frame = _rootViewController.get().view.bounds;
537     [self _updateLocationInfo];
538
539     _startDismissGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(_startToDismissFullscreenChanged:)]);
540     [_startDismissGestureRecognizer setDelegate:self];
541     [_startDismissGestureRecognizer setCancelsTouchesInView:YES];
542     [_startDismissGestureRecognizer setNumberOfTouchesRequired:1];
543     [_startDismissGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
544     [_fullscreenViewController.get().view addGestureRecognizer:_startDismissGestureRecognizer.get()];
545
546     _interactivePanDismissGestureRecognizer = adoptNS([[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_interactiveDismissChanged:)]);
547     [_interactivePanDismissGestureRecognizer setDelegate:self];
548     [_interactivePanDismissGestureRecognizer setCancelsTouchesInView:NO];
549     [_fullscreenViewController.get().view addGestureRecognizer:_interactivePanDismissGestureRecognizer.get()];
550
551     _interactivePinchDismissGestureRecognizer = adoptNS([[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(_interactivePinchDismissChanged:)]);
552     [_interactivePinchDismissGestureRecognizer setDelegate:self];
553     [_interactivePinchDismissGestureRecognizer setCancelsTouchesInView:NO];
554     [_fullscreenViewController.get().view addGestureRecognizer:_interactivePinchDismissGestureRecognizer.get()];
555
556     manager->saveScrollPosition();
557
558     page->setSuppressVisibilityUpdates(true);
559
560     _viewState.store(webView.get());
561
562     _webViewPlaceholder = adoptNS([[WKFullScreenPlaceholderView alloc] init]);
563     [_webViewPlaceholder setParent:self];
564     [[_webViewPlaceholder layer] setName:@"Fullscreen Placeholder View"];
565
566     WKSnapshotConfiguration* config = nil;
567     [webView takeSnapshotWithConfiguration:config completionHandler:^(UIImage * snapshotImage, NSError * error) {
568         RetainPtr<WKWebView> webView = self._webView;
569         auto* page = [self._webView _page];
570         if (!page)
571             return;
572
573         [CATransaction begin];
574         [CATransaction setDisableActions:YES];
575         
576         [[_webViewPlaceholder layer] setContents:(id)[snapshotImage CGImage]];
577         WebKit::replaceViewWithView(webView.get(), _webViewPlaceholder.get());
578
579         WebKit::WKWebViewState().applyTo(webView.get());
580         
581         [webView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
582         [webView setFrame:[_window bounds]];
583         [webView _overrideLayoutParametersWithMinimumLayoutSize:[_window bounds].size maximumUnobscuredSizeOverride:[_window bounds].size];
584         [_window insertSubview:webView.get() atIndex:0];
585         [webView setNeedsLayout];
586         [webView layoutIfNeeded];
587         
588         if (auto* manager = self._manager)
589             manager->setAnimatingFullScreen(true);
590
591         WebCore::ViewportArguments arguments { WebCore::ViewportArguments::CSSDeviceAdaptation };
592         arguments.zoom = 1;
593         arguments.minZoom = 1;
594         arguments.maxZoom = 1;
595         arguments.userZoom = 1;
596         page->setOverrideViewportArguments(arguments);
597
598         _repaintCallback = WebKit::VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
599             _repaintCallback = nullptr;
600             if (auto* manager = [protectedSelf _manager]) {
601                 manager->willEnterFullScreen();
602                 return;
603             }
604
605             ASSERT_NOT_REACHED();
606             [self _exitFullscreenImmediately];
607         });
608         page->forceRepaint(_repaintCallback.copyRef());
609
610         [CATransaction commit];
611     }];
612 }
613
614 - (void)beganEnterFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
615 {
616     if (_fullScreenState != WebKit::WaitingToEnterFullScreen)
617         return;
618     _fullScreenState = WebKit::EnteringFullScreen;
619
620     _initialFrame = initialFrame;
621     _finalFrame = finalFrame;
622     
623     _initialFrame.size = WebKit::sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
624     _finalFrame.size = WebKit::sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
625     _initialFrame = WebKit::safeInlineRect(_initialFrame, [_rootViewController view].frame.size);
626
627     [CATransaction begin];
628     [CATransaction setDisableActions:YES];
629
630     RetainPtr<WKWebView> webView = self._webView;
631     [webView removeFromSuperview];
632
633     [_window setWindowLevel:UIWindowLevelNormal];
634     [_window makeKeyAndVisible];
635     [_fullscreenViewController setPrefersStatusBarHidden:NO];
636     [_fullscreenViewController showUI];
637
638     [CATransaction commit];
639
640     [_rootViewController presentViewController:_fullscreenViewController.get() animated:YES completion:^{
641         _fullScreenState = WebKit::InFullScreen;
642
643         auto* page = [self._webView _page];
644         auto* manager = self._manager;
645         if (page && manager) {
646             [self._webView becomeFirstResponder];
647             manager->didEnterFullScreen();
648             manager->setAnimatingFullScreen(false);
649             page->setSuppressVisibilityUpdates(false);
650             return;
651         }
652
653         ASSERT_NOT_REACHED();
654         [self _exitFullscreenImmediately];
655     }];
656 }
657
658 - (void)requestExitFullScreen
659 {
660     if (auto* manager = self._manager) {
661         manager->requestExitFullScreen();
662         return;
663     }
664
665     ASSERT_NOT_REACHED();
666     [self _exitFullscreenImmediately];
667 }
668
669 - (void)exitFullScreen
670 {
671     if (!self.isFullScreen)
672         return;
673     _fullScreenState = WebKit::WaitingToExitFullScreen;
674
675     if (auto* manager = self._manager) {
676         manager->setAnimatingFullScreen(true);
677         manager->willExitFullScreen();
678         return;
679     }
680
681     ASSERT_NOT_REACHED();
682     [self _exitFullscreenImmediately];
683 }
684
685 - (void)beganExitFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
686 {
687     if (_fullScreenState != WebKit::WaitingToExitFullScreen)
688         return;
689     _fullScreenState = WebKit::ExitingFullScreen;
690
691     _initialFrame = initialFrame;
692     _finalFrame = finalFrame;
693     
694     _initialFrame.size = WebKit::sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
695     _finalFrame.size = WebKit::sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
696     _finalFrame = WebKit::safeInlineRect(_finalFrame, [_rootViewController view].frame.size);
697
698     if (auto* page = [self._webView _page])
699         page->setSuppressVisibilityUpdates(true);
700
701     [_fullscreenViewController setPrefersStatusBarHidden:NO];
702
703     if (_interactiveDismissTransitionCoordinator) {
704         [_interactiveDismissTransitionCoordinator finishInteractiveTransition];
705         _interactiveDismissTransitionCoordinator = nil;
706         return;
707     }
708
709     [self _dismissFullscreenViewController];
710 }
711
712 - (void)_completedExitFullScreen
713 {
714     if (_fullScreenState != WebKit::ExitingFullScreen)
715         return;
716     _fullScreenState = WebKit::NotInFullScreen;
717
718     [CATransaction begin];
719     [CATransaction setDisableActions:YES];
720
721     RetainPtr<WKWebView> webView = self._webView;
722     [[_webViewPlaceholder superview] insertSubview:webView.get() belowSubview:_webViewPlaceholder.get()];
723     [webView setFrame:[_webViewPlaceholder frame]];
724     [webView setAutoresizingMask:[_webViewPlaceholder autoresizingMask]];
725
726     [[webView window] makeKeyAndVisible];
727     [webView becomeFirstResponder];
728
729     _viewState.applyTo(webView.get());
730     if (auto* page = [webView _page])
731         page->setOverrideViewportArguments(WTF::nullopt);
732
733     [webView setNeedsLayout];
734     [webView layoutIfNeeded];
735
736     [CATransaction commit];
737
738     [_window setHidden:YES];
739     _window = nil;
740
741     if (auto* manager = self._manager) {
742         manager->setAnimatingFullScreen(false);
743         manager->didExitFullScreen();
744     }
745
746     if (_repaintCallback) {
747         _repaintCallback->invalidate(WebKit::CallbackBase::Error::OwnerWasInvalidated);
748         ASSERT(!_repaintCallback);
749     }
750
751     _repaintCallback = WebKit::VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
752         _repaintCallback = nullptr;
753         _webViewPlaceholder.get().parent = nil;
754         [_webViewPlaceholder removeFromSuperview];
755
756         if (auto* page = [self._webView _page])
757             page->setSuppressVisibilityUpdates(false);
758     });
759
760     if (auto* page = [self._webView _page])
761         page->forceRepaint(_repaintCallback.copyRef());
762     else
763         _repaintCallback->performCallback();
764
765     [_fullscreenViewController setPrefersStatusBarHidden:YES];
766     _fullscreenViewController = nil;
767 }
768
769 - (void)close
770 {
771     [self _exitFullscreenImmediately];
772     self._webView = nil;
773 }
774
775 - (void)webViewDidRemoveFromSuperviewWhileInFullscreen
776 {
777     if (_fullScreenState == WebKit::InFullScreen && self._webView.window != _window.get())
778         [self _exitFullscreenImmediately];
779 }
780
781 - (void)videoControlsManagerDidChange
782 {
783     if (_fullscreenViewController)
784         [_fullscreenViewController videoControlsManagerDidChange];
785 }
786
787 - (void)placeholderWillMoveToSuperview:(UIView *)superview
788 {
789     if (superview)
790         return;
791
792     dispatch_async(dispatch_get_main_queue(), ^{
793         if ([_webViewPlaceholder superview] == nil && [_webViewPlaceholder parent] == self)
794             [self close];
795     });
796 }
797
798 #pragma mark -
799 #pragma mark UIGestureRecognizerDelegate
800
801 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
802 {
803     return YES;
804 }
805
806 #pragma mark -
807 #pragma mark UIViewControllerTransitioningDelegate
808
809 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
810 {
811     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
812     [animationController setViewController:presented];
813     [animationController setInitialFrame:_initialFrame];
814     [animationController setFinalFrame:_finalFrame];
815     [animationController setAnimatingIn:YES];
816     return animationController.autorelease();
817 }
818
819 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
820 {
821     CGRect initialFrame = _initialFrame;
822     CGRect finalFrame = _finalFrame;
823
824     // Because we're not calling "requestExitFullscreen()" at the beginning of an interactive animation,
825     // the _initialFrame and _finalFrame values are left over from when we entered fullscreen.
826     if (_inInteractiveDismiss)
827         std::swap(initialFrame, finalFrame);
828
829     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
830     [animationController setViewController:dismissed];
831     [animationController setInitialFrame:initialFrame];
832     [animationController setFinalFrame:finalFrame];
833     [animationController setAnimatingIn:NO];
834     return animationController.autorelease();
835 }
836
837 - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
838 {
839     if (!_inInteractiveDismiss)
840         return nil;
841
842     if (![animator isKindOfClass:[WKFullscreenAnimationController class]])
843         return nil;
844
845     if (!_interactiveDismissTransitionCoordinator) {
846         CGPoint anchor = CGPointZero;
847         if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
848             anchor = [_interactivePanDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
849         else if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
850             anchor = [_interactivePinchDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
851         _interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:anchor]);
852     }
853
854     return _interactiveDismissTransitionCoordinator.get();
855 }
856
857 #pragma mark -
858 #pragma mark Internal Interface
859
860 - (void)_exitFullscreenImmediately
861 {
862     if (![self isFullScreen])
863         return;
864
865     auto* manager = self._manager;
866     if (manager)
867         manager->requestExitFullScreen();
868     [self exitFullScreen];
869     _fullScreenState = WebKit::ExitingFullScreen;
870     [self _completedExitFullScreen];
871     RetainPtr<WKWebView> webView = self._webView;
872     _webViewPlaceholder.get().parent = nil;
873     WebKit::replaceViewWithView(_webViewPlaceholder.get(), webView.get());
874     if (auto* page = [webView _page])
875         page->setSuppressVisibilityUpdates(false);
876     if (manager) {
877         manager->didExitFullScreen();
878         manager->setAnimatingFullScreen(false);
879     }
880     _webViewPlaceholder = nil;
881 }
882
883 - (void)_invalidateEVOrganizationName
884 {
885     _EVOrganizationName = nil;
886     _EVOrganizationNameIsValid = NO;
887 }
888
889 - (BOOL)_isSecure
890 {
891     return self._webView.hasOnlySecureContent;
892 }
893
894 - (SecTrustRef)_serverTrust
895 {
896     return self._webView.serverTrust;
897 }
898
899 - (NSString *)_EVOrganizationName
900 {
901     if (!self._isSecure)
902         return nil;
903
904     if (_EVOrganizationNameIsValid)
905         return _EVOrganizationName.get();
906
907     ASSERT(!_EVOrganizationName.get());
908     _EVOrganizationNameIsValid = YES;
909
910     SecTrustRef trust = [self _serverTrust];
911     if (!trust)
912         return nil;
913
914     NSDictionary *infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
915     // If SecTrustCopyInfo returned NULL then it's likely that the SecTrustRef has not been evaluated
916     // and the only way to get the information we need is to call SecTrustEvaluate ourselves.
917     if (!infoDictionary) {
918         SecTrustResultType result = kSecTrustResultProceed;
919
920         // FIXME: This is deprecated <rdar://problem/45894288>.
921         ALLOW_DEPRECATED_DECLARATIONS_BEGIN
922         OSStatus err = SecTrustEvaluate(trust, &result);
923         ALLOW_DEPRECATED_DECLARATIONS_END
924
925         if (err == noErr)
926             infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
927         if (!infoDictionary)
928             return nil;
929     }
930
931     // Make sure that the EV certificate is valid against our certificate chain.
932     id hasEV = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
933     if (![hasEV isKindOfClass:[NSValue class]] || ![hasEV boolValue])
934         return nil;
935
936     // Make sure that we could contact revocation server and it is still valid.
937     id isNotRevoked = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoRevocationKey];
938     if (![isNotRevoked isKindOfClass:[NSValue class]] || ![isNotRevoked boolValue])
939         return nil;
940
941     _EVOrganizationName = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoCompanyNameKey];
942     return _EVOrganizationName.get();
943 }
944
945 - (void)_updateLocationInfo
946 {
947     NSURL* url = self._webView._committedURL;
948
949     NSString *EVOrganizationName = [self _EVOrganizationName];
950     BOOL showsEVOrganizationName = [EVOrganizationName length] > 0;
951
952     NSString *domain = nil;
953
954 #if HAVE(URL_FORMATTING)
955     domain = [url _lp_simplifiedDisplayString];
956 #else
957     if (LinkPresentationLibrary())
958         domain = [url _lp_simplifiedDisplayString];
959     else
960         domain = WTF::userVisibleString(url);
961 #endif
962
963     NSString *text = nil;
964     if ([[url scheme] caseInsensitiveCompare:@"data"] == NSOrderedSame)
965         text = @"data:";
966     else if (showsEVOrganizationName)
967         text = EVOrganizationName;
968     else
969         text = domain;
970
971     [_fullscreenViewController setLocation:text];
972 }
973
974 - (WebKit::WebFullScreenManagerProxy*)_manager
975 {
976     if (auto* page = [self._webView _page])
977         return page->fullScreenManager();
978     return nullptr;
979 }
980
981 - (void)_startToDismissFullscreenChanged:(id)sender
982 {
983     if (_inInteractiveDismiss)
984         return;
985     _inInteractiveDismiss = true;
986     [self _dismissFullscreenViewController];
987 }
988
989 - (void)_dismissFullscreenViewController
990 {
991     [_fullscreenViewController setAnimating:YES];
992     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
993         if (![self._webView _page])
994             return;
995
996         if (_interactiveDismissTransitionCoordinator.get().animator.context.transitionWasCancelled)
997             [_fullscreenViewController setAnimating:NO];
998         else
999             [self _completedExitFullScreen];
1000         
1001         _interactiveDismissTransitionCoordinator = nil;
1002     }];
1003 }
1004
1005 - (void)_interactiveDismissChanged:(id)sender
1006 {
1007     if (!_inInteractiveDismiss)
1008         return;
1009
1010     auto pinchState = [_interactivePinchDismissGestureRecognizer state];
1011     if (pinchState > UIGestureRecognizerStatePossible && pinchState <= UIGestureRecognizerStateEnded)
1012         return;
1013
1014     CGPoint translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
1015     CGPoint velocity = [_interactivePanDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
1016     CGFloat progress = translation.y / (_fullscreenViewController.get().view.bounds.size.height / 2);
1017     progress = std::min(1., std::max(0., progress));
1018
1019     if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
1020         _inInteractiveDismiss = false;
1021
1022         if (progress > 0.25 || (progress > 0 && velocity.y > 5))
1023             [self requestExitFullScreen];
1024         else
1025             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
1026         return;
1027     }
1028
1029     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withTranslation:CGSizeMake(translation.x, translation.y)];
1030 }
1031
1032 - (void)_interactivePinchDismissChanged:(id)sender
1033 {
1034     if (!_inInteractiveDismiss && _interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan) {
1035         [self _startToDismissFullscreenChanged:sender];
1036         return;
1037     }
1038
1039     CGFloat scale = [_interactivePinchDismissGestureRecognizer scale];
1040     CGFloat velocity = [_interactivePinchDismissGestureRecognizer velocity];
1041     CGFloat progress = std::min(1., std::max(0., 1 - scale));
1042
1043     CGPoint translation = CGPointZero;
1044     auto panState = [_interactivePanDismissGestureRecognizer state];
1045     if (panState > UIGestureRecognizerStatePossible && panState <= UIGestureRecognizerStateEnded)
1046         translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
1047
1048     if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
1049         _inInteractiveDismiss = false;
1050         if ((progress > 0.05 && velocity < 0.) || velocity < -2.5)
1051             [self requestExitFullScreen];
1052         else
1053             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
1054         return;
1055     }
1056
1057     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withScale:scale andTranslation:CGSizeMake(translation.x, translation.y)];
1058 }
1059
1060 @end
1061
1062
1063 #endif // PLATFORM(IOS_FAMILY) && ENABLE(FULLSCREEN_API)