CRASH in -[WKFullScreenViewController _manager]
[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 @property (weak, nonatomic) WKWebView *_webView; // Cannot be retained, see <rdar://problem/14884666>.
347 @end
348
349 @implementation WKFullScreenWindowController {
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     self._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     RetainPtr<WKWebView> webView = self._webView;
437
438     _fullscreenViewController = adoptNS([[WKFullScreenViewController alloc] initWithWebView:webView.get()]);
439     [_fullscreenViewController setModalPresentationStyle:UIModalPresentationCustom];
440     [_fullscreenViewController setTransitioningDelegate:self];
441     [_fullscreenViewController setModalPresentationCapturesStatusBarAppearance:YES];
442     [_fullscreenViewController setTarget:self];
443     [_fullscreenViewController setAction:@selector(requestExitFullScreen)];
444     _fullscreenViewController.get().view.frame = _rootViewController.get().view.bounds;
445     [self _updateLocationInfo];
446
447     _startDismissGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc]  initWithTarget:self action:@selector(_startToDismissFullscreenChanged:)]);
448     [_startDismissGestureRecognizer setDelegate:self];
449     [_startDismissGestureRecognizer setCancelsTouchesInView:YES];
450     [_startDismissGestureRecognizer setNumberOfTouchesRequired:1];
451     [_startDismissGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
452     [_fullscreenViewController.get().view addGestureRecognizer:_startDismissGestureRecognizer.get()];
453
454     _interactiveDismissGestureRecognizer = adoptNS([[UIPanGestureRecognizer alloc]  initWithTarget:self action:@selector(_interactiveDismissChanged:)]);
455     [_interactiveDismissGestureRecognizer setDelegate:self];
456     [_interactiveDismissGestureRecognizer setCancelsTouchesInView:NO];
457     [_fullscreenViewController.get().view addGestureRecognizer:_interactiveDismissGestureRecognizer.get()];
458
459     [self _manager]->saveScrollPosition();
460
461     [webView _page]->setSuppressVisibilityUpdates(true);
462
463     _viewState.store(webView.get());
464
465     _webViewPlaceholder = adoptNS([[UIView alloc] init]);
466     [[_webViewPlaceholder layer] setName:@"Fullscreen Placeholder View"];
467
468     WKSnapshotConfiguration* config = nil;
469     [webView takeSnapshotWithConfiguration:config completionHandler:^(UIImage * snapshotImage, NSError * error) {
470         RetainPtr<WKWebView> webView = self._webView;
471         if (![webView _page])
472             return;
473
474         [CATransaction begin];
475         [CATransaction setDisableActions:YES];
476         
477         [[_webViewPlaceholder layer] setContents:(id)[snapshotImage CGImage]];
478         replaceViewWithView(webView.get(), _webViewPlaceholder.get());
479
480         WKWebViewState().applyTo(webView.get());
481         
482         [webView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
483         [webView setFrame:[_window bounds]];
484         [webView _overrideLayoutParametersWithMinimumLayoutSize:[_window bounds].size maximumUnobscuredSizeOverride:[_window bounds].size];
485         [_window insertSubview:webView.get() atIndex:0];
486         [webView setNeedsLayout];
487         [webView layoutIfNeeded];
488         
489         [self _manager]->setAnimatingFullScreen(true);
490
491         _repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
492             _repaintCallback = nullptr;
493             if (auto* manager = [protectedSelf _manager]) {
494                 manager->willEnterFullScreen();
495                 return;
496             }
497
498             ASSERT_NOT_REACHED();
499             [self _exitFullscreenImmediately];
500         });
501         [webView _page]->forceRepaint(_repaintCallback.copyRef());
502
503         [CATransaction commit];
504     }];
505 }
506
507 - (void)beganEnterFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
508 {
509     if (_fullScreenState != WaitingToEnterFullScreen)
510         return;
511     _fullScreenState = EnteringFullScreen;
512
513     _initialFrame = initialFrame;
514     _finalFrame = finalFrame;
515     
516     _initialFrame.size = sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
517     _finalFrame.size = sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
518     
519     [CATransaction begin];
520     [CATransaction setDisableActions:YES];
521
522     RetainPtr<WKWebView> webView = self._webView;
523     [webView removeFromSuperview];
524
525     [_window setWindowLevel:UIWindowLevelNormal];
526     [_window makeKeyAndVisible];
527     [_fullscreenViewController setPrefersStatusBarHidden:NO];
528     [_fullscreenViewController showUI];
529
530     [CATransaction commit];
531
532     [_rootViewController presentViewController:_fullscreenViewController.get() animated:YES completion:^{
533         _fullScreenState = InFullScreen;
534
535         auto* page = [self._webView _page];
536         auto* manager = self._manager;
537         if (page && manager) {
538             manager->didEnterFullScreen();
539             manager->setAnimatingFullScreen(false);
540             page->setSuppressVisibilityUpdates(false);
541             return;
542         }
543
544         ASSERT_NOT_REACHED();
545         [self _exitFullscreenImmediately];
546     }];
547 }
548
549 - (void)requestExitFullScreen
550 {
551     if (auto* manager = self._manager) {
552         manager->requestExitFullScreen();
553         return;
554     }
555
556     ASSERT_NOT_REACHED();
557     [self _exitFullscreenImmediately];
558 }
559
560 - (void)exitFullScreen
561 {
562     if (!self.isFullScreen)
563         return;
564     _fullScreenState = WaitingToExitFullScreen;
565
566     if (auto* manager = self._manager) {
567         manager->setAnimatingFullScreen(true);
568         manager->willExitFullScreen();
569         return;
570     }
571
572     ASSERT_NOT_REACHED();
573     [self _exitFullscreenImmediately];
574 }
575
576 - (void)beganExitFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
577 {
578     if (_fullScreenState != WaitingToExitFullScreen)
579         return;
580     _fullScreenState = ExitingFullScreen;
581
582     _initialFrame = initialFrame;
583     _finalFrame = finalFrame;
584     
585     _initialFrame.size = sizeExpandedToSize(_initialFrame.size, CGSizeMake(1, 1));
586     _finalFrame.size = sizeExpandedToSize(_finalFrame.size, CGSizeMake(1, 1));
587     
588     [self._webView _page]->setSuppressVisibilityUpdates(true);
589
590     [_fullscreenViewController setPrefersStatusBarHidden:NO];
591
592     if (_interactiveDismissTransitionCoordinator) {
593         [_interactiveDismissTransitionCoordinator finishInteractiveTransition];
594         _interactiveDismissTransitionCoordinator = nil;
595         return;
596     }
597
598     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
599         if (![self._webView _page])
600             return;
601
602         [self _completedExitFullScreen];
603     }];
604 }
605
606 - (void)_completedExitFullScreen
607 {
608     if (_fullScreenState != ExitingFullScreen)
609         return;
610     _fullScreenState = NotInFullScreen;
611
612     [CATransaction begin];
613     [CATransaction setDisableActions:YES];
614
615     RetainPtr<WKWebView> webView = self._webView;
616     [[_webViewPlaceholder superview] insertSubview:webView.get() belowSubview:_webViewPlaceholder.get()];
617     [webView setFrame:[_webViewPlaceholder frame]];
618     [webView setAutoresizingMask:[_webViewPlaceholder autoresizingMask]];
619
620     [[webView window] makeKeyAndVisible];
621
622     _viewState.applyTo(webView.get());
623
624     [webView setNeedsLayout];
625     [webView layoutIfNeeded];
626
627     [CATransaction commit];
628
629     [_window setHidden:YES];
630     _window = nil;
631
632     if (auto* manager = self._manager) {
633         manager->setAnimatingFullScreen(false);
634         manager->didExitFullScreen();
635     }
636
637     if (_repaintCallback) {
638         _repaintCallback->invalidate(WebKit::CallbackBase::Error::OwnerWasInvalidated);
639         ASSERT(!_repaintCallback);
640     }
641
642     _repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
643         _repaintCallback = nullptr;
644         [_webViewPlaceholder removeFromSuperview];
645
646         if (auto* page = [self._webView _page])
647             page->setSuppressVisibilityUpdates(false);
648     });
649
650     if (auto* page = [self._webView _page])
651         page->forceRepaint(_repaintCallback.copyRef());
652     else
653         _repaintCallback->performCallback();
654
655     [_fullscreenViewController setPrefersStatusBarHidden:YES];
656 }
657
658 - (void)close
659 {
660     [self _exitFullscreenImmediately];
661     self._webView = nil;
662 }
663
664 - (void)webViewDidRemoveFromSuperviewWhileInFullscreen
665 {
666     if (_fullScreenState == InFullScreen && self._webView.window != _window.get())
667         [self _exitFullscreenImmediately];
668 }
669
670 - (void)videoControlsManagerDidChange
671 {
672     if (_fullscreenViewController)
673         [_fullscreenViewController videoControlsManagerDidChange];
674 }
675
676 #pragma mark -
677 #pragma mark UIGestureRecognizerDelegate
678
679 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
680 {
681     return YES;
682 }
683
684 #pragma mark -
685 #pragma mark UIViewControllerTransitioningDelegate
686
687 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
688 {
689     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
690     [animationController setViewController:presented];
691     [animationController setInitialFrame:_initialFrame];
692     [animationController setFinalFrame:_finalFrame];
693     [animationController setAnimatingIn:YES];
694     return animationController.autorelease();
695 }
696
697 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
698 {
699     CGRect initialFrame = _initialFrame;
700     CGRect finalFrame = _finalFrame;
701
702     // Because we're not calling "requestExitFullscreen()" at the beginning of an interactive animation,
703     // the _initialFrame and _finalFrame values are left over from when we entered fullscreen.
704     if (_inInteractiveDismiss)
705         std::swap(initialFrame, finalFrame);
706
707     RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
708     [animationController setViewController:dismissed];
709     [animationController setInitialFrame:initialFrame];
710     [animationController setFinalFrame:finalFrame];
711     [animationController setAnimatingIn:NO];
712     return animationController.autorelease();
713 }
714
715 - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
716 {
717     if (!_inInteractiveDismiss)
718         return nil;
719
720     if (![animator isKindOfClass:[WKFullscreenAnimationController class]])
721         return nil;
722
723     if (!_interactiveDismissTransitionCoordinator)
724         _interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:CGPointZero]);
725
726     return _interactiveDismissTransitionCoordinator.get();
727 }
728
729 #pragma mark -
730 #pragma mark Internal Interface
731
732 - (void)_exitFullscreenImmediately
733 {
734     if (![self isFullScreen])
735         return;
736
737     auto* manager = self._manager;
738     if (manager)
739         manager->requestExitFullScreen();
740     [self exitFullScreen];
741     _fullScreenState = ExitingFullScreen;
742     [self _completedExitFullScreen];
743     RetainPtr<WKWebView> webView = self._webView;
744     replaceViewWithView(_webViewPlaceholder.get(), webView.get());
745     if (auto* page = [webView _page])
746         page->setSuppressVisibilityUpdates(false);
747     if (manager) {
748         manager->didExitFullScreen();
749         manager->setAnimatingFullScreen(false);
750     }
751     _webViewPlaceholder = nil;
752 }
753
754 - (void)_invalidateEVOrganizationName
755 {
756     _EVOrganizationName = nil;
757     _EVOrganizationNameIsValid = NO;
758 }
759
760 - (BOOL)_isSecure
761 {
762     return self._webView.hasOnlySecureContent;
763 }
764
765 - (SecTrustRef)_serverTrust
766 {
767     return self._webView.serverTrust;
768 }
769
770 - (NSString *)_EVOrganizationName
771 {
772     if (!self._isSecure)
773         return nil;
774
775     if (_EVOrganizationNameIsValid)
776         return _EVOrganizationName.get();
777
778     ASSERT(!_EVOrganizationName.get());
779     _EVOrganizationNameIsValid = YES;
780
781     SecTrustRef trust = [self _serverTrust];
782     if (!trust)
783         return nil;
784
785     NSDictionary *infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
786     // If SecTrustCopyInfo returned NULL then it's likely that the SecTrustRef has not been evaluated
787     // and the only way to get the information we need is to call SecTrustEvaluate ourselves.
788     if (!infoDictionary) {
789         SecTrustResultType result = kSecTrustResultProceed;
790         OSStatus err = SecTrustEvaluate(trust, &result);
791         if (err == noErr)
792             infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
793         if (!infoDictionary)
794             return nil;
795     }
796
797     // Make sure that the EV certificate is valid against our certificate chain.
798     id hasEV = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
799     if (![hasEV isKindOfClass:[NSValue class]] || ![hasEV boolValue])
800         return nil;
801
802     // Make sure that we could contact revocation server and it is still valid.
803     id isNotRevoked = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoRevocationKey];
804     if (![isNotRevoked isKindOfClass:[NSValue class]] || ![isNotRevoked boolValue])
805         return nil;
806
807     _EVOrganizationName = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoCompanyNameKey];
808     return _EVOrganizationName.get();
809 }
810
811 - (void)_updateLocationInfo
812 {
813     NSURL* url = self._webView._committedURL;
814
815     NSString *EVOrganizationName = [self _EVOrganizationName];
816     BOOL showsEVOrganizationName = [EVOrganizationName length] > 0;
817
818     NSString *domain = nil;
819
820 #if HAVE(URL_FORMATTING)
821     domain = [url _lp_simplifiedDisplayString];
822 #else
823     if (LinkPresentationLibrary())
824         domain = [url _lp_simplifiedDisplayString];
825     else
826         domain = userVisibleString(url);
827 #endif
828
829     NSString *text = nil;
830     if ([[url scheme] caseInsensitiveCompare:@"data"] == NSOrderedSame)
831         text = @"data:";
832     else if (showsEVOrganizationName)
833         text = EVOrganizationName;
834     else
835         text = domain;
836
837     [_fullscreenViewController setLocation:text];
838 }
839
840 - (WebFullScreenManagerProxy*)_manager
841 {
842     if (auto* page = [self._webView _page])
843         return page->fullScreenManager();
844     return nullptr;
845 }
846
847 - (void)_startToDismissFullscreenChanged:(id)sender
848 {
849     _inInteractiveDismiss = true;
850     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
851         if (![self._webView _page])
852             return;
853
854         [self _completedExitFullScreen];
855         [_fullscreenViewController setPrefersStatusBarHidden:YES];
856     }];
857 }
858
859 - (void)_interactiveDismissChanged:(id)sender
860 {
861     if (!_inInteractiveDismiss)
862         return;
863
864     CGPoint translation = [_interactiveDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
865     CGPoint velocity = [_interactiveDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
866     CGFloat progress = translation.y / (_fullscreenViewController.get().view.bounds.size.height / 2);
867     progress = std::min(1., std::max(0., progress));
868
869     if (_interactiveDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
870         _inInteractiveDismiss = false;
871
872         if (progress > 0.25 || (progress > 0 && velocity.y > 5))
873             [self requestExitFullScreen];
874         else {
875             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
876             _interactiveDismissTransitionCoordinator = nil;
877         }
878         return;
879     }
880
881     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withTranslation:CGSizeMake(translation.x, translation.y)];
882 }
883
884 @end
885
886 #endif // PLATFORM(IOS) && ENABLE(FULLSCREEN_API)