blob: b3de4ddd94a9caa321b68f8103130c0622d55e64 [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#if PLATFORM(IOS) && ENABLE(FULLSCREEN_API)
#import "WKFullScreenWindowControllerIOS.h"
#import "UIKitSPI.h"
#import "WKFullScreenViewController.h"
#import "WKFullscreenStackView.h"
#import "WKWebView.h"
#import "WKWebViewInternal.h"
#import "WKWebViewPrivate.h"
#import "WebFullScreenManagerProxy.h"
#import "WebPageProxy.h"
#import <Foundation/Foundation.h>
#import <Security/SecCertificate.h>
#import <Security/SecTrust.h>
#import <UIKit/UIVisualEffectView.h>
#import <WebCore/FloatRect.h>
#import <WebCore/GeometryUtilities.h>
#import <WebCore/IntRect.h>
#import <WebCore/LocalizedStrings.h>
#import <WebCore/WebCoreNSURLExtras.h>
#import <pal/spi/cf/CFNetworkSPI.h>
#import <pal/spi/cocoa/LinkPresentationSPI.h>
#import <pal/spi/cocoa/NSStringSPI.h>
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/SoftLinking.h>
#import <wtf/spi/cocoa/SecuritySPI.h>
using namespace WebKit;
using namespace WebCore;
SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(LinkPresentation)
namespace WebKit {
static void replaceViewWithView(UIView *view, UIView *otherView)
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
[otherView setFrame:[view frame]];
[otherView setAutoresizingMask:[view autoresizingMask]];
[[view superview] insertSubview:otherView aboveSubview:view];
[view removeFromSuperview];
[CATransaction commit];
}
enum FullScreenState : NSInteger {
NotInFullScreen,
WaitingToEnterFullScreen,
EnteringFullScreen,
InFullScreen,
WaitingToExitFullScreen,
ExitingFullScreen,
};
struct WKWebViewState {
float _savedTopContentInset = 0.0;
CGFloat _savedPageScale = 1;
CGFloat _savedViewScale = 1.0;
CGFloat _savedZoomScale = 1;
UIEdgeInsets _savedEdgeInset = UIEdgeInsetsZero;
UIEdgeInsets _savedObscuredInsets = UIEdgeInsetsZero;
UIEdgeInsets _savedScrollIndicatorInsets = UIEdgeInsetsZero;
CGPoint _savedContentOffset = CGPointZero;
void applyTo(WKWebView* webView)
{
[webView _setPageScale:_savedPageScale withOrigin:CGPointMake(0, 0)];
[webView _setObscuredInsets:_savedObscuredInsets];
[[webView scrollView] setContentInset:_savedEdgeInset];
[[webView scrollView] setContentOffset:_savedContentOffset];
[[webView scrollView] setScrollIndicatorInsets:_savedScrollIndicatorInsets];
[webView _page]->setTopContentInset(_savedTopContentInset);
[webView _setViewScale:_savedViewScale];
[[webView scrollView] setZoomScale:_savedZoomScale];
}
void store(WKWebView* webView)
{
_savedPageScale = [webView _pageScale];
_savedObscuredInsets = [webView _obscuredInsets];
_savedEdgeInset = [[webView scrollView] contentInset];
_savedContentOffset = [[webView scrollView] contentOffset];
_savedScrollIndicatorInsets = [[webView scrollView] scrollIndicatorInsets];
_savedTopContentInset = [webView _page]->topContentInset();
_savedViewScale = [webView _viewScale];
_savedZoomScale = [[webView scrollView] zoomScale];
}
};
} // namespace WebKit
static const NSTimeInterval kAnimationDuration = 0.2;
#pragma mark -
@interface WKFullscreenAnimationController : NSObject <UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning>
@property (retain, nonatomic) UIViewController* viewController;
@property (nonatomic) CGRect initialFrame;
@property (nonatomic) CGRect finalFrame;
@property (nonatomic, getter=isAnimatingIn) BOOL animatingIn;
@end
@implementation WKFullscreenAnimationController {
CGRect _initialMaskViewBounds;
CGRect _finalMaskViewBounds;
CGAffineTransform _initialAnimatingViewTransform;
CGAffineTransform _finalAnimatingViewTransform;
CGPoint _initialMaskViewCenter;
CGPoint _finalMaskViewCenter;
RetainPtr<UIView> _maskView;
RetainPtr<UIView> _animatingView;
RetainPtr<id<UIViewControllerContextTransitioning>> _context;
CGFloat _initialBackgroundAlpha;
CGFloat _finalBackgroundAlpha;
}
- (void)_createViewsForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
{
_maskView = adoptNS([[UIView alloc] init]);
[_maskView setBackgroundColor:[UIColor blackColor]];
[_maskView setBounds:_initialMaskViewBounds];
[_maskView setCenter:_initialMaskViewCenter];
[_animatingView setMaskView:_maskView.get()];
[_animatingView setTransform:_initialAnimatingViewTransform];
UIView *containerView = [transitionContext containerView];
[containerView addSubview:_animatingView.get()];
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return kAnimationDuration;
}
- (void)configureInitialAndFinalStatesForTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
_context = transitionContext;
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
CGRect inlineFrame = _animatingIn ? _initialFrame : _finalFrame;
CGRect fullscreenFrame = _animatingIn ? _finalFrame : _initialFrame;
_animatingView = _animatingIn ? toView : fromView;
CGRect boundsRect = largestRectWithAspectRatioInsideRect(FloatRect(inlineFrame).size().aspectRatio(), fullscreenFrame);
boundsRect.origin = CGPointZero;
_initialMaskViewBounds = _animatingIn ? boundsRect : [_animatingView bounds];
_initialMaskViewCenter = CGPointMake(CGRectGetMidX([_animatingView bounds]), CGRectGetMidY([_animatingView bounds]));
_finalMaskViewBounds = _animatingIn ? [_animatingView bounds] : boundsRect;
_finalMaskViewCenter = CGPointMake(CGRectGetMidX([_animatingView bounds]), CGRectGetMidY([_animatingView bounds]));
FloatRect scaleRect = smallestRectWithAspectRatioAroundRect(FloatRect(fullscreenFrame).size().aspectRatio(), inlineFrame);
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scaleRect.width() / fullscreenFrame.size.width, scaleRect.height() / fullscreenFrame.size.height);
CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(inlineFrame) - CGRectGetMidX(fullscreenFrame), CGRectGetMidY(inlineFrame) - CGRectGetMidY(fullscreenFrame));
CGAffineTransform finalTransform = CGAffineTransformConcat(scaleTransform, translateTransform);
_initialAnimatingViewTransform = _animatingIn ? finalTransform : CGAffineTransformIdentity;
_finalAnimatingViewTransform = _animatingIn ? CGAffineTransformIdentity : finalTransform;
_initialBackgroundAlpha = _animatingIn ? 0 : 1;
_finalBackgroundAlpha = _animatingIn ? 1 : 0;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
[self configureInitialAndFinalStatesForTransition:transitionContext];
[self _createViewsForTransitionContext:transitionContext];
UIWindow *window = [transitionContext containerView].window;
window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha];
[UIView animateWithDuration:kAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self updateWithProgress:1];
} completion:^(BOOL finished) {
[self animationEnded:![transitionContext transitionWasCancelled]];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
- (void)animationEnded:(BOOL)transitionCompleted
{
if (([self isAnimatingIn] && !transitionCompleted) || (![self isAnimatingIn] && transitionCompleted))
[_animatingView removeFromSuperview];
[_animatingView setMaskView:nil];
_maskView = nil;
_animatingView = nil;
}
- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
[self configureInitialAndFinalStatesForTransition:transitionContext];
[self _createViewsForTransitionContext:transitionContext];
}
- (void)updateWithProgress:(CGFloat)progress
{
CGAffineTransform progressTransform = _initialAnimatingViewTransform;
progressTransform.a += progress * (_finalAnimatingViewTransform.a - _initialAnimatingViewTransform.a);
progressTransform.b += progress * (_finalAnimatingViewTransform.b - _initialAnimatingViewTransform.b);
progressTransform.c += progress * (_finalAnimatingViewTransform.c - _initialAnimatingViewTransform.c);
progressTransform.d += progress * (_finalAnimatingViewTransform.d - _initialAnimatingViewTransform.d);
progressTransform.tx += progress * (_finalAnimatingViewTransform.tx - _initialAnimatingViewTransform.tx);
progressTransform.ty += progress * (_finalAnimatingViewTransform.ty - _initialAnimatingViewTransform.ty);
[_animatingView setTransform:progressTransform];
CGRect progressBounds = _initialMaskViewBounds;
progressBounds.origin.x += progress * (_finalMaskViewBounds.origin.x - _initialMaskViewBounds.origin.x);
progressBounds.origin.y += progress * (_finalMaskViewBounds.origin.y - _initialMaskViewBounds.origin.y);
progressBounds.size.width += progress * (_finalMaskViewBounds.size.width - _initialMaskViewBounds.size.width);
progressBounds.size.height += progress * (_finalMaskViewBounds.size.height - _initialMaskViewBounds.size.height);
[_maskView setBounds:progressBounds];
CGPoint progressCenter = _initialMaskViewCenter;
progressCenter.x += progress * (_finalMaskViewCenter.x - _finalMaskViewCenter.x);
progressCenter.y += progress * (_finalMaskViewCenter.y - _finalMaskViewCenter.y);
[_maskView setCenter:progressCenter];
UIWindow *window = [_animatingView window];
window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
}
- (void)updateWithProgress:(CGFloat)progress translation:(CGSize)translation anchor:(CGPoint)anchor
{
CGAffineTransform progressTransform = _initialAnimatingViewTransform;
progressTransform.a += progress * (_finalAnimatingViewTransform.a - _initialAnimatingViewTransform.a);
progressTransform.b += progress * (_finalAnimatingViewTransform.b - _initialAnimatingViewTransform.b);
progressTransform.c += progress * (_finalAnimatingViewTransform.c - _initialAnimatingViewTransform.c);
progressTransform.d += progress * (_finalAnimatingViewTransform.d - _initialAnimatingViewTransform.d);
progressTransform.tx += _initialAnimatingViewTransform.tx + translation.width;
progressTransform.ty += _initialAnimatingViewTransform.ty + translation.height;
[_animatingView setTransform:progressTransform];
UIWindow *window = [_animatingView window];
window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
}
- (void)end:(BOOL)cancelled {
if (cancelled) {
[UIView animateWithDuration:kAnimationDuration animations:^{
[self updateWithProgress:0];
} completion:^(BOOL finished) {
[_context cancelInteractiveTransition];
[_context completeTransition:NO];
}];
} else {
[UIView animateWithDuration:kAnimationDuration animations:^{
[self updateWithProgress:1];
} completion:^(BOOL finished) {
[_context finishInteractiveTransition];
[_context completeTransition:YES];
}];
}
}
@end
#pragma mark -
@interface WKFullScreenInteractiveTransition : NSObject<UIViewControllerInteractiveTransitioning>
- (id)initWithAnimator:(WKFullscreenAnimationController *)animator anchor:(CGPoint)point;
@end
@implementation WKFullScreenInteractiveTransition {
RetainPtr<WKFullscreenAnimationController> _animator;
RetainPtr<id<UIViewControllerContextTransitioning>> _context;
CGPoint _anchor;
}
- (id)initWithAnimator:(WKFullscreenAnimationController *)animator anchor:(CGPoint)point
{
if (!(self = [super init]))
return nil;
_animator = animator;
_anchor = point;
return self;
}
- (BOOL)wantsInteractiveStart
{
return YES;
}
- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
_context = transitionContext;
[_animator startInteractiveTransition:transitionContext];
}
- (void)updateInteractiveTransition:(CGFloat)progress withTranslation:(CGSize)translation
{
[_animator updateWithProgress:progress translation:translation anchor:_anchor];
[_context updateInteractiveTransition:progress];
}
- (void)cancelInteractiveTransition
{
[_animator end:YES];
}
- (void)finishInteractiveTransition
{
[_animator end:NO];
}
@end
#pragma mark -
@interface WKFullScreenWindowController () <UIGestureRecognizerDelegate>
@end
@implementation WKFullScreenWindowController {
WKWebView *_webView; // Cannot be retained, see <rdar://problem/14884666>.
RetainPtr<UIView> _webViewPlaceholder;
FullScreenState _fullScreenState;
WKWebViewState _viewState;
RetainPtr<UIWindow> _window;
RetainPtr<UIViewController> _rootViewController;
RefPtr<WebKit::VoidCallback> _repaintCallback;
RetainPtr<UIViewController> _viewControllerForPresentation;
RetainPtr<WKFullScreenViewController> _fullscreenViewController;
RetainPtr<UISwipeGestureRecognizer> _startDismissGestureRecognizer;
RetainPtr<UIPanGestureRecognizer> _interactiveDismissGestureRecognizer;
RetainPtr<WKFullScreenInteractiveTransition> _interactiveDismissTransitionCoordinator;
CGRect _initialFrame;
CGRect _finalFrame;
RetainPtr<NSString> _EVOrganizationName;
BOOL _EVOrganizationNameIsValid;
BOOL _inInteractiveDismiss;
RetainPtr<id> _notificationListener;
}
#pragma mark -
#pragma mark Initialization
- (id)initWithWebView:(WKWebView *)webView
{
if (!(self = [super init]))
return nil;
_webView = webView;
return self;
}
- (void)dealloc
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
#pragma mark -
#pragma mark Accessors
- (BOOL)isFullScreen
{
return _fullScreenState == WaitingToEnterFullScreen
|| _fullScreenState == EnteringFullScreen
|| _fullScreenState == InFullScreen;
}
- (UIView *)webViewPlaceholder
{
return _webViewPlaceholder.get();
}
#pragma mark -
#pragma mark External Interface
- (void)enterFullScreen
{
if ([self isFullScreen])
return;
[self _invalidateEVOrganizationName];
_fullScreenState = WaitingToEnterFullScreen;
_window = adoptNS([[UIWindow alloc] init]);
[_window setBackgroundColor:[UIColor clearColor]];
[_window setWindowLevel:UIWindowLevelNormal - 1];
[_window setHidden:NO];
_rootViewController = [[UIViewController alloc] init];
_rootViewController.get().view = [[[UIView alloc] initWithFrame:_window.get().bounds] autorelease];
_rootViewController.get().view.backgroundColor = [UIColor clearColor];
_rootViewController.get().view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
[_rootViewController setModalPresentationStyle:UIModalPresentationCustom];
[_rootViewController setTransitioningDelegate:self];
_window.get().rootViewController = _rootViewController.get();
_fullscreenViewController = adoptNS([[WKFullScreenViewController alloc] initWithWebView:_webView]);
[_fullscreenViewController setModalPresentationStyle:UIModalPresentationCustom];
[_fullscreenViewController setTransitioningDelegate:self];
[_fullscreenViewController setModalPresentationCapturesStatusBarAppearance:YES];
[_fullscreenViewController setTarget:self];
[_fullscreenViewController setAction:@selector(requestExitFullScreen)];
_fullscreenViewController.get().view.frame = _rootViewController.get().view.bounds;
[self _updateLocationInfo];
_startDismissGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(_startToDismissFullscreenChanged:)]);
[_startDismissGestureRecognizer setDelegate:self];
[_startDismissGestureRecognizer setCancelsTouchesInView:YES];
[_startDismissGestureRecognizer setNumberOfTouchesRequired:1];
[_startDismissGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
[_fullscreenViewController.get().view addGestureRecognizer:_startDismissGestureRecognizer.get()];
_interactiveDismissGestureRecognizer = adoptNS([[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_interactiveDismissChanged:)]);
[_interactiveDismissGestureRecognizer setDelegate:self];
[_interactiveDismissGestureRecognizer setCancelsTouchesInView:NO];
[_fullscreenViewController.get().view addGestureRecognizer:_interactiveDismissGestureRecognizer.get()];
[self _manager]->saveScrollPosition();
[_webView _page]->setSuppressVisibilityUpdates(true);
_viewState.store(_webView);
_webViewPlaceholder = adoptNS([[UIView alloc] init]);
[[_webViewPlaceholder layer] setName:@"Fullscreen Placeholder View"];
WKSnapshotConfiguration* config = nil;
[_webView takeSnapshotWithConfiguration:config completionHandler:^(UIImage * snapshotImage, NSError * error) {
if (![_webView _page])
return;
[CATransaction begin];
[CATransaction setDisableActions:YES];
[[_webViewPlaceholder layer] setContents:(id)[snapshotImage CGImage]];
replaceViewWithView(_webView, _webViewPlaceholder.get());
WKWebViewState().applyTo(_webView);
[_webView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[_webView setFrame:[_window bounds]];
[_webView _overrideLayoutParametersWithMinimumLayoutSize:[_window bounds].size maximumUnobscuredSizeOverride:[_window bounds].size];
[_window insertSubview:_webView atIndex:0];
[_webView setNeedsLayout];
[_webView layoutIfNeeded];
[self _manager]->setAnimatingFullScreen(true);
_repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
_repaintCallback = nullptr;
if (auto* manager = [protectedSelf _manager]) {
manager->willEnterFullScreen();
return;
}
ASSERT_NOT_REACHED();
[self _exitFullscreenImmediately];
});
[_webView _page]->forceRepaint(_repaintCallback.copyRef());
[CATransaction commit];
}];
}
- (void)beganEnterFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
{
if (_fullScreenState != WaitingToEnterFullScreen)
return;
_fullScreenState = EnteringFullScreen;
_initialFrame = initialFrame;
_finalFrame = finalFrame;
[CATransaction begin];
[CATransaction setDisableActions:YES];
[_webView removeFromSuperview];
[_window setWindowLevel:UIWindowLevelNormal];
[_window makeKeyAndVisible];
[_fullscreenViewController setPrefersStatusBarHidden:NO];
[_fullscreenViewController showUI];
[CATransaction commit];
[_rootViewController presentViewController:_fullscreenViewController.get() animated:YES completion:^{
_fullScreenState = InFullScreen;
auto* page = [_webView _page];
auto* manager = self._manager;
if (page && manager) {
manager->didEnterFullScreen();
manager->setAnimatingFullScreen(false);
page->setSuppressVisibilityUpdates(false);
return;
}
ASSERT_NOT_REACHED();
[self _exitFullscreenImmediately];
}];
}
- (void)requestExitFullScreen
{
if (auto* manager = self._manager) {
manager->requestExitFullScreen();
return;
}
ASSERT_NOT_REACHED();
[self _exitFullscreenImmediately];
}
- (void)exitFullScreen
{
if (!self.isFullScreen)
return;
_fullScreenState = WaitingToExitFullScreen;
if (auto* manager = self._manager) {
manager->setAnimatingFullScreen(true);
manager->willExitFullScreen();
return;
}
ASSERT_NOT_REACHED();
[self _exitFullscreenImmediately];
}
- (void)beganExitFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
{
if (_fullScreenState != WaitingToExitFullScreen)
return;
_fullScreenState = ExitingFullScreen;
_initialFrame = initialFrame;
_finalFrame = finalFrame;
[_webView _page]->setSuppressVisibilityUpdates(true);
[_fullscreenViewController setPrefersStatusBarHidden:NO];
if (_interactiveDismissTransitionCoordinator) {
[_interactiveDismissTransitionCoordinator finishInteractiveTransition];
_interactiveDismissTransitionCoordinator = nil;
return;
}
[_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
if (![_webView _page])
return;
[self _completedExitFullScreen];
}];
}
- (void)_completedExitFullScreen
{
if (_fullScreenState != ExitingFullScreen)
return;
_fullScreenState = NotInFullScreen;
[CATransaction begin];
[CATransaction setDisableActions:YES];
[[_webViewPlaceholder superview] insertSubview:_webView belowSubview:_webViewPlaceholder.get()];
[_webView setFrame:[_webViewPlaceholder frame]];
[_webView setAutoresizingMask:[_webViewPlaceholder autoresizingMask]];
[[_webView window] makeKeyAndVisible];
_viewState.applyTo(_webView);
[_webView setNeedsLayout];
[_webView layoutIfNeeded];
[CATransaction commit];
[_window setHidden:YES];
_window = nil;
if (auto* manager = self._manager) {
manager->setAnimatingFullScreen(false);
manager->didExitFullScreen();
}
if (_repaintCallback) {
_repaintCallback->invalidate(WebKit::CallbackBase::Error::OwnerWasInvalidated);
ASSERT(!_repaintCallback);
}
_repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
_repaintCallback = nullptr;
[_webViewPlaceholder removeFromSuperview];
if (![_webView _page])
return;
[_webView _page]->setSuppressVisibilityUpdates(false);
});
if (auto* page = [_webView _page])
page->forceRepaint(_repaintCallback.copyRef());
else
_repaintCallback->performCallback();
[_fullscreenViewController setPrefersStatusBarHidden:YES];
}
- (void)close
{
[self _exitFullscreenImmediately];
_webView = nil;
}
- (void)webViewDidRemoveFromSuperviewWhileInFullscreen
{
if (_fullScreenState == InFullScreen && _webView.window != _window.get())
[self _exitFullscreenImmediately];
}
- (void)videoControlsManagerDidChange
{
if (_fullscreenViewController)
[_fullscreenViewController videoControlsManagerDidChange];
}
#pragma mark -
#pragma mark UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
#pragma mark -
#pragma mark UIViewControllerTransitioningDelegate
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
[animationController setViewController:presented];
[animationController setInitialFrame:_initialFrame];
[animationController setFinalFrame:_finalFrame];
[animationController setAnimatingIn:YES];
return animationController.autorelease();
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
CGRect initialFrame = _initialFrame;
CGRect finalFrame = _finalFrame;
// Because we're not calling "requestExitFullscreen()" at the beginning of an interactive animation,
// the _initialFrame and _finalFrame values are left over from when we entered fullscreen.
if (_inInteractiveDismiss)
std::swap(initialFrame, finalFrame);
RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
[animationController setViewController:dismissed];
[animationController setInitialFrame:initialFrame];
[animationController setFinalFrame:finalFrame];
[animationController setAnimatingIn:NO];
return animationController.autorelease();
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
{
if (!_inInteractiveDismiss)
return nil;
if (![animator isKindOfClass:[WKFullscreenAnimationController class]])
return nil;
if (!_interactiveDismissTransitionCoordinator)
_interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:CGPointZero]);
return _interactiveDismissTransitionCoordinator.get();
}
#pragma mark -
#pragma mark Internal Interface
- (void)_exitFullscreenImmediately
{
if (![self isFullScreen])
return;
auto* manager = self._manager;
if (manager)
manager->requestExitFullScreen();
[self exitFullScreen];
_fullScreenState = ExitingFullScreen;
[self _completedExitFullScreen];
replaceViewWithView(_webViewPlaceholder.get(), _webView);
if (auto* page = [_webView _page])
page->setSuppressVisibilityUpdates(false);
if (manager) {
manager->didExitFullScreen();
manager->setAnimatingFullScreen(false);
}
_webViewPlaceholder = nil;
}
- (void)_invalidateEVOrganizationName
{
_EVOrganizationName = nil;
_EVOrganizationNameIsValid = NO;
}
- (BOOL)_isSecure
{
return _webView.hasOnlySecureContent;
}
- (SecTrustRef)_serverTrust
{
return _webView.serverTrust;
}
- (NSString *)_EVOrganizationName
{
if (!self._isSecure)
return nil;
if (_EVOrganizationNameIsValid)
return _EVOrganizationName.get();
ASSERT(!_EVOrganizationName.get());
_EVOrganizationNameIsValid = YES;
SecTrustRef trust = [self _serverTrust];
if (!trust)
return nil;
NSDictionary *infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
// If SecTrustCopyInfo returned NULL then it's likely that the SecTrustRef has not been evaluated
// and the only way to get the information we need is to call SecTrustEvaluate ourselves.
if (!infoDictionary) {
SecTrustResultType result = kSecTrustResultProceed;
OSStatus err = SecTrustEvaluate(trust, &result);
if (err == noErr)
infoDictionary = [(__bridge NSDictionary *)SecTrustCopyInfo(trust) autorelease];
if (!infoDictionary)
return nil;
}
// Make sure that the EV certificate is valid against our certificate chain.
id hasEV = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
if (![hasEV isKindOfClass:[NSValue class]] || ![hasEV boolValue])
return nil;
// Make sure that we could contact revocation server and it is still valid.
id isNotRevoked = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoRevocationKey];
if (![isNotRevoked isKindOfClass:[NSValue class]] || ![isNotRevoked boolValue])
return nil;
_EVOrganizationName = [infoDictionary objectForKey:(__bridge NSString *)kSecTrustInfoCompanyNameKey];
return _EVOrganizationName.get();
}
- (void)_updateLocationInfo
{
NSURL* url = _webView._committedURL;
NSString *EVOrganizationName = [self _EVOrganizationName];
BOOL showsEVOrganizationName = [EVOrganizationName length] > 0;
NSString *domain = nil;
if (LinkPresentationLibrary())
domain = [url _lp_simplifiedDisplayString];
else
domain = userVisibleString(url);
NSString *text = nil;
if ([[url scheme] caseInsensitiveCompare:@"data"] == NSOrderedSame)
text = @"data:";
else if (showsEVOrganizationName)
text = EVOrganizationName;
else
text = domain;
[_fullscreenViewController setLocation:text];
}
- (WebFullScreenManagerProxy*)_manager
{
if (![_webView _page])
return nullptr;
return [_webView _page]->fullScreenManager();
}
- (void)_startToDismissFullscreenChanged:(id)sender
{
_inInteractiveDismiss = true;
[_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
if (![_webView _page])
return;
[self _completedExitFullScreen];
[_fullscreenViewController setPrefersStatusBarHidden:YES];
}];
}
- (void)_interactiveDismissChanged:(id)sender
{
if (!_inInteractiveDismiss)
return;
CGPoint translation = [_interactiveDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
CGPoint velocity = [_interactiveDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
CGFloat progress = translation.y / (_fullscreenViewController.get().view.bounds.size.height / 2);
progress = std::min(1., std::max(0., progress));
if (_interactiveDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
_inInteractiveDismiss = false;
if (progress > 0.25 || (progress > 0 && velocity.y > 5))
[self requestExitFullScreen];
else {
[_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
_interactiveDismissTransitionCoordinator = nil;
}
return;
}
[_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withTranslation:CGSizeMake(translation.x, translation.y)];
}
@end
#endif // PLATFORM(IOS) && ENABLE(FULLSCREEN_API)