a7a8bb7f8f462104aeb85639fa4447c36ba940fb
[WebKit-https.git] / Source / WebKit / UIProcess / ios / fullscreen / WKFullScreenWindowControllerIOS.mm
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27
28 #if PLATFORM(IOS) && ENABLE(FULLSCREEN_API)
29 #import "WKFullScreenWindowControllerIOS.h"
30
31 #import "UIKitSPI.h"
32 #import "WKFullScreenViewController.h"
33 #import "WKFullscreenStackView.h"
34 #import "WKWebView.h"
35 #import "WKWebViewInternal.h"
36 #import "WKWebViewPrivate.h"
37 #import "WebFullScreenManagerProxy.h"
38 #import "WebPageProxy.h"
39 #import <Foundation/Foundation.h>
40 #import <Security/SecCertificate.h>
41 #import <Security/SecTrust.h>
42 #import <UIKit/UIVisualEffectView.h>
43 #import <WebCore/FloatRect.h>
44 #import <WebCore/GeometryUtilities.h>
45 #import <WebCore/IntRect.h>
46 #import <WebCore/LocalizedStrings.h>
47 #import <WebCore/WebCoreNSURLExtras.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/spi/cocoa/SecuritySPI.h>
54
55 using namespace WebKit;
56 using namespace WebCore;
57
58 #if !HAVE(URL_FORMATTING)
59 SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(LinkPresentation)
60 #endif
61
62 namespace WebKit {
63
64 static CGSize sizeExpandedToSize(CGSize initial, CGSize other)
65 {
66     return CGSizeMake(std::max(initial.width, other.width),  std::max(initial.height, other.height));
67 }
68
69 static void replaceViewWithView(UIView *view, UIView *otherView)
70 {
71     [CATransaction begin];
72     [CATransaction setDisableActions:YES];
73     [otherView setFrame:[view frame]];
74     [otherView setAutoresizingMask:[view autoresizingMask]];
75     [[view superview] insertSubview:otherView aboveSubview:view];
76     [view removeFromSuperview];
77     [CATransaction commit];
78 }
79
80 enum FullScreenState : NSInteger {
81     NotInFullScreen,
82     WaitingToEnterFullScreen,
83     EnteringFullScreen,
84     InFullScreen,
85     WaitingToExitFullScreen,
86     ExitingFullScreen,
87 };
88
89 struct WKWebViewState {
90     float _savedTopContentInset = 0.0;
91     CGFloat _savedPageScale = 1;
92     CGFloat _savedViewScale = 1.0;
93     CGFloat _savedZoomScale = 1;
94     UIEdgeInsets _savedEdgeInset = UIEdgeInsetsZero;
95     UIEdgeInsets _savedObscuredInsets = UIEdgeInsetsZero;
96     UIEdgeInsets _savedScrollIndicatorInsets = UIEdgeInsetsZero;
97     CGPoint _savedContentOffset = CGPointZero;
98
99     void applyTo(WKWebView* webView)
100     {
101         [webView _setPageScale:_savedPageScale withOrigin:CGPointMake(0, 0)];
102         [webView _setObscuredInsets:_savedObscuredInsets];
103         [[webView scrollView] setContentInset:_savedEdgeInset];
104         [[webView scrollView] setContentOffset:_savedContentOffset];
105         [[webView scrollView] setScrollIndicatorInsets:_savedScrollIndicatorInsets];
106         [webView _page]->setTopContentInset(_savedTopContentInset);
107         [webView _setViewScale:_savedViewScale];
108         [[webView scrollView] setZoomScale:_savedZoomScale];
109     }
110     
111     void store(WKWebView* webView)
112     {
113         _savedPageScale = [webView _pageScale];
114         _savedObscuredInsets = [webView _obscuredInsets];
115         _savedEdgeInset = [[webView scrollView] contentInset];
116         _savedContentOffset = [[webView scrollView] contentOffset];
117         _savedScrollIndicatorInsets = [[webView scrollView] scrollIndicatorInsets];
118         _savedTopContentInset = [webView _page]->topContentInset();
119         _savedViewScale = [webView _viewScale];
120         _savedZoomScale = [[webView scrollView] zoomScale];
121     }
122 };
123
124 } // namespace WebKit
125
126 static const NSTimeInterval kAnimationDuration = 0.2;
127
128 #pragma mark -
129
130 @interface WKFullscreenAnimationController : NSObject <UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning>
131 @property (retain, nonatomic) UIViewController* viewController;
132 @property (nonatomic) CGRect initialFrame;
133 @property (nonatomic) CGRect finalFrame;
134 @property (nonatomic, getter=isAnimatingIn) BOOL animatingIn;
135 @end
136
137 @implementation WKFullscreenAnimationController {
138     CGRect _initialMaskViewBounds;
139     CGRect _finalMaskViewBounds;
140     CGAffineTransform _initialAnimatingViewTransform;
141     CGAffineTransform _finalAnimatingViewTransform;
142     CGPoint _initialMaskViewCenter;
143     CGPoint _finalMaskViewCenter;
144     RetainPtr<UIView> _maskView;
145     RetainPtr<UIView> _animatingView;
146     RetainPtr<id<UIViewControllerContextTransitioning>> _context;
147     CGFloat _initialBackgroundAlpha;
148     CGFloat _finalBackgroundAlpha;
149 }
150
151 - (void)_createViewsForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
152 {
153     _maskView = adoptNS([[UIView alloc] init]);
154     [_maskView setBackgroundColor:[UIColor blackColor]];
155     [_maskView setBounds:_initialMaskViewBounds];
156     [_maskView setCenter:_initialMaskViewCenter];
157     [_animatingView setMaskView:_maskView.get()];
158     [_animatingView setTransform:_initialAnimatingViewTransform];
159
160     UIView *containerView = [transitionContext containerView];
161     [containerView addSubview:_animatingView.get()];
162 }
163
164 - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
165 {
166     return kAnimationDuration;
167 }
168
169 - (void)configureInitialAndFinalStatesForTransition:(id<UIViewControllerContextTransitioning>)transitionContext
170 {
171     _context = transitionContext;
172     UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
173     UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
174
175     CGRect inlineFrame = _animatingIn ? _initialFrame : _finalFrame;
176     CGRect fullscreenFrame = _animatingIn ? _finalFrame : _initialFrame;
177     _animatingView = _animatingIn ? toView : fromView;
178
179     CGRect boundsRect = largestRectWithAspectRatioInsideRect(FloatRect(inlineFrame).size().aspectRatio(), fullscreenFrame);
180     boundsRect.origin = CGPointZero;
181
182     _initialMaskViewBounds = _animatingIn ? boundsRect : [_animatingView bounds];
183     _initialMaskViewCenter = CGPointMake(CGRectGetMidX([_animatingView bounds]), CGRectGetMidY([_animatingView bounds]));
184     _finalMaskViewBounds = _animatingIn ? [_animatingView bounds] : boundsRect;
185     _finalMaskViewCenter = CGPointMake(CGRectGetMidX([_animatingView bounds]), CGRectGetMidY([_animatingView bounds]));
186
187     FloatRect scaleRect = smallestRectWithAspectRatioAroundRect(FloatRect(fullscreenFrame).size().aspectRatio(), inlineFrame);
188     CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scaleRect.width() / fullscreenFrame.size.width, scaleRect.height() / fullscreenFrame.size.height);
189     CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(inlineFrame) - CGRectGetMidX(fullscreenFrame), CGRectGetMidY(inlineFrame) - CGRectGetMidY(fullscreenFrame));
190
191     CGAffineTransform finalTransform = CGAffineTransformConcat(scaleTransform, translateTransform);
192     _initialAnimatingViewTransform = _animatingIn ? finalTransform : CGAffineTransformIdentity;
193     _finalAnimatingViewTransform = _animatingIn ? CGAffineTransformIdentity : finalTransform;
194
195     _initialBackgroundAlpha = _animatingIn ? 0 : 1;
196     _finalBackgroundAlpha = _animatingIn ? 1 : 0;
197 }
198
199 - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
200 {
201     [self configureInitialAndFinalStatesForTransition:transitionContext];
202     [self _createViewsForTransitionContext:transitionContext];
203
204     UIWindow *window = [transitionContext containerView].window;
205     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha];
206
207     [UIView animateWithDuration:kAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
208         [self updateWithProgress:1];
209     } completion:^(BOOL finished) {
210         [self animationEnded:![transitionContext transitionWasCancelled]];
211         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
212     }];
213 }
214
215 - (void)animationEnded:(BOOL)transitionCompleted
216 {
217     if (([self isAnimatingIn] && !transitionCompleted) || (![self isAnimatingIn] && transitionCompleted))
218         [_animatingView removeFromSuperview];
219
220     [_animatingView setMaskView:nil];
221     _maskView = nil;
222     _animatingView = nil;
223 }
224
225 - (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext
226 {
227     [self configureInitialAndFinalStatesForTransition:transitionContext];
228     [self _createViewsForTransitionContext:transitionContext];
229 }
230
231 - (void)updateWithProgress:(CGFloat)progress
232 {
233     CGAffineTransform progressTransform = _initialAnimatingViewTransform;
234     progressTransform.a += progress * (_finalAnimatingViewTransform.a - _initialAnimatingViewTransform.a);
235     progressTransform.b += progress * (_finalAnimatingViewTransform.b - _initialAnimatingViewTransform.b);
236     progressTransform.c += progress * (_finalAnimatingViewTransform.c - _initialAnimatingViewTransform.c);
237     progressTransform.d += progress * (_finalAnimatingViewTransform.d - _initialAnimatingViewTransform.d);
238     progressTransform.tx += progress * (_finalAnimatingViewTransform.tx - _initialAnimatingViewTransform.tx);
239     progressTransform.ty += progress * (_finalAnimatingViewTransform.ty - _initialAnimatingViewTransform.ty);
240     [_animatingView setTransform:progressTransform];
241
242     CGRect progressBounds = _initialMaskViewBounds;
243     progressBounds.origin.x += progress * (_finalMaskViewBounds.origin.x - _initialMaskViewBounds.origin.x);
244     progressBounds.origin.y += progress * (_finalMaskViewBounds.origin.y - _initialMaskViewBounds.origin.y);
245     progressBounds.size.width += progress * (_finalMaskViewBounds.size.width - _initialMaskViewBounds.size.width);
246     progressBounds.size.height += progress * (_finalMaskViewBounds.size.height - _initialMaskViewBounds.size.height);
247     [_maskView setBounds:progressBounds];
248
249     CGPoint progressCenter = _initialMaskViewCenter;
250     progressCenter.x += progress * (_finalMaskViewCenter.x - _finalMaskViewCenter.x);
251     progressCenter.y += progress * (_finalMaskViewCenter.y - _finalMaskViewCenter.y);
252     [_maskView setCenter:progressCenter];
253
254     UIWindow *window = [_animatingView window];
255     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
256 }
257
258 - (void)updateWithProgress:(CGFloat)progress translation:(CGSize)translation anchor:(CGPoint)anchor
259 {
260     CGAffineTransform progressTransform = _initialAnimatingViewTransform;
261     progressTransform.a += progress * (_finalAnimatingViewTransform.a - _initialAnimatingViewTransform.a);
262     progressTransform.b += progress * (_finalAnimatingViewTransform.b - _initialAnimatingViewTransform.b);
263     progressTransform.c += progress * (_finalAnimatingViewTransform.c - _initialAnimatingViewTransform.c);
264     progressTransform.d += progress * (_finalAnimatingViewTransform.d - _initialAnimatingViewTransform.d);
265     progressTransform.tx += _initialAnimatingViewTransform.tx + translation.width;
266     progressTransform.ty += _initialAnimatingViewTransform.ty + translation.height;
267     [_animatingView setTransform:progressTransform];
268
269     UIWindow *window = [_animatingView window];
270     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
271 }
272
273 - (void)end:(BOOL)cancelled {
274     if (cancelled) {
275         [UIView animateWithDuration:kAnimationDuration animations:^{
276             [self updateWithProgress:0];
277         } completion:^(BOOL finished) {
278             [_context cancelInteractiveTransition];
279             [_context completeTransition:NO];
280         }];
281     } else {
282         [UIView animateWithDuration:kAnimationDuration animations:^{
283             [self updateWithProgress:1];
284         } completion:^(BOOL finished) {
285             [_context finishInteractiveTransition];
286             [_context completeTransition:YES];
287         }];
288     }
289 }
290
291 @end
292
293 #pragma mark -
294
295 @interface WKFullScreenInteractiveTransition : NSObject<UIViewControllerInteractiveTransitioning>
296 - (id)initWithAnimator:(WKFullscreenAnimationController *)animator anchor:(CGPoint)point;
297 @end
298
299 @implementation WKFullScreenInteractiveTransition {
300     RetainPtr<WKFullscreenAnimationController> _animator;
301     RetainPtr<id<UIViewControllerContextTransitioning>> _context;
302     CGPoint _anchor;
303 }
304
305 - (id)initWithAnimator:(WKFullscreenAnimationController *)animator anchor:(CGPoint)point
306 {
307     if (!(self = [super init]))
308         return nil;
309
310     _animator = animator;
311     _anchor = point;
312     return self;
313 }
314
315 - (BOOL)wantsInteractiveStart
316 {
317     return YES;
318 }
319
320 - (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext
321 {
322     _context = transitionContext;
323     [_animator startInteractiveTransition:transitionContext];
324 }
325
326 - (void)updateInteractiveTransition:(CGFloat)progress withTranslation:(CGSize)translation
327 {
328     [_animator updateWithProgress:progress translation:translation anchor:_anchor];
329     [_context updateInteractiveTransition:progress];
330 }
331
332 - (void)cancelInteractiveTransition
333 {
334     [_animator end:YES];
335 }
336
337 - (void)finishInteractiveTransition
338 {
339     [_animator end:NO];
340 }
341 @end
342
343 #pragma mark -
344
345 @interface WKFullScreenWindowController () <UIGestureRecognizerDelegate>
346 @end
347
348 @implementation WKFullScreenWindowController {
349     WKWebView *_webView; // Cannot be retained, see <rdar://problem/14884666>.
350     RetainPtr<UIView> _webViewPlaceholder;
351
352     FullScreenState _fullScreenState;
353     WKWebViewState _viewState;
354
355     RetainPtr<UIWindow> _window;
356     RetainPtr<UIViewController> _rootViewController;
357
358     RefPtr<WebKit::VoidCallback> _repaintCallback;
359     RetainPtr<UIViewController> _viewControllerForPresentation;
360     RetainPtr<WKFullScreenViewController> _fullscreenViewController;
361     RetainPtr<UISwipeGestureRecognizer> _startDismissGestureRecognizer;
362     RetainPtr<UIPanGestureRecognizer> _interactiveDismissGestureRecognizer;
363     RetainPtr<WKFullScreenInteractiveTransition> _interactiveDismissTransitionCoordinator;
364
365     CGRect _initialFrame;
366     CGRect _finalFrame;
367
368     RetainPtr<NSString> _EVOrganizationName;
369     BOOL _EVOrganizationNameIsValid;
370     BOOL _inInteractiveDismiss;
371
372     RetainPtr<id> _notificationListener;
373 }
374
375 #pragma mark -
376 #pragma mark Initialization
377 - (id)initWithWebView:(WKWebView *)webView
378 {
379     if (!(self = [super init]))
380         return nil;
381
382     _webView = webView;
383
384     return self;
385 }
386
387 - (void)dealloc
388 {
389     [NSObject cancelPreviousPerformRequestsWithTarget:self];
390     [[NSNotificationCenter defaultCenter] removeObserver:self];
391
392     [super dealloc];
393 }
394
395 #pragma mark -
396 #pragma mark Accessors
397
398 - (BOOL)isFullScreen
399 {
400     return _fullScreenState == WaitingToEnterFullScreen
401         || _fullScreenState == EnteringFullScreen
402         || _fullScreenState == InFullScreen;
403 }
404
405 - (UIView *)webViewPlaceholder
406 {
407     return _webViewPlaceholder.get();
408 }
409
410 #pragma mark -
411 #pragma mark External Interface
412
413 - (void)enterFullScreen
414 {
415     if ([self isFullScreen])
416         return;
417
418     [self _invalidateEVOrganizationName];
419
420     _fullScreenState = WaitingToEnterFullScreen;
421
422     _window = adoptNS([[UIWindow alloc] init]);
423     [_window setBackgroundColor:[UIColor clearColor]];
424     [_window setWindowLevel:UIWindowLevelNormal - 1];
425     [_window setHidden:NO];
426
427     _rootViewController = [[UIViewController alloc] init];
428     _rootViewController.get().view = [[[UIView alloc] initWithFrame:_window.get().bounds] autorelease];
429     _rootViewController.get().view.backgroundColor = [UIColor clearColor];
430     _rootViewController.get().view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
431     [_rootViewController setModalPresentationStyle:UIModalPresentationCustom];
432     [_rootViewController setTransitioningDelegate:self];
433
434     _window.get().rootViewController = _rootViewController.get();
435
436     _fullscreenViewController = adoptNS([[WKFullScreenViewController alloc] initWithWebView:_webView]);
437     [_fullscreenViewController setModalPresentationStyle:UIModalPresentationCustom];
438     [_fullscreenViewController setTransitioningDelegate:self];
439     [_fullscreenViewController setModalPresentationCapturesStatusBarAppearance:YES];
440     [_fullscreenViewController setTarget:self];
441     [_fullscreenViewController setAction:@selector(requestExitFullScreen)];
442     _fullscreenViewController.get().view.frame = _rootViewController.get().view.bounds;
443     [self _updateLocationInfo];
444
445     _startDismissGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc]  initWithTarget:self action:@selector(_startToDismissFullscreenChanged:)]);
446     [_startDismissGestureRecognizer setDelegate:self];
447     [_startDismissGestureRecognizer setCancelsTouchesInView:YES];
448     [_startDismissGestureRecognizer setNumberOfTouchesRequired:1];
449     [_startDismissGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
450     [_fullscreenViewController.get().view addGestureRecognizer:_startDismissGestureRecognizer.get()];
451
452     _interactiveDismissGestureRecognizer = adoptNS([[UIPanGestureRecognizer alloc]  initWithTarget:self action:@selector(_interactiveDismissChanged:)]);
453     [_interactiveDismissGestureRecognizer setDelegate:self];
454     [_interactiveDismissGestureRecognizer setCancelsTouchesInView:NO];
455     [_fullscreenViewController.get().view addGestureRecognizer:_interactiveDismissGestureRecognizer.get()];
456
457     [self _manager]->saveScrollPosition();
458
459     [_webView _page]->setSuppressVisibilityUpdates(true);
460
461     _viewState.store(_webView);
462
463     _webViewPlaceholder = adoptNS([[UIView alloc] init]);
464     [[_webViewPlaceholder layer] setName:@"Fullscreen Placeholder View"];
465
466     WKSnapshotConfiguration* config = nil;
467     [_webView takeSnapshotWithConfiguration:config completionHandler:^(UIImage * snapshotImage, NSError * error) {
468         if (![_webView _page])
469             return;
470
471         [CATransaction begin];
472         [CATransaction setDisableActions:YES];
473         
474         [[_webViewPlaceholder layer] setContents:(id)[snapshotImage CGImage]];
475         replaceViewWithView(_webView, _webViewPlaceholder.get());
476
477         WKWebViewState().applyTo(_webView);
478         
479         [_webView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
480         [_webView setFrame:[_window bounds]];
481         [_webView _overrideLayoutParametersWithMinimumLayoutSize:[_window bounds].size maximumUnobscuredSizeOverride:[_window bounds].size];
482         [_window insertSubview:_webView atIndex:0];
483         [_webView setNeedsLayout];
484         [_webView layoutIfNeeded];
485         
486         [self _manager]->setAnimatingFullScreen(true);
487
488         _repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
489             _repaintCallback = nullptr;
490             if (auto* manager = [protectedSelf _manager]) {
491                 manager->willEnterFullScreen();
492                 return;
493             }
494
495             ASSERT_NOT_REACHED();
496             [self _exitFullscreenImmediately];
497         });
498         [_webView _page]->forceRepaint(_repaintCallback.copyRef());
499
500         [CATransaction commit];
501     }];
502 }
503
504 - (void)beganEnterFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
505 {
506     if (_fullScreenState != WaitingToEnterFullScreen)
507         return;
508     _fullScreenState = EnteringFullScreen;
509
510     _initialFrame = initialFrame;
511     _finalFrame = finalFrame;
512     
513     _initialFrame.size = sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
514     _finalFrame.size = sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
515     
516     [CATransaction begin];
517     [CATransaction setDisableActions:YES];
518
519     [_webView removeFromSuperview];
520
521     [_window setWindowLevel:UIWindowLevelNormal];
522     [_window makeKeyAndVisible];
523     [_fullscreenViewController setPrefersStatusBarHidden:NO];
524     [_fullscreenViewController showUI];
525
526     [CATransaction commit];
527
528     [_rootViewController presentViewController:_fullscreenViewController.get() animated:YES completion:^{
529         _fullScreenState = InFullScreen;
530
531         auto* page = [_webView _page];
532         auto* manager = self._manager;
533         if (page && manager) {
534             manager->didEnterFullScreen();
535             manager->setAnimatingFullScreen(false);
536             page->setSuppressVisibilityUpdates(false);
537             return;
538         }
539
540         ASSERT_NOT_REACHED();
541         [self _exitFullscreenImmediately];
542     }];
543 }
544
545 - (void)requestExitFullScreen
546 {
547     if (auto* manager = self._manager) {
548         manager->requestExitFullScreen();
549         return;
550     }
551
552     ASSERT_NOT_REACHED();
553     [self _exitFullscreenImmediately];
554 }
555
556 - (void)exitFullScreen
557 {
558     if (!self.isFullScreen)
559         return;
560     _fullScreenState = WaitingToExitFullScreen;
561
562     if (auto* manager = self._manager) {
563         manager->setAnimatingFullScreen(true);
564         manager->willExitFullScreen();
565         return;
566     }
567
568     ASSERT_NOT_REACHED();
569     [self _exitFullscreenImmediately];
570 }
571
572 - (void)beganExitFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
573 {
574     if (_fullScreenState != WaitingToExitFullScreen)
575         return;
576     _fullScreenState = ExitingFullScreen;
577
578     _initialFrame = initialFrame;
579     _finalFrame = finalFrame;
580     
581     _initialFrame.size = sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
582     _finalFrame.size = sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
583     
584     [_webView _page]->setSuppressVisibilityUpdates(true);
585
586     [_fullscreenViewController setPrefersStatusBarHidden:NO];
587
588     if (_interactiveDismissTransitionCoordinator) {
589         [_interactiveDismissTransitionCoordinator finishInteractiveTransition];
590         _interactiveDismissTransitionCoordinator = nil;
591         return;
592     }
593
594     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
595         if (![_webView _page])
596             return;
597
598         [self _completedExitFullScreen];
599     }];
600 }
601
602 - (void)_completedExitFullScreen
603 {
604     if (_fullScreenState != ExitingFullScreen)
605         return;
606     _fullScreenState = NotInFullScreen;
607
608     [CATransaction begin];
609     [CATransaction setDisableActions:YES];
610
611     [[_webViewPlaceholder superview] insertSubview:_webView belowSubview:_webViewPlaceholder.get()];
612     [_webView setFrame:[_webViewPlaceholder frame]];
613     [_webView setAutoresizingMask:[_webViewPlaceholder autoresizingMask]];
614
615     [[_webView window] makeKeyAndVisible];
616
617     _viewState.applyTo(_webView);
618
619     [_webView setNeedsLayout];
620     [_webView layoutIfNeeded];
621
622     [CATransaction commit];
623
624     [_window setHidden:YES];
625     _window = nil;
626
627     if (auto* manager = self._manager) {
628         manager->setAnimatingFullScreen(false);
629         manager->didExitFullScreen();
630     }
631
632     if (_repaintCallback) {
633         _repaintCallback->invalidate(WebKit::CallbackBase::Error::OwnerWasInvalidated);
634         ASSERT(!_repaintCallback);
635     }
636
637     _repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
638         _repaintCallback = nullptr;
639         [_webViewPlaceholder removeFromSuperview];
640
641         if (![_webView _page])
642             return;
643
644         [_webView _page]->setSuppressVisibilityUpdates(false);
645     });
646
647     if (auto* page = [_webView _page])
648         page->forceRepaint(_repaintCallback.copyRef());
649     else
650         _repaintCallback->performCallback();
651
652     [_fullscreenViewController setPrefersStatusBarHidden:YES];
653 }
654
655 - (void)close
656 {
657     [self _exitFullscreenImmediately];
658     _webView = nil;
659 }
660
661 - (void)webViewDidRemoveFromSuperviewWhileInFullscreen
662 {
663     if (_fullScreenState == InFullScreen && _webView.window != _window.get())
664         [self _exitFullscreenImmediately];
665 }
666
667 - (void)videoControlsManagerDidChange
668 {
669     if (_fullscreenViewController)
670         [_fullscreenViewController videoControlsManagerDidChange];
671 }
672
673 #pragma mark -
674 #pragma mark UIGestureRecognizerDelegate
675
676 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
677 {
678     return YES;
679 }
680
681 #pragma mark -
682 #pragma mark UIViewControllerTransitioningDelegate
683
684 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
685 {
686     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
687     [animationController setViewController:presented];
688     [animationController setInitialFrame:_initialFrame];
689     [animationController setFinalFrame:_finalFrame];
690     [animationController setAnimatingIn:YES];
691     return animationController.autorelease();
692 }
693
694 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
695 {
696     CGRect initialFrame = _initialFrame;
697     CGRect finalFrame = _finalFrame;
698
699     // Because we're not calling "requestExitFullscreen()" at the beginning of an interactive animation,
700     // the _initialFrame and _finalFrame values are left over from when we entered fullscreen.
701     if (_inInteractiveDismiss)
702         std::swap(initialFrame, finalFrame);
703
704     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
705     [animationController setViewController:dismissed];
706     [animationController setInitialFrame:initialFrame];
707     [animationController setFinalFrame:finalFrame];
708     [animationController setAnimatingIn:NO];
709     return animationController.autorelease();
710 }
711
712 - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
713 {
714     if (!_inInteractiveDismiss)
715         return nil;
716
717     if (![animator isKindOfClass:[WKFullscreenAnimationController class]])
718         return nil;
719
720     if (!_interactiveDismissTransitionCoordinator)
721         _interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:CGPointZero]);
722
723     return _interactiveDismissTransitionCoordinator.get();
724 }
725
726 #pragma mark -
727 #pragma mark Internal Interface
728
729 - (void)_exitFullscreenImmediately
730 {
731     if (![self isFullScreen])
732         return;
733
734     auto* manager = self._manager;
735     if (manager)
736         manager->requestExitFullScreen();
737     [self exitFullScreen];
738     _fullScreenState = ExitingFullScreen;
739     [self _completedExitFullScreen];
740     replaceViewWithView(_webViewPlaceholder.get(), _webView);
741     if (auto* page = [_webView _page])
742         page->setSuppressVisibilityUpdates(false);
743     if (manager) {
744         manager->didExitFullScreen();
745         manager->setAnimatingFullScreen(false);
746     }
747     _webViewPlaceholder = nil;
748 }
749
750 - (void)_invalidateEVOrganizationName
751 {
752     _EVOrganizationName = nil;
753     _EVOrganizationNameIsValid = NO;
754 }
755
756 - (BOOL)_isSecure
757 {
758     return _webView.hasOnlySecureContent;
759 }
760
761 - (SecTrustRef)_serverTrust
762 {
763     return _webView.serverTrust;
764 }
765
766 - (NSString *)_EVOrganizationName
767 {
768     if (!self._isSecure)
769         return nil;
770
771     if (_EVOrganizationNameIsValid)
772         return _EVOrganizationName.get();
773
774     ASSERT(!_EVOrganizationName.get());
775     _EVOrganizationNameIsValid = YES;
776
777     SecTrustRef trust = [self _serverTrust];
778     if (!trust)
779         return nil;
780
781     NSDictionary *infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
782     // If SecTrustCopyInfo returned NULL then it's likely that the SecTrustRef has not been evaluated
783     // and the only way to get the information we need is to call SecTrustEvaluate ourselves.
784     if (!infoDictionary) {
785         SecTrustResultType result = kSecTrustResultProceed;
786         OSStatus err = SecTrustEvaluate(trust, &result);
787         if (err == noErr)
788             infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
789         if (!infoDictionary)
790             return nil;
791     }
792
793     // Make sure that the EV certificate is valid against our certificate chain.
794     id hasEV = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
795     if (![hasEV isKindOfClass:[NSValue class]] || ![hasEV boolValue])
796         return nil;
797
798     // Make sure that we could contact revocation server and it is still valid.
799     id isNotRevoked = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoRevocationKey];
800     if (![isNotRevoked isKindOfClass:[NSValue class]] || ![isNotRevoked boolValue])
801         return nil;
802
803     _EVOrganizationName = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoCompanyNameKey];
804     return _EVOrganizationName.get();
805 }
806
807 - (void)_updateLocationInfo
808 {
809     NSURL* url = _webView._committedURL;
810
811     NSString *EVOrganizationName = [self _EVOrganizationName];
812     BOOL showsEVOrganizationName = [EVOrganizationName length] > 0;
813
814     NSString *domain = nil;
815
816 #if HAVE(URL_FORMATTING)
817     domain = [url _lp_simplifiedDisplayString];
818 #else
819     if (LinkPresentationLibrary())
820         domain = [url _lp_simplifiedDisplayString];
821     else
822         domain = userVisibleString(url);
823 #endif
824
825     NSString *text = nil;
826     if ([[url scheme] caseInsensitiveCompare:@"data"] == NSOrderedSame)
827         text = @"data:";
828     else if (showsEVOrganizationName)
829         text = EVOrganizationName;
830     else
831         text = domain;
832
833     [_fullscreenViewController setLocation:text];
834 }
835
836 - (WebFullScreenManagerProxy*)_manager
837 {
838     if (![_webView _page])
839         return nullptr;
840     return [_webView _page]->fullScreenManager();
841 }
842
843 - (void)_startToDismissFullscreenChanged:(id)sender
844 {
845     _inInteractiveDismiss = true;
846     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
847         if (![_webView _page])
848             return;
849
850         [self _completedExitFullScreen];
851         [_fullscreenViewController setPrefersStatusBarHidden:YES];
852     }];
853 }
854
855 - (void)_interactiveDismissChanged:(id)sender
856 {
857     if (!_inInteractiveDismiss)
858         return;
859
860     CGPoint translation = [_interactiveDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
861     CGPoint velocity = [_interactiveDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
862     CGFloat progress = translation.y / (_fullscreenViewController.get().view.bounds.size.height / 2);
863     progress = std::min(1., std::max(0., progress));
864
865     if (_interactiveDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
866         _inInteractiveDismiss = false;
867
868         if (progress > 0.25 || (progress > 0 && velocity.y > 5))
869             [self requestExitFullScreen];
870         else {
871             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
872             _interactiveDismissTransitionCoordinator = nil;
873         }
874         return;
875     }
876
877     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withTranslation:CGSizeMake(translation.x, translation.y)];
878 }
879
880 @end
881
882 #endif // PLATFORM(IOS) && ENABLE(FULLSCREEN_API)