/* * Copyright (C) 2014, 2015 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" #import "WKWebViewInternal.h" #if WK_API_ENABLED #import "APIFormClient.h" #import "APISerializedScriptValue.h" #import "CompletionHandlerCallChecker.h" #import "DiagnosticLoggingClient.h" #import "FindClient.h" #import "LegacySessionStateCoding.h" #import "NavigationState.h" #import "RemoteLayerTreeScrollingPerformanceData.h" #import "RemoteLayerTreeTransaction.h" #import "RemoteObjectRegistry.h" #import "RemoteObjectRegistryMessages.h" #import "UIDelegate.h" #import "ViewGestureController.h" #import "ViewSnapshotStore.h" #import "WKBackForwardListInternal.h" #import "WKBackForwardListItemInternal.h" #import "WKBrowsingContextHandleInternal.h" #import "WKErrorInternal.h" #import "WKHistoryDelegatePrivate.h" #import "WKLayoutMode.h" #import "WKNSData.h" #import "WKNSURLExtras.h" #import "WKNavigationDelegate.h" #import "WKNavigationInternal.h" #import "WKPreferencesInternal.h" #import "WKProcessPoolInternal.h" #import "WKSharedAPICast.h" #import "WKUIDelegate.h" #import "WKUIDelegatePrivate.h" #import "WKUserContentControllerInternal.h" #import "WKWebViewConfigurationInternal.h" #import "WKWebViewContentProvider.h" #import "WKWebsiteDataStoreInternal.h" #import "WebBackForwardList.h" #import "WebCertificateInfo.h" #import "WebFormSubmissionListenerProxy.h" #import "WebKitSystemInterface.h" #import "WebPageGroup.h" #import "WebPageProxy.h" #import "WebPreferencesKeys.h" #import "WebProcessPool.h" #import "WebProcessProxy.h" #import "_WKDiagnosticLoggingDelegate.h" #import "_WKFindDelegate.h" #import "_WKFormDelegate.h" #import "_WKRemoteObjectRegistryInternal.h" #import "_WKSessionStateInternal.h" #import "_WKVisitedLinkProviderInternal.h" #import <WebCore/IOSurface.h> #import <wtf/HashMap.h> #import <wtf/MathExtras.h> #import <wtf/NeverDestroyed.h> #import <wtf/Optional.h> #import <wtf/RetainPtr.h> #if PLATFORM(IOS) #import "_WKFrameHandleInternal.h" #import "_WKWebViewPrintFormatter.h" #import "PrintInfo.h" #import "ProcessThrottler.h" #import "RemoteLayerTreeDrawingAreaProxy.h" #import "RemoteScrollingCoordinatorProxy.h" #import "UIKitSPI.h" #import "WKContentViewInteraction.h" #import "WKPDFView.h" #import "WKScrollView.h" #import "WKWebViewContentProviderRegistry.h" #import "WebPageMessages.h" #import "WebVideoFullscreenManagerProxy.h" #import <UIKit/UIApplication.h> #import <WebCore/CoreGraphicsSPI.h> #import <WebCore/FrameLoaderTypes.h> #import <WebCore/InspectorOverlay.h> #import <WebCore/QuartzCoreSPI.h> @interface UIScrollView (UIScrollViewInternal) - (void)_adjustForAutomaticKeyboardInfo:(NSDictionary*)info animated:(BOOL)animated lastAdjustment:(CGFloat*)lastAdjustment; - (BOOL)_isScrollingToTop; - (BOOL)_isInterruptingDeceleration; - (CGPoint)_animatedTargetOffset; @end @interface UIPeripheralHost(UIKitInternal) - (CGFloat)getVerticalOverlapForView:(UIView *)view usingKeyboardInfo:(NSDictionary *)info; @end @interface UIView (UIViewInternal) - (UIViewController *)_viewControllerForAncestor; @end @interface UIWindow (UIWindowInternal) - (BOOL)_isHostedInAnotherProcess; @end @interface UIViewController (UIViewControllerInternal) - (UIViewController *)_rootAncestorViewController; - (UIViewController *)_viewControllerForSupportedInterfaceOrientations; @end enum class DynamicViewportUpdateMode { NotResizing, ResizingWithAnimation, ResizingWithDocumentHidden, }; #if __has_include(<WebKitAdditions/RemoteLayerBackingStoreAdditions.mm>) #import <WebKitAdditions/RemoteLayerBackingStoreAdditions.mm> #else namespace WebKit { #if USE(IOSURFACE) static WebCore::IOSurface::Format bufferFormat(bool) { return WebCore::IOSurface::Format::RGBA; } #endif // USE(IOSURFACE) } // namespace WebKit #endif #endif // PLATFORM(IOS) #if PLATFORM(MAC) #import "WKViewInternal.h" #import <WebCore/ColorMac.h> #endif NSString * const _WKShouldOpenExternalURLsKey = @"_WKShouldOpenExternalURLsKey"; static HashMap<WebKit::WebPageProxy*, WKWebView *>& pageToViewMap() { static NeverDestroyed<HashMap<WebKit::WebPageProxy*, WKWebView *>> map; return map; } WKWebView* fromWebPageProxy(WebKit::WebPageProxy& page) { return pageToViewMap().get(&page); } @implementation WKWebView { std::unique_ptr<WebKit::NavigationState> _navigationState; std::unique_ptr<WebKit::UIDelegate> _uiDelegate; RetainPtr<_WKRemoteObjectRegistry> _remoteObjectRegistry; _WKRenderingProgressEvents _observedRenderingProgressEvents; WebKit::WeakObjCPtr<id <_WKFormDelegate>> _formDelegate; #if PLATFORM(IOS) RetainPtr<WKScrollView> _scrollView; RetainPtr<WKContentView> _contentView; BOOL _overridesMinimumLayoutSize; CGSize _minimumLayoutSizeOverride; BOOL _overridesMaximumUnobscuredSize; CGSize _maximumUnobscuredSizeOverride; CGRect _inputViewBounds; CGFloat _viewportMetaTagWidth; BOOL _viewportMetaTagWidthWasExplicit; BOOL _viewportMetaTagCameFromImageDocument; CGFloat _initialScaleFactor; BOOL _fastClickingIsDisabled; BOOL _allowsLinkPreview; UIEdgeInsets _obscuredInsets; BOOL _haveSetObscuredInsets; BOOL _isChangingObscuredInsetsInteractively; UIInterfaceOrientation _interfaceOrientationOverride; BOOL _overridesInterfaceOrientation; BOOL _allowsViewportShrinkToFit; BOOL _hasCommittedLoadForMainFrame; BOOL _needsResetViewStateAfterCommitLoadForMainFrame; uint64_t _firstPaintAfterCommitLoadTransactionID; DynamicViewportUpdateMode _dynamicViewportUpdateMode; CATransform3D _resizeAnimationTransformAdjustments; uint64_t _resizeAnimationTransformTransactionID; RetainPtr<UIView> _resizeAnimationView; CGFloat _lastAdjustmentForScroller; Optional<CGRect> _frozenVisibleContentRect; Optional<CGRect> _frozenUnobscuredContentRect; BOOL _needsToRestoreExposedRect; WebCore::FloatRect _exposedRectToRestore; BOOL _needsToRestoreUnobscuredCenter; WebCore::FloatPoint _unobscuredCenterToRestore; uint64_t _firstTransactionIDAfterPageRestore; double _scaleToRestore; std::unique_ptr<WebKit::ViewGestureController> _gestureController; BOOL _allowsBackForwardNavigationGestures; RetainPtr<UIView <WKWebViewContentProvider>> _customContentView; RetainPtr<UIView> _customContentFixedOverlayView; WebCore::Color _scrollViewBackgroundColor; BOOL _delayUpdateVisibleContentRects; BOOL _hadDelayedUpdateVisibleContentRects; BOOL _pageIsPrintingToPDF; RetainPtr<CGPDFDocumentRef> _printedDocument; Vector<std::function<void ()>> _snapshotsDeferredDuringResize; #endif #if PLATFORM(MAC) RetainPtr<WKView> _wkView; #endif } - (instancetype)initWithFrame:(CGRect)frame { return [self initWithFrame:frame configuration:adoptNS([[WKWebViewConfiguration alloc] init]).get()]; } #if PLATFORM(IOS) static int32_t deviceOrientationForUIInterfaceOrientation(UIInterfaceOrientation orientation) { switch (orientation) { case UIInterfaceOrientationUnknown: case UIInterfaceOrientationPortrait: return 0; case UIInterfaceOrientationPortraitUpsideDown: return 180; case UIInterfaceOrientationLandscapeLeft: return -90; case UIInterfaceOrientationLandscapeRight: return 90; } } static int32_t deviceOrientation() { return deviceOrientationForUIInterfaceOrientation([[UIApplication sharedApplication] statusBarOrientation]); } - (BOOL)_isShowingVideoPictureInPicture { if (!_page || !_page->videoFullscreenManager()) return false; return _page->videoFullscreenManager()->hasMode(WebCore::HTMLMediaElementEnums::VideoFullscreenModePictureInPicture); } - (BOOL)_mayAutomaticallyShowVideoPictureInPicture { #if (__IPHONE_OS_VERSION_MIN_REQUIRED <= 80200) || !HAVE(AVKIT) return false; #else if (!_page || !_page->videoFullscreenManager()) return false; return _page->videoFullscreenManager()->mayAutomaticallyShowVideoPictureInPicture(); #endif } static bool shouldAllowPictureInPictureMediaPlayback() { #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000 static bool shouldAllowPictureInPictureMediaPlayback = iosExecutableWasLinkedOnOrAfterVersion(wkIOSSystemVersion_9_0); return shouldAllowPictureInPictureMediaPlayback; #else return false; #endif } #endif - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration { if (!(self = [super initWithFrame:frame])) return nil; if (!configuration) [NSException raise:NSInvalidArgumentException format:@"Configuration cannot be nil"]; _configuration = adoptNS([configuration copy]); if (WKWebView *relatedWebView = [_configuration _relatedWebView]) { WKProcessPool *processPool = [_configuration processPool]; WKProcessPool *relatedWebViewProcessPool = [relatedWebView->_configuration processPool]; if (processPool && processPool != relatedWebViewProcessPool) [NSException raise:NSInvalidArgumentException format:@"Related web view %@ has process pool %@ but configuration specifies a different process pool %@", relatedWebView, relatedWebViewProcessPool, configuration.processPool]; [_configuration setProcessPool:relatedWebViewProcessPool]; } [_configuration _validate]; CGRect bounds = self.bounds; WebKit::WebProcessPool& processPool = *[_configuration processPool]->_processPool; WebKit::WebPageConfiguration webPageConfiguration; webPageConfiguration.preferences = [_configuration preferences]->_preferences.get(); if (WKWebView *relatedWebView = [_configuration _relatedWebView]) webPageConfiguration.relatedPage = relatedWebView->_page.get(); webPageConfiguration.userContentController = [_configuration userContentController]->_userContentControllerProxy.get(); webPageConfiguration.visitedLinkProvider = [_configuration _visitedLinkProvider]->_visitedLinkProvider.get(); webPageConfiguration.websiteDataStore = &[_configuration websiteDataStore]->_websiteDataStore->websiteDataStore(); webPageConfiguration.sessionID = webPageConfiguration.websiteDataStore->sessionID(); webPageConfiguration.treatsSHA1SignedCertificatesAsInsecure = [_configuration _treatsSHA1SignedCertificatesAsInsecure]; RefPtr<WebKit::WebPageGroup> pageGroup; NSString *groupIdentifier = configuration._groupIdentifier; if (groupIdentifier.length) { pageGroup = WebKit::WebPageGroup::create(configuration._groupIdentifier); webPageConfiguration.pageGroup = pageGroup.get(); } webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::suppressesIncrementalRenderingKey(), WebKit::WebPreferencesStore::Value(!![_configuration suppressesIncrementalRendering])); #if PLATFORM(IOS) webPageConfiguration.alwaysRunsAtForegroundPriority = [_configuration _alwaysRunsAtForegroundPriority]; webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::allowsInlineMediaPlaybackKey(), WebKit::WebPreferencesStore::Value(!![_configuration allowsInlineMediaPlayback])); webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::allowsPictureInPictureMediaPlaybackKey(), WebKit::WebPreferencesStore::Value(!![_configuration allowsPictureInPictureMediaPlayback] && shouldAllowPictureInPictureMediaPlayback())); webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::requiresUserGestureForMediaPlaybackKey(), WebKit::WebPreferencesStore::Value(!![_configuration requiresUserActionForMediaPlayback])); #endif #if ENABLE(WIRELESS_PLAYBACK_TARGET) webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::allowsAirPlayForMediaPlaybackKey(), WebKit::WebPreferencesStore::Value(!![_configuration allowsAirPlayForMediaPlayback])); #endif #if PLATFORM(IOS) _scrollView = adoptNS([[WKScrollView alloc] initWithFrame:bounds]); [_scrollView setInternalDelegate:self]; [_scrollView setBouncesZoom:YES]; [self addSubview:_scrollView.get()]; _contentView = adoptNS([[WKContentView alloc] initWithFrame:bounds processPool:processPool configuration:WTF::move(webPageConfiguration) webView:self]); _page = [_contentView page]; _page->setDeviceOrientation(deviceOrientation()); [_contentView layer].anchorPoint = CGPointZero; [_contentView setFrame:bounds]; [_scrollView addSubview:_contentView.get()]; [_scrollView addSubview:[_contentView unscaledView]]; [self _updateScrollViewBackground]; _viewportMetaTagWidth = WebCore::ViewportArguments::ValueAuto; _initialScaleFactor = 1; _fastClickingIsDisabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitFastClickingDisabled"]; [self _frameOrBoundsChanged]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(_keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; [center addObserver:self selector:@selector(_keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil]; [center addObserver:self selector:@selector(_keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [center addObserver:self selector:@selector(_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; [center addObserver:self selector:@selector(_windowDidRotate:) name:UIWindowDidRotateNotification object:nil]; [center addObserver:self selector:@selector(_contentSizeCategoryDidChange:) name:UIContentSizeCategoryDidChangeNotification object:nil]; _page->contentSizeCategoryDidChange([self _contentSizeCategory]); [[_configuration _contentProviderRegistry] addPage:*_page]; #endif #if PLATFORM(MAC) _wkView = adoptNS([[WKView alloc] initWithFrame:bounds processPool:processPool configuration:WTF::move(webPageConfiguration) webView:self]); [self addSubview:_wkView.get()]; _page = WebKit::toImpl([_wkView pageRef]); #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 [_wkView _setAutomaticallyAdjustsContentInsets:YES]; #endif #endif _page->setBackgroundExtendsBeyondPage(true); if (NSString *applicationNameForUserAgent = configuration.applicationNameForUserAgent) _page->setApplicationNameForUserAgent(applicationNameForUserAgent); _navigationState = std::make_unique<WebKit::NavigationState>(self); _uiDelegate = std::make_unique<WebKit::UIDelegate>(self); _page->setFindClient(std::make_unique<WebKit::FindClient>(self)); _page->setDiagnosticLoggingClient(std::make_unique<WebKit::DiagnosticLoggingClient>(self)); pageToViewMap().add(_page.get(), self); return self; } - (instancetype)initWithCoder:(NSCoder *)coder { [self release]; return nil; } - (void)dealloc { #if PLATFORM(IOS) [_contentView _webViewDestroyed]; #endif if (_remoteObjectRegistry) _page->process().processPool().removeMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID()); _page->close(); [_remoteObjectRegistry _invalidate]; #if PLATFORM(IOS) [[_configuration _contentProviderRegistry] removePage:*_page]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [_scrollView setInternalDelegate:nil]; #endif pageToViewMap().remove(_page.get()); [super dealloc]; } - (WKWebViewConfiguration *)configuration { return [[_configuration copy] autorelease]; } - (WKBackForwardList *)backForwardList { return wrapper(_page->backForwardList()); } - (id <WKNavigationDelegate>)navigationDelegate { return _navigationState->navigationDelegate().autorelease(); } - (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate { _page->setNavigationClient(_navigationState->createNavigationClient()); _navigationState->setNavigationDelegate(navigationDelegate); } - (id <WKUIDelegate>)UIDelegate { return _uiDelegate->delegate().autorelease(); } - (void)setUIDelegate:(id<WKUIDelegate>)UIDelegate { _page->setUIClient(_uiDelegate->createUIClient()); _uiDelegate->setDelegate(UIDelegate); } - (WKNavigation *)loadRequest:(NSURLRequest *)request { auto navigation = _page->loadRequest(request); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL { if (![URL isFileURL]) [NSException raise:NSInvalidArgumentException format:@"%@ is not a file URL", URL]; if (![readAccessURL isFileURL]) [NSException raise:NSInvalidArgumentException format:@"%@ is not a file URL", readAccessURL]; auto navigation = _page->loadFile([URL _web_originalDataAsWTFString], [readAccessURL _web_originalDataAsWTFString]); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; return [self loadData:data MIMEType:@"text/html" characterEncodingName:@"UTF-8" baseURL:baseURL]; } - (WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL { auto navigation = _page->loadData(API::Data::createWithoutCopying(data).ptr(), MIMEType, characterEncodingName, baseURL.absoluteString); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item { auto navigation = _page->goToBackForwardItem(&item._item); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (NSString *)title { return _page->pageLoadState().title(); } - (NSURL *)URL { return [NSURL _web_URLWithWTFString:_page->pageLoadState().activeURL()]; } - (BOOL)isLoading { return _page->pageLoadState().isLoading(); } - (double)estimatedProgress { return _page->pageLoadState().estimatedProgress(); } - (BOOL)hasOnlySecureContent { return _page->pageLoadState().hasOnlySecureContent(); } - (NSArray *)certificateChain { auto certificateInfo = _page->pageLoadState().certificateInfo(); if (!certificateInfo) return @[ ]; return (NSArray *)certificateInfo->certificateInfo().certificateChain() ?: @[ ]; } - (BOOL)canGoBack { return _page->pageLoadState().canGoBack(); } - (BOOL)canGoForward { return _page->pageLoadState().canGoForward(); } - (WKNavigation *)goBack { auto navigation = _page->goBack(); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (WKNavigation *)goForward { auto navigation = _page->goForward(); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (WKNavigation *)reload { const bool reloadFromOrigin = false; const bool contentBlockersEnabled = true; auto navigation = _page->reload(reloadFromOrigin, contentBlockersEnabled); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (WKNavigation *)reloadFromOrigin { const bool reloadFromOrigin = true; const bool contentBlockersEnabled = true; auto navigation = _page->reload(reloadFromOrigin, contentBlockersEnabled); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (void)stopLoading { _page->stopLoading(); } static WKErrorCode callbackErrorCode(WebKit::CallbackBase::Error error) { switch (error) { case WebKit::CallbackBase::Error::None: ASSERT_NOT_REACHED(); return WKErrorUnknown; case WebKit::CallbackBase::Error::Unknown: return WKErrorUnknown; case WebKit::CallbackBase::Error::ProcessExited: return WKErrorWebContentProcessTerminated; case WebKit::CallbackBase::Error::OwnerWasInvalidated: return WKErrorWebViewInvalidated; } } - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler { auto handler = adoptNS([completionHandler copy]); _page->runJavaScriptInMainFrame(javaScriptString, [handler](API::SerializedScriptValue* serializedScriptValue, bool hadException, WebKit::ScriptValueCallback::Error errorCode) { if (!handler) return; if (errorCode != WebKit::ScriptValueCallback::Error::None) { auto error = createNSError(callbackErrorCode(errorCode)); if (errorCode == WebKit::ScriptValueCallback::Error::OwnerWasInvalidated) { // The OwnerWasInvalidated callback is synchronous. We don't want to call the block from within it // because that can trigger re-entrancy bugs in WebKit. // FIXME: It would be even better if GenericCallback did this for us. dispatch_async(dispatch_get_main_queue(), [handler, error] { auto rawHandler = (void (^)(id, NSError *))handler.get(); rawHandler(nil, error.get()); }); return; } auto rawHandler = (void (^)(id, NSError *))handler.get(); rawHandler(nil, error.get()); return; } auto rawHandler = (void (^)(id, NSError *))handler.get(); if (hadException) { ASSERT(!serializedScriptValue); rawHandler(nil, createNSError(WKErrorJavaScriptExceptionOccurred).get()); return; } if (!serializedScriptValue) { rawHandler(nil, createNSError(WKErrorJavaScriptResultTypeIsUnsupported).get()); return; } id body = API::SerializedScriptValue::deserialize(*serializedScriptValue->internalRepresentation(), 0); rawHandler(body, nil); }); } - (NSString *)customUserAgent { return _page->customUserAgent(); } - (void)setCustomUserAgent:(NSString *)customUserAgent { _page->setCustomUserAgent(customUserAgent); } - (WKPageRef)_pageForTesting { return toAPI(_page.get()); } - (BOOL)allowsLinkPreview { #if PLATFORM(MAC) return [_wkView allowsLinkPreview]; #elif PLATFORM(IOS) return _allowsLinkPreview; #endif } - (void)setAllowsLinkPreview:(BOOL)allowsLinkPreview { #if PLATFORM(MAC) [_wkView setAllowsLinkPreview:allowsLinkPreview]; return; #elif PLATFORM(IOS) if (_allowsLinkPreview == allowsLinkPreview) return; _allowsLinkPreview = allowsLinkPreview; #if HAVE(LINK_PREVIEW) if (_allowsLinkPreview) [_contentView _registerPreview]; else [_contentView _unregisterPreview]; #endif // HAVE(LINK_PREVIEW) #endif // PLATFORM(IOS) } #pragma mark iOS-specific methods #if PLATFORM(IOS) - (BOOL)_isBackground { if ([self _isDisplayingPDF]) return [(WKPDFView *)_customContentView isBackground]; return [_contentView isBackground]; } - (void)setFrame:(CGRect)frame { CGRect oldFrame = self.frame; [super setFrame:frame]; if (!CGSizeEqualToSize(oldFrame.size, frame.size)) [self _frameOrBoundsChanged]; } - (void)setBounds:(CGRect)bounds { CGRect oldBounds = self.bounds; [super setBounds:bounds]; [_customContentFixedOverlayView setFrame:self.bounds]; if (!CGSizeEqualToSize(oldBounds.size, bounds.size)) [self _frameOrBoundsChanged]; } - (UIScrollView *)scrollView { return _scrollView.get(); } - (WKBrowsingContextController *)browsingContextController { return [_contentView browsingContextController]; } - (BOOL)becomeFirstResponder { return [self._currentContentView becomeFirstResponder] || [super becomeFirstResponder]; } - (BOOL)canBecomeFirstResponder { if (self._currentContentView == _contentView && [_contentView isResigningFirstResponder]) return NO; return YES; } static inline CGFloat floorToDevicePixel(CGFloat input, float deviceScaleFactor) { return CGFloor(input * deviceScaleFactor) / deviceScaleFactor; } static inline bool pointsEqualInDevicePixels(CGPoint a, CGPoint b, float deviceScaleFactor) { return fabs(a.x * deviceScaleFactor - b.x * deviceScaleFactor) < std::numeric_limits<float>::epsilon() && fabs(a.y * deviceScaleFactor - b.y * deviceScaleFactor) < std::numeric_limits<float>::epsilon(); } static CGSize roundScrollViewContentSize(const WebKit::WebPageProxy& page, CGSize contentSize) { float deviceScaleFactor = page.deviceScaleFactor(); return CGSizeMake(floorToDevicePixel(contentSize.width, deviceScaleFactor), floorToDevicePixel(contentSize.height, deviceScaleFactor)); } - (UIView *)_currentContentView { return _customContentView ? _customContentView.get() : _contentView.get(); } - (void)_setHasCustomContentView:(BOOL)pageHasCustomContentView loadedMIMEType:(const WTF::String&)mimeType { if (pageHasCustomContentView) { [_customContentView removeFromSuperview]; [_customContentFixedOverlayView removeFromSuperview]; Class representationClass = [[_configuration _contentProviderRegistry] providerForMIMEType:mimeType]; ASSERT(representationClass); _customContentView = adoptNS([[representationClass alloc] web_initWithFrame:self.bounds webView:self]); _customContentFixedOverlayView = adoptNS([[UIView alloc] initWithFrame:self.bounds]); [_customContentFixedOverlayView setUserInteractionEnabled:NO]; [[_contentView unscaledView] removeFromSuperview]; [_contentView removeFromSuperview]; [_scrollView addSubview:_customContentView.get()]; [self addSubview:_customContentFixedOverlayView.get()]; [_customContentView web_setMinimumSize:self.bounds.size]; [_customContentView web_setFixedOverlayView:_customContentFixedOverlayView.get()]; } else if (_customContentView) { [_customContentView removeFromSuperview]; _customContentView = nullptr; [_customContentFixedOverlayView removeFromSuperview]; _customContentFixedOverlayView = nullptr; [_scrollView addSubview:_contentView.get()]; [_scrollView addSubview:[_contentView unscaledView]]; [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)]; [_customContentFixedOverlayView setFrame:self.bounds]; [self addSubview:_customContentFixedOverlayView.get()]; } if (self.isFirstResponder && self._currentContentView.canBecomeFirstResponder) [self._currentContentView becomeFirstResponder]; } - (void)_didFinishLoadingDataForCustomContentProviderWithSuggestedFilename:(const String&)suggestedFilename data:(NSData *)data { ASSERT(_customContentView); [_customContentView web_setContentProviderData:data suggestedFilename:suggestedFilename]; // FIXME: It may make more sense for custom content providers to invoke this when they're ready, // because there's no guarantee that all custom content providers will lay out synchronously. _page->didLayoutForCustomContentProvider(); } - (void)_willInvokeUIScrollViewDelegateCallback { _delayUpdateVisibleContentRects = YES; } - (void)_didInvokeUIScrollViewDelegateCallback { _delayUpdateVisibleContentRects = NO; if (_hadDelayedUpdateVisibleContentRects) { _hadDelayedUpdateVisibleContentRects = NO; [self _updateVisibleContentRects]; } } static CGFloat contentZoomScale(WKWebView *webView) { CGFloat scale = webView._currentContentView.layer.affineTransform.a; ASSERT(scale == [webView->_scrollView zoomScale]); return scale; } static WebCore::Color baseScrollViewBackgroundColor(WKWebView *webView) { if (webView->_customContentView) return [webView->_customContentView backgroundColor].CGColor; if (webView->_gestureController) { WebCore::Color color = webView->_gestureController->backgroundColorForCurrentSnapshot(); if (color.isValid()) return color; } return webView->_page->pageExtendedBackgroundColor(); } static WebCore::Color scrollViewBackgroundColor(WKWebView *webView) { if (!webView.opaque) return WebCore::Color::transparent; WebCore::Color color = baseScrollViewBackgroundColor(webView); if (!color.isValid()) color = WebCore::Color::white; CGFloat zoomScale = contentZoomScale(webView); CGFloat minimumZoomScale = [webView->_scrollView minimumZoomScale]; if (zoomScale < minimumZoomScale) { CGFloat slope = 12; CGFloat opacity = std::max<CGFloat>(1 - slope * (minimumZoomScale - zoomScale), 0); color = WebCore::colorWithOverrideAlpha(color.rgb(), opacity); } return color; } - (void)_updateScrollViewBackground { WebCore::Color color = scrollViewBackgroundColor(self); if (_scrollViewBackgroundColor == color) return; _scrollViewBackgroundColor = color; auto uiBackgroundColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)]); [_scrollView setBackgroundColor:uiBackgroundColor.get()]; // Update the indicator style based on the lightness/darkness of the background color. double hue, saturation, lightness; color.getHSL(hue, saturation, lightness); if (lightness <= .5 && color.alpha() > 0) [_scrollView setIndicatorStyle:UIScrollViewIndicatorStyleWhite]; else [_scrollView setIndicatorStyle:UIScrollViewIndicatorStyleDefault]; } - (CGPoint)_adjustedContentOffset:(CGPoint)point { CGPoint result = point; UIEdgeInsets contentInset = [self _computedContentInset]; result.x -= contentInset.left; result.y -= contentInset.top; return result; } - (UIEdgeInsets)_computedContentInset { if (_haveSetObscuredInsets) return _obscuredInsets; return [_scrollView contentInset]; } - (void)_processDidExit { if (!_customContentView && _dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) { NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()]; [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView]; [_scrollView insertSubview:[_contentView unscaledView] atIndex:indexOfResizeAnimationView + 1]; [_resizeAnimationView removeFromSuperview]; _resizeAnimationView = nil; _resizeAnimationTransformAdjustments = CATransform3DIdentity; } [_contentView setFrame:self.bounds]; [_scrollView setBackgroundColor:[UIColor whiteColor]]; [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]]; [_scrollView setZoomScale:1]; _viewportMetaTagWidth = WebCore::ViewportArguments::ValueAuto; _initialScaleFactor = 1; _hasCommittedLoadForMainFrame = NO; _needsResetViewStateAfterCommitLoadForMainFrame = NO; _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing; [_contentView setHidden:NO]; _needsToRestoreExposedRect = NO; _needsToRestoreUnobscuredCenter = NO; _scrollViewBackgroundColor = WebCore::Color(); _delayUpdateVisibleContentRects = NO; _hadDelayedUpdateVisibleContentRects = NO; } - (void)_didCommitLoadForMainFrame { _firstPaintAfterCommitLoadTransactionID = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).nextLayerTreeTransactionID(); _hasCommittedLoadForMainFrame = YES; _needsResetViewStateAfterCommitLoadForMainFrame = YES; } static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset) { UIEdgeInsets contentInsets = scrollView.contentInset; CGSize contentSize = scrollView.contentSize; CGSize scrollViewSize = scrollView.bounds.size; CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width; contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x); contentOffset.x = std::max(-contentInsets.left, contentOffset.x); CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height; contentOffset.y = std::min(maxVerticalOffset, contentOffset.y); contentOffset.y = std::max(-contentInsets.top, contentOffset.y); return contentOffset; } static void changeContentOffsetBoundedInValidRange(UIScrollView *scrollView, WebCore::FloatPoint contentOffset) { scrollView.contentOffset = contentOffsetBoundedInValidRange(scrollView, contentOffset); } - (WebCore::FloatRect)visibleRectInViewCoordinates { WebCore::FloatRect bounds = self.bounds; bounds.moveBy([_scrollView contentOffset]); WebCore::FloatRect contentViewBounds = [_contentView bounds]; bounds.intersect(contentViewBounds); return bounds; } // WebCore stores the page scale factor as float instead of double. When we get a scale from WebCore, // we need to ignore differences that are within a small rounding error on floats. static inline bool areEssentiallyEqualAsFloat(float a, float b) { return WTF::areEssentiallyEqual(a, b); } - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction { if (_customContentView) return; if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) { if (layerTreeTransaction.transactionID() >= _resizeAnimationTransformTransactionID) { [_resizeAnimationView layer].sublayerTransform = _resizeAnimationTransformAdjustments; if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::ResizingWithDocumentHidden) { [_contentView setHidden:NO]; [self _endAnimatedResize]; } } return; } CGSize newContentSize = roundScrollViewContentSize(*_page, [_contentView frame].size); [_scrollView _setContentSizePreservingContentOffsetDuringRubberband:newContentSize]; [_scrollView setMinimumZoomScale:layerTreeTransaction.minimumScaleFactor()]; [_scrollView setMaximumZoomScale:layerTreeTransaction.maximumScaleFactor()]; [_scrollView setZoomEnabled:layerTreeTransaction.allowsUserScaling()]; if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom]) [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()]; _viewportMetaTagWidth = layerTreeTransaction.viewportMetaTagWidth(); _viewportMetaTagWidthWasExplicit = layerTreeTransaction.viewportMetaTagWidthWasExplicit(); _viewportMetaTagCameFromImageDocument = layerTreeTransaction.viewportMetaTagCameFromImageDocument(); _initialScaleFactor = layerTreeTransaction.initialScaleFactor(); if (![_contentView _mayDisableDoubleTapGesturesDuringSingleTap]) [_contentView _setDoubleTapGesturesEnabled:self._allowsDoubleTapGestures]; [self _updateScrollViewBackground]; if (_gestureController) _gestureController->setRenderTreeSize(layerTreeTransaction.renderTreeSize()); if (_needsResetViewStateAfterCommitLoadForMainFrame && layerTreeTransaction.transactionID() >= _firstPaintAfterCommitLoadTransactionID) { _needsResetViewStateAfterCommitLoadForMainFrame = NO; [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]]; [self _updateVisibleContentRects]; if (_observedRenderingProgressEvents & _WKRenderingProgressEventFirstPaint) _navigationState->didFirstPaint(); } bool isTransactionAfterPageRestore = layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore; if (_needsToRestoreExposedRect && isTransactionAfterPageRestore) { _needsToRestoreExposedRect = NO; if (areEssentiallyEqualAsFloat(contentZoomScale(self), _scaleToRestore)) { WebCore::FloatPoint exposedPosition = _exposedRectToRestore.location(); exposedPosition.scale(_scaleToRestore, _scaleToRestore); changeContentOffsetBoundedInValidRange(_scrollView.get(), exposedPosition); if (_gestureController) _gestureController->didRestoreScrollPosition(); } [self _updateVisibleContentRects]; } if (_needsToRestoreUnobscuredCenter && isTransactionAfterPageRestore) { _needsToRestoreUnobscuredCenter = NO; if (areEssentiallyEqualAsFloat(contentZoomScale(self), _scaleToRestore)) { CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, _obscuredInsets); WebCore::FloatSize unobscuredContentSizeAtNewScale(unobscuredRect.size.width / _scaleToRestore, unobscuredRect.size.height / _scaleToRestore); WebCore::FloatPoint topLeftInDocumentCoordinates(_unobscuredCenterToRestore.x() - unobscuredContentSizeAtNewScale.width() / 2, _unobscuredCenterToRestore.y() - unobscuredContentSizeAtNewScale.height() / 2); topLeftInDocumentCoordinates.scale(_scaleToRestore, _scaleToRestore); topLeftInDocumentCoordinates.moveBy(WebCore::FloatPoint(-_obscuredInsets.left, -_obscuredInsets.top)); changeContentOffsetBoundedInValidRange(_scrollView.get(), topLeftInDocumentCoordinates); if (_gestureController) _gestureController->didRestoreScrollPosition(); } [self _updateVisibleContentRects]; } if (WebKit::RemoteLayerTreeScrollingPerformanceData* scrollPerfData = _page->scrollingPerformanceData()) scrollPerfData->didCommitLayerTree([self visibleRectInViewCoordinates]); } - (void)_dynamicViewportUpdateChangedTargetToScale:(double)newScale position:(CGPoint)newScrollPosition nextValidLayerTreeTransactionID:(uint64_t)nextValidLayerTreeTransactionID { if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) { CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11; double currentTargetScale = animatingScaleTarget * [[_contentView layer] transform].m11; double scale = newScale / currentTargetScale; _resizeAnimationTransformAdjustments = CATransform3DMakeScale(scale, scale, 1); CGPoint newContentOffset = [self _adjustedContentOffset:CGPointMake(newScrollPosition.x * newScale, newScrollPosition.y * newScale)]; CGPoint currentContentOffset = [_scrollView contentOffset]; _resizeAnimationTransformAdjustments.m41 = (currentContentOffset.x - newContentOffset.x) / animatingScaleTarget; _resizeAnimationTransformAdjustments.m42 = (currentContentOffset.y - newContentOffset.y) / animatingScaleTarget; _resizeAnimationTransformTransactionID = nextValidLayerTreeTransactionID; } } - (void)_couldNotRestorePageState { // The gestureController may be waiting for the scroll position to be restored // in order to remove the swipe snapshot. Since the scroll position could not be // restored, tell the gestureController it was restored so that it no longer waits // for it. if (_gestureController) _gestureController->didRestoreScrollPosition(); } - (void)_restorePageStateToExposedRect:(WebCore::FloatRect)exposedRect scale:(double)scale { if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) return; if (_customContentView) return; _needsToRestoreUnobscuredCenter = NO; _needsToRestoreExposedRect = YES; _firstTransactionIDAfterPageRestore = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).nextLayerTreeTransactionID(); _exposedRectToRestore = exposedRect; _scaleToRestore = scale; } - (void)_restorePageStateToUnobscuredCenter:(WebCore::FloatPoint)center scale:(double)scale { if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) return; if (_customContentView) return; _needsToRestoreExposedRect = NO; _needsToRestoreUnobscuredCenter = YES; _firstTransactionIDAfterPageRestore = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).nextLayerTreeTransactionID(); _unobscuredCenterToRestore = center; _scaleToRestore = scale; } - (PassRefPtr<WebKit::ViewSnapshot>)_takeViewSnapshot { float deviceScale = WKGetScreenScaleFactor(); WebCore::FloatSize snapshotSize(self.bounds.size); snapshotSize.scale(deviceScale, deviceScale); CATransform3D transform = CATransform3DMakeScale(deviceScale, deviceScale, 1); #if USE(IOSURFACE) WebCore::IOSurface::Format snapshotFormat = WebKit::bufferFormat(true /* is opaque */); auto surface = WebCore::IOSurface::create(WebCore::expandedIntSize(snapshotSize), WebCore::ColorSpaceSRGB, snapshotFormat); CARenderServerRenderLayerWithTransform(MACH_PORT_NULL, self.layer.context.contextId, reinterpret_cast<uint64_t>(self.layer), surface->surface(), 0, 0, &transform); return WebKit::ViewSnapshot::create(WTF::move(surface)); #else uint32_t slotID = [WebKit::ViewSnapshotStore::snapshottingContext() createImageSlot:snapshotSize hasAlpha:YES]; if (!slotID) return nullptr; CARenderServerCaptureLayerWithTransform(MACH_PORT_NULL, self.layer.context.contextId, (uint64_t)self.layer, slotID, 0, 0, &transform); WebCore::IntSize imageSize = WebCore::expandedIntSize(WebCore::FloatSize(snapshotSize)); return WebKit::ViewSnapshot::create(slotID, imageSize, imageSize.width() * imageSize.height() * 4); #endif } - (void)_zoomToPoint:(WebCore::FloatPoint)point atScale:(double)scale animated:(BOOL)animated { CFTimeInterval duration = 0; CGFloat zoomScale = contentZoomScale(self); if (animated) { const double maximumZoomDuration = 0.4; const double minimumZoomDuration = 0.1; const double zoomDurationFactor = 0.3; duration = std::min(fabs(log(zoomScale) - log(scale)) * zoomDurationFactor + minimumZoomDuration, maximumZoomDuration); } if (scale != zoomScale) _page->willStartUserTriggeredZooming(); [_scrollView _zoomToCenter:point scale:scale duration:duration]; } - (void)_zoomToRect:(WebCore::FloatRect)targetRect atScale:(double)scale origin:(WebCore::FloatPoint)origin animated:(BOOL)animated { // FIXME: Some of this could be shared with _scrollToRect. const double visibleRectScaleChange = contentZoomScale(self) / scale; const WebCore::FloatRect visibleRect([self convertRect:self.bounds toView:self._currentContentView]); const WebCore::FloatRect unobscuredRect([self _contentRectForUserInteraction]); const WebCore::FloatSize topLeftObscuredInsetAfterZoom((unobscuredRect.minXMinYCorner() - visibleRect.minXMinYCorner()) * visibleRectScaleChange); const WebCore::FloatSize bottomRightObscuredInsetAfterZoom((visibleRect.maxXMaxYCorner() - unobscuredRect.maxXMaxYCorner()) * visibleRectScaleChange); const WebCore::FloatSize unobscuredRectSizeAfterZoom(unobscuredRect.size() * visibleRectScaleChange); // Center to the target rect. WebCore::FloatPoint unobscuredRectLocationAfterZoom = targetRect.location() - (unobscuredRectSizeAfterZoom - targetRect.size()) * 0.5; // Center to the tap point instead in case the target rect won't fit in a direction. if (targetRect.width() > unobscuredRectSizeAfterZoom.width()) unobscuredRectLocationAfterZoom.setX(origin.x() - unobscuredRectSizeAfterZoom.width() / 2); if (targetRect.height() > unobscuredRectSizeAfterZoom.height()) unobscuredRectLocationAfterZoom.setY(origin.y() - unobscuredRectSizeAfterZoom.height() / 2); // We have computed where we want the unobscured rect to be. Now adjust for the obscuring insets. WebCore::FloatRect visibleRectAfterZoom(unobscuredRectLocationAfterZoom, unobscuredRectSizeAfterZoom); visibleRectAfterZoom.move(-topLeftObscuredInsetAfterZoom); visibleRectAfterZoom.expand(topLeftObscuredInsetAfterZoom + bottomRightObscuredInsetAfterZoom); [self _zoomToPoint:visibleRectAfterZoom.center() atScale:scale animated:animated]; } static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOffset, WebCore::FloatSize contentSize, WebCore::FloatSize unobscuredContentSize) { WebCore::FloatSize maximumContentOffset = contentSize - unobscuredContentSize; contentOffset = contentOffset.shrunkTo(WebCore::FloatPoint(maximumContentOffset.width(), maximumContentOffset.height())); contentOffset = contentOffset.expandedTo(WebCore::FloatPoint()); return contentOffset; } - (void)_scrollToContentOffset:(WebCore::FloatPoint)contentOffsetInPageCoordinates scrollOrigin:(WebCore::IntPoint)scrollOrigin { if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) return; WebCore::FloatPoint contentOffsetRespectingOrigin = scrollOrigin + toFloatSize(contentOffsetInPageCoordinates); WebCore::FloatPoint scaledOffset = contentOffsetRespectingOrigin; CGFloat zoomScale = contentZoomScale(self); scaledOffset.scale(zoomScale, zoomScale); CGPoint contentOffsetInScrollViewCoordinates = [self _adjustedContentOffset:scaledOffset]; contentOffsetInScrollViewCoordinates = contentOffsetBoundedInValidRange(_scrollView.get(), contentOffsetInScrollViewCoordinates); [_scrollView _stopScrollingAndZoomingAnimations]; if (!CGPointEqualToPoint(contentOffsetInScrollViewCoordinates, [_scrollView contentOffset])) [_scrollView setContentOffset:contentOffsetInScrollViewCoordinates]; else { // If we haven't changed anything, there would not be any VisibleContentRect update sent to the content. // The WebProcess would keep the invalid contentOffset as its scroll position. // To synchronize the WebProcess with what is on screen, we send the VisibleContentRect again. _page->resendLastVisibleContentRects(); } } - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance { WebCore::FloatRect unobscuredContentRect([self _contentRectForUserInteraction]); WebCore::FloatPoint unobscuredContentOffset = unobscuredContentRect.location(); WebCore::FloatSize contentSize([self._currentContentView bounds].size); // Center the target rect in the scroll view. // If the target doesn't fit in the scroll view, center on the gesture location instead. WebCore::FloatPoint newUnobscuredContentOffset; if (targetRect.width() <= unobscuredContentRect.width()) newUnobscuredContentOffset.setX(targetRect.x() - (unobscuredContentRect.width() - targetRect.width()) / 2); else newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2); if (targetRect.height() <= unobscuredContentRect.height()) newUnobscuredContentOffset.setY(targetRect.y() - (unobscuredContentRect.height() - targetRect.height()) / 2); else newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2); newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size()); if (unobscuredContentOffset == newUnobscuredContentOffset) { if (targetRect.width() > unobscuredContentRect.width()) newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2); if (targetRect.height() > unobscuredContentRect.height()) newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2); newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size()); } WebCore::FloatSize scrollViewOffsetDelta = newUnobscuredContentOffset - unobscuredContentOffset; scrollViewOffsetDelta.scale(contentZoomScale(self)); float scrollDistance = scrollViewOffsetDelta.diagonalLength(); if (scrollDistance < minimumScrollDistance) return false; [_contentView willStartZoomOrScroll]; [_scrollView setContentOffset:([_scrollView contentOffset] + scrollViewOffsetDelta) animated:YES]; return true; } - (void)_scrollByContentOffset:(WebCore::FloatPoint)contentOffsetDelta { WebCore::FloatPoint scaledOffsetDelta = contentOffsetDelta; CGFloat zoomScale = contentZoomScale(self); scaledOffsetDelta.scale(zoomScale, zoomScale); CGPoint currentOffset = [_scrollView _isAnimatingScroll] ? [_scrollView _animatedTargetOffset] : [_scrollView contentOffset]; CGPoint boundedOffset = contentOffsetBoundedInValidRange(_scrollView.get(), currentOffset + scaledOffsetDelta); if (CGPointEqualToPoint(boundedOffset, currentOffset)) return; [_contentView willStartZoomOrScroll]; [_scrollView setContentOffset:boundedOffset animated:YES]; } - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin animated:(BOOL)animated { [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale] animated:animated]; } - (void)_zoomToInitialScaleWithOrigin:(WebCore::FloatPoint)origin animated:(BOOL)animated { ASSERT(_initialScaleFactor > 0); [self _zoomToPoint:origin atScale:_initialScaleFactor animated:animated]; } // focusedElementRect and selectionRect are both in document coordinates. - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRectInDocumentCoordinates selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll { const double WKWebViewStandardFontSize = 16; const double kMinimumHeightToShowContentAboveKeyboard = 106; const CFTimeInterval UIWebFormAnimationDuration = 0.25; const double CaretOffsetFromWindowEdge = 20; // Zoom around the element's bounding frame. We use a "standard" size to determine the proper frame. double scale = allowScaling ? std::min(std::max(WKWebViewStandardFontSize / fontSize, minimumScale), maximumScale) : contentZoomScale(self); CGFloat documentWidth = [_contentView bounds].size.width; scale = CGRound(documentWidth * scale) / documentWidth; UIWindow *window = [_scrollView window]; WebCore::FloatRect focusedElementRectInNewScale = focusedElementRectInDocumentCoordinates; focusedElementRectInNewScale.scale(scale); focusedElementRectInNewScale.moveBy([_contentView frame].origin); // Find the portion of the view that is visible on the screen. UIViewController *topViewController = [[[_scrollView _viewControllerForAncestor] _rootAncestorViewController] _viewControllerForSupportedInterfaceOrientations]; UIView *fullScreenView = topViewController.view; if (!fullScreenView) fullScreenView = window; CGRect unobscuredScrollViewRectInWebViewCoordinates = UIEdgeInsetsInsetRect([self bounds], _obscuredInsets); CGRect visibleScrollViewBoundsInWebViewCoordinates = CGRectIntersection(unobscuredScrollViewRectInWebViewCoordinates, [fullScreenView convertRect:[fullScreenView bounds] toView:self]); CGRect formAssistantFrameInWebViewCoordinates = [window convertRect:_inputViewBounds toView:self]; CGRect intersectionBetweenScrollViewAndFormAssistant = CGRectIntersection(visibleScrollViewBoundsInWebViewCoordinates, formAssistantFrameInWebViewCoordinates); CGSize visibleSize = visibleScrollViewBoundsInWebViewCoordinates.size; CGFloat visibleOffsetFromTop = 0; if (!CGRectIsEmpty(intersectionBetweenScrollViewAndFormAssistant)) { CGFloat heightVisibleAboveFormAssistant = CGRectGetMinY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates); CGFloat heightVisibleBelowFormAssistant = CGRectGetMaxY(visibleScrollViewBoundsInWebViewCoordinates) - CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant); if (heightVisibleAboveFormAssistant >= kMinimumHeightToShowContentAboveKeyboard || heightVisibleBelowFormAssistant < heightVisibleAboveFormAssistant) visibleSize.height = heightVisibleAboveFormAssistant; else { visibleSize.height = heightVisibleBelowFormAssistant; visibleOffsetFromTop = CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates); } } BOOL selectionRectIsNotNull = !selectionRectInDocumentCoordinates.isZero(); if (!forceScroll) { CGRect currentlyVisibleRegionInWebViewCoordinates; currentlyVisibleRegionInWebViewCoordinates.origin = unobscuredScrollViewRectInWebViewCoordinates.origin; currentlyVisibleRegionInWebViewCoordinates.origin.y += visibleOffsetFromTop; currentlyVisibleRegionInWebViewCoordinates.size = visibleSize; // Don't bother scrolling if the entire node is already visible, whether or not we got a selectionRect. if (CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:focusedElementRectInDocumentCoordinates fromView:_contentView.get()])) return; // Don't bother scrolling if we have a valid selectionRect and it is already visible. if (selectionRectIsNotNull && CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:selectionRectInDocumentCoordinates fromView:_contentView.get()])) return; } // We want to zoom to the left/top corner of the DOM node, with as much spacing on all sides as we // can get based on the visible area after zooming (workingFrame). The spacing in either dimension is half the // difference between the size of the DOM node and the size of the visible frame. CGFloat horizontalSpaceInWebViewCoordinates = std::max((visibleSize.width - focusedElementRectInNewScale.width()) / 2.0, 0.0); CGFloat verticalSpaceInWebViewCoordinates = std::max((visibleSize.height - focusedElementRectInNewScale.height()) / 2.0, 0.0); CGPoint topLeft; topLeft.x = focusedElementRectInNewScale.x() - horizontalSpaceInWebViewCoordinates; topLeft.y = focusedElementRectInNewScale.y() - verticalSpaceInWebViewCoordinates - visibleOffsetFromTop; CGFloat minimumAllowableHorizontalOffsetInWebViewCoordinates = -INFINITY; CGFloat minimumAllowableVerticalOffsetInWebViewCoordinates = -INFINITY; if (selectionRectIsNotNull) { WebCore::FloatRect selectionRectInNewScale = selectionRectInDocumentCoordinates; selectionRectInNewScale.scale(scale); selectionRectInNewScale.moveBy([_contentView frame].origin); minimumAllowableHorizontalOffsetInWebViewCoordinates = CGRectGetMaxX(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.width; minimumAllowableVerticalOffsetInWebViewCoordinates = CGRectGetMaxY(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.height - visibleOffsetFromTop; } WebCore::FloatRect documentBoundsInNewScale = [_contentView bounds]; documentBoundsInNewScale.scale(scale); documentBoundsInNewScale.moveBy([_contentView frame].origin); // Constrain the left edge in document coordinates so that: // - it isn't so small that the scrollVisibleRect isn't visible on the screen // - it isn't so great that the document's right edge is less than the right edge of the screen if (selectionRectIsNotNull && topLeft.x < minimumAllowableHorizontalOffsetInWebViewCoordinates) topLeft.x = minimumAllowableHorizontalOffsetInWebViewCoordinates; else { CGFloat maximumAllowableHorizontalOffset = CGRectGetMaxX(documentBoundsInNewScale) - visibleSize.width; if (topLeft.x > maximumAllowableHorizontalOffset) topLeft.x = maximumAllowableHorizontalOffset; } // Constrain the top edge in document coordinates so that: // - it isn't so small that the scrollVisibleRect isn't visible on the screen // - it isn't so great that the document's bottom edge is higher than the top of the form assistant if (selectionRectIsNotNull && topLeft.y < minimumAllowableVerticalOffsetInWebViewCoordinates) topLeft.y = minimumAllowableVerticalOffsetInWebViewCoordinates; else { CGFloat maximumAllowableVerticalOffset = CGRectGetMaxY(documentBoundsInNewScale) - visibleSize.height; if (topLeft.y > maximumAllowableVerticalOffset) topLeft.y = maximumAllowableVerticalOffset; } WebCore::FloatPoint newCenter = CGPointMake(topLeft.x + unobscuredScrollViewRectInWebViewCoordinates.size.width / 2.0, topLeft.y + unobscuredScrollViewRectInWebViewCoordinates.size.height / 2.0); if (scale != contentZoomScale(self)) _page->willStartUserTriggeredZooming(); // The newCenter has been computed in the new scale, but _zoomToCenter expected the center to be in the original scale. newCenter.scale(1 / scale, 1 / scale); [_scrollView _zoomToCenter:newCenter scale:scale duration:UIWebFormAnimationDuration force:YES]; } - (CGFloat)_targetContentZoomScaleForRect:(const WebCore::FloatRect&)targetRect currentScale:(double)currentScale fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale { WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size); double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width(); double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height(); horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale); verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale); return fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale; } - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance { const float maximumScaleFactorDeltaForPanScroll = 0.02; double currentScale = contentZoomScale(self); double targetScale = [self _targetContentZoomScaleForRect:targetRect currentScale:currentScale fitEntireRect:fitEntireRect minimumScale:minimumScale maximumScale:maximumScale]; if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) { if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance]) return true; } else if (targetScale != currentScale) { [self _zoomToRect:targetRect atScale:targetScale origin:origin animated:YES]; return true; } return false; } - (void)didMoveToWindow { _page->viewStateDidChange(WebCore::ViewState::AllFlags); } - (void)setOpaque:(BOOL)opaque { BOOL oldOpaque = self.opaque; [super setOpaque:opaque]; [_contentView setOpaque:opaque]; if (oldOpaque == opaque) return; _page->setDrawsBackground(opaque); [self _updateScrollViewBackground]; } - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; [_contentView setBackgroundColor:backgroundColor]; } #pragma mark - UIScrollViewDelegate - (BOOL)usesStandardContentView { return !_customContentView; } - (CGSize)scrollView:(UIScrollView*)scrollView contentSizeForZoomScale:(CGFloat)scale withProposedSize:(CGSize)proposedSize { return roundScrollViewContentSize(*_page, proposedSize); } - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { ASSERT(_scrollView == scrollView); if (_customContentView) return _customContentView.get(); return _contentView.get(); } - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { if (![self usesStandardContentView]) return; if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan) { _page->willStartUserTriggeredZooming(); [_contentView scrollViewWillStartPanOrPinchGesture]; } [_contentView willStartZoomOrScroll]; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { if (![self usesStandardContentView]) return; if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan) [_contentView scrollViewWillStartPanOrPinchGesture]; [_contentView willStartZoomOrScroll]; #if ENABLE(CSS_SCROLL_SNAP) && ENABLE(ASYNC_SCROLLING) // FIXME: We will want to detect whether snapping will occur before beginning to drag. See WebPageProxy::didCommitLayerTree. WebKit::RemoteScrollingCoordinatorProxy* coordinator = _page->scrollingCoordinatorProxy(); ASSERT(scrollView == _scrollView.get()); CGFloat scrollDecelerationFactor = (coordinator && coordinator->shouldSetScrollViewDecelerationRateFast()) ? UIScrollViewDecelerationRateFast : [_scrollView preferredScrollDecelerationFactor]; scrollView.horizontalScrollDecelerationFactor = scrollDecelerationFactor; scrollView.verticalScrollDecelerationFactor = scrollDecelerationFactor; #endif } - (void)_didFinishScrolling { if (![self usesStandardContentView]) return; [self _updateVisibleContentRects]; [_contentView didFinishScrolling]; } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { // Work around <rdar://problem/16374753> by avoiding deceleration while // zooming. We'll animate to the right place once the zoom finishes. if ([scrollView isZooming]) *targetContentOffset = [scrollView contentOffset]; #if ENABLE(CSS_SCROLL_SNAP) && ENABLE(ASYNC_SCROLLING) if (WebKit::RemoteScrollingCoordinatorProxy* coordinator = _page->scrollingCoordinatorProxy()) { // FIXME: Here, I'm finding the maximum horizontal/vertical scroll offsets. There's probably a better way to do this. CGSize maxScrollOffsets = CGSizeMake(scrollView.contentSize.width - scrollView.bounds.size.width, scrollView.contentSize.height - scrollView.bounds.size.height); CGRect fullViewRect = self.bounds; UIEdgeInsets contentInset; id<WKUIDelegatePrivate> uiDelegatePrivate = static_cast<id <WKUIDelegatePrivate>>([self UIDelegate]); if ([uiDelegatePrivate respondsToSelector:@selector(_webView:finalObscuredInsetsForScrollView:withVelocity:targetContentOffset:)]) contentInset = [uiDelegatePrivate _webView:self finalObscuredInsetsForScrollView:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; else contentInset = [self _computedContentInset]; CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, contentInset); coordinator->adjustTargetContentOffsetForSnapping(maxScrollOffsets, velocity, unobscuredRect.origin.y, targetContentOffset); } #endif } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called. if (!decelerate) [self _didFinishScrolling]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self _didFinishScrolling]; } - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { [self _didFinishScrolling]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (![self usesStandardContentView]) [_customContentView scrollViewDidScroll:(UIScrollView *)scrollView]; [self _updateVisibleContentRects]; if (WebKit::RemoteLayerTreeScrollingPerformanceData* scrollPerfData = _page->scrollingPerformanceData()) scrollPerfData->didScroll([self visibleRectInViewCoordinates]); } - (void)scrollViewDidZoom:(UIScrollView *)scrollView { [self _updateScrollViewBackground]; [self _updateVisibleContentRects]; } - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale { ASSERT(scrollView == _scrollView); [self _updateVisibleContentRects]; [_contentView didZoomToScale:scale]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { [self _didFinishScrolling]; } - (void)_scrollViewDidInterruptDecelerating:(UIScrollView *)scrollView { if (![self usesStandardContentView]) return; [_contentView didInterruptScrolling]; [self _updateVisibleContentRects]; } - (void)_frameOrBoundsChanged { CGRect bounds = self.bounds; [_scrollView setFrame:bounds]; if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing) { if (!_overridesMinimumLayoutSize) _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(bounds.size)); if (!_overridesMaximumUnobscuredSize) _page->setMaximumUnobscuredSize(WebCore::FloatSize(bounds.size)); if (WebKit::DrawingAreaProxy* drawingArea = _page->drawingArea()) drawingArea->setSize(WebCore::IntSize(bounds.size), WebCore::IntSize(), WebCore::IntSize()); } [_customContentView web_setMinimumSize:bounds.size]; [self _updateVisibleContentRects]; } // Unobscured content rect where the user can interact. When the keyboard is up, this should be the area above or bellow the keyboard, wherever there is enough space. - (CGRect)_contentRectForUserInteraction { // FIXME: handle split keyboard. UIEdgeInsets obscuredInsets = _obscuredInsets; obscuredInsets.bottom = std::max(_obscuredInsets.bottom, _inputViewBounds.size.height); CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, obscuredInsets); return [self convertRect:unobscuredRect toView:self._currentContentView]; } // Ideally UIScrollView would expose this for us: <rdar://problem/21394567>. - (BOOL)_scrollViewIsRubberBanding { float deviceScaleFactor = _page->deviceScaleFactor(); CGPoint contentOffset = [_scrollView contentOffset]; CGPoint boundedOffset = contentOffsetBoundedInValidRange(_scrollView.get(), contentOffset); return !pointsEqualInDevicePixels(contentOffset, boundedOffset, deviceScaleFactor); } - (void)_updateVisibleContentRects { if (![self usesStandardContentView]) { [_customContentView web_computedContentInsetDidChange]; return; } if (_delayUpdateVisibleContentRects) { _hadDelayedUpdateVisibleContentRects = YES; return; } if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) return; if (_needsResetViewStateAfterCommitLoadForMainFrame) return; if ([_scrollView isZoomBouncing]) return; CGRect fullViewRect = self.bounds; CGRect visibleRectInContentCoordinates = _frozenVisibleContentRect ? _frozenVisibleContentRect.value() : [self convertRect:fullViewRect toView:_contentView.get()]; CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]); CGRect unobscuredRectInContentCoordinates = _frozenUnobscuredContentRect ? _frozenUnobscuredContentRect.value() : [self convertRect:unobscuredRect toView:_contentView.get()]; CGFloat scaleFactor = contentZoomScale(self); BOOL isStableState = !(_isChangingObscuredInsetsInteractively || [_scrollView isDragging] || [_scrollView isDecelerating] || [_scrollView isZooming] || [_scrollView _isAnimatingZoom] || [_scrollView _isScrollingToTop] || [self _scrollViewIsRubberBanding]); // FIXME: this can be made static after we stop supporting iOS 8.x. if (isStableState && [_scrollView respondsToSelector:@selector(_isInterruptingDeceleration)]) isStableState = ![_scrollView performSelector:@selector(_isInterruptingDeceleration)]; #if ENABLE(CSS_SCROLL_SNAP) && ENABLE(ASYNC_SCROLLING) if (isStableState) { WebKit::RemoteScrollingCoordinatorProxy* coordinator = _page->scrollingCoordinatorProxy(); if (coordinator && coordinator->hasActiveSnapPoint()) { CGRect fullViewRect = self.bounds; CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]); CGPoint currentPoint = [_scrollView contentOffset]; CGPoint activePoint = coordinator->nearestActiveContentInsetAdjustedSnapPoint(unobscuredRect.origin.y, currentPoint); if (!CGPointEqualToPoint(activePoint, currentPoint)) { RetainPtr<WKScrollView> strongScrollView = _scrollView; dispatch_async(dispatch_get_main_queue(), [strongScrollView, activePoint] { [strongScrollView setContentOffset:activePoint animated:NO]; }); } } } #endif [_contentView didUpdateVisibleRect:visibleRectInContentCoordinates unobscuredRect:unobscuredRectInContentCoordinates unobscuredRectInScrollViewCoordinates:unobscuredRect scale:scaleFactor minimumScale:[_scrollView minimumZoomScale] inStableState:isStableState isChangingObscuredInsetsInteractively:_isChangingObscuredInsetsInteractively]; } - (void)_didFinishLoadForMainFrame { if (_gestureController) _gestureController->didFinishLoadForMainFrame(); } - (void)_didFailLoadForMainFrame { if (_gestureController) _gestureController->didFailLoadForMainFrame(); } - (void)_didSameDocumentNavigationForMainFrame:(WebKit::SameDocumentNavigationType)navigationType { [_customContentView web_didSameDocumentNavigation:toAPI(navigationType)]; if (_gestureController) _gestureController->didSameDocumentNavigationForMainFrame(navigationType); } - (void)_keyboardChangedWithInfo:(NSDictionary *)keyboardInfo adjustScrollView:(BOOL)adjustScrollView { NSValue *endFrameValue = [keyboardInfo objectForKey:UIKeyboardFrameEndUserInfoKey]; if (!endFrameValue) return; // The keyboard rect is always in screen coordinates. In the view services case the window does not // have the interface orientation rotation transformation; its host does. So, it makes no sense to // clip the keyboard rect against its screen. if ([[self window] _isHostedInAnotherProcess]) _inputViewBounds = [self.window convertRect:[endFrameValue CGRectValue] fromWindow:nil]; else _inputViewBounds = [self.window convertRect:CGRectIntersection([endFrameValue CGRectValue], self.window.screen.bounds) fromWindow:nil]; [self _updateVisibleContentRects]; if (adjustScrollView) [_scrollView _adjustForAutomaticKeyboardInfo:keyboardInfo animated:YES lastAdjustment:&_lastAdjustmentForScroller]; } - (BOOL)_shouldUpdateKeyboardWithInfo:(NSDictionary *)keyboardInfo { if ([_contentView isAssistingNode]) return YES; NSNumber *isLocalKeyboard = [keyboardInfo valueForKey:UIKeyboardIsLocalUserInfoKey]; return isLocalKeyboard && !isLocalKeyboard.boolValue; } - (void)_keyboardWillChangeFrame:(NSNotification *)notification { if ([self _shouldUpdateKeyboardWithInfo:notification.userInfo]) [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES]; } - (void)_keyboardDidChangeFrame:(NSNotification *)notification { [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:NO]; } - (void)_keyboardWillShow:(NSNotification *)notification { if ([self _shouldUpdateKeyboardWithInfo:notification.userInfo]) [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES]; } - (void)_keyboardWillHide:(NSNotification *)notification { // Ignore keyboard will hide notifications sent during rotation. They're just there for // backwards compatibility reasons and processing the will hide notification would // temporarily screw up the the unobscured view area. if ([[UIPeripheralHost sharedInstance] rotationState]) return; [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES]; } - (void)_windowDidRotate:(NSNotification *)notification { if (!_overridesInterfaceOrientation) _page->setDeviceOrientation(deviceOrientation()); } - (void)_contentSizeCategoryDidChange:(NSNotification *)notification { _page->contentSizeCategoryDidChange([self _contentSizeCategory]); } - (NSString *)_contentSizeCategory { return [[UIApplication sharedApplication] preferredContentSizeCategory]; } - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures { if (_allowsBackForwardNavigationGestures == allowsBackForwardNavigationGestures) return; _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures; if (allowsBackForwardNavigationGestures) { if (!_gestureController) { _gestureController = std::make_unique<WebKit::ViewGestureController>(*_page); _gestureController->installSwipeHandler(self, [self scrollView]); _gestureController->setAlternateBackForwardListSourceView([_configuration _alternateWebViewForNavigationGestures]); } } else _gestureController = nullptr; _page->setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures); } - (BOOL)allowsBackForwardNavigationGestures { return _allowsBackForwardNavigationGestures; } - (void)_navigationGestureDidBegin { // During a back/forward swipe, there's a view interposed between this view and the content view that has // an offset and animation on it, which results in computing incorrect rectangles. Work around by using // frozen rects during swipes. CGRect fullViewRect = self.bounds; CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]); _frozenVisibleContentRect = [self convertRect:fullViewRect toView:_contentView.get()]; _frozenUnobscuredContentRect = [self convertRect:unobscuredRect toView:_contentView.get()]; } - (void)_navigationGestureDidEnd { _frozenVisibleContentRect = Nullopt; _frozenUnobscuredContentRect = Nullopt; } #endif // PLATFORM(IOS) #pragma mark OS X-specific methods #if PLATFORM(MAC) - (BOOL)becomeFirstResponder { return [[self window] makeFirstResponder: _wkView.get()]; } - (BOOL)acceptsFirstResponder { return [_wkView acceptsFirstResponder]; } - (void)resizeSubviewsWithOldSize:(NSSize)oldSize { [_wkView setFrame:self.bounds]; } - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures { [_wkView setAllowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures]; } - (BOOL)allowsBackForwardNavigationGestures { return [_wkView allowsBackForwardNavigationGestures]; } - (void)setAllowsMagnification:(BOOL)allowsMagnification { [_wkView setAllowsMagnification:allowsMagnification]; } - (BOOL)allowsMagnification { return [_wkView allowsMagnification]; } - (void)setMagnification:(CGFloat)magnification { [_wkView setMagnification:magnification]; } - (CGFloat)magnification { return [_wkView magnification]; } - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point { [_wkView setMagnification:magnification centeredAtPoint:NSPointFromCGPoint(point)]; } - (BOOL)_ignoresNonWheelEvents { return [_wkView _ignoresNonWheelEvents]; } - (void)_setIgnoresNonWheelEvents:(BOOL)ignoresNonWheelEvents { [_wkView _setIgnoresNonWheelEvents:ignoresNonWheelEvents]; } - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender { return [_wkView draggingEntered:sender]; } - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender { return [_wkView draggingUpdated:sender]; } - (void)draggingExited:(id <NSDraggingInfo>)sender { [_wkView draggingExited:sender]; } - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender { return [_wkView prepareForDragOperation:sender]; } - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender { return [_wkView performDragOperation:sender]; } #endif // PLATFORM(MAC) #if ENABLE(VIDEO) - (void)_mediaDocumentNaturalSizeChanged:(NSSize)newSize { id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([self UIDelegate]); if ([uiDelegate respondsToSelector:@selector(_webView:mediaDocumentNaturalSizeChanged:)]) [uiDelegate _webView:self mediaDocumentNaturalSizeChanged:newSize]; } #endif @end @implementation WKWebView (WKPrivate) - (WKNavigation *)_loadRequest:(NSURLRequest *)request withOptions:(NSDictionary *)loadOptions { return [self loadRequest:request]; } - (BOOL)_isEditable { return _page->isEditable(); } - (void)_setEditable:(BOOL)editable { _page->setEditable(editable); #if !PLATFORM(IOS) [_wkView _addFontPanelObserver]; #endif } - (_WKRemoteObjectRegistry *)_remoteObjectRegistry { if (!_remoteObjectRegistry) { _remoteObjectRegistry = adoptNS([[_WKRemoteObjectRegistry alloc] _initWithMessageSender:*_page]); _page->process().processPool().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID(), [_remoteObjectRegistry remoteObjectRegistry]); } return _remoteObjectRegistry.get(); } - (WKBrowsingContextHandle *)_handle { return [[[WKBrowsingContextHandle alloc] _initWithPageID:_page->pageID()] autorelease]; } - (_WKRenderingProgressEvents)_observedRenderingProgressEvents { return _observedRenderingProgressEvents; } - (id <WKHistoryDelegatePrivate>)_historyDelegate { return _navigationState->historyDelegate().autorelease(); } - (void)_setHistoryDelegate:(id <WKHistoryDelegatePrivate>)historyDelegate { _page->setHistoryClient(_navigationState->createHistoryClient()); _navigationState->setHistoryDelegate(historyDelegate); } - (NSURL *)_unreachableURL { return [NSURL _web_URLWithWTFString:_page->pageLoadState().unreachableURL()]; } - (void)_loadAlternateHTMLString:(NSString *)string baseURL:(NSURL *)baseURL forUnreachableURL:(NSURL *)unreachableURL { _page->loadAlternateHTMLString(string, [baseURL _web_originalDataAsWTFString], [unreachableURL _web_originalDataAsWTFString]); } - (NSArray *)_certificateChain { if (WebKit::WebFrameProxy* mainFrame = _page->mainFrame()) return mainFrame->certificateInfo() ? (NSArray *)mainFrame->certificateInfo()->certificateInfo().certificateChain() : nil; return nil; } - (NSURL *)_committedURL { return [NSURL _web_URLWithWTFString:_page->pageLoadState().url()]; } - (NSString *)_MIMEType { if (_page->mainFrame()) return _page->mainFrame()->mimeType(); return nil; } - (NSString *)_userAgent { return _page->userAgent(); } - (NSString *)_applicationNameForUserAgent { return _page->applicationNameForUserAgent(); } - (void)_setApplicationNameForUserAgent:(NSString *)applicationNameForUserAgent { _page->setApplicationNameForUserAgent(applicationNameForUserAgent); } - (NSString *)_customUserAgent { return self.customUserAgent; } - (void)_setCustomUserAgent:(NSString *)customUserAgent { self.customUserAgent = customUserAgent; } - (void)_setUserContentExtensionsEnabled:(BOOL)userContentExtensionsEnabled { // This is kept for binary compatibility with iOS 9. } - (BOOL)_userContentExtensionsEnabled { // This is kept for binary compatibility with iOS 9. return true; } - (pid_t)_webProcessIdentifier { return _page->isValid() ? _page->processIdentifier() : 0; } - (void)_killWebContentProcess { if (!_page->isValid()) return; _page->process().terminate(); } - (WKNavigation *)_reloadWithoutContentBlockers { const bool reloadFromOrigin = false; const bool contentBlockersEnabled = false; auto navigation = _page->reload(reloadFromOrigin, contentBlockersEnabled); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (void)_killWebContentProcessAndResetState { _page->terminateProcess(); } #if PLATFORM(IOS) static WebCore::FloatSize activeMinimumLayoutSize(WKWebView *webView, const CGRect& bounds) { return WebCore::FloatSize(webView->_overridesMinimumLayoutSize ? webView->_minimumLayoutSizeOverride : bounds.size); } static WebCore::FloatSize activeMaximumUnobscuredSize(WKWebView *webView, const CGRect& bounds) { return WebCore::FloatSize(webView->_overridesMaximumUnobscuredSize ? webView->_maximumUnobscuredSizeOverride : bounds.size); } static int32_t activeOrientation(WKWebView *webView) { return webView->_overridesInterfaceOrientation ? deviceOrientationForUIInterfaceOrientation(webView->_interfaceOrientationOverride) : webView->_page->deviceOrientation(); } - (void (^)(void))_retainActiveFocusedState { ++_activeFocusedStateRetainCount; // FIXME: Use something like CompletionHandlerCallChecker to ensure that the returned block is called before it's released. return [[[self] { --_activeFocusedStateRetainCount; } copy] autorelease]; } - (void)_becomeFirstResponderWithSelectionMovingForward:(BOOL)selectingForward completionHandler:(void (^)(BOOL didBecomeFirstResponder))completionHandler { typeof(completionHandler) completionHandlerCopy = nil; if (completionHandler) completionHandlerCopy = Block_copy(completionHandler); [_contentView _becomeFirstResponderWithSelectionMovingForward:selectingForward completionHandler:[completionHandlerCopy](BOOL didBecomeFirstResponder) { if (!completionHandlerCopy) return; completionHandlerCopy(didBecomeFirstResponder); Block_release(completionHandlerCopy); }]; } - (id)_snapshotLayerContentsForBackForwardListItem:(WKBackForwardListItem *)item { if (_page->backForwardList().currentItem() == &item._item) _page->recordNavigationSnapshot(*_page->backForwardList().currentItem()); if (auto* viewSnapshot = item._item.snapshot()) return viewSnapshot->asLayerContents(); return nil; } #endif - (void)_didRelaunchProcess { #if PLATFORM(IOS) CGRect bounds = self.bounds; WebCore::FloatSize minimalLayoutSize = activeMinimumLayoutSize(self, bounds); _page->setViewportConfigurationMinimumLayoutSize(minimalLayoutSize); _page->setMaximumUnobscuredSize(activeMaximumUnobscuredSize(self, bounds)); #endif } - (NSData *)_sessionStateData { WebKit::SessionState sessionState = _page->sessionState(); // FIXME: This should not use the legacy session state encoder. return [wrapper(*WebKit::encodeLegacySessionState(sessionState).release().leakRef()) autorelease]; } - (_WKSessionState *)_sessionState { return adoptNS([[_WKSessionState alloc] _initWithSessionState:_page->sessionState()]).autorelease(); } - (void)_restoreFromSessionStateData:(NSData *)sessionStateData { // FIXME: This should not use the legacy session state decoder. WebKit::SessionState sessionState; if (!WebKit::decodeLegacySessionState(static_cast<const uint8_t*>(sessionStateData.bytes), sessionStateData.length, sessionState)) return; _page->restoreFromSessionState(WTF::move(sessionState), true); } - (WKNavigation *)_restoreSessionState:(_WKSessionState *)sessionState andNavigate:(BOOL)navigate { auto navigation = _page->restoreFromSessionState(sessionState->_sessionState, navigate); if (!navigation) return nil; return [wrapper(*navigation.release().leakRef()) autorelease]; } - (void)_close { _page->close(); } - (BOOL)_allowsRemoteInspection { #if ENABLE(REMOTE_INSPECTOR) return _page->allowsRemoteInspection(); #else return NO; #endif } - (void)_setAllowsRemoteInspection:(BOOL)allow { #if ENABLE(REMOTE_INSPECTOR) _page->setAllowsRemoteInspection(allow); #endif } - (BOOL)_addsVisitedLinks { return _page->addsVisitedLinks(); } - (void)_setAddsVisitedLinks:(BOOL)addsVisitedLinks { _page->setAddsVisitedLinks(addsVisitedLinks); } - (BOOL)_networkRequestsInProgress { return _page->pageLoadState().networkRequestsInProgress(); } static inline WebCore::LayoutMilestones layoutMilestones(_WKRenderingProgressEvents events) { WebCore::LayoutMilestones milestones = 0; if (events & _WKRenderingProgressEventFirstLayout) milestones |= WebCore::DidFirstLayout; if (events & _WKRenderingProgressEventFirstVisuallyNonEmptyLayout) milestones |= WebCore::DidFirstVisuallyNonEmptyLayout; if (events & _WKRenderingProgressEventFirstPaintWithSignificantArea) milestones |= WebCore::DidHitRelevantRepaintedObjectsAreaThreshold; if (events & _WKRenderingProgressEventReachedSessionRestorationRenderTreeSizeThreshold) milestones |= WebCore::ReachedSessionRestorationRenderTreeSizeThreshold; if (events & _WKRenderingProgressEventFirstLayoutAfterSuppressedIncrementalRendering) milestones |= WebCore::DidFirstLayoutAfterSuppressedIncrementalRendering; if (events & _WKRenderingProgressEventFirstPaintAfterSuppressedIncrementalRendering) milestones |= WebCore::DidFirstPaintAfterSuppressedIncrementalRendering; return milestones; } - (void)_setObservedRenderingProgressEvents:(_WKRenderingProgressEvents)observedRenderingProgressEvents { _observedRenderingProgressEvents = observedRenderingProgressEvents; _page->listenForLayoutMilestones(layoutMilestones(observedRenderingProgressEvents)); } - (void)_getMainResourceDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler { auto handler = adoptNS([completionHandler copy]); _page->getMainResourceDataOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) { void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get(); if (error != WebKit::CallbackBase::Error::None) { // FIXME: Pipe a proper error in from the WebPageProxy. RetainPtr<NSError> error = adoptNS([[NSError alloc] init]); completionHandlerBlock(nil, error.get()); } else completionHandlerBlock(wrapper(*data), nil); }); } - (void)_getWebArchiveDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler { auto handler = adoptNS([completionHandler copy]); _page->getWebArchiveOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) { void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get(); if (error != WebKit::CallbackBase::Error::None) { // FIXME: Pipe a proper error in from the WebPageProxy. RetainPtr<NSError> error = adoptNS([[NSError alloc] init]); completionHandlerBlock(nil, error.get()); } else completionHandlerBlock(wrapper(*data), nil); }); } - (_WKPaginationMode)_paginationMode { switch (_page->paginationMode()) { case WebCore::Pagination::Unpaginated: return _WKPaginationModeUnpaginated; case WebCore::Pagination::LeftToRightPaginated: return _WKPaginationModeLeftToRight; case WebCore::Pagination::RightToLeftPaginated: return _WKPaginationModeRightToLeft; case WebCore::Pagination::TopToBottomPaginated: return _WKPaginationModeTopToBottom; case WebCore::Pagination::BottomToTopPaginated: return _WKPaginationModeBottomToTop; } ASSERT_NOT_REACHED(); return _WKPaginationModeUnpaginated; } - (void)_setPaginationMode:(_WKPaginationMode)paginationMode { WebCore::Pagination::Mode mode; switch (paginationMode) { case _WKPaginationModeUnpaginated: mode = WebCore::Pagination::Unpaginated; break; case _WKPaginationModeLeftToRight: mode = WebCore::Pagination::LeftToRightPaginated; break; case _WKPaginationModeRightToLeft: mode = WebCore::Pagination::RightToLeftPaginated; break; case _WKPaginationModeTopToBottom: mode = WebCore::Pagination::TopToBottomPaginated; break; case _WKPaginationModeBottomToTop: mode = WebCore::Pagination::BottomToTopPaginated; break; default: return; } _page->setPaginationMode(mode); } - (BOOL)_paginationBehavesLikeColumns { return _page->paginationBehavesLikeColumns(); } - (void)_setPaginationBehavesLikeColumns:(BOOL)behavesLikeColumns { _page->setPaginationBehavesLikeColumns(behavesLikeColumns); } - (CGFloat)_pageLength { return _page->pageLength(); } - (void)_setPageLength:(CGFloat)pageLength { _page->setPageLength(pageLength); } - (CGFloat)_gapBetweenPages { return _page->gapBetweenPages(); } - (void)_setGapBetweenPages:(CGFloat)gapBetweenPages { _page->setGapBetweenPages(gapBetweenPages); } - (NSUInteger)_pageCount { return _page->pageCount(); } - (BOOL)_supportsTextZoom { return _page->supportsTextZoom(); } - (double)_textZoomFactor { return _page->textZoomFactor(); } - (void)_setTextZoomFactor:(double)zoomFactor { _page->setTextZoomFactor(zoomFactor); } - (double)_pageZoomFactor { return _page->pageZoomFactor(); } - (void)_setPageZoomFactor:(double)zoomFactor { _page->setPageZoomFactor(zoomFactor); } - (id <_WKDiagnosticLoggingDelegate>)_diagnosticLoggingDelegate { return [static_cast<WebKit::DiagnosticLoggingClient&>(_page->diagnosticLoggingClient()).delegate().leakRef() autorelease]; } - (void)_setDiagnosticLoggingDelegate:(id<_WKDiagnosticLoggingDelegate>)diagnosticLoggingDelegate { static_cast<WebKit::DiagnosticLoggingClient&>(_page->diagnosticLoggingClient()).setDelegate(diagnosticLoggingDelegate); } - (id <_WKFindDelegate>)_findDelegate { return [static_cast<WebKit::FindClient&>(_page->findClient()).delegate().leakRef() autorelease]; } - (void)_setFindDelegate:(id<_WKFindDelegate>)findDelegate { static_cast<WebKit::FindClient&>(_page->findClient()).setDelegate(findDelegate); } static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions) { unsigned findOptions = 0; if (wkFindOptions & _WKFindOptionsCaseInsensitive) findOptions |= WebKit::FindOptionsCaseInsensitive; if (wkFindOptions & _WKFindOptionsAtWordStarts) findOptions |= WebKit::FindOptionsAtWordStarts; if (wkFindOptions & _WKFindOptionsTreatMedialCapitalAsWordStart) findOptions |= WebKit::FindOptionsTreatMedialCapitalAsWordStart; if (wkFindOptions & _WKFindOptionsBackwards) findOptions |= WebKit::FindOptionsBackwards; if (wkFindOptions & _WKFindOptionsWrapAround) findOptions |= WebKit::FindOptionsWrapAround; if (wkFindOptions & _WKFindOptionsShowOverlay) findOptions |= WebKit::FindOptionsShowOverlay; if (wkFindOptions & _WKFindOptionsShowFindIndicator) findOptions |= WebKit::FindOptionsShowFindIndicator; if (wkFindOptions & _WKFindOptionsShowHighlight) findOptions |= WebKit::FindOptionsShowHighlight; if (wkFindOptions & _WKFindOptionsDetermineMatchIndex) findOptions |= WebKit::FindOptionsDetermineMatchIndex; return static_cast<WebKit::FindOptions>(findOptions); } - (void)_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount { #if PLATFORM(IOS) if (_customContentView) { [_customContentView web_countStringMatches:string options:options maxCount:maxCount]; return; } #endif _page->countStringMatches(string, toFindOptions(options), maxCount); } - (void)_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount { #if PLATFORM(IOS) if (_customContentView) { [_customContentView web_findString:string options:options maxCount:maxCount]; return; } #endif _page->findString(string, toFindOptions(options), maxCount); } - (void)_hideFindUI { #if PLATFORM(IOS) if (_customContentView) { [_customContentView web_hideFindUI]; return; } #endif _page->hideFindUI(); } - (void)_saveBackForwardSnapshotForItem:(WKBackForwardListItem *)item { _page->recordNavigationSnapshot(item._item); } - (id <_WKFormDelegate>)_formDelegate { return _formDelegate.getAutoreleased(); } - (void)_setFormDelegate:(id <_WKFormDelegate>)formDelegate { _formDelegate = formDelegate; class FormClient : public API::FormClient { public: explicit FormClient(WKWebView *webView) : m_webView(webView) { } virtual ~FormClient() { } virtual void willSubmitForm(WebKit::WebPageProxy&, WebKit::WebFrameProxy&, WebKit::WebFrameProxy& sourceFrame, const Vector<std::pair<WTF::String, WTF::String>>& textFieldValues, API::Object* userData, Ref<WebKit::WebFormSubmissionListenerProxy>&& listener) override { if (userData && userData->type() != API::Object::Type::Data) { ASSERT(!userData || userData->type() == API::Object::Type::Data); m_webView->_page->process().connection()->markCurrentlyDispatchedMessageAsInvalid(); listener->continueSubmission(); return; } auto formDelegate = m_webView->_formDelegate.get(); if (![formDelegate respondsToSelector:@selector(_webView:willSubmitFormValues:userObject:submissionHandler:)]) { listener->continueSubmission(); return; } auto valueMap = adoptNS([[NSMutableDictionary alloc] initWithCapacity:textFieldValues.size()]); for (const auto& pair : textFieldValues) [valueMap setObject:pair.second forKey:pair.first]; NSObject <NSSecureCoding> *userObject = nil; if (API::Data* data = static_cast<API::Data*>(userData)) { auto nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:const_cast<void*>(static_cast<const void*>(data->bytes())) length:data->size() freeWhenDone:NO]); auto unarchiver = adoptNS([[NSKeyedUnarchiver alloc] initForReadingWithData:nsData.get()]); [unarchiver setRequiresSecureCoding:YES]; @try { userObject = [unarchiver decodeObjectOfClass:[NSObject class] forKey:@"userObject"]; } @catch (NSException *exception) { LOG_ERROR("Failed to decode user data: %@", exception); } } RefPtr<WebKit::WebFormSubmissionListenerProxy> localListener = WTF::move(listener); RefPtr<WebKit::CompletionHandlerCallChecker> checker = WebKit::CompletionHandlerCallChecker::create(formDelegate.get(), @selector(_webView:willSubmitFormValues:userObject:submissionHandler:)); [formDelegate _webView:m_webView willSubmitFormValues:valueMap.get() userObject:userObject submissionHandler:[localListener, checker] { checker->didCallCompletionHandler(); localListener->continueSubmission(); }]; } private: WKWebView *m_webView; }; if (formDelegate) _page->setFormClient(std::make_unique<FormClient>(self)); else _page->setFormClient(nullptr); } - (BOOL)_isDisplayingStandaloneImageDocument { if (auto* mainFrame = _page->mainFrame()) return mainFrame->isDisplayingStandaloneImageDocument(); return NO; } - (BOOL)_isDisplayingStandaloneMediaDocument { if (auto* mainFrame = _page->mainFrame()) return mainFrame->isDisplayingStandaloneMediaDocument(); return NO; } - (BOOL)_isShowingNavigationGestureSnapshot { return _page->isShowingNavigationGestureSnapshot(); } - (_WKLayoutMode)_layoutMode { #if PLATFORM(MAC) switch ([_wkView _layoutMode]) { case kWKLayoutModeFixedSize: return _WKLayoutModeFixedSize; case kWKLayoutModeDynamicSizeComputedFromViewScale: return _WKLayoutModeDynamicSizeComputedFromViewScale; case kWKLayoutModeDynamicSizeWithMinimumViewSize: return _WKLayoutModeDynamicSizeWithMinimumViewSize; case kWKLayoutModeDynamicSizeComputedFromMinimumDocumentSize: return _WKLayoutModeDynamicSizeComputedFromMinimumDocumentSize; case kWKLayoutModeViewSize: default: return _WKLayoutModeViewSize; } #else return _page->useFixedLayout() ? _WKLayoutModeFixedSize : _WKLayoutModeViewSize; #endif } - (void)_setLayoutMode:(_WKLayoutMode)layoutMode { #if PLATFORM(MAC) WKLayoutMode wkViewLayoutMode; switch (layoutMode) { case _WKLayoutModeFixedSize: wkViewLayoutMode = kWKLayoutModeFixedSize; break; case _WKLayoutModeDynamicSizeComputedFromViewScale: wkViewLayoutMode = kWKLayoutModeDynamicSizeComputedFromViewScale; break; case _WKLayoutModeDynamicSizeWithMinimumViewSize: wkViewLayoutMode = kWKLayoutModeDynamicSizeWithMinimumViewSize; break; case _WKLayoutModeDynamicSizeComputedFromMinimumDocumentSize: wkViewLayoutMode = kWKLayoutModeDynamicSizeComputedFromMinimumDocumentSize; break; case _WKLayoutModeViewSize: default: wkViewLayoutMode = kWKLayoutModeViewSize; break; } [_wkView _setLayoutMode:wkViewLayoutMode]; #else _page->setUseFixedLayout(layoutMode == _WKLayoutModeFixedSize || layoutMode == _WKLayoutModeDynamicSizeComputedFromViewScale); #endif } - (CGSize)_fixedLayoutSize { return _page->fixedLayoutSize(); } - (void)_setFixedLayoutSize:(CGSize)fixedLayoutSize { _page->setFixedLayoutSize(WebCore::expandedIntSize(WebCore::FloatSize(fixedLayoutSize))); } - (CGFloat)_viewScale { return _page->viewScaleFactor(); } - (void)_setViewScale:(CGFloat)viewScale { #if PLATFORM(MAC) [_wkView _setViewScale:viewScale]; #else if (viewScale <= 0 || isnan(viewScale) || isinf(viewScale)) [NSException raise:NSInvalidArgumentException format:@"View scale should be a positive number"]; _page->scaleView(viewScale); #endif } - (void)_setMinimumViewSize:(CGSize)minimumViewSize { #if PLATFORM(MAC) [_wkView _setMinimumViewSize:minimumViewSize]; #endif } - (CGSize)_minimumViewSize { #if PLATFORM(MAC) return [_wkView _minimumViewSize]; #else return CGSizeZero; #endif } #pragma mark scrollperf methods - (void)_setScrollPerformanceDataCollectionEnabled:(BOOL)enabled { _page->setScrollPerformanceDataCollectionEnabled(enabled); } - (BOOL)_scrollPerformanceDataCollectionEnabled { return _page->scrollPerformanceDataCollectionEnabled(); } - (NSArray *)_scrollPerformanceData { #if PLATFORM(IOS) if (WebKit::RemoteLayerTreeScrollingPerformanceData* scrollPerfData = _page->scrollingPerformanceData()) return scrollPerfData->data(); #endif return nil; } #pragma mark media playback restrictions - (BOOL)_allowsMediaDocumentInlinePlayback { #if PLATFORM(IOS) return _page->allowsMediaDocumentInlinePlayback(); #else return NO; #endif } - (void)_setAllowsMediaDocumentInlinePlayback:(BOOL)flag { #if PLATFORM(IOS) _page->setAllowsMediaDocumentInlinePlayback(flag); #endif } #pragma mark iOS-specific methods #if PLATFORM(IOS) - (CGSize)_minimumLayoutSizeOverride { ASSERT(_overridesMinimumLayoutSize); return _minimumLayoutSizeOverride; } - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride { _overridesMinimumLayoutSize = YES; if (CGSizeEqualToSize(_minimumLayoutSizeOverride, minimumLayoutSizeOverride)) return; _minimumLayoutSizeOverride = minimumLayoutSizeOverride; if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing) _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(minimumLayoutSizeOverride)); } - (UIEdgeInsets)_obscuredInsets { return _obscuredInsets; } - (void)_setObscuredInsets:(UIEdgeInsets)obscuredInsets { ASSERT(obscuredInsets.top >= 0); ASSERT(obscuredInsets.left >= 0); ASSERT(obscuredInsets.bottom >= 0); ASSERT(obscuredInsets.right >= 0); _haveSetObscuredInsets = YES; if (UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, obscuredInsets)) return; _obscuredInsets = obscuredInsets; [self _updateVisibleContentRects]; } - (void)_setInterfaceOrientationOverride:(UIInterfaceOrientation)interfaceOrientation { if (!_overridesInterfaceOrientation) [[NSNotificationCenter defaultCenter] removeObserver:self name:UIWindowDidRotateNotification object:nil]; _overridesInterfaceOrientation = YES; if (interfaceOrientation == _interfaceOrientationOverride) return; _interfaceOrientationOverride = interfaceOrientation; if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing) _page->setDeviceOrientation(deviceOrientationForUIInterfaceOrientation(_interfaceOrientationOverride)); } - (UIInterfaceOrientation)_interfaceOrientationOverride { ASSERT(_overridesInterfaceOrientation); return _interfaceOrientationOverride; } - (CGSize)_maximumUnobscuredSizeOverride { ASSERT(_overridesMaximumUnobscuredSize); return _maximumUnobscuredSizeOverride; } - (void)_setMaximumUnobscuredSizeOverride:(CGSize)size { ASSERT(size.width <= self.bounds.size.width && size.height <= self.bounds.size.height); _overridesMaximumUnobscuredSize = YES; if (CGSizeEqualToSize(_maximumUnobscuredSizeOverride, size)) return; _maximumUnobscuredSizeOverride = size; if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing) _page->setMaximumUnobscuredSize(WebCore::FloatSize(size)); } - (void)_setBackgroundExtendsBeyondPage:(BOOL)backgroundExtends { _page->setBackgroundExtendsBeyondPage(backgroundExtends); } - (BOOL)_backgroundExtendsBeyondPage { return _page->backgroundExtendsBeyondPage(); } - (void)_setAllowsViewportShrinkToFit:(BOOL)allowShrinkToFit { _allowsViewportShrinkToFit = allowShrinkToFit; } - (BOOL)_allowsViewportShrinkToFit { return _allowsViewportShrinkToFit; } - (void)_beginInteractiveObscuredInsetsChange { ASSERT(!_isChangingObscuredInsetsInteractively); _isChangingObscuredInsetsInteractively = YES; } - (void)_endInteractiveObscuredInsetsChange { ASSERT(_isChangingObscuredInsetsInteractively); _isChangingObscuredInsetsInteractively = NO; [self _updateVisibleContentRects]; } - (void)_hideContentUntilNextUpdate { if (auto* area = _page->drawingArea()) area->hideContentUntilAnyUpdate(); } - (void)_beginAnimatedResizeWithUpdates:(void (^)(void))updateBlock { CGRect oldBounds = self.bounds; WebCore::FloatRect oldUnobscuredContentRect = _page->unobscuredContentRect(); if (_customContentView || !_hasCommittedLoadForMainFrame || CGRectIsEmpty(oldBounds) || oldUnobscuredContentRect.isEmpty()) { updateBlock(); return; } _dynamicViewportUpdateMode = DynamicViewportUpdateMode::ResizingWithAnimation; WebCore::FloatSize oldMinimumLayoutSize = activeMinimumLayoutSize(self, oldBounds); WebCore::FloatSize oldMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, oldBounds); int32_t oldOrientation = activeOrientation(self); UIEdgeInsets oldObscuredInsets = _obscuredInsets; updateBlock(); CGRect newBounds = self.bounds; WebCore::FloatSize newMinimumLayoutSize = activeMinimumLayoutSize(self, newBounds); WebCore::FloatSize newMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, newBounds); int32_t newOrientation = activeOrientation(self); UIEdgeInsets newObscuredInsets = _obscuredInsets; CGRect futureUnobscuredRectInSelfCoordinates = UIEdgeInsetsInsetRect(newBounds, _obscuredInsets); CGRect contentViewBounds = [_contentView bounds]; ASSERT_WITH_MESSAGE(!(_overridesMinimumLayoutSize && newMinimumLayoutSize.isEmpty()), "Clients controlling the layout size should maintain a valid layout size to minimize layouts."); if (CGRectIsEmpty(newBounds) || newMinimumLayoutSize.isEmpty() || CGRectIsEmpty(futureUnobscuredRectInSelfCoordinates) || CGRectIsEmpty(contentViewBounds)) { _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing; [self _frameOrBoundsChanged]; if (_overridesMinimumLayoutSize) _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(newMinimumLayoutSize)); if (_overridesMaximumUnobscuredSize) _page->setMaximumUnobscuredSize(WebCore::FloatSize(newMaximumUnobscuredSize)); if (_overridesInterfaceOrientation) _page->setDeviceOrientation(newOrientation); [self _updateVisibleContentRects]; return; } if (CGRectEqualToRect(oldBounds, newBounds) && oldMinimumLayoutSize == newMinimumLayoutSize && oldMaximumUnobscuredSize == newMaximumUnobscuredSize && oldOrientation == newOrientation && UIEdgeInsetsEqualToEdgeInsets(oldObscuredInsets, newObscuredInsets)) { _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing; [self _updateVisibleContentRects]; return; } _resizeAnimationTransformAdjustments = CATransform3DIdentity; NSUInteger indexOfContentView = [[_scrollView subviews] indexOfObject:_contentView.get()]; _resizeAnimationView = adoptNS([[UIView alloc] init]); [_scrollView insertSubview:_resizeAnimationView.get() atIndex:indexOfContentView]; [_resizeAnimationView addSubview:_contentView.get()]; [_resizeAnimationView addSubview:[_contentView unscaledView]]; CGSize contentSizeInContentViewCoordinates = contentViewBounds.size; [_scrollView setMinimumZoomScale:std::min(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView minimumZoomScale])]; [_scrollView setMaximumZoomScale:std::max(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView maximumZoomScale])]; // Compute the new scale to keep the current content width in the scrollview. CGFloat oldWebViewWidthInContentViewCoordinates = oldUnobscuredContentRect.width(); CGFloat visibleContentViewWidthInContentCoordinates = std::min(contentSizeInContentViewCoordinates.width, oldWebViewWidthInContentViewCoordinates); CGFloat targetScale = newMinimumLayoutSize.width() / visibleContentViewWidthInContentCoordinates; CGFloat resizeAnimationViewAnimationScale = targetScale / contentZoomScale(self); [_resizeAnimationView setTransform:CGAffineTransformMakeScale(resizeAnimationViewAnimationScale, resizeAnimationViewAnimationScale)]; // Compute a new position to keep the content centered. CGPoint originalContentCenter = oldUnobscuredContentRect.center(); CGPoint originalContentCenterInSelfCoordinates = [self convertPoint:originalContentCenter fromView:_contentView.get()]; CGPoint futureUnobscuredRectCenterInSelfCoordinates = CGPointMake(futureUnobscuredRectInSelfCoordinates.origin.x + futureUnobscuredRectInSelfCoordinates.size.width / 2, futureUnobscuredRectInSelfCoordinates.origin.y + futureUnobscuredRectInSelfCoordinates.size.height / 2); CGPoint originalContentOffset = [_scrollView contentOffset]; CGPoint contentOffset = originalContentOffset; contentOffset.x += (originalContentCenterInSelfCoordinates.x - futureUnobscuredRectCenterInSelfCoordinates.x); contentOffset.y += (originalContentCenterInSelfCoordinates.y - futureUnobscuredRectCenterInSelfCoordinates.y); // Limit the new offset within the scrollview, we do not want to rubber band programmatically. CGSize futureContentSizeInSelfCoordinates = CGSizeMake(contentSizeInContentViewCoordinates.width * targetScale, contentSizeInContentViewCoordinates.height * targetScale); CGFloat maxHorizontalOffset = futureContentSizeInSelfCoordinates.width - newBounds.size.width + _obscuredInsets.right; contentOffset.x = std::min(contentOffset.x, maxHorizontalOffset); CGFloat maxVerticalOffset = futureContentSizeInSelfCoordinates.height - newBounds.size.height + _obscuredInsets.bottom; contentOffset.y = std::min(contentOffset.y, maxVerticalOffset); contentOffset.x = std::max(contentOffset.x, -_obscuredInsets.left); contentOffset.y = std::max(contentOffset.y, -_obscuredInsets.top); // Make the top/bottom edges "sticky" within 1 pixel. if (oldUnobscuredContentRect.maxY() > contentSizeInContentViewCoordinates.height - 1) contentOffset.y = maxVerticalOffset; if (oldUnobscuredContentRect.y() < 1) contentOffset.y = -_obscuredInsets.top; // FIXME: if we have content centered after double tap to zoom, we should also try to keep that rect in view. [_scrollView setContentSize:roundScrollViewContentSize(*_page, futureContentSizeInSelfCoordinates)]; [_scrollView setContentOffset:contentOffset]; CGRect visibleRectInContentCoordinates = [self convertRect:newBounds toView:_contentView.get()]; CGRect unobscuredRectInContentCoordinates = [self convertRect:futureUnobscuredRectInSelfCoordinates toView:_contentView.get()]; _page->dynamicViewportSizeUpdate(newMinimumLayoutSize, newMaximumUnobscuredSize, visibleRectInContentCoordinates, unobscuredRectInContentCoordinates, futureUnobscuredRectInSelfCoordinates, targetScale, newOrientation); if (WebKit::DrawingAreaProxy* drawingArea = _page->drawingArea()) drawingArea->setSize(WebCore::IntSize(newBounds.size), WebCore::IntSize(), WebCore::IntSize()); } - (void)_endAnimatedResize { if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing) return; _page->synchronizeDynamicViewportUpdate(); NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()]; [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView]; [_scrollView insertSubview:[_contentView unscaledView] atIndex:indexOfResizeAnimationView + 1]; CALayer *contentViewLayer = [_contentView layer]; CGFloat adjustmentScale = _resizeAnimationTransformAdjustments.m11; contentViewLayer.sublayerTransform = CATransform3DIdentity; CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11; CALayer *contentLayer = [_contentView layer]; CATransform3D contentLayerTransform = contentLayer.transform; CGFloat currentScale = [[_resizeAnimationView layer] transform].m11 * contentLayerTransform.m11; // We cannot use [UIScrollView setZoomScale:] directly because the UIScrollView delegate would get a callback with // an invalid contentOffset. The real content offset is only set below. // Since there is no public API for setting both the zoomScale and the contentOffset, we set the zoomScale manually // on the zoom layer and then only change the contentOffset. CGFloat adjustedScale = adjustmentScale * currentScale; contentLayerTransform.m11 = adjustedScale; contentLayerTransform.m22 = adjustedScale; contentLayer.transform = contentLayerTransform; CGPoint currentScrollOffset = [_scrollView contentOffset]; double horizontalScrollAdjustement = _resizeAnimationTransformAdjustments.m41 * animatingScaleTarget; double verticalScrollAdjustment = _resizeAnimationTransformAdjustments.m42 * animatingScaleTarget; [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)]; [_scrollView setContentOffset:CGPointMake(currentScrollOffset.x - horizontalScrollAdjustement, currentScrollOffset.y - verticalScrollAdjustment)]; [_resizeAnimationView removeFromSuperview]; _resizeAnimationView = nil; _resizeAnimationTransformAdjustments = CATransform3DIdentity; _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing; [_contentView setHidden:NO]; [self _updateVisibleContentRects]; while (!_snapshotsDeferredDuringResize.isEmpty()) _snapshotsDeferredDuringResize.takeLast()(); } - (void)_resizeWhileHidingContentWithUpdates:(void (^)(void))updateBlock { [self _beginAnimatedResizeWithUpdates:updateBlock]; if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::ResizingWithAnimation) { [_contentView setHidden:YES]; _dynamicViewportUpdateMode = DynamicViewportUpdateMode::ResizingWithDocumentHidden; } } - (void)_setOverlaidAccessoryViewsInset:(CGSize)inset { [_customContentView web_setOverlaidAccessoryViewsInset:inset]; } - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler { if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) { // Defer snapshotting until after the current resize completes. void (^copiedCompletionHandler)(CGImageRef) = [completionHandler copy]; RetainPtr<WKWebView> retainedSelf = self; _snapshotsDeferredDuringResize.append([retainedSelf, rectInViewCoordinates, imageWidth, copiedCompletionHandler] { [retainedSelf _snapshotRect:rectInViewCoordinates intoImageOfWidth:imageWidth completionHandler:copiedCompletionHandler]; [copiedCompletionHandler release]; }); return; } CGRect snapshotRectInContentCoordinates = [self convertRect:rectInViewCoordinates toView:self._currentContentView]; CGFloat imageScale = imageWidth / snapshotRectInContentCoordinates.size.width; CGFloat imageHeight = imageScale * snapshotRectInContentCoordinates.size.height; CGSize imageSize = CGSizeMake(imageWidth, imageHeight); #if USE(IOSURFACE) // If we are parented and thus won't incur a significant penalty from paging in tiles, snapshot the view hierarchy directly. if (CADisplay *display = self.window.screen._display) { auto surface = WebCore::IOSurface::create(WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebCore::ColorSpaceSRGB); CGFloat imageScaleInViewCoordinates = imageWidth / rectInViewCoordinates.size.width; CATransform3D transform = CATransform3DMakeScale(imageScaleInViewCoordinates, imageScaleInViewCoordinates, 1); transform = CATransform3DTranslate(transform, -rectInViewCoordinates.origin.x, -rectInViewCoordinates.origin.y, 0); CARenderServerRenderDisplayLayerWithTransformAndTimeOffset(MACH_PORT_NULL, (CFStringRef)display.name, self.layer.context.contextId, reinterpret_cast<uint64_t>(self.layer), surface->surface(), 0, 0, &transform, 0); completionHandler(surface->createImage().get()); return; } #endif if (_customContentView) { UIGraphicsBeginImageContextWithOptions(imageSize, YES, 1); UIView *customContentView = _customContentView.get(); [customContentView.backgroundColor set]; UIRectFill(CGRectMake(0, 0, imageWidth, imageHeight)); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, -snapshotRectInContentCoordinates.origin.x * imageScale, -snapshotRectInContentCoordinates.origin.y * imageScale); CGContextScaleCTM(context, imageScale, imageScale); [customContentView.layer renderInContext:context]; completionHandler([UIGraphicsGetImageFromCurrentImageContext() CGImage]); UIGraphicsEndImageContext(); return; } void(^copiedCompletionHandler)(CGImageRef) = [completionHandler copy]; _page->takeSnapshot(WebCore::enclosingIntRect(snapshotRectInContentCoordinates), WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebKit::SnapshotOptionsExcludeDeviceScaleFactor, [=](const WebKit::ShareableBitmap::Handle& imageHandle, WebKit::CallbackBase::Error) { if (imageHandle.isNull()) { copiedCompletionHandler(nullptr); [copiedCompletionHandler release]; return; } RefPtr<WebKit::ShareableBitmap> bitmap = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::Protection::ReadOnly); if (!bitmap) { copiedCompletionHandler(nullptr); [copiedCompletionHandler release]; return; } RetainPtr<CGImageRef> cgImage; cgImage = bitmap->makeCGImage(); copiedCompletionHandler(cgImage.get()); [copiedCompletionHandler release]; }); } - (void)_overrideLayoutParametersWithMinimumLayoutSize:(CGSize)minimumLayoutSize minimumLayoutSizeForMinimalUI:(CGSize)minimumLayoutSizeForMinimalUI maximumUnobscuredSizeOverride:(CGSize)maximumUnobscuredSizeOverride { UNUSED_PARAM(minimumLayoutSizeForMinimalUI); [self _overrideLayoutParametersWithMinimumLayoutSize:minimumLayoutSize maximumUnobscuredSizeOverride:maximumUnobscuredSizeOverride]; } - (void)_overrideLayoutParametersWithMinimumLayoutSize:(CGSize)minimumLayoutSize maximumUnobscuredSizeOverride:(CGSize)maximumUnobscuredSizeOverride { [self _setMinimumLayoutSizeOverride:minimumLayoutSize]; [self _setMaximumUnobscuredSizeOverride:maximumUnobscuredSizeOverride]; } - (UIView *)_viewForFindUI { return [self viewForZoomingInScrollView:[self scrollView]]; } - (BOOL)_isDisplayingPDF { return [_customContentView isKindOfClass:[WKPDFView class]]; } - (NSData *)_dataForDisplayedPDF { if (![self _isDisplayingPDF]) return nil; CGPDFDocumentRef pdfDocument = [(WKPDFView *)_customContentView pdfDocument]; return [(NSData *)CGDataProviderCopyData(CGPDFDocumentGetDataProvider(pdfDocument)) autorelease]; } - (NSString *)_suggestedFilenameForDisplayedPDF { if (![self _isDisplayingPDF]) return nil; return [(WKPDFView *)_customContentView.get() suggestedFilename]; } - (BOOL)_allowsDoubleTapGestures { if (_fastClickingIsDisabled) return YES; // If the page is not user scalable, we don't allow double tap gestures. if (![_scrollView isZoomEnabled] || [_scrollView minimumZoomScale] >= [_scrollView maximumZoomScale]) return NO; // If the viewport width was not explicit, we allow double tap gestures. if (!_viewportMetaTagWidthWasExplicit || _viewportMetaTagCameFromImageDocument) return YES; // For scalable viewports, only disable double tap gestures if the viewport width is device width. if (_viewportMetaTagWidth != WebCore::ViewportArguments::ValueDeviceWidth) return YES; return !areEssentiallyEqualAsFloat(contentZoomScale(self), _initialScaleFactor); } - (_WKWebViewPrintFormatter *)_webViewPrintFormatter { UIViewPrintFormatter *viewPrintFormatter = self.viewPrintFormatter; ASSERT([viewPrintFormatter isKindOfClass:[_WKWebViewPrintFormatter class]]); return (_WKWebViewPrintFormatter *)viewPrintFormatter; } #else // #if PLATFORM(IOS) #pragma mark - OS X-specific methods - (NSColor *)_pageExtendedBackgroundColor { WebCore::Color color = _page->pageExtendedBackgroundColor(); if (!color.isValid()) return nil; return nsColor(color); } - (BOOL)_drawsTransparentBackground { return _page->drawsTransparentBackground(); } - (void)_setDrawsTransparentBackground:(BOOL)drawsTransparentBackground { _page->setDrawsTransparentBackground(drawsTransparentBackground); } - (void)_setOverrideDeviceScaleFactor:(CGFloat)deviceScaleFactor { [_wkView _setOverrideDeviceScaleFactor:deviceScaleFactor]; } - (CGFloat)_overrideDeviceScaleFactor { return [_wkView _overrideDeviceScaleFactor]; } - (void)_setTopContentInset:(CGFloat)contentInset { [_wkView _setTopContentInset:contentInset]; } - (CGFloat)_topContentInset { return [_wkView _topContentInset]; } #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 - (void)_setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets { [_wkView _setAutomaticallyAdjustsContentInsets:automaticallyAdjustsContentInsets]; } - (BOOL)_automaticallyAdjustsContentInsets { return [_wkView _automaticallyAdjustsContentInsets]; } #endif #endif @end #if !TARGET_OS_IPHONE @implementation WKWebView (WKIBActions) - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item { SEL action = item.action; if (action == @selector(goBack:)) return !!_page->backForwardList().backItem(); if (action == @selector(goForward:)) return !!_page->backForwardList().forwardItem(); if (action == @selector(stopLoading:)) { // FIXME: Return no if we're stopped. return YES; } if (action == @selector(reload:) || action == @selector(reloadFromOrigin:)) { // FIXME: Return no if we're loading. return YES; } return NO; } - (IBAction)goBack:(id)sender { [self goBack]; } - (IBAction)goForward:(id)sender { [self goForward]; } - (IBAction)reload:(id)sender { [self reload]; } - (IBAction)reloadFromOrigin:(id)sender { [self reloadFromOrigin]; } - (IBAction)stopLoading:(id)sender { _page->stopLoading(); } @end #endif #if PLATFORM(IOS) @implementation WKWebView (_WKWebViewPrintFormatter) - (Class)_printFormatterClass { return [_WKWebViewPrintFormatter class]; } - (NSInteger)_computePageCountAndStartDrawingToPDFForFrame:(_WKFrameHandle *)frame printInfo:(const WebKit::PrintInfo&)printInfo firstPage:(uint32_t)firstPage computedTotalScaleFactor:(double&)totalScaleFactor { if ([self _isDisplayingPDF]) return CGPDFDocumentGetNumberOfPages([(WKPDFView *)_customContentView pdfDocument]); _pageIsPrintingToPDF = YES; Vector<WebCore::IntRect> pageRects; uint64_t frameID = frame ? frame._frameID : _page->mainFrame()->frameID(); if (!_page->sendSync(Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF(frameID, printInfo, firstPage), Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF::Reply(pageRects, totalScaleFactor))) return 0; return pageRects.size(); } - (void)_endPrinting { _pageIsPrintingToPDF = NO; _printedDocument = nullptr; _page->send(Messages::WebPage::EndPrinting()); } // FIXME: milliseconds::max() overflows when converted to nanoseconds, causing condition_variable::wait_for() to believe // a timeout occurred on any spurious wakeup. Use nanoseconds::max() (converted to ms) to avoid this. We should perhaps // change waitForAndDispatchImmediately() to take nanoseconds to avoid this issue. static constexpr std::chrono::milliseconds didFinishLoadingTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds::max()); - (CGPDFDocumentRef)_printedDocument { if ([self _isDisplayingPDF]) { ASSERT(!_pageIsPrintingToPDF); return [(WKPDFView *)_customContentView pdfDocument]; } if (_pageIsPrintingToPDF) { if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::DidFinishDrawingPagesToPDF>(_page->pageID(), didFinishLoadingTimeout)) { ASSERT_NOT_REACHED(); return nullptr; } ASSERT(!_pageIsPrintingToPDF); } return _printedDocument.get(); } - (void)_setPrintedDocument:(CGPDFDocumentRef)printedDocument { if (!_pageIsPrintingToPDF) return; ASSERT(![self _isDisplayingPDF]); _printedDocument = printedDocument; _pageIsPrintingToPDF = NO; } @end #endif #endif // WK_API_ENABLED