2 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #import "WKWebViewInternal.h"
31 #import "APIFormClient.h"
32 #import "FindClient.h"
33 #import "NavigationState.h"
34 #import "RemoteLayerTreeTransaction.h"
35 #import "RemoteObjectRegistry.h"
36 #import "RemoteObjectRegistryMessages.h"
37 #import "UIDelegate.h"
38 #import "ViewGestureController.h"
39 #import "ViewSnapshotStore.h"
40 #import "WKBackForwardListInternal.h"
41 #import "WKBackForwardListItemInternal.h"
42 #import "WKBrowsingContextHandleInternal.h"
43 #import "WKErrorInternal.h"
44 #import "WKHistoryDelegatePrivate.h"
46 #import "WKNSURLExtras.h"
47 #import "WKNavigationDelegate.h"
48 #import "WKNavigationInternal.h"
49 #import "WKPreferencesInternal.h"
50 #import "WKProcessPoolInternal.h"
51 #import "WKUIDelegate.h"
52 #import "WKUIDelegatePrivate.h"
53 #import "WKUserContentControllerInternal.h"
54 #import "WKWebViewConfigurationInternal.h"
55 #import "WKWebViewContentProvider.h"
56 #import "WebBackForwardList.h"
57 #import "WebCertificateInfo.h"
58 #import "WebContext.h"
59 #import "WebFormSubmissionListenerProxy.h"
60 #import "WebKitSystemInterface.h"
61 #import "WebPageGroup.h"
62 #import "WebPageProxy.h"
63 #import "WebPreferencesKeys.h"
64 #import "WebProcessProxy.h"
65 #import "WebSerializedScriptValue.h"
66 #import "_WKFindDelegate.h"
67 #import "_WKFormDelegate.h"
68 #import "_WKRemoteObjectRegistryInternal.h"
69 #import "_WKVisitedLinkProviderInternal.h"
70 #import "_WKWebsiteDataStoreInternal.h"
71 #import <JavaScriptCore/JSContext.h>
72 #import <JavaScriptCore/JSValue.h>
73 #import <wtf/HashMap.h>
74 #import <wtf/NeverDestroyed.h>
75 #import <wtf/RetainPtr.h>
78 #import "_WKFrameHandleInternal.h"
79 #import "_WKWebViewPrintFormatter.h"
81 #import "ProcessThrottler.h"
82 #import "RemoteLayerTreeDrawingAreaProxy.h"
84 #import "WKScrollView.h"
85 #import "WKWebViewContentProviderRegistry.h"
86 #import "WebPageMessages.h"
87 #import <CoreGraphics/CGFloat.h>
88 #import <CoreGraphics/CGPDFDocumentPrivate.h>
89 #import <UIKit/UIApplication.h>
90 #import <UIKit/UIDevice_Private.h>
91 #import <UIKit/UIPeripheralHost_Private.h>
92 #import <UIKit/UIWindow_Private.h>
93 #import <QuartzCore/CARenderServer.h>
94 #import <QuartzCore/QuartzCorePrivate.h>
95 #import <WebCore/InspectorOverlay.h>
97 @interface UIScrollView (UIScrollViewInternal)
98 - (void)_adjustForAutomaticKeyboardInfo:(NSDictionary*)info animated:(BOOL)animated lastAdjustment:(CGFloat*)lastAdjustment;
99 - (BOOL)_isScrollingToTop;
102 @interface UIPeripheralHost(UIKitInternal)
103 - (CGFloat)getVerticalOverlapForView:(UIView *)view usingKeyboardInfo:(NSDictionary *)info;
106 @interface UIView (UIViewInternal)
107 - (UIViewController *)_viewControllerForAncestor;
110 @interface UIWindow (UIWindowInternal)
111 - (BOOL)_isHostedInAnotherProcess;
114 @interface UIViewController (UIViewControllerInternal)
115 - (UIViewController *)_rootAncestorViewController;
116 - (UIViewController *)_viewControllerForSupportedInterfaceOrientations;
121 #import "WKViewInternal.h"
122 #import <WebCore/ColorMac.h>
126 static HashMap<WebKit::WebPageProxy*, WKWebView *>& pageToViewMap()
128 static NeverDestroyed<HashMap<WebKit::WebPageProxy*, WKWebView *>> map;
132 WKWebView* fromWebPageProxy(WebKit::WebPageProxy& page)
134 return pageToViewMap().get(&page);
137 @implementation WKWebView {
138 std::unique_ptr<WebKit::NavigationState> _navigationState;
139 std::unique_ptr<WebKit::UIDelegate> _uiDelegate;
141 RetainPtr<_WKRemoteObjectRegistry> _remoteObjectRegistry;
142 _WKRenderingProgressEvents _observedRenderingProgressEvents;
144 WebKit::WeakObjCPtr<id <_WKFormDelegate>> _formDelegate;
146 RetainPtr<WKScrollView> _scrollView;
147 RetainPtr<WKContentView> _contentView;
149 BOOL _overridesMinimumLayoutSize;
150 CGSize _minimumLayoutSizeOverride;
151 BOOL _overridesMinimumLayoutSizeForMinimalUI;
152 CGSize _minimumLayoutSizeOverrideForMinimalUI;
153 BOOL _overridesMaximumUnobscuredSize;
154 CGSize _maximumUnobscuredSizeOverride;
156 BOOL _needsToNotifyDelegateAboutMinimalUI;
157 CGRect _inputViewBounds;
158 CGFloat _viewportMetaTagWidth;
160 UIEdgeInsets _obscuredInsets;
161 bool _isChangingObscuredInsetsInteractively;
163 UIInterfaceOrientation _interfaceOrientationOverride;
164 BOOL _overridesInterfaceOrientation;
166 BOOL _needsResetViewStateAfterCommitLoadForMainFrame;
167 uint64_t _firstPaintAfterCommitLoadTransactionID;
168 BOOL _isAnimatingResize;
169 CATransform3D _resizeAnimationTransformAdjustments;
170 RetainPtr<UIView> _resizeAnimationView;
171 CGFloat _lastAdjustmentForScroller;
173 BOOL _needsToRestoreExposedRect;
174 WebCore::FloatRect _exposedRectToRestore;
175 BOOL _needsToRestoreUnobscuredCenter;
176 WebCore::FloatPoint _unobscuredCenterToRestore;
177 uint64_t _firstTransactionIDAfterPageRestore;
178 double _scaleToRestore;
180 std::unique_ptr<WebKit::ViewGestureController> _gestureController;
181 BOOL _allowsBackForwardNavigationGestures;
183 RetainPtr<UIView <WKWebViewContentProvider>> _customContentView;
184 RetainPtr<UIView> _customContentFixedOverlayView;
186 WebCore::Color _scrollViewBackgroundColor;
188 BOOL _delayUpdateVisibleContentRects;
189 BOOL _hadDelayedUpdateVisibleContentRects;
191 BOOL _pageIsPrintingToPDF;
192 RetainPtr<CGPDFDocumentRef> _printedDocument;
195 RetainPtr<WKView> _wkView;
199 - (instancetype)initWithFrame:(CGRect)frame
201 return [self initWithFrame:frame configuration:adoptNS([[WKWebViewConfiguration alloc] init]).get()];
205 static int32_t deviceOrientationForUIInterfaceOrientation(UIInterfaceOrientation orientation)
207 switch (orientation) {
208 case UIInterfaceOrientationUnknown:
209 case UIInterfaceOrientationPortrait:
211 case UIInterfaceOrientationPortraitUpsideDown:
213 case UIInterfaceOrientationLandscapeLeft:
215 case UIInterfaceOrientationLandscapeRight:
220 static int32_t deviceOrientation()
222 return deviceOrientationForUIInterfaceOrientation([[UIApplication sharedApplication] statusBarOrientation]);
226 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
228 if (!(self = [super initWithFrame:frame]))
231 _configuration = adoptNS([configuration copy]);
233 if (WKWebView *relatedWebView = [_configuration _relatedWebView]) {
234 WKProcessPool *processPool = [_configuration processPool];
235 WKProcessPool *relatedWebViewProcessPool = [relatedWebView->_configuration processPool];
236 if (processPool && processPool != relatedWebViewProcessPool)
237 [NSException raise:NSInvalidArgumentException format:@"Related web view %@ has process pool %@ but configuration specifies a different process pool %@", relatedWebView, relatedWebViewProcessPool, configuration.processPool];
239 [_configuration setProcessPool:relatedWebViewProcessPool];
242 [_configuration _validate];
244 CGRect bounds = self.bounds;
246 WebKit::WebContext& context = *[_configuration processPool]->_context;
248 WebKit::WebPageConfiguration webPageConfiguration;
249 webPageConfiguration.preferences = [_configuration preferences]->_preferences.get();
250 if (WKWebView *relatedWebView = [_configuration _relatedWebView])
251 webPageConfiguration.relatedPage = relatedWebView->_page.get();
253 webPageConfiguration.userContentController = [_configuration userContentController]->_userContentControllerProxy.get();
254 webPageConfiguration.visitedLinkProvider = [_configuration _visitedLinkProvider]->_visitedLinkProvider.get();
255 webPageConfiguration.session = [_configuration _websiteDataStore]->_session.get();
257 RefPtr<WebKit::WebPageGroup> pageGroup;
258 NSString *groupIdentifier = configuration._groupIdentifier;
259 if (groupIdentifier.length) {
260 pageGroup = WebKit::WebPageGroup::create(configuration._groupIdentifier);
261 webPageConfiguration.pageGroup = pageGroup.get();
264 webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::suppressesIncrementalRenderingKey(), WebKit::WebPreferencesStore::Value(!![_configuration suppressesIncrementalRendering]));
267 webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::mediaPlaybackAllowsInlineKey(), WebKit::WebPreferencesStore::Value(!![_configuration allowsInlineMediaPlayback]));
268 webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::mediaPlaybackRequiresUserGestureKey(), WebKit::WebPreferencesStore::Value(!![_configuration mediaPlaybackRequiresUserAction]));
269 webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::mediaPlaybackAllowsAirPlayKey(), WebKit::WebPreferencesStore::Value(!![_configuration mediaPlaybackAllowsAirPlay]));
273 _scrollView = adoptNS([[WKScrollView alloc] initWithFrame:bounds]);
274 [_scrollView setInternalDelegate:self];
275 [_scrollView setBouncesZoom:YES];
277 [self addSubview:_scrollView.get()];
278 [_scrollView setBackgroundColor:[UIColor whiteColor]];
280 _contentView = adoptNS([[WKContentView alloc] initWithFrame:bounds context:context configuration:std::move(webPageConfiguration) webView:self]);
282 _page = [_contentView page];
283 _page->setApplicationNameForUserAgent([@"Mobile/" stringByAppendingString:[UIDevice currentDevice].buildVersion]);
284 _page->setDeviceOrientation(deviceOrientation());
286 [_contentView layer].anchorPoint = CGPointZero;
287 [_contentView setFrame:bounds];
288 [_scrollView addSubview:_contentView.get()];
289 _viewportMetaTagWidth = -1;
291 [self _frameOrBoundsChanged];
293 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
294 [center addObserver:self selector:@selector(_keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
295 [center addObserver:self selector:@selector(_keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];
296 [center addObserver:self selector:@selector(_keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
297 [center addObserver:self selector:@selector(_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
298 [center addObserver:self selector:@selector(_windowDidRotate:) name:UIWindowDidRotateNotification object:nil];
299 [center addObserver:self selector:@selector(_contentSizeCategoryDidChange:) name:UIContentSizeCategoryDidChangeNotification object:nil];
300 _page->contentSizeCategoryDidChange([self _contentSizeCategory]);
302 [[_configuration _contentProviderRegistry] addPage:*_page];
306 _wkView = [[WKView alloc] initWithFrame:bounds context:context configuration:std::move(webPageConfiguration) webView:self];
307 [self addSubview:_wkView.get()];
308 _page = WebKit::toImpl([_wkView pageRef]);
310 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
311 [_wkView _setAutomaticallyAdjustsContentInsets:YES];
315 _page->setBackgroundExtendsBeyondPage(true);
317 _navigationState = std::make_unique<WebKit::NavigationState>(self);
318 _page->setPolicyClient(_navigationState->createPolicyClient());
319 _page->setLoaderClient(_navigationState->createLoaderClient());
321 _uiDelegate = std::make_unique<WebKit::UIDelegate>(self);
322 _page->setUIClient(_uiDelegate->createUIClient());
324 _page->setFindClient(std::make_unique<WebKit::FindClient>(self));
326 pageToViewMap().add(_page.get(), self);
331 - (instancetype)initWithCoder:(NSCoder *)coder
341 [_remoteObjectRegistry _invalidate];
343 [[_configuration _contentProviderRegistry] removePage:*_page];
344 [[NSNotificationCenter defaultCenter] removeObserver:self];
347 pageToViewMap().remove(_page.get());
352 - (WKWebViewConfiguration *)configuration
354 return [[_configuration copy] autorelease];
357 - (WKBackForwardList *)backForwardList
359 return wrapper(_page->backForwardList());
362 - (id <WKNavigationDelegate>)navigationDelegate
364 return _navigationState->navigationDelegate().autorelease();
367 - (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate
369 _navigationState->setNavigationDelegate(navigationDelegate);
372 - (id <WKUIDelegate>)UIDelegate
374 return _uiDelegate->delegate().autorelease();
377 - (void)setUIDelegate:(id<WKUIDelegate>)UIDelegate
379 _uiDelegate->setDelegate(UIDelegate);
382 - (WKNavigation *)loadRequest:(NSURLRequest *)request
384 uint64_t navigationID = _page->loadRequest(request);
385 auto navigation = _navigationState->createLoadRequestNavigation(navigationID, request);
387 return navigation.autorelease();
390 - (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL
392 uint64_t navigationID = _page->loadHTMLString(string, baseURL.absoluteString);
393 auto navigation = _navigationState->createLoadDataNavigation(navigationID);
395 return navigation.autorelease();
398 - (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item
400 uint64_t navigationID = _page->goToBackForwardItem(&item._item);
402 auto navigation = _navigationState->createBackForwardNavigation(navigationID, item._item);
404 return navigation.autorelease();
409 return _page->pageLoadState().title();
414 return [NSURL _web_URLWithWTFString:_page->pageLoadState().activeURL()];
419 return _page->pageLoadState().isLoading();
422 - (double)estimatedProgress
424 return _page->pageLoadState().estimatedProgress();
427 - (BOOL)hasOnlySecureContent
429 return _page->pageLoadState().hasOnlySecureContent();
434 return _page->pageLoadState().canGoBack();
439 return _page->pageLoadState().canGoForward();
442 - (WKNavigation *)goBack
444 uint64_t navigationID = _page->goBack();
448 ASSERT(_page->backForwardList().currentItem());
449 auto navigation = _navigationState->createBackForwardNavigation(navigationID, *_page->backForwardList().currentItem());
451 return navigation.autorelease();
454 - (WKNavigation *)goForward
456 uint64_t navigationID = _page->goForward();
460 ASSERT(_page->backForwardList().currentItem());
461 auto navigation = _navigationState->createBackForwardNavigation(navigationID, *_page->backForwardList().currentItem());
463 return navigation.autorelease();
466 - (WKNavigation *)reload
468 uint64_t navigationID = _page->reload(false);
469 ASSERT(navigationID);
471 auto navigation = _navigationState->createReloadNavigation(navigationID);
472 return navigation.autorelease();
475 - (WKNavigation *)reloadFromOrigin
477 uint64_t navigationID = _page->reload(true);
478 ASSERT(navigationID);
480 auto navigation = _navigationState->createReloadNavigation(navigationID);
481 return navigation.autorelease();
486 _page->stopLoading();
489 static WKErrorCode callbackErrorCode(WebKit::CallbackBase::Error error)
492 case WebKit::CallbackBase::Error::None:
493 ASSERT_NOT_REACHED();
494 return WKErrorUnknown;
496 case WebKit::CallbackBase::Error::Unknown:
497 return WKErrorUnknown;
499 case WebKit::CallbackBase::Error::ProcessExited:
500 return WKErrorWebContentProcessTerminated;
502 case WebKit::CallbackBase::Error::OwnerWasInvalidated:
503 return WKErrorWebViewInvalidated;
507 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler
509 auto handler = adoptNS([completionHandler copy]);
511 _page->runJavaScriptInMainFrame(javaScriptString, [handler](WebKit::WebSerializedScriptValue* serializedScriptValue, WebKit::ScriptValueCallback::Error errorCode) {
515 auto completionHandler = (void (^)(id, NSError *))handler.get();
517 if (errorCode != WebKit::ScriptValueCallback::Error::None) {
518 auto error = createNSError(callbackErrorCode(errorCode));
519 if (errorCode == WebKit::ScriptValueCallback::Error::OwnerWasInvalidated) {
520 // The OwnerWasInvalidated callback is synchronous. We don't want to call the block from within it
521 // because that can trigger re-entrancy bugs in WebKit.
522 // FIXME: It would be even better if GenericCallback did this for us.
523 dispatch_async(dispatch_get_main_queue(), [completionHandler, error] {
524 completionHandler(nil, error.get());
529 completionHandler(nil, error.get());
533 if (!serializedScriptValue) {
534 completionHandler(nil, createNSError(WKErrorJavaScriptExceptionOccurred).get());
538 auto context = adoptNS([[JSContext alloc] init]);
539 JSValueRef valueRef = serializedScriptValue->deserialize([context JSGlobalContextRef], 0);
540 JSValue *value = [JSValue valueWithJSValueRef:valueRef inContext:context.get()];
542 completionHandler([value toObject], nil);
546 #pragma mark iOS-specific methods
549 - (void)setFrame:(CGRect)frame
551 CGRect oldFrame = self.frame;
552 [super setFrame:frame];
554 if (!CGSizeEqualToSize(oldFrame.size, frame.size))
555 [self _frameOrBoundsChanged];
558 - (void)setBounds:(CGRect)bounds
560 CGRect oldBounds = self.bounds;
561 [super setBounds:bounds];
562 [_customContentFixedOverlayView setFrame:self.bounds];
564 if (!CGSizeEqualToSize(oldBounds.size, bounds.size))
565 [self _frameOrBoundsChanged];
568 - (UIScrollView *)scrollView
570 return _scrollView.get();
573 - (WKBrowsingContextController *)browsingContextController
575 return [_contentView browsingContextController];
578 static inline CGFloat floorToDevicePixel(CGFloat input, float deviceScaleFactor)
580 return CGFloor(input * deviceScaleFactor) / deviceScaleFactor;
583 static CGSize roundScrollViewContentSize(const WebKit::WebPageProxy& page, CGSize contentSize)
585 float deviceScaleFactor = page.deviceScaleFactor();
586 return CGSizeMake(floorToDevicePixel(contentSize.width, deviceScaleFactor), floorToDevicePixel(contentSize.height, deviceScaleFactor));
589 - (UIView *)_currentContentView
591 return _customContentView ? _customContentView.get() : _contentView.get();
594 - (void)_setHasCustomContentView:(BOOL)pageHasCustomContentView loadedMIMEType:(const WTF::String&)mimeType
596 if (pageHasCustomContentView) {
597 [_customContentView removeFromSuperview];
598 [_customContentFixedOverlayView removeFromSuperview];
600 Class representationClass = [[_configuration _contentProviderRegistry] providerForMIMEType:mimeType];
601 ASSERT(representationClass);
602 _customContentView = adoptNS([[representationClass alloc] web_initWithFrame:self.bounds webView:self]);
603 _customContentFixedOverlayView = adoptNS([[UIView alloc] initWithFrame:self.bounds]);
604 [_customContentFixedOverlayView setUserInteractionEnabled:NO];
606 [_contentView removeFromSuperview];
607 [_scrollView addSubview:_customContentView.get()];
608 [self addSubview:_customContentFixedOverlayView.get()];
610 [_customContentView web_setMinimumSize:self.bounds.size];
611 [_customContentView web_setFixedOverlayView:_customContentFixedOverlayView.get()];
612 } else if (_customContentView) {
613 [_customContentView removeFromSuperview];
614 _customContentView = nullptr;
616 [_customContentFixedOverlayView removeFromSuperview];
617 _customContentFixedOverlayView = nullptr;
619 [_scrollView addSubview:_contentView.get()];
620 [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
622 [_customContentFixedOverlayView setFrame:self.bounds];
623 [self addSubview:_customContentFixedOverlayView.get()];
627 - (void)_didFinishLoadingDataForCustomContentProviderWithSuggestedFilename:(const String&)suggestedFilename data:(NSData *)data
629 ASSERT(_customContentView);
630 [_customContentView web_setContentProviderData:data suggestedFilename:suggestedFilename];
633 - (void)_setViewportMetaTagWidth:(float)newWidth
635 _viewportMetaTagWidth = newWidth;
638 - (void)_willInvokeUIScrollViewDelegateCallback
640 _delayUpdateVisibleContentRects = YES;
643 - (void)_didInvokeUIScrollViewDelegateCallback
645 _delayUpdateVisibleContentRects = NO;
646 if (_hadDelayedUpdateVisibleContentRects) {
647 _hadDelayedUpdateVisibleContentRects = NO;
648 [self _updateVisibleContentRects];
652 static CGFloat contentZoomScale(WKWebView* webView)
654 CGFloat scale = [[webView._currentContentView layer] affineTransform].a;
655 ASSERT(scale == [webView->_scrollView zoomScale]);
659 - (void)_updateScrollViewBackground
661 WebCore::Color color;
662 if (_customContentView)
663 color = [_customContentView backgroundColor].CGColor;
665 color = _page->pageExtendedBackgroundColor();
666 CGFloat zoomScale = contentZoomScale(self);
667 CGFloat minimumZoomScale = [_scrollView minimumZoomScale];
668 if (zoomScale < minimumZoomScale) {
670 CGFloat opacity = std::max<CGFloat>(1 - slope * (minimumZoomScale - zoomScale), 0);
671 color = WebCore::colorWithOverrideAlpha(color.rgb(), opacity);
674 if (_scrollViewBackgroundColor == color)
677 _scrollViewBackgroundColor = color;
679 auto uiBackgroundColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)]);
680 [_scrollView setBackgroundColor:uiBackgroundColor.get()];
683 - (void)_setUsesMinimalUI:(BOOL)usesMinimalUI
685 _usesMinimalUI = usesMinimalUI;
686 _needsToNotifyDelegateAboutMinimalUI = YES;
689 - (BOOL)_usesMinimalUI
691 return _usesMinimalUI;
694 - (CGPoint)_adjustedContentOffset:(CGPoint)point
696 CGPoint result = point;
697 UIEdgeInsets contentInset = [self _computedContentInset];
699 result.x -= contentInset.left;
700 result.y -= contentInset.top;
705 - (UIEdgeInsets)_computedContentInset
707 if (!UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, UIEdgeInsetsZero))
708 return _obscuredInsets;
710 return [_scrollView contentInset];
713 - (void)_processDidExit
715 if (!_customContentView && _isAnimatingResize) {
716 NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
717 [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
718 [_resizeAnimationView removeFromSuperview];
719 _resizeAnimationView = nil;
721 _isAnimatingResize = NO;
722 _resizeAnimationTransformAdjustments = CATransform3DIdentity;
724 [_contentView setFrame:self.bounds];
725 [_scrollView setBackgroundColor:[UIColor whiteColor]];
726 [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
727 [_scrollView setZoomScale:1];
729 _viewportMetaTagWidth = -1;
730 _needsResetViewStateAfterCommitLoadForMainFrame = NO;
731 _needsToRestoreExposedRect = NO;
732 _needsToRestoreUnobscuredCenter = NO;
733 _scrollViewBackgroundColor = WebCore::Color();
734 _delayUpdateVisibleContentRects = NO;
735 _hadDelayedUpdateVisibleContentRects = NO;
738 - (void)_didCommitLoadForMainFrame
740 _firstPaintAfterCommitLoadTransactionID = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
742 _needsResetViewStateAfterCommitLoadForMainFrame = YES;
746 static void changeContentOffsetBoundedInValidRange(UIScrollView *scrollView, WebCore::FloatPoint contentOffset)
748 UIEdgeInsets contentInsets = scrollView.contentInset;
749 CGSize contentSize = scrollView.contentSize;
750 CGSize scrollViewSize = scrollView.bounds.size;
752 float maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
753 if (contentOffset.x() > maxHorizontalOffset)
754 contentOffset.setX(maxHorizontalOffset);
755 float maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
756 if (contentOffset.y() > maxVerticalOffset)
757 contentOffset.setY(maxVerticalOffset);
758 if (contentOffset.x() < -contentInsets.left)
759 contentOffset.setX(-contentInsets.left);
760 if (contentOffset.y() < -contentInsets.top)
761 contentOffset.setY(-contentInsets.top);
762 scrollView.contentOffset = contentOffset;
765 - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
767 if (_customContentView)
770 if (_isAnimatingResize) {
771 [_resizeAnimationView layer].sublayerTransform = _resizeAnimationTransformAdjustments;
775 CGSize newContentSize = roundScrollViewContentSize(*_page, [_contentView frame].size);
776 [_scrollView _setContentSizePreservingContentOffsetDuringRubberband:newContentSize];
778 [_scrollView setMinimumZoomScale:layerTreeTransaction.minimumScaleFactor()];
779 [_scrollView setMaximumZoomScale:layerTreeTransaction.maximumScaleFactor()];
780 [_scrollView setZoomEnabled:layerTreeTransaction.allowsUserScaling()];
781 if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom])
782 [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()];
784 [self _updateScrollViewBackground];
786 if (_gestureController)
787 _gestureController->setRenderTreeSize(layerTreeTransaction.renderTreeSize());
789 if (_needsToNotifyDelegateAboutMinimalUI || _needsResetViewStateAfterCommitLoadForMainFrame) {
790 _needsToNotifyDelegateAboutMinimalUI = NO;
792 auto delegate = _uiDelegate->delegate();
793 if ([delegate respondsToSelector:@selector(_webView:usesMinimalUI:)])
794 [static_cast<id <WKUIDelegatePrivate>>(delegate.get()) _webView:self usesMinimalUI:_usesMinimalUI];
797 if (_needsResetViewStateAfterCommitLoadForMainFrame && layerTreeTransaction.transactionID() >= _firstPaintAfterCommitLoadTransactionID) {
798 _needsResetViewStateAfterCommitLoadForMainFrame = NO;
799 [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
800 [self _updateVisibleContentRects];
803 if (_needsToRestoreExposedRect && layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore) {
804 _needsToRestoreExposedRect = NO;
806 if (withinEpsilon(contentZoomScale(self), _scaleToRestore)) {
807 WebCore::FloatPoint exposedPosition = _exposedRectToRestore.location();
808 exposedPosition.scale(_scaleToRestore, _scaleToRestore);
810 changeContentOffsetBoundedInValidRange(_scrollView.get(), exposedPosition);
812 [self _updateVisibleContentRects];
815 if (_needsToRestoreUnobscuredCenter && layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore) {
816 _needsToRestoreUnobscuredCenter = NO;
818 if (withinEpsilon(contentZoomScale(self), _scaleToRestore)) {
819 CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, _obscuredInsets);
820 WebCore::FloatSize unobscuredContentSizeAtNewScale(unobscuredRect.size.width / _scaleToRestore, unobscuredRect.size.height / _scaleToRestore);
821 WebCore::FloatPoint topLeftInDocumentCoordinate(_unobscuredCenterToRestore.x() - unobscuredContentSizeAtNewScale.width() / 2, _unobscuredCenterToRestore.y() - unobscuredContentSizeAtNewScale.height() / 2);
823 topLeftInDocumentCoordinate.scale(_scaleToRestore, _scaleToRestore);
824 topLeftInDocumentCoordinate.moveBy(WebCore::FloatPoint(-_obscuredInsets.left, -_obscuredInsets.top));
826 changeContentOffsetBoundedInValidRange(_scrollView.get(), topLeftInDocumentCoordinate);
828 [self _updateVisibleContentRects];
832 - (void)_dynamicViewportUpdateChangedTargetToScale:(double)newScale position:(CGPoint)newScrollPosition
834 if (_isAnimatingResize) {
835 CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
836 double currentTargetScale = animatingScaleTarget * [[_contentView layer] transform].m11;
837 double scale = newScale / currentTargetScale;
838 _resizeAnimationTransformAdjustments = CATransform3DMakeScale(scale, scale, 0);
840 CGPoint newContentOffset = [self _adjustedContentOffset:CGPointMake(newScrollPosition.x * newScale, newScrollPosition.y * newScale)];
841 CGPoint currentContentOffset = [_scrollView contentOffset];
843 _resizeAnimationTransformAdjustments.m41 = (currentContentOffset.x - newContentOffset.x) / animatingScaleTarget;
844 _resizeAnimationTransformAdjustments.m42 = (currentContentOffset.y - newContentOffset.y) / animatingScaleTarget;
848 - (void)_restorePageStateToExposedRect:(WebCore::FloatRect)exposedRect scale:(double)scale
850 if (_isAnimatingResize)
853 if (_customContentView)
856 _needsToRestoreUnobscuredCenter = NO;
857 _needsToRestoreExposedRect = YES;
858 _firstTransactionIDAfterPageRestore = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
859 _exposedRectToRestore = exposedRect;
860 _scaleToRestore = scale;
863 - (void)_restorePageStateToUnobscuredCenter:(WebCore::FloatPoint)center scale:(double)scale
865 if (_isAnimatingResize)
868 if (_customContentView)
871 _needsToRestoreExposedRect = NO;
872 _needsToRestoreUnobscuredCenter = YES;
873 _firstTransactionIDAfterPageRestore = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
874 _unobscuredCenterToRestore = center;
875 _scaleToRestore = scale;
878 - (WebKit::ViewSnapshot)_takeViewSnapshot
880 float deviceScale = WKGetScreenScaleFactor();
881 CGSize snapshotSize = self.bounds.size;
882 snapshotSize.width *= deviceScale;
883 snapshotSize.height *= deviceScale;
885 WebKit::ViewSnapshot snapshot;
886 snapshot.slotID = [WebKit::ViewSnapshotStore::snapshottingContext() createImageSlot:snapshotSize hasAlpha:YES];
888 CATransform3D transform = CATransform3DMakeScale(deviceScale, deviceScale, 1);
889 CARenderServerCaptureLayerWithTransform(MACH_PORT_NULL, self.layer.context.contextId, (uint64_t)self.layer, snapshot.slotID, 0, 0, &transform);
891 snapshot.size = WebCore::expandedIntSize(WebCore::FloatSize(snapshotSize));
892 snapshot.imageSizeInBytes = snapshotSize.width * snapshotSize.height * 4;
893 snapshot.backgroundColor = _page->pageExtendedBackgroundColor();
898 - (void)_zoomToPoint:(WebCore::FloatPoint)point atScale:(double)scale
900 double maximumZoomDuration = 0.4;
901 double minimumZoomDuration = 0.1;
902 double zoomDurationFactor = 0.3;
904 CGFloat zoomScale = contentZoomScale(self);
905 CFTimeInterval duration = std::min(fabs(log(zoomScale) - log(scale)) * zoomDurationFactor + minimumZoomDuration, maximumZoomDuration);
907 if (scale != zoomScale)
908 _page->willStartUserTriggeredZooming();
910 [_scrollView _zoomToCenter:point scale:scale duration:duration];
913 - (void)_zoomToRect:(WebCore::FloatRect)targetRect atScale:(double)scale origin:(WebCore::FloatPoint)origin
915 // FIMXE: Some of this could be shared with _scrollToRect.
916 const double visibleRectScaleChange = contentZoomScale(self) / scale;
917 const WebCore::FloatRect visibleRect([self convertRect:self.bounds toView:self._currentContentView]);
918 const WebCore::FloatRect unobscuredRect([self _contentRectForUserInteraction]);
920 const WebCore::FloatSize topLeftObscuredInsetAfterZoom((unobscuredRect.minXMinYCorner() - visibleRect.minXMinYCorner()) * visibleRectScaleChange);
921 const WebCore::FloatSize bottomRightObscuredInsetAfterZoom((visibleRect.maxXMaxYCorner() - unobscuredRect.maxXMaxYCorner()) * visibleRectScaleChange);
923 const WebCore::FloatSize unobscuredRectSizeAfterZoom(unobscuredRect.size() * visibleRectScaleChange);
925 // Center to the target rect.
926 WebCore::FloatPoint unobscuredRectLocationAfterZoom = targetRect.location() - (unobscuredRectSizeAfterZoom - targetRect.size()) * 0.5;
928 // Center to the tap point instead in case the target rect won't fit in a direction.
929 if (targetRect.width() > unobscuredRectSizeAfterZoom.width())
930 unobscuredRectLocationAfterZoom.setX(origin.x() - unobscuredRectSizeAfterZoom.width() / 2);
931 if (targetRect.height() > unobscuredRectSizeAfterZoom.height())
932 unobscuredRectLocationAfterZoom.setY(origin.y() - unobscuredRectSizeAfterZoom.height() / 2);
934 // We have computed where we want the unobscured rect to be. Now adjust for the obscuring insets.
935 WebCore::FloatRect visibleRectAfterZoom(unobscuredRectLocationAfterZoom, unobscuredRectSizeAfterZoom);
936 visibleRectAfterZoom.move(-topLeftObscuredInsetAfterZoom);
937 visibleRectAfterZoom.expand(topLeftObscuredInsetAfterZoom + bottomRightObscuredInsetAfterZoom);
939 [self _zoomToPoint:visibleRectAfterZoom.center() atScale:scale];
942 static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOffset, WebCore::FloatSize contentSize, WebCore::FloatSize unobscuredContentSize)
944 WebCore::FloatSize maximumContentOffset = contentSize - unobscuredContentSize;
945 contentOffset = contentOffset.shrunkTo(WebCore::FloatPoint(maximumContentOffset.width(), maximumContentOffset.height()));
946 contentOffset = contentOffset.expandedTo(WebCore::FloatPoint());
947 return contentOffset;
950 - (void)_scrollToContentOffset:(WebCore::FloatPoint)contentOffset
952 if (_isAnimatingResize)
955 [_scrollView _stopScrollingAndZoomingAnimations];
957 WebCore::FloatPoint scaledOffset = contentOffset;
958 CGFloat zoomScale = contentZoomScale(self);
959 scaledOffset.scale(zoomScale, zoomScale);
961 [_scrollView setContentOffset:[self _adjustedContentOffset:scaledOffset]];
964 - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance
966 WebCore::FloatRect unobscuredContentRect([self _contentRectForUserInteraction]);
967 WebCore::FloatPoint unobscuredContentOffset = unobscuredContentRect.location();
968 WebCore::FloatSize contentSize([self._currentContentView bounds].size);
970 // Center the target rect in the scroll view.
971 // If the target doesn't fit in the scroll view, center on the gesture location instead.
972 WebCore::FloatPoint newUnobscuredContentOffset;
973 if (targetRect.width() <= unobscuredContentRect.width())
974 newUnobscuredContentOffset.setX(targetRect.x() - (unobscuredContentRect.width() - targetRect.width()) / 2);
976 newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
977 if (targetRect.height() <= unobscuredContentRect.height())
978 newUnobscuredContentOffset.setY(targetRect.y() - (unobscuredContentRect.height() - targetRect.height()) / 2);
980 newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
981 newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
983 if (unobscuredContentOffset == newUnobscuredContentOffset) {
984 if (targetRect.width() > unobscuredContentRect.width())
985 newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
986 if (targetRect.height() > unobscuredContentRect.height())
987 newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
988 newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
991 WebCore::FloatSize scrollViewOffsetDelta = newUnobscuredContentOffset - unobscuredContentOffset;
992 scrollViewOffsetDelta.scale(contentZoomScale(self));
994 float scrollDistance = scrollViewOffsetDelta.diagonalLength();
995 if (scrollDistance < minimumScrollDistance)
998 [_scrollView setContentOffset:([_scrollView contentOffset] + scrollViewOffsetDelta) animated:YES];
1002 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin
1004 [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale]];
1007 // focusedElementRect and selectionRect are both in document coordinates.
1008 - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRectInDocumentCoordinates selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll
1010 const double WKWebViewStandardFontSize = 16;
1011 const double kMinimumHeightToShowContentAboveKeyboard = 106;
1012 const CFTimeInterval UIWebFormAnimationDuration = 0.25;
1013 const double CaretOffsetFromWindowEdge = 20;
1015 // Zoom around the element's bounding frame. We use a "standard" size to determine the proper frame.
1016 double scale = allowScaling ? std::min(std::max(WKWebViewStandardFontSize / fontSize, minimumScale), maximumScale) : contentZoomScale(self);
1017 CGFloat documentWidth = [_contentView bounds].size.width;
1018 scale = CGRound(documentWidth * scale) / documentWidth;
1020 UIWindow *window = [_scrollView window];
1022 WebCore::FloatRect focusedElementRectInNewScale = focusedElementRectInDocumentCoordinates;
1023 focusedElementRectInNewScale.scale(scale);
1024 focusedElementRectInNewScale.moveBy([_contentView frame].origin);
1026 // Find the portion of the view that is visible on the screen.
1027 UIViewController *topViewController = [[[_scrollView _viewControllerForAncestor] _rootAncestorViewController] _viewControllerForSupportedInterfaceOrientations];
1028 UIView *fullScreenView = topViewController.view;
1029 if (!fullScreenView)
1030 fullScreenView = window;
1032 CGRect unobscuredScrollViewRectInWebViewCoordinates = UIEdgeInsetsInsetRect([self bounds], _obscuredInsets);
1033 CGRect visibleScrollViewBoundsInWebViewCoordinates = CGRectIntersection(unobscuredScrollViewRectInWebViewCoordinates, [fullScreenView convertRect:[fullScreenView bounds] toView:self]);
1034 CGRect formAssistantFrameInWebViewCoordinates = [window convertRect:_inputViewBounds toView:self];
1035 CGRect intersectionBetweenScrollViewAndFormAssistant = CGRectIntersection(visibleScrollViewBoundsInWebViewCoordinates, formAssistantFrameInWebViewCoordinates);
1036 CGSize visibleSize = visibleScrollViewBoundsInWebViewCoordinates.size;
1038 CGFloat visibleOffsetFromTop = 0;
1039 if (!CGRectIsEmpty(intersectionBetweenScrollViewAndFormAssistant)) {
1040 CGFloat heightVisibleAboveFormAssistant = CGRectGetMinY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1041 CGFloat heightVisibleBelowFormAssistant = CGRectGetMaxY(visibleScrollViewBoundsInWebViewCoordinates) - CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant);
1043 if (heightVisibleAboveFormAssistant >= kMinimumHeightToShowContentAboveKeyboard || heightVisibleBelowFormAssistant < heightVisibleAboveFormAssistant)
1044 visibleSize.height = heightVisibleAboveFormAssistant;
1046 visibleSize.height = heightVisibleBelowFormAssistant;
1047 visibleOffsetFromTop = CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1051 BOOL selectionRectIsNotNull = !selectionRectInDocumentCoordinates.isZero();
1053 CGRect currentlyVisibleRegionInWebViewCoordinates;
1054 currentlyVisibleRegionInWebViewCoordinates.origin = unobscuredScrollViewRectInWebViewCoordinates.origin;
1055 currentlyVisibleRegionInWebViewCoordinates.origin.y += visibleOffsetFromTop;
1056 currentlyVisibleRegionInWebViewCoordinates.size = visibleSize;
1058 // Don't bother scrolling if the entire node is already visible, whether or not we got a selectionRect.
1059 if (CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:focusedElementRectInDocumentCoordinates fromView:_contentView.get()]))
1062 // Don't bother scrolling if we have a valid selectionRect and it is already visible.
1063 if (selectionRectIsNotNull && CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:selectionRectInDocumentCoordinates fromView:_contentView.get()]))
1067 // We want to zoom to the left/top corner of the DOM node, with as much spacing on all sides as we
1068 // can get based on the visible area after zooming (workingFrame). The spacing in either dimension is half the
1069 // difference between the size of the DOM node and the size of the visible frame.
1070 CGFloat horizontalSpaceInWebViewCoordinates = std::max((visibleSize.width - focusedElementRectInNewScale.width()) / 2.0, 0.0);
1071 CGFloat verticalSpaceInWebViewCoordinates = std::max((visibleSize.height - focusedElementRectInNewScale.height()) / 2.0, 0.0);
1074 topLeft.x = focusedElementRectInNewScale.x() - horizontalSpaceInWebViewCoordinates;
1075 topLeft.y = focusedElementRectInNewScale.y() - verticalSpaceInWebViewCoordinates - visibleOffsetFromTop;
1077 CGFloat minimumAllowableHorizontalOffsetInWebViewCoordinates = -INFINITY;
1078 CGFloat minimumAllowableVerticalOffsetInWebViewCoordinates = -INFINITY;
1079 if (selectionRectIsNotNull) {
1080 WebCore::FloatRect selectionRectInNewScale = selectionRectInDocumentCoordinates;
1081 selectionRectInNewScale.scale(scale);
1082 selectionRectInNewScale.moveBy([_contentView frame].origin);
1083 minimumAllowableHorizontalOffsetInWebViewCoordinates = CGRectGetMaxX(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.width;
1084 minimumAllowableVerticalOffsetInWebViewCoordinates = CGRectGetMaxY(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.height - visibleOffsetFromTop;
1087 WebCore::FloatRect documentBoundsInNewScale = [_contentView bounds];
1088 documentBoundsInNewScale.scale(scale);
1089 documentBoundsInNewScale.moveBy([_contentView frame].origin);
1091 // Constrain the left edge in document coordinates so that:
1092 // - it isn't so small that the scrollVisibleRect isn't visible on the screen
1093 // - it isn't so great that the document's right edge is less than the right edge of the screen
1094 if (selectionRectIsNotNull && topLeft.x < minimumAllowableHorizontalOffsetInWebViewCoordinates)
1095 topLeft.x = minimumAllowableHorizontalOffsetInWebViewCoordinates;
1097 CGFloat maximumAllowableHorizontalOffset = CGRectGetMaxX(documentBoundsInNewScale) - visibleSize.width;
1098 if (topLeft.x > maximumAllowableHorizontalOffset)
1099 topLeft.x = maximumAllowableHorizontalOffset;
1102 // Constrain the top edge in document coordinates so that:
1103 // - it isn't so small that the scrollVisibleRect isn't visible on the screen
1104 // - it isn't so great that the document's bottom edge is higher than the top of the form assistant
1105 if (selectionRectIsNotNull && topLeft.y < minimumAllowableVerticalOffsetInWebViewCoordinates)
1106 topLeft.y = minimumAllowableVerticalOffsetInWebViewCoordinates;
1108 CGFloat maximumAllowableVerticalOffset = CGRectGetMaxY(documentBoundsInNewScale) - visibleSize.height;
1109 if (topLeft.y > maximumAllowableVerticalOffset)
1110 topLeft.y = maximumAllowableVerticalOffset;
1113 WebCore::FloatPoint newCenter = CGPointMake(topLeft.x + unobscuredScrollViewRectInWebViewCoordinates.size.width / 2.0, topLeft.y + unobscuredScrollViewRectInWebViewCoordinates.size.height / 2.0);
1115 if (scale != contentZoomScale(self))
1116 _page->willStartUserTriggeredZooming();
1118 // The newCenter has been computed in the new scale, but _zoomToCenter expected the center to be in the original scale.
1119 newCenter.scale(1 / scale, 1 / scale);
1120 [_scrollView _zoomToCenter:newCenter
1122 duration:UIWebFormAnimationDuration
1126 - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
1128 const float maximumScaleFactorDeltaForPanScroll = 0.02;
1130 double currentScale = contentZoomScale(self);
1132 WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
1133 double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width();
1134 double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height();
1136 horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale);
1137 verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale);
1139 double targetScale = fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
1140 if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) {
1141 if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance])
1143 } else if (targetScale != currentScale) {
1144 [self _zoomToRect:targetRect atScale:targetScale origin:origin];
1151 - (void)didMoveToWindow
1153 _page->viewStateDidChange(WebCore::ViewState::IsInWindow);
1156 #pragma mark - UIScrollViewDelegate
1158 - (BOOL)usesStandardContentView
1160 return !_customContentView;
1163 - (CGSize)scrollView:(UIScrollView*)scrollView contentSizeForZoomScale:(CGFloat)scale withProposedSize:(CGSize)proposedSize
1165 return roundScrollViewContentSize(*_page, proposedSize);
1168 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
1170 ASSERT(_scrollView == scrollView);
1172 if (_customContentView)
1173 return _customContentView.get();
1175 return _contentView.get();
1178 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
1180 if (![self usesStandardContentView])
1183 if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan) {
1184 _page->willStartUserTriggeredZooming();
1185 [_contentView scrollViewWillStartPanOrPinchGesture];
1187 [_contentView willStartZoomOrScroll];
1190 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
1192 if (![self usesStandardContentView])
1195 if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
1196 [_contentView scrollViewWillStartPanOrPinchGesture];
1197 [_contentView willStartZoomOrScroll];
1200 - (void)_didFinishScrolling
1202 if (![self usesStandardContentView])
1205 [self _updateVisibleContentRects];
1206 [_contentView didFinishScrolling];
1209 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
1211 // Work around <rdar://problem/16374753> by avoiding deceleration while
1212 // zooming. We'll animate to the right place once the zoom finishes.
1213 if ([scrollView isZooming])
1214 *targetContentOffset = [scrollView contentOffset];
1217 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
1219 // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called.
1221 [self _didFinishScrolling];
1224 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
1226 [self _didFinishScrolling];
1229 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
1231 [self _didFinishScrolling];
1234 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
1236 if (![self usesStandardContentView])
1237 [_customContentView scrollViewDidScroll:(UIScrollView *)scrollView];
1239 [self _updateVisibleContentRects];
1242 - (void)scrollViewDidZoom:(UIScrollView *)scrollView
1244 [self _updateScrollViewBackground];
1245 [self _updateVisibleContentRects];
1248 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
1250 ASSERT(scrollView == _scrollView);
1251 [self _updateVisibleContentRects];
1252 [_contentView didZoomToScale:scale];
1255 - (void)_frameOrBoundsChanged
1257 CGRect bounds = self.bounds;
1258 if (!_isAnimatingResize) {
1259 if (!_overridesMinimumLayoutSize)
1260 _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(bounds.size));
1261 if (!_overridesMinimumLayoutSizeForMinimalUI)
1262 _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(WebCore::FloatSize(bounds.size));
1263 if (!_overridesMaximumUnobscuredSize)
1264 _page->setMaximumUnobscuredSize(WebCore::FloatSize(bounds.size));
1267 [_scrollView setFrame:bounds];
1268 [_contentView setMinimumSize:bounds.size];
1269 [_customContentView web_setMinimumSize:bounds.size];
1270 [self _updateVisibleContentRects];
1273 // 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.
1274 - (CGRect)_contentRectForUserInteraction
1276 // FIXME: handle split keyboard.
1277 UIEdgeInsets obscuredInsets = _obscuredInsets;
1278 obscuredInsets.bottom = std::max(_obscuredInsets.bottom, _inputViewBounds.size.height);
1279 CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, obscuredInsets);
1280 return [self convertRect:unobscuredRect toView:self._currentContentView];
1283 - (void)_updateVisibleContentRects
1285 if (![self usesStandardContentView]) {
1286 [_customContentView web_computedContentInsetDidChange];
1290 if (_delayUpdateVisibleContentRects) {
1291 _hadDelayedUpdateVisibleContentRects = YES;
1295 if (_isAnimatingResize)
1298 if (_needsResetViewStateAfterCommitLoadForMainFrame)
1301 CGRect fullViewRect = self.bounds;
1302 CGRect visibleRectInContentCoordinates = [self convertRect:fullViewRect toView:_contentView.get()];
1304 CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]);
1305 CGRect unobscuredRectInContentCoordinates = [self convertRect:unobscuredRect toView:_contentView.get()];
1307 CGFloat scaleFactor = contentZoomScale(self);
1309 BOOL isStableState = !(_isChangingObscuredInsetsInteractively || [_scrollView isDragging] || [_scrollView isDecelerating] || [_scrollView isZooming] || [_scrollView isZoomBouncing] || [_scrollView _isAnimatingZoom] || [_scrollView _isScrollingToTop]);
1310 [_contentView didUpdateVisibleRect:visibleRectInContentCoordinates
1311 unobscuredRect:unobscuredRectInContentCoordinates
1312 unobscuredRectInScrollViewCoordinates:unobscuredRect
1313 scale:scaleFactor minimumScale:[_scrollView minimumZoomScale]
1314 inStableState:isStableState isChangingObscuredInsetsInteractively:_isChangingObscuredInsetsInteractively];
1317 - (void)_keyboardChangedWithInfo:(NSDictionary *)keyboardInfo adjustScrollView:(BOOL)adjustScrollView
1319 NSValue *endFrameValue = [keyboardInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
1323 // The keyboard rect is always in screen coordinates. In the view services case the window does not
1324 // have the interface orientation rotation transformation; its host does. So, it makes no sense to
1325 // clip the keyboard rect against its screen.
1326 if ([[self window] _isHostedInAnotherProcess])
1327 _inputViewBounds = [self.window convertRect:[endFrameValue CGRectValue] fromWindow:nil];
1329 _inputViewBounds = [self.window convertRect:CGRectIntersection([endFrameValue CGRectValue], self.window.screen.bounds) fromWindow:nil];
1331 [self _updateVisibleContentRects];
1333 if (adjustScrollView)
1334 [_scrollView _adjustForAutomaticKeyboardInfo:keyboardInfo animated:YES lastAdjustment:&_lastAdjustmentForScroller];
1337 - (void)_keyboardWillChangeFrame:(NSNotification *)notification
1339 if ([_contentView isAssistingNode])
1340 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1343 - (void)_keyboardDidChangeFrame:(NSNotification *)notification
1345 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:NO];
1348 - (void)_keyboardWillShow:(NSNotification *)notification
1350 if ([_contentView isAssistingNode])
1351 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1354 - (void)_keyboardWillHide:(NSNotification *)notification
1356 // Ignore keyboard will hide notifications sent during rotation. They're just there for
1357 // backwards compatibility reasons and processing the will hide notification would
1358 // temporarily screw up the the unobscured view area.
1359 if ([[UIPeripheralHost sharedInstance] rotationState])
1362 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1365 - (void)_windowDidRotate:(NSNotification *)notification
1367 if (!_overridesInterfaceOrientation)
1368 _page->setDeviceOrientation(deviceOrientation());
1371 - (void)_contentSizeCategoryDidChange:(NSNotification *)notification
1373 _page->contentSizeCategoryDidChange([self _contentSizeCategory]);
1376 - (NSString *)_contentSizeCategory
1378 return [[UIApplication sharedApplication] preferredContentSizeCategory];
1381 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1383 if (_allowsBackForwardNavigationGestures == allowsBackForwardNavigationGestures)
1386 _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
1388 if (allowsBackForwardNavigationGestures) {
1389 if (!_gestureController) {
1390 _gestureController = std::make_unique<WebKit::ViewGestureController>(*_page);
1391 _gestureController->installSwipeHandler(self, [self scrollView]);
1394 _gestureController = nullptr;
1396 _page->setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures);
1399 - (BOOL)allowsBackForwardNavigationGestures
1401 return _allowsBackForwardNavigationGestures;
1406 #pragma mark OS X-specific methods
1410 - (void)resizeSubviewsWithOldSize:(NSSize)oldSize
1412 [_wkView setFrame:self.bounds];
1415 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1417 [_wkView setAllowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures];
1420 - (BOOL)allowsBackForwardNavigationGestures
1422 return [_wkView allowsBackForwardNavigationGestures];
1425 - (void)setAllowsMagnification:(BOOL)allowsMagnification
1427 [_wkView setAllowsMagnification:allowsMagnification];
1430 - (BOOL)allowsMagnification
1432 return [_wkView allowsMagnification];
1435 - (void)setMagnification:(CGFloat)magnification
1437 [_wkView setMagnification:magnification];
1440 - (CGFloat)magnification
1442 return [_wkView magnification];
1445 - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point
1447 [_wkView setMagnification:magnification centeredAtPoint:NSPointFromCGPoint(point)];
1454 @implementation WKWebView (WKPrivate)
1456 - (_WKRemoteObjectRegistry *)_remoteObjectRegistry
1458 if (!_remoteObjectRegistry) {
1459 _remoteObjectRegistry = adoptNS([[_WKRemoteObjectRegistry alloc] _initWithMessageSender:*_page]);
1460 _page->process().context().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID(), [_remoteObjectRegistry remoteObjectRegistry]);
1463 return _remoteObjectRegistry.get();
1466 - (WKBrowsingContextHandle *)_handle
1468 return [[[WKBrowsingContextHandle alloc] _initWithPageID:_page->pageID()] autorelease];
1471 - (_WKRenderingProgressEvents)_observedRenderingProgressEvents
1473 return _observedRenderingProgressEvents;
1476 - (id <WKHistoryDelegatePrivate>)_historyDelegate
1478 return _navigationState->historyDelegate().autorelease();
1481 - (void)_setHistoryDelegate:(id <WKHistoryDelegatePrivate>)historyDelegate
1483 _navigationState->setHistoryDelegate(historyDelegate);
1486 - (NSURL *)_unreachableURL
1488 return [NSURL _web_URLWithWTFString:_page->pageLoadState().unreachableURL()];
1491 - (void)_loadAlternateHTMLString:(NSString *)string baseURL:(NSURL *)baseURL forUnreachableURL:(NSURL *)unreachableURL
1493 _page->loadAlternateHTMLString(string, [baseURL _web_originalDataAsWTFString], [unreachableURL _web_originalDataAsWTFString]);
1496 - (WKNavigation *)_reload
1498 return [self reload];
1501 - (NSArray *)_certificateChain
1503 if (WebKit::WebFrameProxy* mainFrame = _page->mainFrame())
1504 return mainFrame->certificateInfo() ? (NSArray *)mainFrame->certificateInfo()->certificateInfo().certificateChain() : nil;
1509 - (NSURL *)_committedURL
1511 return [NSURL _web_URLWithWTFString:_page->pageLoadState().url()];
1514 - (NSString *)_MIMEType
1516 if (_page->mainFrame())
1517 return _page->mainFrame()->mimeType();
1522 - (NSString *)_applicationNameForUserAgent
1524 return _page->applicationNameForUserAgent();
1527 - (void)_setApplicationNameForUserAgent:(NSString *)applicationNameForUserAgent
1529 _page->setApplicationNameForUserAgent(applicationNameForUserAgent);
1532 - (NSString *)_customUserAgent
1534 return _page->customUserAgent();
1537 - (void)_setCustomUserAgent:(NSString *)_customUserAgent
1539 _page->setCustomUserAgent(_customUserAgent);
1542 - (pid_t)_webProcessIdentifier
1544 return _page->isValid() ? _page->processIdentifier() : 0;
1547 - (void)_killWebContentProcess
1549 if (!_page->isValid())
1552 _page->process().terminate();
1555 - (void)_didRelaunchProcess
1558 WebCore::FloatSize boundsSize(self.bounds.size);
1559 _page->setViewportConfigurationMinimumLayoutSize(_overridesMinimumLayoutSize ? WebCore::FloatSize(_minimumLayoutSizeOverride) : boundsSize);
1560 _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(_overridesMinimumLayoutSizeForMinimalUI ? WebCore::FloatSize(_minimumLayoutSizeOverrideForMinimalUI) : boundsSize);
1561 _page->setMaximumUnobscuredSize(_overridesMaximumUnobscuredSize ? WebCore::FloatSize(_maximumUnobscuredSizeOverride) : boundsSize);
1565 - (NSData *)_sessionState
1567 return [wrapper(*_page->sessionStateData(nullptr).leakRef()) autorelease];
1570 static void releaseNSData(unsigned char*, const void* data)
1572 [(NSData *)data release];
1575 - (void)_restoreFromSessionState:(NSData *)sessionState
1577 [sessionState retain];
1578 uint64_t navigationID = _page->restoreFromSessionStateData(API::Data::createWithoutCopying((const unsigned char*)sessionState.bytes, sessionState.length, releaseNSData, sessionState).get());
1580 _navigationState->createReloadNavigation(navigationID);
1588 - (BOOL)_allowsRemoteInspection
1590 #if ENABLE(REMOTE_INSPECTOR)
1591 return _page->allowsRemoteInspection();
1597 - (void)_setAllowsRemoteInspection:(BOOL)allow
1599 #if ENABLE(REMOTE_INSPECTOR)
1600 _page->setAllowsRemoteInspection(allow);
1604 - (BOOL)_addsVisitedLinks
1606 return _page->addsVisitedLinks();
1609 - (void)_setAddsVisitedLinks:(BOOL)addsVisitedLinks
1611 _page->setAddsVisitedLinks(addsVisitedLinks);
1614 static inline WebCore::LayoutMilestones layoutMilestones(_WKRenderingProgressEvents events)
1616 WebCore::LayoutMilestones milestones = 0;
1618 if (events & _WKRenderingProgressEventFirstLayout)
1619 milestones |= WebCore::DidFirstLayout;
1621 if (events & _WKRenderingProgressEventFirstPaintWithSignificantArea)
1622 milestones |= WebCore::DidHitRelevantRepaintedObjectsAreaThreshold;
1627 - (void)_setObservedRenderingProgressEvents:(_WKRenderingProgressEvents)observedRenderingProgressEvents
1629 _observedRenderingProgressEvents = observedRenderingProgressEvents;
1630 _page->listenForLayoutMilestones(layoutMilestones(observedRenderingProgressEvents));
1633 - (void)_getMainResourceDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
1635 auto handler = adoptNS([completionHandler copy]);
1637 _page->getMainResourceDataOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
1638 void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
1639 if (error != WebKit::CallbackBase::Error::None) {
1640 // FIXME: Pipe a proper error in from the WebPageProxy.
1641 RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
1642 completionHandlerBlock(nil, error.get());
1644 completionHandlerBlock(wrapper(*data), nil);
1648 - (void)_getWebArchiveDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
1650 auto handler = adoptNS([completionHandler copy]);
1652 _page->getWebArchiveOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
1653 void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
1654 if (error != WebKit::CallbackBase::Error::None) {
1655 // FIXME: Pipe a proper error in from the WebPageProxy.
1656 RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
1657 completionHandlerBlock(nil, error.get());
1659 completionHandlerBlock(wrapper(*data), nil);
1663 - (_WKPaginationMode)_paginationMode
1665 switch (_page->paginationMode()) {
1666 case WebCore::Pagination::Unpaginated:
1667 return _WKPaginationModeUnpaginated;
1668 case WebCore::Pagination::LeftToRightPaginated:
1669 return _WKPaginationModeLeftToRight;
1670 case WebCore::Pagination::RightToLeftPaginated:
1671 return _WKPaginationModeRightToLeft;
1672 case WebCore::Pagination::TopToBottomPaginated:
1673 return _WKPaginationModeTopToBottom;
1674 case WebCore::Pagination::BottomToTopPaginated:
1675 return _WKPaginationModeBottomToTop;
1678 ASSERT_NOT_REACHED();
1679 return _WKPaginationModeUnpaginated;
1682 - (void)_setPaginationMode:(_WKPaginationMode)paginationMode
1684 WebCore::Pagination::Mode mode;
1685 switch (paginationMode) {
1686 case _WKPaginationModeUnpaginated:
1687 mode = WebCore::Pagination::Unpaginated;
1689 case _WKPaginationModeLeftToRight:
1690 mode = WebCore::Pagination::LeftToRightPaginated;
1692 case _WKPaginationModeRightToLeft:
1693 mode = WebCore::Pagination::RightToLeftPaginated;
1695 case _WKPaginationModeTopToBottom:
1696 mode = WebCore::Pagination::TopToBottomPaginated;
1698 case _WKPaginationModeBottomToTop:
1699 mode = WebCore::Pagination::BottomToTopPaginated;
1705 _page->setPaginationMode(mode);
1708 - (BOOL)_paginationBehavesLikeColumns
1710 return _page->paginationBehavesLikeColumns();
1713 - (void)_setPaginationBehavesLikeColumns:(BOOL)behavesLikeColumns
1715 _page->setPaginationBehavesLikeColumns(behavesLikeColumns);
1718 - (CGFloat)_pageLength
1720 return _page->pageLength();
1723 - (void)_setPageLength:(CGFloat)pageLength
1725 _page->setPageLength(pageLength);
1728 - (CGFloat)_gapBetweenPages
1730 return _page->gapBetweenPages();
1733 - (void)_setGapBetweenPages:(CGFloat)gapBetweenPages
1735 _page->setGapBetweenPages(gapBetweenPages);
1738 - (NSUInteger)_pageCount
1740 return _page->pageCount();
1743 - (BOOL)_supportsTextZoom
1745 return _page->supportsTextZoom();
1748 - (double)_textZoomFactor
1750 return _page->textZoomFactor();
1753 - (void)_setTextZoomFactor:(double)zoomFactor
1755 _page->setTextZoomFactor(zoomFactor);
1758 - (double)_pageZoomFactor
1760 return _page->pageZoomFactor();
1763 - (void)_setPageZoomFactor:(double)zoomFactor
1765 _page->setPageZoomFactor(zoomFactor);
1768 - (id <_WKFindDelegate>)_findDelegate
1770 return [static_cast<WebKit::FindClient&>(_page->findClient()).delegate().leakRef() autorelease];
1773 - (void)_setFindDelegate:(id<_WKFindDelegate>)findDelegate
1775 static_cast<WebKit::FindClient&>(_page->findClient()).setDelegate(findDelegate);
1778 static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
1780 unsigned findOptions = 0;
1782 if (wkFindOptions & _WKFindOptionsCaseInsensitive)
1783 findOptions |= WebKit::FindOptionsCaseInsensitive;
1784 if (wkFindOptions & _WKFindOptionsAtWordStarts)
1785 findOptions |= WebKit::FindOptionsAtWordStarts;
1786 if (wkFindOptions & _WKFindOptionsTreatMedialCapitalAsWordStart)
1787 findOptions |= WebKit::FindOptionsTreatMedialCapitalAsWordStart;
1788 if (wkFindOptions & _WKFindOptionsBackwards)
1789 findOptions |= WebKit::FindOptionsBackwards;
1790 if (wkFindOptions & _WKFindOptionsWrapAround)
1791 findOptions |= WebKit::FindOptionsWrapAround;
1792 if (wkFindOptions & _WKFindOptionsShowOverlay)
1793 findOptions |= WebKit::FindOptionsShowOverlay;
1794 if (wkFindOptions & _WKFindOptionsShowFindIndicator)
1795 findOptions |= WebKit::FindOptionsShowFindIndicator;
1796 if (wkFindOptions & _WKFindOptionsShowHighlight)
1797 findOptions |= WebKit::FindOptionsShowHighlight;
1798 if (wkFindOptions & _WKFindOptionsDetermineMatchIndex)
1799 findOptions |= WebKit::FindOptionsDetermineMatchIndex;
1801 return static_cast<WebKit::FindOptions>(findOptions);
1804 - (void)_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
1806 _page->countStringMatches(string, toFindOptions(options), maxCount);
1809 - (void)_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
1811 _page->findString(string, toFindOptions(options), maxCount);
1816 _page->hideFindUI();
1819 - (id <_WKFormDelegate>)_formDelegate
1821 return _formDelegate.getAutoreleased();
1824 - (void)_setFormDelegate:(id <_WKFormDelegate>)formDelegate
1826 _formDelegate = formDelegate;
1828 class FormClient : public API::FormClient {
1830 explicit FormClient(WKWebView *webView)
1831 : m_webView(webView)
1835 virtual ~FormClient() { }
1837 virtual bool willSubmitForm(WebKit::WebPageProxy*, WebKit::WebFrameProxy*, WebKit::WebFrameProxy* sourceFrame, const Vector<std::pair<WTF::String, WTF::String>>& textFieldValues, API::Object* userData, WebKit::WebFormSubmissionListenerProxy* listener) override
1839 if (userData && userData->type() != API::Object::Type::Data) {
1840 ASSERT(!userData || userData->type() == API::Object::Type::Data);
1841 m_webView->_page->process().connection()->markCurrentlyDispatchedMessageAsInvalid();
1845 auto formDelegate = m_webView->_formDelegate.get();
1847 if (![formDelegate respondsToSelector:@selector(_webView:willSubmitFormValues:userObject:submissionHandler:)])
1850 auto valueMap = adoptNS([[NSMutableDictionary alloc] initWithCapacity:textFieldValues.size()]);
1851 for (const auto& pair : textFieldValues)
1852 [valueMap setObject:pair.second forKey:pair.first];
1854 NSObject <NSSecureCoding> *userObject = nil;
1855 if (API::Data* data = static_cast<API::Data*>(userData)) {
1856 auto nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:const_cast<void*>(static_cast<const void*>(data->bytes())) length:data->size() freeWhenDone:NO]);
1857 auto unarchiver = adoptNS([[NSKeyedUnarchiver alloc] initForReadingWithData:nsData.get()]);
1858 [unarchiver setRequiresSecureCoding:YES];
1860 userObject = [unarchiver decodeObjectOfClass:[NSObject class] forKey:@"userObject"];
1861 } @catch (NSException *exception) {
1862 LOG_ERROR("Failed to decode user data: %@", exception);
1866 [formDelegate _webView:m_webView willSubmitFormValues:valueMap.get() userObject:userObject submissionHandler:^{
1867 listener->continueSubmission();
1873 WKWebView *m_webView;
1877 _page->setFormClient(std::make_unique<FormClient>(self));
1879 _page->setFormClient(nullptr);
1882 - (BOOL)_isDisplayingStandaloneImageDocument
1884 if (auto* mainFrame = _page->mainFrame())
1885 return mainFrame->isDisplayingStandaloneImageDocument();
1889 #pragma mark iOS-specific methods
1893 - (CGSize)_minimumLayoutSizeOverride
1895 ASSERT(_overridesMinimumLayoutSize);
1896 return _minimumLayoutSizeOverride;
1899 - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride
1901 _overridesMinimumLayoutSize = YES;
1902 if (CGSizeEqualToSize(_minimumLayoutSizeOverride, minimumLayoutSizeOverride))
1905 _minimumLayoutSizeOverride = minimumLayoutSizeOverride;
1906 if (!_isAnimatingResize)
1907 _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(minimumLayoutSizeOverride));
1910 - (CGSize)_minimumLayoutSizeOverrideForMinimalUI
1912 ASSERT(_overridesMinimumLayoutSizeForMinimalUI);
1913 return _minimumLayoutSizeOverrideForMinimalUI;
1916 - (void)_setMinimumLayoutSizeOverrideForMinimalUI:(CGSize)size
1918 _overridesMinimumLayoutSizeForMinimalUI = YES;
1919 if (CGSizeEqualToSize(_minimumLayoutSizeOverrideForMinimalUI, size))
1922 _minimumLayoutSizeOverrideForMinimalUI = size;
1923 if (!_isAnimatingResize)
1924 _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(WebCore::FloatSize(size));
1927 - (UIEdgeInsets)_obscuredInsets
1929 return _obscuredInsets;
1932 - (void)_setObscuredInsets:(UIEdgeInsets)obscuredInsets
1934 ASSERT(obscuredInsets.top >= 0);
1935 ASSERT(obscuredInsets.left >= 0);
1936 ASSERT(obscuredInsets.bottom >= 0);
1937 ASSERT(obscuredInsets.right >= 0);
1939 if (UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, obscuredInsets))
1942 _obscuredInsets = obscuredInsets;
1944 [self _updateVisibleContentRects];
1947 - (void)_setInterfaceOrientationOverride:(UIInterfaceOrientation)interfaceOrientation
1949 if (!_overridesInterfaceOrientation)
1950 [[NSNotificationCenter defaultCenter] removeObserver:self name:UIWindowDidRotateNotification object:nil];
1952 _overridesInterfaceOrientation = YES;
1954 if (interfaceOrientation == _interfaceOrientationOverride)
1957 _interfaceOrientationOverride = interfaceOrientation;
1959 if (!_isAnimatingResize)
1960 _page->setDeviceOrientation(deviceOrientationForUIInterfaceOrientation(_interfaceOrientationOverride));
1963 - (UIInterfaceOrientation)_interfaceOrientationOverride
1965 ASSERT(_overridesInterfaceOrientation);
1966 return _interfaceOrientationOverride;
1969 - (CGSize)_maximumUnobscuredSizeOverride
1971 ASSERT(_overridesMaximumUnobscuredSize);
1972 return _maximumUnobscuredSizeOverride;
1975 - (void)_setMaximumUnobscuredSizeOverride:(CGSize)size
1977 ASSERT(size.width <= self.bounds.size.width && size.height <= self.bounds.size.height);
1978 _overridesMaximumUnobscuredSize = YES;
1979 if (CGSizeEqualToSize(_maximumUnobscuredSizeOverride, size))
1982 _maximumUnobscuredSizeOverride = size;
1983 if (!_isAnimatingResize)
1984 _page->setMaximumUnobscuredSize(WebCore::FloatSize(size));
1987 - (void)_setBackgroundExtendsBeyondPage:(BOOL)backgroundExtends
1989 _page->setBackgroundExtendsBeyondPage(backgroundExtends);
1992 - (BOOL)_backgroundExtendsBeyondPage
1994 return _page->backgroundExtendsBeyondPage();
1997 - (void)_beginInteractiveObscuredInsetsChange
1999 ASSERT(!_isChangingObscuredInsetsInteractively);
2000 _isChangingObscuredInsetsInteractively = YES;
2003 - (void)_endInteractiveObscuredInsetsChange
2005 ASSERT(_isChangingObscuredInsetsInteractively);
2006 _isChangingObscuredInsetsInteractively = NO;
2007 [self _updateVisibleContentRects];
2010 - (void)_beginAnimatedResizeWithUpdates:(void (^)(void))updateBlock
2012 _isAnimatingResize = YES;
2014 if (_customContentView) {
2019 _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2021 NSUInteger indexOfContentView = [[_scrollView subviews] indexOfObject:_contentView.get()];
2022 _resizeAnimationView = adoptNS([[UIView alloc] init]);
2023 [_scrollView insertSubview:_resizeAnimationView.get() atIndex:indexOfContentView];
2024 [_resizeAnimationView addSubview:_contentView.get()];
2025 WebCore::FloatRect oldUnobscuredContentRect = _page->unobscuredContentRect();
2029 CGRect newBounds = self.bounds;
2030 CGSize newMinimumLayoutSize = newBounds.size;
2032 CGSize contentSizeInContentViewCoordinates = [_contentView bounds].size;
2033 [_scrollView setMinimumZoomScale:std::min(newMinimumLayoutSize.width / contentSizeInContentViewCoordinates.width, [_scrollView minimumZoomScale])];
2034 [_scrollView setMaximumZoomScale:std::max(newMinimumLayoutSize.width / contentSizeInContentViewCoordinates.width, [_scrollView maximumZoomScale])];
2036 // Compute the new scale to keep the current content width in the scrollview.
2037 CGFloat oldWebViewWidthInContentViewCoordinates = oldUnobscuredContentRect.width();
2038 CGFloat visibleContentViewWidthInContentCoordinates = std::min(contentSizeInContentViewCoordinates.width, oldWebViewWidthInContentViewCoordinates);
2039 CGFloat targetScale = newMinimumLayoutSize.width / visibleContentViewWidthInContentCoordinates;
2040 CGFloat resizeAnimationViewAnimationScale = targetScale / contentZoomScale(self);
2041 [_resizeAnimationView setTransform:CGAffineTransformMakeScale(resizeAnimationViewAnimationScale, resizeAnimationViewAnimationScale)];
2043 // Compute a new position to keep the content centered.
2044 CGPoint originalContentCenter = oldUnobscuredContentRect.center();
2045 CGPoint originalContentCenterInSelfCoordinates = [self convertPoint:originalContentCenter fromView:_contentView.get()];
2046 CGRect futureUnobscuredRectInSelfCoordinates = UIEdgeInsetsInsetRect(newBounds, _obscuredInsets);
2047 CGPoint futureUnobscuredRectCenterInSelfCoordinates = CGPointMake(futureUnobscuredRectInSelfCoordinates.origin.x + futureUnobscuredRectInSelfCoordinates.size.width / 2, futureUnobscuredRectInSelfCoordinates.origin.y + futureUnobscuredRectInSelfCoordinates.size.height / 2);
2049 CGPoint originalContentOffset = [_scrollView contentOffset];
2050 CGPoint contentOffset = originalContentOffset;
2051 contentOffset.x += (originalContentCenterInSelfCoordinates.x - futureUnobscuredRectCenterInSelfCoordinates.x);
2052 contentOffset.y += (originalContentCenterInSelfCoordinates.y - futureUnobscuredRectCenterInSelfCoordinates.y);
2054 // Limit the new offset within the scrollview, we do not want to rubber band programmatically.
2055 CGSize futureContentSizeInSelfCoordinates = CGSizeMake(contentSizeInContentViewCoordinates.width * targetScale, contentSizeInContentViewCoordinates.height * targetScale);
2056 CGFloat maxHorizontalOffset = futureContentSizeInSelfCoordinates.width - newBounds.size.width + _obscuredInsets.right;
2057 contentOffset.x = std::min(contentOffset.x, maxHorizontalOffset);
2058 CGFloat maxVerticalOffset = futureContentSizeInSelfCoordinates.height - newBounds.size.height + _obscuredInsets.bottom;
2059 contentOffset.y = std::min(contentOffset.y, maxVerticalOffset);
2061 contentOffset.x = std::max(contentOffset.x, -_obscuredInsets.left);
2062 contentOffset.y = std::max(contentOffset.y, -_obscuredInsets.top);
2064 // Make the top/bottom edges "sticky" within 1 pixel.
2065 if (oldUnobscuredContentRect.maxY() > contentSizeInContentViewCoordinates.height - 1)
2066 contentOffset.y = maxVerticalOffset;
2067 if (oldUnobscuredContentRect.y() < 1)
2068 contentOffset.y = -_obscuredInsets.top;
2070 // FIXME: if we have content centered after double tap to zoom, we should also try to keep that rect in view.
2071 [_scrollView setContentSize:roundScrollViewContentSize(*_page, futureContentSizeInSelfCoordinates)];
2072 [_scrollView setContentOffset:contentOffset];
2074 CGRect visibleRectInContentCoordinates = [self convertRect:newBounds toView:_contentView.get()];
2075 CGRect unobscuredRectInContentCoordinates = [self convertRect:futureUnobscuredRectInSelfCoordinates toView:_contentView.get()];
2077 CGSize minimumLayoutSize = newBounds.size;
2078 if (_overridesMinimumLayoutSize)
2079 minimumLayoutSize = _minimumLayoutSizeOverride;
2081 CGSize minimumLayoutSizeForMinimalUI = minimumLayoutSize;
2082 if (_overridesMinimumLayoutSizeForMinimalUI)
2083 minimumLayoutSizeForMinimalUI = _minimumLayoutSizeOverrideForMinimalUI;
2085 CGSize maximumUnobscuredSize = newBounds.size;
2086 if (_overridesMaximumUnobscuredSize)
2087 maximumUnobscuredSize = _maximumUnobscuredSizeOverride;
2089 int32_t orientation;
2090 if (_overridesInterfaceOrientation)
2091 orientation = deviceOrientationForUIInterfaceOrientation(_interfaceOrientationOverride);
2093 orientation = _page->deviceOrientation();
2095 _page->dynamicViewportSizeUpdate(WebCore::FloatSize(minimumLayoutSize), WebCore::FloatSize(minimumLayoutSizeForMinimalUI), WebCore::FloatSize(maximumUnobscuredSize), visibleRectInContentCoordinates, unobscuredRectInContentCoordinates, futureUnobscuredRectInSelfCoordinates, targetScale, orientation);
2098 - (void)_endAnimatedResize
2100 _page->synchronizeDynamicViewportUpdate();
2102 if (!_customContentView && _isAnimatingResize) {
2103 NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
2104 [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
2106 CALayer *contentViewLayer = [_contentView layer];
2107 CATransform3D resizeAnimationTransformAdjustements = _resizeAnimationTransformAdjustments;
2108 CGFloat adjustmentScale = resizeAnimationTransformAdjustements.m11;
2109 contentViewLayer.sublayerTransform = CATransform3DIdentity;
2111 CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
2112 CALayer *contentLayer = [_contentView layer];
2113 CATransform3D contentLayerTransform = contentLayer.transform;
2114 CGFloat currentScale = [[_resizeAnimationView layer] transform].m11 * contentLayerTransform.m11;
2116 // We cannot use [UIScrollView setZoomScale:] directly because the UIScrollView delegate would get a callback with
2117 // an invalid contentOffset. The real content offset is only set below.
2118 // Since there is no public API for setting both the zoomScale and the contentOffset, we set the zoomScale manually
2119 // on the zoom layer and then only change the contentOffset.
2120 CGFloat adjustedScale = adjustmentScale * currentScale;
2121 contentLayerTransform.m11 = adjustedScale;
2122 contentLayerTransform.m22 = adjustedScale;
2123 contentLayer.transform = contentLayerTransform;
2125 CGPoint currentScrollOffset = [_scrollView contentOffset];
2126 double horizontalScrollAdjustement = _resizeAnimationTransformAdjustments.m41 * animatingScaleTarget;
2127 double verticalScrollAdjustment = _resizeAnimationTransformAdjustments.m42 * animatingScaleTarget;
2129 [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
2130 [_scrollView setContentOffset:CGPointMake(currentScrollOffset.x - horizontalScrollAdjustement, currentScrollOffset.y - verticalScrollAdjustment)];
2132 [_resizeAnimationView removeFromSuperview];
2133 _resizeAnimationView = nil;
2136 _isAnimatingResize = NO;
2137 _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2139 [self _updateVisibleContentRects];
2142 - (void)_setOverlaidAccessoryViewsInset:(CGSize)inset
2144 [_customContentView web_setOverlaidAccessoryViewsInset:inset];
2147 - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler
2149 CGRect snapshotRectInContentCoordinates = [self convertRect:rectInViewCoordinates toView:_contentView.get()];
2150 CGFloat imageHeight = imageWidth / snapshotRectInContentCoordinates.size.width * snapshotRectInContentCoordinates.size.height;
2151 CGSize imageSize = CGSizeMake(imageWidth, imageHeight);
2153 void(^copiedCompletionHandler)(CGImageRef) = [completionHandler copy];
2154 _page->takeSnapshot(WebCore::enclosingIntRect(snapshotRectInContentCoordinates), WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebKit::SnapshotOptionsExcludeDeviceScaleFactor, [=](const WebKit::ShareableBitmap::Handle& imageHandle, WebKit::CallbackBase::Error) {
2155 if (imageHandle.isNull()) {
2156 copiedCompletionHandler(nullptr);
2157 [copiedCompletionHandler release];
2161 RefPtr<WebKit::ShareableBitmap> bitmap = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::ReadOnly);
2164 copiedCompletionHandler(nullptr);
2165 [copiedCompletionHandler release];
2169 RetainPtr<CGImageRef> cgImage;
2170 cgImage = bitmap->makeCGImage();
2171 copiedCompletionHandler(cgImage.get());
2172 [copiedCompletionHandler release];
2176 - (void)_overrideLayoutParametersWithMinimumLayoutSize:(CGSize)minimumLayoutSize minimumLayoutSizeForMinimalUI:(CGSize)minimumLayoutSizeForMinimalUI maximumUnobscuredSizeOverride:(CGSize)maximumUnobscuredSizeOverride
2178 // FIXME: After Safari is updated to use this function instead of setting the parameters separately, we should remove
2179 // the individual setters and send a single message to send everything at once to the WebProcess.
2180 self._minimumLayoutSizeOverride = minimumLayoutSize;
2181 self._minimumLayoutSizeOverrideForMinimalUI = minimumLayoutSizeForMinimalUI;
2182 self._maximumUnobscuredSizeOverride = maximumUnobscuredSizeOverride;
2185 - (UIView *)_viewForFindUI
2187 return [self viewForZoomingInScrollView:[self scrollView]];
2190 - (BOOL)_isDisplayingPDF
2192 return [_customContentView isKindOfClass:[WKPDFView class]];
2195 - (NSData *)_dataForDisplayedPDF
2197 if (![self _isDisplayingPDF])
2199 CGPDFDocumentRef pdfDocument = [(WKPDFView *)_customContentView pdfDocument];
2200 return [(NSData *)CGDataProviderCopyData(CGPDFDocumentGetDataProvider(pdfDocument)) autorelease];
2203 - (NSString *)_suggestedFilenameForDisplayedPDF
2205 if (![self _isDisplayingPDF])
2207 return [(WKPDFView *)_customContentView.get() suggestedFilename];
2210 - (CGFloat)_viewportMetaTagWidth
2212 return _viewportMetaTagWidth;
2215 - (_WKWebViewPrintFormatter *)_webViewPrintFormatter
2217 UIViewPrintFormatter *viewPrintFormatter = self.viewPrintFormatter;
2218 ASSERT([viewPrintFormatter isKindOfClass:[_WKWebViewPrintFormatter class]]);
2219 return (_WKWebViewPrintFormatter *)viewPrintFormatter;
2224 #pragma mark - OS X-specific methods
2226 - (NSColor *)_pageExtendedBackgroundColor
2228 WebCore::Color color = _page->pageExtendedBackgroundColor();
2229 if (!color.isValid())
2232 return nsColor(color);
2235 - (BOOL)_drawsTransparentBackground
2237 return _page->drawsTransparentBackground();
2240 - (void)_setDrawsTransparentBackground:(BOOL)drawsTransparentBackground
2242 _page->setDrawsTransparentBackground(drawsTransparentBackground);
2245 - (void)_setTopContentInset:(CGFloat)contentInset
2247 [_wkView _setTopContentInset:contentInset];
2250 - (CGFloat)_topContentInset
2252 return [_wkView _topContentInset];
2255 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
2257 - (void)_setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets
2259 [_wkView _setAutomaticallyAdjustsContentInsets:automaticallyAdjustsContentInsets];
2262 - (BOOL)_automaticallyAdjustsContentInsets
2264 return [_wkView _automaticallyAdjustsContentInsets];
2273 #if !TARGET_OS_IPHONE
2275 @implementation WKWebView (WKIBActions)
2277 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
2279 SEL action = item.action;
2281 if (action == @selector(goBack:))
2282 return !!_page->backForwardList().backItem();
2284 if (action == @selector(goForward:))
2285 return !!_page->backForwardList().forwardItem();
2287 if (action == @selector(stopLoading:)) {
2288 // FIXME: Return no if we're stopped.
2292 if (action == @selector(reload:) || action == @selector(reloadFromOrigin:)) {
2293 // FIXME: Return no if we're loading.
2300 - (IBAction)goBack:(id)sender
2305 - (IBAction)goForward:(id)sender
2310 - (IBAction)reload:(id)sender
2315 - (IBAction)reloadFromOrigin:(id)sender
2317 [self reloadFromOrigin];
2320 - (IBAction)stopLoading:(id)sender
2322 _page->stopLoading();
2330 @implementation WKWebView (_WKWebViewPrintFormatter)
2332 - (Class)_printFormatterClass
2334 return [_WKWebViewPrintFormatter class];
2337 - (NSInteger)_computePageCountAndStartDrawingToPDFForFrame:(_WKFrameHandle *)frame printInfo:(const WebKit::PrintInfo&)printInfo firstPage:(uint32_t)firstPage computedTotalScaleFactor:(double&)totalScaleFactor
2339 if ([self _isDisplayingPDF])
2340 return CGPDFDocumentGetNumberOfPages([(WKPDFView *)_customContentView pdfDocument]);
2342 _pageIsPrintingToPDF = YES;
2343 Vector<WebCore::IntRect> pageRects;
2344 uint64_t frameID = frame ? frame._frameID : _page->mainFrame()->frameID();
2345 if (!_page->sendSync(Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF(frameID, printInfo, firstPage), Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF::Reply(pageRects, totalScaleFactor)))
2347 return pageRects.size();
2350 - (void)_endPrinting
2352 _pageIsPrintingToPDF = NO;
2353 _printedDocument = nullptr;
2354 _page->send(Messages::WebPage::EndPrinting());
2357 // FIXME: milliseconds::max() overflows when converted to nanoseconds, causing condition_variable::wait_for() to believe
2358 // a timeout occurred on any spurious wakeup. Use nanoseconds::max() (converted to ms) to avoid this. We should perhaps
2359 // change waitForAndDispatchImmediately() to take nanoseconds to avoid this issue.
2360 static constexpr std::chrono::milliseconds didFinishLoadingTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds::max());
2362 - (CGPDFDocumentRef)_printedDocument
2364 if ([self _isDisplayingPDF]) {
2365 ASSERT(!_pageIsPrintingToPDF);
2366 return [(WKPDFView *)_customContentView pdfDocument];
2369 if (_pageIsPrintingToPDF) {
2370 if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::DidFinishDrawingPagesToPDF>(_page->pageID(), didFinishLoadingTimeout)) {
2371 ASSERT_NOT_REACHED();
2374 ASSERT(!_pageIsPrintingToPDF);
2376 return _printedDocument.get();
2379 - (void)_setPrintedDocument:(CGPDFDocumentRef)printedDocument
2381 if (!_pageIsPrintingToPDF)
2383 ASSERT(![self _isDisplayingPDF]);
2384 _printedDocument = printedDocument;
2385 _pageIsPrintingToPDF = NO;
2391 #endif // WK_API_ENABLED