WKFullScreenWindowControllerIOS.mm [plain text]
/*
* 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 "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 const NSTimeInterval showHideAnimationDuration = 0.1;
static const NSTimeInterval autoHideDelay = 4.0;
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
@interface _WKFullScreenViewController : UIViewController <UIGestureRecognizerDelegate>
@property (retain, nonatomic) NSArray *savedConstraints;
@property (retain, nonatomic) UIView *contentView;
@property (retain, nonatomic) id target;
@property (assign, nonatomic) SEL action;
@end
@implementation _WKFullScreenViewController {
RetainPtr<UIView> _backgroundView;
RetainPtr<UILongPressGestureRecognizer> _touchGestureRecognizer;
RetainPtr<UIButton> _cancelButton;
RetainPtr<UIButton> _locationButton;
RetainPtr<UIVisualEffectView> _visualEffectView;
}
- (void)dealloc
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
void (^webViewUpdateBlock)() = ^{
[(WKWebView *)[self contentView] _overrideLayoutParametersWithMinimumLayoutSize:size maximumUnobscuredSizeOverride:size];
};
[(WKWebView *)[self contentView] _beginAnimatedResizeWithUpdates:webViewUpdateBlock];
[(WKWebView *)[self contentView] _setInterfaceOrientationOverride:[UIApp statusBarOrientation]];
} completion:^(id <UIViewControllerTransitionCoordinatorContext>context) {
[(WKWebView *)[self contentView] _endAnimatedResize];
}];
}
+ (void)configureView:(UIView *)view withBackgroundFillOfColor:(UIColor *)fillColor opacity:(CGFloat)opacity filter:(NSString *)filter
{
_UIVisualEffectLayerConfig *baseLayerConfig = [_UIVisualEffectLayerConfig layerWithFillColor:fillColor opacity:opacity filterType:filter];
[[[_UIVisualEffectConfig configWithContentConfig:baseLayerConfig] contentConfig] configureLayerView:view];
}
- (void)_updateTransparencyOfVisualEffectView:(UIVisualEffectView *)visualEffectView
{
RetainPtr<UIVisualEffect> visualEffect;
if (UIAccessibilityIsReduceTransparencyEnabled()) {
visualEffect = [UIVisualEffect emptyEffect];
[[visualEffectView contentView] setBackgroundColor:[UIColor colorWithRed:(43.0 / 255.0) green:(46.0 / 255.0) blue:(48.0 / 255.0) alpha:1.0]];
} else {
RetainPtr<UIColorEffect> saturationEffect = [UIColorEffect colorEffectSaturate:1.8];
RetainPtr<UIBlurEffect> blurEffect = [UIBlurEffect effectWithBlurRadius:UIRoundToScreenScale(17.5, [UIScreen mainScreen])];
RetainPtr<UIVisualEffect> combinedEffects = [UIVisualEffect effectCombiningEffects:@[blurEffect.get(), saturationEffect.get()]];
visualEffect = combinedEffects;
[[visualEffectView contentView] setBackgroundColor:nil];
}
[visualEffectView setEffect:visualEffect.get()];
}
- (UIVisualEffectView *)visualEffectViewWithFrame:(CGRect)frame
{
RetainPtr<UIVisualEffectView> visualEffectView = adoptNS([[UIVisualEffectView alloc] initWithEffect:[UIVisualEffect emptyEffect]]);
[self _updateTransparencyOfVisualEffectView:visualEffectView.get()];
RetainPtr<UIView> backLayerTintView = adoptNS([[UIView alloc] initWithFrame:[visualEffectView bounds]]);
[backLayerTintView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[backLayerTintView setUserInteractionEnabled:NO];
[[self class] configureView:backLayerTintView.get() withBackgroundFillOfColor:[UIColor colorWithWhite:0.0 alpha:0.55] opacity:1.0 filter:kCAFilterNormalBlendMode];
[[visualEffectView contentView] addSubview:backLayerTintView.get()];
RetainPtr<UIView> topLayerTintView = adoptNS([[UIView alloc] initWithFrame:[visualEffectView bounds]]);
[topLayerTintView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[topLayerTintView setUserInteractionEnabled:NO];
[[self class] configureView:topLayerTintView.get() withBackgroundFillOfColor:[UIColor colorWithWhite:1.0 alpha:0.14] opacity:1.0 filter:kCAFilterNormalBlendMode];
[[visualEffectView contentView] addSubview:topLayerTintView.get()];
[self _updateTransparencyOfVisualEffectView:visualEffectView.get()];
return visualEffectView.autorelease();
}
static UIEdgeInsets mirrorEdgeInsets(UIEdgeInsets insets)
{
return UIEdgeInsetsMake(insets.top, insets.right, insets.bottom, insets.left);
}
- (void)_updateLayoutMargins
{
UIView *view = [self view];
[view setPreservesSuperviewLayoutMargins:NO];
UIEdgeInsets targetInsets = [view safeAreaInsets];
targetInsets.top = std::max(targetInsets.top, 20.0);
[view setLayoutMargins:targetInsets];
}
- (void)viewDidLayoutSubviews
{
[self _updateLayoutMargins];
}
- (void)setLocation:(NSString *)locationName secure:(BOOL)secure trustedName:(BOOL)trustedName trustedSite:(BOOL)trustedSite
{
UIColor *greenTint = [UIColor colorWithRed:100 / 255.0 green:175 / 255.0 blue:99 / 255.0 alpha:1.0];
UIColor *whiteTint = [UIColor whiteColor];
float hPadding = 14;
NSString *lockImageName = @"LockMini";
float lockSpacing = secure ? 10 : 0;
UIEdgeInsets locationContentEdgeInsets = UIEdgeInsetsMake(0, hPadding+lockSpacing, 0, hPadding);
UIEdgeInsets locationImageEdgeInsets = UIEdgeInsetsMake(0, -lockSpacing, 0, 0);
if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:[[self view] semanticContentAttribute]] == UIUserInterfaceLayoutDirectionRightToLeft) {
locationContentEdgeInsets = mirrorEdgeInsets(locationContentEdgeInsets);
locationImageEdgeInsets = mirrorEdgeInsets(locationImageEdgeInsets);
[_locationButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentRight];
}
[_locationButton setTitleColor:(trustedName ? greenTint : whiteTint) forState:UIControlStateNormal];
if (secure) {
NSBundle *bundle = [NSBundle bundleForClass:[WKFullScreenWindowController class]];
UIImage *lockImage = [UIImage imageNamed:lockImageName inBundle:bundle compatibleWithTraitCollection:nil];
[_locationButton setImage:[lockImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
[[_locationButton imageView] setTintColor:trustedSite ? greenTint : whiteTint];
[_locationButton setTintColor:trustedSite ? greenTint : whiteTint];
} else
[_locationButton setImage:nil forState:UIControlStateNormal];
[_locationButton setContentEdgeInsets:locationContentEdgeInsets];
[_locationButton setImageEdgeInsets:locationImageEdgeInsets];
[_locationButton setTitle:locationName forState:UIControlStateNormal];
[[_locationButton titleLabel] setLineBreakMode:NSLineBreakByTruncatingTail];
[[_locationButton titleLabel] setAdjustsFontSizeToFitWidth:NO];
}
- (void)createSubviews
{
_visualEffectView = [self visualEffectViewWithFrame:CGRectMake(0, 0, 20, 20)];
[_visualEffectView setTranslatesAutoresizingMaskIntoConstraints:NO];
[[self view] addSubview:_visualEffectView.get()];
_cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_cancelButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[_cancelButton setAdjustsImageWhenHighlighted:NO];
[_cancelButton setBackgroundColor: [UIColor blackColor]];
[[_cancelButton layer] setCompositingFilter:[CAFilter filterWithType:kCAFilterPlusL]];
[_cancelButton setTitle:WEB_UI_STRING("Done", "Text of button that exits element fullscreen.") forState:UIControlStateNormal];
[_cancelButton setTintColor:[UIColor whiteColor]];
[_cancelButton addTarget:self action:@selector(cancelAction:) forControlEvents:UIControlEventTouchUpInside];
[[self view] addSubview:_cancelButton.get()];
_locationButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_locationButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[_locationButton setAdjustsImageWhenHighlighted:NO];
[_locationButton setBackgroundColor:[UIColor blackColor]];
[[_locationButton layer] setCompositingFilter:[CAFilter filterWithType:kCAFilterPlusL]];
[[self view] addSubview:_locationButton.get()];
UILayoutGuide* containerGuide = [[self view] layoutMarginsGuide];
[[[_visualEffectView leftAnchor] constraintEqualToAnchor:[[self view] leftAnchor]] setActive:YES];
[[[_visualEffectView rightAnchor] constraintEqualToAnchor:[[self view] rightAnchor]] setActive:YES];
[[[_visualEffectView topAnchor] constraintEqualToAnchor:[[self view] topAnchor]] setActive:YES];
[[[_visualEffectView heightAnchor] constraintGreaterThanOrEqualToConstant:20] setActive:YES];
NSLayoutConstraint *bottom = [[_visualEffectView bottomAnchor] constraintEqualToAnchor:[containerGuide topAnchor]];
[bottom setPriority:UILayoutPriorityRequired - 1];
[bottom setActive:YES];
[[[_cancelButton leadingAnchor] constraintEqualToAnchor:[containerGuide leadingAnchor]] setActive:YES];
[[[_cancelButton topAnchor] constraintEqualToAnchor:[[self view] topAnchor]] setActive:YES];
[[[_cancelButton bottomAnchor] constraintEqualToAnchor:[_visualEffectView bottomAnchor]] setActive:YES];
[[[_locationButton heightAnchor] constraintEqualToConstant:20] setActive:YES];
[[[_locationButton bottomAnchor] constraintEqualToAnchor:[_visualEffectView bottomAnchor]] setActive:YES];
[[[_locationButton leadingAnchor] constraintGreaterThanOrEqualToAnchor:[_cancelButton trailingAnchor]] setActive:YES];
[[[_locationButton trailingAnchor] constraintLessThanOrEqualToAnchor:[[self view] trailingAnchor]] setActive:YES];
NSLayoutConstraint *centeringConstraint = [[_locationButton centerXAnchor] constraintEqualToAnchor:[[self view] centerXAnchor]];
[centeringConstraint setPriority:UILayoutPriorityDefaultLow];
[centeringConstraint setActive:YES];
[_visualEffectView setAlpha:0];
[_cancelButton setAlpha:0];
[_locationButton setAlpha:0];
[_visualEffectView setHidden:YES];
[_cancelButton setHidden:YES];
[_locationButton setHidden:YES];
}
- (void)loadView
{
[self setView:adoptNS([[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]).get()];
[[self view] setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
_touchGestureRecognizer = adoptNS([[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showCancelButton:)]);
[_touchGestureRecognizer setDelegate:self];
[_touchGestureRecognizer setCancelsTouchesInView:NO];
[_touchGestureRecognizer setMinimumPressDuration:0];
[[self view] addGestureRecognizer:_touchGestureRecognizer.get()];
[self createSubviews];
}
- (void)viewWillAppear:(BOOL)animated
{
[[self contentView] setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[[self contentView] setFrame:[[self view] bounds]];
[[self view] insertSubview:[self contentView] atIndex:0];
}
- (void)viewDidAppear:(BOOL)animated
{
[self _updateLayoutMargins];
}
- (void)cancelAction:(id)sender
{
[[self target] performSelector:[self action]];
}
- (void)hideCancelButton
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideCancelButton) object:nil];
[UIView animateWithDuration:showHideAnimationDuration animations:^{
[_visualEffectView setAlpha:0];
[_cancelButton setAlpha:0];
[_locationButton setAlpha:0];
} completion:^(BOOL finished){
if (finished) {
[_cancelButton setHidden:YES];
[_locationButton setHidden:YES];
[_visualEffectView setHidden:YES];
}
}];
}
- (void)showCancelButton:(id)sender
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideCancelButton) object:nil];
[self performSelector:@selector(hideCancelButton) withObject:nil afterDelay:autoHideDelay];
[UIView animateWithDuration:showHideAnimationDuration animations: ^{
[_visualEffectView setHidden:NO];
[_cancelButton setHidden:NO];
[_locationButton setHidden:NO];
[_visualEffectView setAlpha:1];
[_cancelButton setAlpha:1];
[_locationButton setAlpha:1];
}];
}
- (void)setTarget:(id)target action:(SEL)action
{
[self setTarget:target];
[self setAction:action];
}
// MARK - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
@end
@interface _WKFullscreenRootViewController : UIViewController
@end
@implementation _WKFullscreenRootViewController {
BOOL _showsStatusBar;
}
- (void)setShowsStatusBar:(BOOL)value
{
_showsStatusBar = value;
[self setNeedsStatusBarAppearanceUpdate];
}
- (BOOL)prefersStatusBarHidden
{
return !_showsStatusBar;
}
@end
@interface WKFullscreenAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
@property (retain, nonatomic) UIViewController* viewController;
@property (nonatomic) CGRect initialFrame;
@property (nonatomic) CGRect finalFrame;
@property (nonatomic, getter=isAnimatingIn) BOOL animatingIn;
@end
@implementation WKFullscreenAnimationController
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
const NSTimeInterval animationDuration = 0.2;
return animationDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *containerView = [transitionContext containerView];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
CGRect inlineFrame = _animatingIn ? _initialFrame : _finalFrame;
CGRect fullscreenFrame = _animatingIn ? _finalFrame : _initialFrame;
UIView *animatingView = _animatingIn ? toView : fromView;
CGRect boundsRect = largestRectWithAspectRatioInsideRect(FloatRect(inlineFrame).size().aspectRatio(), fullscreenFrame);
boundsRect.origin = CGPointZero;
RetainPtr<UIView> maskView = adoptNS([[UIView alloc] init]);
[maskView setBackgroundColor:[UIColor blackColor]];
[maskView setBounds:_animatingIn ? boundsRect : [animatingView bounds]];
[maskView setCenter:CGPointMake(CGRectGetMidX([animatingView bounds]), CGRectGetMidY([animatingView bounds]))];
[animatingView setMaskView:maskView.get()];
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);
[animatingView setTransform:_animatingIn ? finalTransform : CGAffineTransformIdentity];
[containerView addSubview:animatingView];
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[animatingView setTransform:_animatingIn ? CGAffineTransformIdentity : finalTransform];
[maskView setBounds:_animatingIn ? animatingView.bounds : boundsRect];
[maskView setCenter:CGPointMake(CGRectGetMidX([animatingView bounds]), CGRectGetMidY([animatingView bounds]))];
} completion:^(BOOL finished){
BOOL success = ![transitionContext transitionWasCancelled];
if (([self isAnimatingIn] && !success) || (![self isAnimatingIn] && success))
[animatingView removeFromSuperview];
[transitionContext completeTransition:success];
[animatingView setMaskView:nil];
}];
}
- (void)animationEnded:(BOOL)transitionCompleted
{
}
@end
@implementation WKFullScreenWindowController {
WKWebView *_webView; // Cannot be retained, see <rdar://problem/14884666>.
RetainPtr<UIView> _webViewPlaceholder;
FullScreenState _fullScreenState;
WKWebViewState _viewState;
RetainPtr<UIWindow> _window;
RetainPtr<_WKFullscreenRootViewController> _rootViewController;
RefPtr<WebKit::VoidCallback> _repaintCallback;
RetainPtr<UIViewController> _viewControllerForPresentation;
RetainPtr<_WKFullScreenViewController> _fullscreenViewController;
CGRect _initialFrame;
CGRect _finalFrame;
RetainPtr<NSString> _EVOrganizationName;
BOOL _EVOrganizationNameIsValid;
}
#pragma mark -
#pragma mark Initialization
- (id)initWithWebView:(WKWebView *)webView
{
if (![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;
}
- (WebCoreFullScreenPlaceholderView *)webViewPlaceholder
{
return nil;
}
#pragma mark -
#pragma mark Exposed Interface
- (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) {
OSStatus err = SecTrustEvaluate(trust, NULL);
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 secure:self.isSecure trustedName:showsEVOrganizationName trustedSite:!!EVOrganizationName];
}
- (void)enterFullScreen
{
if ([self isFullScreen])
return;
[self _invalidateEVOrganizationName];
_fullScreenState = WaitingToEnterFullScreen;
_window = adoptNS([[UIWindow alloc] init]);
[_window setBackgroundColor:[UIColor clearColor]];
_rootViewController = adoptNS([[_WKFullscreenRootViewController alloc] init]);
[_window setRootViewController:_rootViewController.get()];
[[_window rootViewController] setView:adoptNS([[UIView alloc] initWithFrame:[_window bounds]]).get()];
[[[_window rootViewController] view] setBackgroundColor:[UIColor clearColor]];
[[[_window rootViewController] view] setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[_window setWindowLevel:UIWindowLevelNormal - 1];
[_window setHidden:NO];
_viewControllerForPresentation = [_window rootViewController];
_fullscreenViewController = adoptNS([[_WKFullScreenViewController alloc] init]);
[_fullscreenViewController setTransitioningDelegate:self];
[_fullscreenViewController setModalPresentationStyle:UIModalPresentationCustom];
[_fullscreenViewController setTarget:self action:@selector(requestExitFullScreen)];
[[_fullscreenViewController view] setFrame:[[_viewControllerForPresentation view] bounds]];
[self updateLocationInfo];
[self _manager]->saveScrollPosition();
[_webView _page]->setSuppressVisibilityUpdates(true);
_viewState.store(_webView);
_webViewPlaceholder = adoptNS([[UIView alloc] init]);
[[_webViewPlaceholder layer] setName:@"Fullscreen Placeholder Vfiew"];
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]];
[_window insertSubview:_webView atIndex:0];
[_webView _overrideLayoutParametersWithMinimumLayoutSize:[_window bounds].size maximumUnobscuredSizeOverride:[_window bounds].size];
[_webView setNeedsLayout];
[_webView layoutIfNeeded];
[self _manager]->setAnimatingFullScreen(true);
_repaintCallback = VoidCallback::create([protectedSelf = retainPtr(self), self](WebKit::CallbackBase::Error) {
_repaintCallback = nullptr;
if (![_webView _page])
return;
[protectedSelf _manager]->willEnterFullScreen();
});
[_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];
[_fullscreenViewController setContentView:_webView];
[_window setWindowLevel:UIWindowLevelNormal];
[_window makeKeyAndVisible];
[_rootViewController setShowsStatusBar:YES];
[CATransaction commit];
[_viewControllerForPresentation presentViewController:_fullscreenViewController.get() animated:YES completion:^{
[self completedEnterFullScreen];
[_rootViewController setShowsStatusBar:NO];
}];
}
- (void)completedEnterFullScreen
{
if (![_webView _page])
return;
_fullScreenState = InFullScreen;
[self _manager]->didEnterFullScreen();
[self _manager]->setAnimatingFullScreen(false);
[_webView _page]->setSuppressVisibilityUpdates(false);
}
- (void)exitFullScreen
{
if (![self isFullScreen])
return;
_fullScreenState = WaitingToExitFullScreen;
[self _manager]->setAnimatingFullScreen(true);
[self _manager]->willExitFullScreen();
}
- (void)requestExitFullScreen
{
[self _manager]->requestExitFullScreen();
}
- (void)beganExitFullScreenWithInitialFrame:(CGRect)initialFrame finalFrame:(CGRect)finalFrame
{
if (_fullScreenState != WaitingToExitFullScreen)
return;
_fullScreenState = ExitingFullScreen;
_initialFrame = initialFrame;
_finalFrame = finalFrame;
[_webView _page]->setSuppressVisibilityUpdates(true);
[_rootViewController setShowsStatusBar:YES];
[_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
if (![_webView _page])
return;
[self completedExitFullScreen];
[_rootViewController setShowsStatusBar:NO];
}];
}
- (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;
[self _manager]->setAnimatingFullScreen(false);
[self _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);
});
[_webView _page]->forceRepaint(_repaintCallback.copyRef());
}
- (void)exitFullscreenImmediately
{
if (![self isFullScreen])
return;
if (![_webView _page])
return;
[self _manager]->requestExitFullScreen();
[self exitFullScreen];
_fullScreenState = ExitingFullScreen;
[self completedExitFullScreen];
replaceViewWithView(_webViewPlaceholder.get(), _webView);
[_webView _page]->setSuppressVisibilityUpdates(false);
[self _manager]->didExitFullScreen();
[self _manager]->setAnimatingFullScreen(false);
_webViewPlaceholder = nil;
}
- (void)close
{
[self exitFullscreenImmediately];
_webView = nil;
}
- (void)webViewDidRemoveFromSuperviewWhileInFullscreen
{
if (_fullScreenState == InFullScreen && _webView.window != _window.get())
[self exitFullscreenImmediately];
}
#pragma mark -
#pragma mark Internal Interface
- (WebFullScreenManagerProxy*)_manager
{
if (![_webView _page])
return nullptr;
return [_webView _page]->fullScreenManager();
}
#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
{
RetainPtr<WKFullscreenAnimationController> animationController = adoptNS([[WKFullscreenAnimationController alloc] init]);
[animationController setViewController:dismissed];
[animationController setInitialFrame:_initialFrame];
[animationController setFinalFrame:_finalFrame];
[animationController setAnimatingIn:NO];
return animationController.autorelease();
}
@end
#endif // PLATFORM(IOS) && ENABLE(FULLSCREEN_API)