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]);
311 _page->setBackgroundExtendsBeyondPage(true);
313 _navigationState = std::make_unique<WebKit::NavigationState>(self);
314 _page->setPolicyClient(_navigationState->createPolicyClient());
315 _page->setLoaderClient(_navigationState->createLoaderClient());
317 _uiDelegate = std::make_unique<WebKit::UIDelegate>(self);
318 _page->setUIClient(_uiDelegate->createUIClient());
320 _page->setFindClient(std::make_unique<WebKit::FindClient>(self));
322 pageToViewMap().add(_page.get(), self);
327 - (instancetype)initWithCoder:(NSCoder *)coder
337 [_remoteObjectRegistry _invalidate];
339 [[_configuration _contentProviderRegistry] removePage:*_page];
340 [[NSNotificationCenter defaultCenter] removeObserver:self];
343 pageToViewMap().remove(_page.get());
348 - (WKWebViewConfiguration *)configuration
350 return [[_configuration copy] autorelease];
353 - (WKBackForwardList *)backForwardList
355 return wrapper(_page->backForwardList());
358 - (id <WKNavigationDelegate>)navigationDelegate
360 return _navigationState->navigationDelegate().autorelease();
363 - (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate
365 _navigationState->setNavigationDelegate(navigationDelegate);
368 - (id <WKUIDelegate>)UIDelegate
370 return _uiDelegate->delegate().autorelease();
373 - (void)setUIDelegate:(id<WKUIDelegate>)UIDelegate
375 _uiDelegate->setDelegate(UIDelegate);
378 - (WKNavigation *)loadRequest:(NSURLRequest *)request
380 uint64_t navigationID = _page->loadRequest(request);
381 auto navigation = _navigationState->createLoadRequestNavigation(navigationID, request);
383 return navigation.autorelease();
386 - (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL
388 uint64_t navigationID = _page->loadHTMLString(string, baseURL.absoluteString);
389 auto navigation = _navigationState->createLoadDataNavigation(navigationID);
391 return navigation.autorelease();
394 - (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item
396 uint64_t navigationID = _page->goToBackForwardItem(&item._item);
398 auto navigation = _navigationState->createBackForwardNavigation(navigationID, item._item);
400 return navigation.autorelease();
405 return _page->pageLoadState().title();
410 return [NSURL _web_URLWithWTFString:_page->pageLoadState().activeURL()];
415 return _page->pageLoadState().isLoading();
418 - (double)estimatedProgress
420 return _page->pageLoadState().estimatedProgress();
423 - (BOOL)hasOnlySecureContent
425 return _page->pageLoadState().hasOnlySecureContent();
430 return _page->pageLoadState().canGoBack();
435 return _page->pageLoadState().canGoForward();
438 - (WKNavigation *)goBack
440 uint64_t navigationID = _page->goBack();
444 ASSERT(_page->backForwardList().currentItem());
445 auto navigation = _navigationState->createBackForwardNavigation(navigationID, *_page->backForwardList().currentItem());
447 return navigation.autorelease();
450 - (WKNavigation *)goForward
452 uint64_t navigationID = _page->goForward();
456 ASSERT(_page->backForwardList().currentItem());
457 auto navigation = _navigationState->createBackForwardNavigation(navigationID, *_page->backForwardList().currentItem());
459 return navigation.autorelease();
462 - (WKNavigation *)reload
464 uint64_t navigationID = _page->reload(false);
465 ASSERT(navigationID);
467 auto navigation = _navigationState->createReloadNavigation(navigationID);
468 return navigation.autorelease();
471 - (WKNavigation *)reloadFromOrigin
473 uint64_t navigationID = _page->reload(true);
474 ASSERT(navigationID);
476 auto navigation = _navigationState->createReloadNavigation(navigationID);
477 return navigation.autorelease();
482 _page->stopLoading();
485 static WKErrorCode callbackErrorCode(WebKit::CallbackBase::Error error)
488 case WebKit::CallbackBase::Error::None:
489 ASSERT_NOT_REACHED();
490 return WKErrorUnknown;
492 case WebKit::CallbackBase::Error::Unknown:
493 return WKErrorUnknown;
495 case WebKit::CallbackBase::Error::ProcessExited:
496 return WKErrorWebContentProcessTerminated;
498 case WebKit::CallbackBase::Error::OwnerWasInvalidated:
499 return WKErrorWebViewInvalidated;
503 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler
505 auto handler = adoptNS([completionHandler copy]);
507 _page->runJavaScriptInMainFrame(javaScriptString, [handler](WebKit::WebSerializedScriptValue* serializedScriptValue, WebKit::ScriptValueCallback::Error errorCode) {
511 auto completionHandler = (void (^)(id, NSError *))handler.get();
513 if (errorCode != WebKit::ScriptValueCallback::Error::None) {
514 auto error = createNSError(callbackErrorCode(errorCode));
515 if (errorCode == WebKit::ScriptValueCallback::Error::OwnerWasInvalidated) {
516 // The OwnerWasInvalidated callback is synchronous. We don't want to call the block from within it
517 // because that can trigger re-entrancy bugs in WebKit.
518 // FIXME: It would be even better if GenericCallback did this for us.
519 dispatch_async(dispatch_get_main_queue(), [completionHandler, error] {
520 completionHandler(nil, error.get());
525 completionHandler(nil, error.get());
529 if (!serializedScriptValue) {
530 completionHandler(nil, createNSError(WKErrorJavaScriptExceptionOccurred).get());
534 auto context = adoptNS([[JSContext alloc] init]);
535 JSValueRef valueRef = serializedScriptValue->deserialize([context JSGlobalContextRef], 0);
536 JSValue *value = [JSValue valueWithJSValueRef:valueRef inContext:context.get()];
538 completionHandler([value toObject], nil);
542 #pragma mark iOS-specific methods
545 - (void)setFrame:(CGRect)frame
547 CGRect oldFrame = self.frame;
548 [super setFrame:frame];
550 if (!CGSizeEqualToSize(oldFrame.size, frame.size))
551 [self _frameOrBoundsChanged];
554 - (void)setBounds:(CGRect)bounds
556 CGRect oldBounds = self.bounds;
557 [super setBounds:bounds];
558 [_customContentFixedOverlayView setFrame:self.bounds];
560 if (!CGSizeEqualToSize(oldBounds.size, bounds.size))
561 [self _frameOrBoundsChanged];
564 - (UIScrollView *)scrollView
566 return _scrollView.get();
569 - (WKBrowsingContextController *)browsingContextController
571 return [_contentView browsingContextController];
574 static inline CGFloat floorToDevicePixel(CGFloat input, float deviceScaleFactor)
576 return CGFloor(input * deviceScaleFactor) / deviceScaleFactor;
579 static CGSize roundScrollViewContentSize(const WebKit::WebPageProxy& page, CGSize contentSize)
581 float deviceScaleFactor = page.deviceScaleFactor();
582 return CGSizeMake(floorToDevicePixel(contentSize.width, deviceScaleFactor), floorToDevicePixel(contentSize.height, deviceScaleFactor));
585 - (UIView *)_currentContentView
587 return _customContentView ? _customContentView.get() : _contentView.get();
590 - (void)_setHasCustomContentView:(BOOL)pageHasCustomContentView loadedMIMEType:(const WTF::String&)mimeType
592 if (pageHasCustomContentView) {
593 [_customContentView removeFromSuperview];
594 [_customContentFixedOverlayView removeFromSuperview];
596 Class representationClass = [[_configuration _contentProviderRegistry] providerForMIMEType:mimeType];
597 ASSERT(representationClass);
598 _customContentView = adoptNS([[representationClass alloc] web_initWithFrame:self.bounds webView:self]);
599 _customContentFixedOverlayView = adoptNS([[UIView alloc] initWithFrame:self.bounds]);
600 [_customContentFixedOverlayView setUserInteractionEnabled:NO];
602 [_contentView removeFromSuperview];
603 [_scrollView addSubview:_customContentView.get()];
604 [self addSubview:_customContentFixedOverlayView.get()];
606 [_customContentView web_setMinimumSize:self.bounds.size];
607 [_customContentView web_setFixedOverlayView:_customContentFixedOverlayView.get()];
608 } else if (_customContentView) {
609 [_customContentView removeFromSuperview];
610 _customContentView = nullptr;
612 [_customContentFixedOverlayView removeFromSuperview];
613 _customContentFixedOverlayView = nullptr;
615 [_scrollView addSubview:_contentView.get()];
616 [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
618 [_customContentFixedOverlayView setFrame:self.bounds];
619 [self addSubview:_customContentFixedOverlayView.get()];
623 - (void)_didFinishLoadingDataForCustomContentProviderWithSuggestedFilename:(const String&)suggestedFilename data:(NSData *)data
625 ASSERT(_customContentView);
626 [_customContentView web_setContentProviderData:data suggestedFilename:suggestedFilename];
629 - (void)_setViewportMetaTagWidth:(float)newWidth
631 _viewportMetaTagWidth = newWidth;
634 - (void)_willInvokeUIScrollViewDelegateCallback
636 _delayUpdateVisibleContentRects = YES;
639 - (void)_didInvokeUIScrollViewDelegateCallback
641 _delayUpdateVisibleContentRects = NO;
642 if (_hadDelayedUpdateVisibleContentRects) {
643 _hadDelayedUpdateVisibleContentRects = NO;
644 [self _updateVisibleContentRects];
648 static CGFloat contentZoomScale(WKWebView* webView)
650 CGFloat scale = [[webView._currentContentView layer] affineTransform].a;
651 ASSERT(scale == [webView->_scrollView zoomScale]);
655 - (void)_updateScrollViewBackground
657 WebCore::Color color;
658 if (_customContentView)
659 color = [_customContentView backgroundColor].CGColor;
661 color = _page->pageExtendedBackgroundColor();
662 CGFloat zoomScale = contentZoomScale(self);
663 CGFloat minimumZoomScale = [_scrollView minimumZoomScale];
664 if (zoomScale < minimumZoomScale) {
666 CGFloat opacity = std::max<CGFloat>(1 - slope * (minimumZoomScale - zoomScale), 0);
667 color = WebCore::colorWithOverrideAlpha(color.rgb(), opacity);
670 if (_scrollViewBackgroundColor == color)
673 _scrollViewBackgroundColor = color;
675 auto uiBackgroundColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)]);
676 [_scrollView setBackgroundColor:uiBackgroundColor.get()];
679 - (void)_setUsesMinimalUI:(BOOL)usesMinimalUI
681 _usesMinimalUI = usesMinimalUI;
682 _needsToNotifyDelegateAboutMinimalUI = YES;
685 - (BOOL)_usesMinimalUI
687 return _usesMinimalUI;
690 - (CGPoint)_adjustedContentOffset:(CGPoint)point
692 CGPoint result = point;
693 UIEdgeInsets contentInset = [self _computedContentInset];
695 result.x -= contentInset.left;
696 result.y -= contentInset.top;
701 - (UIEdgeInsets)_computedContentInset
703 if (!UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, UIEdgeInsetsZero))
704 return _obscuredInsets;
706 return [_scrollView contentInset];
709 - (void)_processDidExit
711 if (!_customContentView && _isAnimatingResize) {
712 NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
713 [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
714 [_resizeAnimationView removeFromSuperview];
715 _resizeAnimationView = nil;
717 _isAnimatingResize = NO;
718 _resizeAnimationTransformAdjustments = CATransform3DIdentity;
720 [_contentView setFrame:self.bounds];
721 [_scrollView setBackgroundColor:[UIColor whiteColor]];
722 [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
723 [_scrollView setZoomScale:1];
725 _viewportMetaTagWidth = -1;
726 _needsResetViewStateAfterCommitLoadForMainFrame = NO;
727 _needsToRestoreExposedRect = NO;
728 _needsToRestoreUnobscuredCenter = NO;
729 _scrollViewBackgroundColor = WebCore::Color();
730 _delayUpdateVisibleContentRects = NO;
731 _hadDelayedUpdateVisibleContentRects = NO;
734 - (void)_didCommitLoadForMainFrame
736 _firstPaintAfterCommitLoadTransactionID = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
738 _needsResetViewStateAfterCommitLoadForMainFrame = YES;
742 static inline bool withinEpsilon(float a, float b)
744 return fabs(a - b) < std::numeric_limits<float>::epsilon();
747 static void changeContentOffsetBoundedInValidRange(UIScrollView *scrollView, WebCore::FloatPoint contentOffset)
749 UIEdgeInsets contentInsets = scrollView.contentInset;
750 CGSize contentSize = scrollView.contentSize;
751 CGSize scrollViewSize = scrollView.bounds.size;
753 float maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
754 if (contentOffset.x() > maxHorizontalOffset)
755 contentOffset.setX(maxHorizontalOffset);
756 float maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
757 if (contentOffset.y() > maxVerticalOffset)
758 contentOffset.setY(maxVerticalOffset);
759 if (contentOffset.x() < -contentInsets.left)
760 contentOffset.setX(-contentInsets.left);
761 if (contentOffset.y() < -contentInsets.top)
762 contentOffset.setY(-contentInsets.top);
763 scrollView.contentOffset = contentOffset;
766 - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
768 if (_customContentView)
771 if (_isAnimatingResize) {
772 [_resizeAnimationView layer].sublayerTransform = _resizeAnimationTransformAdjustments;
776 CGSize newContentSize = roundScrollViewContentSize(*_page, [_contentView frame].size);
777 [_scrollView _setContentSizePreservingContentOffsetDuringRubberband:newContentSize];
779 [_scrollView setMinimumZoomScale:layerTreeTransaction.minimumScaleFactor()];
780 [_scrollView setMaximumZoomScale:layerTreeTransaction.maximumScaleFactor()];
781 [_scrollView setZoomEnabled:layerTreeTransaction.allowsUserScaling()];
782 if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom])
783 [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()];
785 [self _updateScrollViewBackground];
787 if (_gestureController)
788 _gestureController->setRenderTreeSize(layerTreeTransaction.renderTreeSize());
790 if (_needsToNotifyDelegateAboutMinimalUI || _needsResetViewStateAfterCommitLoadForMainFrame) {
791 _needsToNotifyDelegateAboutMinimalUI = NO;
793 auto delegate = _uiDelegate->delegate();
794 if ([delegate respondsToSelector:@selector(_webView:usesMinimalUI:)])
795 [static_cast<id <WKUIDelegatePrivate>>(delegate.get()) _webView:self usesMinimalUI:_usesMinimalUI];
798 if (_needsResetViewStateAfterCommitLoadForMainFrame && layerTreeTransaction.transactionID() >= _firstPaintAfterCommitLoadTransactionID) {
799 _needsResetViewStateAfterCommitLoadForMainFrame = NO;
800 [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
801 [self _updateVisibleContentRects];
804 if (_needsToRestoreExposedRect && layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore) {
805 _needsToRestoreExposedRect = NO;
807 if (withinEpsilon(contentZoomScale(self), _scaleToRestore)) {
808 WebCore::FloatPoint exposedPosition = _exposedRectToRestore.location();
809 exposedPosition.scale(_scaleToRestore, _scaleToRestore);
811 changeContentOffsetBoundedInValidRange(_scrollView.get(), exposedPosition);
813 [self _updateVisibleContentRects];
816 if (_needsToRestoreUnobscuredCenter && layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore) {
817 _needsToRestoreUnobscuredCenter = NO;
819 if (withinEpsilon(contentZoomScale(self), _scaleToRestore)) {
820 CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, _obscuredInsets);
821 WebCore::FloatSize unobscuredContentSizeAtNewScale(unobscuredRect.size.width / _scaleToRestore, unobscuredRect.size.height / _scaleToRestore);
822 WebCore::FloatPoint topLeftInDocumentCoordinate(_unobscuredCenterToRestore.x() - unobscuredContentSizeAtNewScale.width() / 2, _unobscuredCenterToRestore.y() - unobscuredContentSizeAtNewScale.height() / 2);
824 topLeftInDocumentCoordinate.scale(_scaleToRestore, _scaleToRestore);
825 topLeftInDocumentCoordinate.moveBy(WebCore::FloatPoint(-_obscuredInsets.left, -_obscuredInsets.top));
827 changeContentOffsetBoundedInValidRange(_scrollView.get(), topLeftInDocumentCoordinate);
829 [self _updateVisibleContentRects];
833 - (void)_dynamicViewportUpdateChangedTargetToScale:(double)newScale position:(CGPoint)newScrollPosition
835 if (_isAnimatingResize) {
836 CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
837 double currentTargetScale = animatingScaleTarget * [[_contentView layer] transform].m11;
838 double scale = newScale / currentTargetScale;
839 _resizeAnimationTransformAdjustments = CATransform3DMakeScale(scale, scale, 0);
841 CGPoint newContentOffset = [self _adjustedContentOffset:CGPointMake(newScrollPosition.x * newScale, newScrollPosition.y * newScale)];
842 CGPoint currentContentOffset = [_scrollView contentOffset];
844 _resizeAnimationTransformAdjustments.m41 = (currentContentOffset.x - newContentOffset.x) / animatingScaleTarget;
845 _resizeAnimationTransformAdjustments.m42 = (currentContentOffset.y - newContentOffset.y) / animatingScaleTarget;
849 - (void)_restorePageStateToExposedRect:(WebCore::FloatRect)exposedRect scale:(double)scale
851 if (_isAnimatingResize)
854 if (_customContentView)
857 _needsToRestoreUnobscuredCenter = NO;
858 _needsToRestoreExposedRect = YES;
859 _firstTransactionIDAfterPageRestore = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
860 _exposedRectToRestore = exposedRect;
861 _scaleToRestore = scale;
864 - (void)_restorePageStateToUnobscuredCenter:(WebCore::FloatPoint)center scale:(double)scale
866 if (_isAnimatingResize)
869 if (_customContentView)
872 _needsToRestoreExposedRect = NO;
873 _needsToRestoreUnobscuredCenter = YES;
874 _firstTransactionIDAfterPageRestore = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
875 _unobscuredCenterToRestore = center;
876 _scaleToRestore = scale;
879 - (WebKit::ViewSnapshot)_takeViewSnapshot
881 float deviceScale = WKGetScreenScaleFactor();
882 CGSize snapshotSize = self.bounds.size;
883 snapshotSize.width *= deviceScale;
884 snapshotSize.height *= deviceScale;
886 WebKit::ViewSnapshot snapshot;
887 snapshot.slotID = [WebKit::ViewSnapshotStore::snapshottingContext() createImageSlot:snapshotSize hasAlpha:YES];
889 CATransform3D transform = CATransform3DMakeScale(deviceScale, deviceScale, 1);
890 CARenderServerCaptureLayerWithTransform(MACH_PORT_NULL, self.layer.context.contextId, (uint64_t)self.layer, snapshot.slotID, 0, 0, &transform);
892 snapshot.size = WebCore::expandedIntSize(WebCore::FloatSize(snapshotSize));
893 snapshot.imageSizeInBytes = snapshotSize.width * snapshotSize.height * 4;
894 snapshot.backgroundColor = _page->pageExtendedBackgroundColor();
899 - (void)_zoomToPoint:(WebCore::FloatPoint)point atScale:(double)scale
901 double maximumZoomDuration = 0.4;
902 double minimumZoomDuration = 0.1;
903 double zoomDurationFactor = 0.3;
905 CGFloat zoomScale = contentZoomScale(self);
906 CFTimeInterval duration = std::min(fabs(log(zoomScale) - log(scale)) * zoomDurationFactor + minimumZoomDuration, maximumZoomDuration);
908 if (scale != zoomScale)
909 _page->willStartUserTriggeredZooming();
911 [_scrollView _zoomToCenter:point scale:scale duration:duration];
914 - (void)_zoomToRect:(WebCore::FloatRect)targetRect atScale:(double)scale origin:(WebCore::FloatPoint)origin
916 // FIMXE: Some of this could be shared with _scrollToRect.
917 const double visibleRectScaleChange = contentZoomScale(self) / scale;
918 const WebCore::FloatRect visibleRect([self convertRect:self.bounds toView:self._currentContentView]);
919 const WebCore::FloatRect unobscuredRect([self _contentRectForUserInteraction]);
921 const WebCore::FloatSize topLeftObscuredInsetAfterZoom((unobscuredRect.minXMinYCorner() - visibleRect.minXMinYCorner()) * visibleRectScaleChange);
922 const WebCore::FloatSize bottomRightObscuredInsetAfterZoom((visibleRect.maxXMaxYCorner() - unobscuredRect.maxXMaxYCorner()) * visibleRectScaleChange);
924 const WebCore::FloatSize unobscuredRectSizeAfterZoom(unobscuredRect.size() * visibleRectScaleChange);
926 // Center to the target rect.
927 WebCore::FloatPoint unobscuredRectLocationAfterZoom = targetRect.location() - (unobscuredRectSizeAfterZoom - targetRect.size()) * 0.5;
929 // Center to the tap point instead in case the target rect won't fit in a direction.
930 if (targetRect.width() > unobscuredRectSizeAfterZoom.width())
931 unobscuredRectLocationAfterZoom.setX(origin.x() - unobscuredRectSizeAfterZoom.width() / 2);
932 if (targetRect.height() > unobscuredRectSizeAfterZoom.height())
933 unobscuredRectLocationAfterZoom.setY(origin.y() - unobscuredRectSizeAfterZoom.height() / 2);
935 // We have computed where we want the unobscured rect to be. Now adjust for the obscuring insets.
936 WebCore::FloatRect visibleRectAfterZoom(unobscuredRectLocationAfterZoom, unobscuredRectSizeAfterZoom);
937 visibleRectAfterZoom.move(-topLeftObscuredInsetAfterZoom);
938 visibleRectAfterZoom.expand(topLeftObscuredInsetAfterZoom + bottomRightObscuredInsetAfterZoom);
940 [self _zoomToPoint:visibleRectAfterZoom.center() atScale:scale];
943 static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOffset, WebCore::FloatSize contentSize, WebCore::FloatSize unobscuredContentSize)
945 WebCore::FloatSize maximumContentOffset = contentSize - unobscuredContentSize;
946 contentOffset = contentOffset.shrunkTo(WebCore::FloatPoint(maximumContentOffset.width(), maximumContentOffset.height()));
947 contentOffset = contentOffset.expandedTo(WebCore::FloatPoint());
948 return contentOffset;
951 - (void)_scrollToContentOffset:(WebCore::FloatPoint)contentOffset
953 if (_isAnimatingResize)
956 [_scrollView _stopScrollingAndZoomingAnimations];
958 WebCore::FloatPoint scaledOffset = contentOffset;
959 CGFloat zoomScale = contentZoomScale(self);
960 scaledOffset.scale(zoomScale, zoomScale);
962 [_scrollView setContentOffset:[self _adjustedContentOffset:scaledOffset]];
965 - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance
967 WebCore::FloatRect unobscuredContentRect([self _contentRectForUserInteraction]);
968 WebCore::FloatPoint unobscuredContentOffset = unobscuredContentRect.location();
969 WebCore::FloatSize contentSize([self._currentContentView bounds].size);
971 // Center the target rect in the scroll view.
972 // If the target doesn't fit in the scroll view, center on the gesture location instead.
973 WebCore::FloatPoint newUnobscuredContentOffset;
974 if (targetRect.width() <= unobscuredContentRect.width())
975 newUnobscuredContentOffset.setX(targetRect.x() - (unobscuredContentRect.width() - targetRect.width()) / 2);
977 newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
978 if (targetRect.height() <= unobscuredContentRect.height())
979 newUnobscuredContentOffset.setY(targetRect.y() - (unobscuredContentRect.height() - targetRect.height()) / 2);
981 newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
982 newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
984 if (unobscuredContentOffset == newUnobscuredContentOffset) {
985 if (targetRect.width() > unobscuredContentRect.width())
986 newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
987 if (targetRect.height() > unobscuredContentRect.height())
988 newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
989 newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
992 WebCore::FloatSize scrollViewOffsetDelta = newUnobscuredContentOffset - unobscuredContentOffset;
993 scrollViewOffsetDelta.scale(contentZoomScale(self));
995 float scrollDistance = scrollViewOffsetDelta.diagonalLength();
996 if (scrollDistance < minimumScrollDistance)
999 [_scrollView setContentOffset:([_scrollView contentOffset] + scrollViewOffsetDelta) animated:YES];
1003 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin
1005 [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale]];
1008 // focusedElementRect and selectionRect are both in document coordinates.
1009 - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRectInDocumentCoordinates selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll
1011 const double WKWebViewStandardFontSize = 16;
1012 const double kMinimumHeightToShowContentAboveKeyboard = 106;
1013 const CFTimeInterval UIWebFormAnimationDuration = 0.25;
1014 const double CaretOffsetFromWindowEdge = 20;
1016 // Zoom around the element's bounding frame. We use a "standard" size to determine the proper frame.
1017 double scale = allowScaling ? std::min(std::max(WKWebViewStandardFontSize / fontSize, minimumScale), maximumScale) : contentZoomScale(self);
1018 CGFloat documentWidth = [_contentView bounds].size.width;
1019 scale = CGRound(documentWidth * scale) / documentWidth;
1021 UIWindow *window = [_scrollView window];
1023 WebCore::FloatRect focusedElementRectInNewScale = focusedElementRectInDocumentCoordinates;
1024 focusedElementRectInNewScale.scale(scale);
1025 focusedElementRectInNewScale.moveBy([_contentView frame].origin);
1027 // Find the portion of the view that is visible on the screen.
1028 UIViewController *topViewController = [[[_scrollView _viewControllerForAncestor] _rootAncestorViewController] _viewControllerForSupportedInterfaceOrientations];
1029 UIView *fullScreenView = topViewController.view;
1030 if (!fullScreenView)
1031 fullScreenView = window;
1033 CGRect unobscuredScrollViewRectInWebViewCoordinates = UIEdgeInsetsInsetRect([self bounds], _obscuredInsets);
1034 CGRect visibleScrollViewBoundsInWebViewCoordinates = CGRectIntersection(unobscuredScrollViewRectInWebViewCoordinates, [fullScreenView convertRect:[fullScreenView bounds] toView:self]);
1035 CGRect formAssistantFrameInWebViewCoordinates = [window convertRect:_inputViewBounds toView:self];
1036 CGRect intersectionBetweenScrollViewAndFormAssistant = CGRectIntersection(visibleScrollViewBoundsInWebViewCoordinates, formAssistantFrameInWebViewCoordinates);
1037 CGSize visibleSize = visibleScrollViewBoundsInWebViewCoordinates.size;
1039 CGFloat visibleOffsetFromTop = 0;
1040 if (!CGRectIsEmpty(intersectionBetweenScrollViewAndFormAssistant)) {
1041 CGFloat heightVisibleAboveFormAssistant = CGRectGetMinY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1042 CGFloat heightVisibleBelowFormAssistant = CGRectGetMaxY(visibleScrollViewBoundsInWebViewCoordinates) - CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant);
1044 if (heightVisibleAboveFormAssistant >= kMinimumHeightToShowContentAboveKeyboard || heightVisibleBelowFormAssistant < heightVisibleAboveFormAssistant)
1045 visibleSize.height = heightVisibleAboveFormAssistant;
1047 visibleSize.height = heightVisibleBelowFormAssistant;
1048 visibleOffsetFromTop = CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1052 BOOL selectionRectIsNotNull = !selectionRectInDocumentCoordinates.isZero();
1054 CGRect currentlyVisibleRegionInWebViewCoordinates;
1055 currentlyVisibleRegionInWebViewCoordinates.origin = unobscuredScrollViewRectInWebViewCoordinates.origin;
1056 currentlyVisibleRegionInWebViewCoordinates.origin.y += visibleOffsetFromTop;
1057 currentlyVisibleRegionInWebViewCoordinates.size = visibleSize;
1059 // Don't bother scrolling if the entire node is already visible, whether or not we got a selectionRect.
1060 if (CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:focusedElementRectInDocumentCoordinates fromView:_contentView.get()]))
1063 // Don't bother scrolling if we have a valid selectionRect and it is already visible.
1064 if (selectionRectIsNotNull && CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:selectionRectInDocumentCoordinates fromView:_contentView.get()]))
1068 // We want to zoom to the left/top corner of the DOM node, with as much spacing on all sides as we
1069 // can get based on the visible area after zooming (workingFrame). The spacing in either dimension is half the
1070 // difference between the size of the DOM node and the size of the visible frame.
1071 CGFloat horizontalSpaceInWebViewCoordinates = std::max((visibleSize.width - focusedElementRectInNewScale.width()) / 2.0, 0.0);
1072 CGFloat verticalSpaceInWebViewCoordinates = std::max((visibleSize.height - focusedElementRectInNewScale.height()) / 2.0, 0.0);
1075 topLeft.x = focusedElementRectInNewScale.x() - horizontalSpaceInWebViewCoordinates;
1076 topLeft.y = focusedElementRectInNewScale.y() - verticalSpaceInWebViewCoordinates - visibleOffsetFromTop;
1078 CGFloat minimumAllowableHorizontalOffsetInWebViewCoordinates = -INFINITY;
1079 CGFloat minimumAllowableVerticalOffsetInWebViewCoordinates = -INFINITY;
1080 if (selectionRectIsNotNull) {
1081 WebCore::FloatRect selectionRectInNewScale = selectionRectInDocumentCoordinates;
1082 selectionRectInNewScale.scale(scale);
1083 selectionRectInNewScale.moveBy([_contentView frame].origin);
1084 minimumAllowableHorizontalOffsetInWebViewCoordinates = CGRectGetMaxX(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.width;
1085 minimumAllowableVerticalOffsetInWebViewCoordinates = CGRectGetMaxY(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.height - visibleOffsetFromTop;
1088 WebCore::FloatRect documentBoundsInNewScale = [_contentView bounds];
1089 documentBoundsInNewScale.scale(scale);
1090 documentBoundsInNewScale.moveBy([_contentView frame].origin);
1092 // Constrain the left edge in document coordinates so that:
1093 // - it isn't so small that the scrollVisibleRect isn't visible on the screen
1094 // - it isn't so great that the document's right edge is less than the right edge of the screen
1095 if (selectionRectIsNotNull && topLeft.x < minimumAllowableHorizontalOffsetInWebViewCoordinates)
1096 topLeft.x = minimumAllowableHorizontalOffsetInWebViewCoordinates;
1098 CGFloat maximumAllowableHorizontalOffset = CGRectGetMaxX(documentBoundsInNewScale) - visibleSize.width;
1099 if (topLeft.x > maximumAllowableHorizontalOffset)
1100 topLeft.x = maximumAllowableHorizontalOffset;
1103 // Constrain the top edge in document coordinates so that:
1104 // - it isn't so small that the scrollVisibleRect isn't visible on the screen
1105 // - it isn't so great that the document's bottom edge is higher than the top of the form assistant
1106 if (selectionRectIsNotNull && topLeft.y < minimumAllowableVerticalOffsetInWebViewCoordinates)
1107 topLeft.y = minimumAllowableVerticalOffsetInWebViewCoordinates;
1109 CGFloat maximumAllowableVerticalOffset = CGRectGetMaxY(documentBoundsInNewScale) - visibleSize.height;
1110 if (topLeft.y > maximumAllowableVerticalOffset)
1111 topLeft.y = maximumAllowableVerticalOffset;
1114 WebCore::FloatPoint newCenter = CGPointMake(topLeft.x + unobscuredScrollViewRectInWebViewCoordinates.size.width / 2.0, topLeft.y + unobscuredScrollViewRectInWebViewCoordinates.size.height / 2.0);
1116 if (scale != contentZoomScale(self))
1117 _page->willStartUserTriggeredZooming();
1119 // The newCenter has been computed in the new scale, but _zoomToCenter expected the center to be in the original scale.
1120 newCenter.scale(1 / scale, 1 / scale);
1121 [_scrollView _zoomToCenter:newCenter
1123 duration:UIWebFormAnimationDuration
1127 - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
1129 const float maximumScaleFactorDeltaForPanScroll = 0.02;
1131 double currentScale = contentZoomScale(self);
1133 WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
1134 double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width();
1135 double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height();
1137 horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale);
1138 verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale);
1140 double targetScale = fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
1141 if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) {
1142 if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance])
1144 } else if (targetScale != currentScale) {
1145 [self _zoomToRect:targetRect atScale:targetScale origin:origin];
1152 - (void)didMoveToWindow
1154 _page->viewStateDidChange(WebCore::ViewState::IsInWindow);
1157 #pragma mark - UIScrollViewDelegate
1159 - (BOOL)usesStandardContentView
1161 return !_customContentView;
1164 - (CGSize)scrollView:(UIScrollView*)scrollView contentSizeForZoomScale:(CGFloat)scale withProposedSize:(CGSize)proposedSize
1166 return roundScrollViewContentSize(*_page, proposedSize);
1169 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
1171 ASSERT(_scrollView == scrollView);
1173 if (_customContentView)
1174 return _customContentView.get();
1176 return _contentView.get();
1179 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
1181 if (![self usesStandardContentView])
1184 if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan) {
1185 _page->willStartUserTriggeredZooming();
1186 [_contentView scrollViewWillStartPanOrPinchGesture];
1188 [_contentView willStartZoomOrScroll];
1191 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
1193 if (![self usesStandardContentView])
1196 if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
1197 [_contentView scrollViewWillStartPanOrPinchGesture];
1198 [_contentView willStartZoomOrScroll];
1201 - (void)_didFinishScrolling
1203 if (![self usesStandardContentView])
1206 [self _updateVisibleContentRects];
1207 [_contentView didFinishScrolling];
1210 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
1212 // Work around <rdar://problem/16374753> by avoiding deceleration while
1213 // zooming. We'll animate to the right place once the zoom finishes.
1214 if ([scrollView isZooming])
1215 *targetContentOffset = [scrollView contentOffset];
1218 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
1220 // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called.
1222 [self _didFinishScrolling];
1225 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
1227 [self _didFinishScrolling];
1230 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
1232 [self _didFinishScrolling];
1235 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
1237 if (![self usesStandardContentView])
1238 [_customContentView scrollViewDidScroll:(UIScrollView *)scrollView];
1240 [self _updateVisibleContentRects];
1243 - (void)scrollViewDidZoom:(UIScrollView *)scrollView
1245 [self _updateScrollViewBackground];
1246 [self _updateVisibleContentRects];
1249 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
1251 ASSERT(scrollView == _scrollView);
1252 [self _updateVisibleContentRects];
1253 [_contentView didZoomToScale:scale];
1256 - (void)_frameOrBoundsChanged
1258 CGRect bounds = self.bounds;
1259 if (!_isAnimatingResize) {
1260 if (!_overridesMinimumLayoutSize)
1261 _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(bounds.size));
1262 if (!_overridesMinimumLayoutSizeForMinimalUI)
1263 _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(WebCore::FloatSize(bounds.size));
1264 if (!_overridesMaximumUnobscuredSize)
1265 _page->setMaximumUnobscuredSize(WebCore::FloatSize(bounds.size));
1268 [_scrollView setFrame:bounds];
1269 [_contentView setMinimumSize:bounds.size];
1270 [_customContentView web_setMinimumSize:bounds.size];
1271 [self _updateVisibleContentRects];
1274 // 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.
1275 - (CGRect)_contentRectForUserInteraction
1277 // FIXME: handle split keyboard.
1278 UIEdgeInsets obscuredInsets = _obscuredInsets;
1279 obscuredInsets.bottom = std::max(_obscuredInsets.bottom, _inputViewBounds.size.height);
1280 CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, obscuredInsets);
1281 return [self convertRect:unobscuredRect toView:self._currentContentView];
1284 - (void)_updateVisibleContentRects
1286 if (![self usesStandardContentView]) {
1287 [_customContentView web_computedContentInsetDidChange];
1291 if (_delayUpdateVisibleContentRects) {
1292 _hadDelayedUpdateVisibleContentRects = YES;
1296 if (_isAnimatingResize)
1299 if (_needsResetViewStateAfterCommitLoadForMainFrame)
1302 CGRect fullViewRect = self.bounds;
1303 CGRect visibleRectInContentCoordinates = [self convertRect:fullViewRect toView:_contentView.get()];
1305 CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]);
1306 CGRect unobscuredRectInContentCoordinates = [self convertRect:unobscuredRect toView:_contentView.get()];
1308 CGFloat scaleFactor = contentZoomScale(self);
1310 BOOL isStableState = !(_isChangingObscuredInsetsInteractively || [_scrollView isDragging] || [_scrollView isDecelerating] || [_scrollView isZooming] || [_scrollView isZoomBouncing] || [_scrollView _isAnimatingZoom] || [_scrollView _isScrollingToTop]);
1311 [_contentView didUpdateVisibleRect:visibleRectInContentCoordinates
1312 unobscuredRect:unobscuredRectInContentCoordinates
1313 unobscuredRectInScrollViewCoordinates:unobscuredRect
1314 scale:scaleFactor minimumScale:[_scrollView minimumZoomScale]
1315 inStableState:isStableState isChangingObscuredInsetsInteractively:_isChangingObscuredInsetsInteractively];
1318 - (void)_keyboardChangedWithInfo:(NSDictionary *)keyboardInfo adjustScrollView:(BOOL)adjustScrollView
1320 NSValue *endFrameValue = [keyboardInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
1324 // The keyboard rect is always in screen coordinates. In the view services case the window does not
1325 // have the interface orientation rotation transformation; its host does. So, it makes no sense to
1326 // clip the keyboard rect against its screen.
1327 if ([[self window] _isHostedInAnotherProcess])
1328 _inputViewBounds = [self.window convertRect:[endFrameValue CGRectValue] fromWindow:nil];
1330 _inputViewBounds = [self.window convertRect:CGRectIntersection([endFrameValue CGRectValue], self.window.screen.bounds) fromWindow:nil];
1332 [self _updateVisibleContentRects];
1334 if (adjustScrollView)
1335 [_scrollView _adjustForAutomaticKeyboardInfo:keyboardInfo animated:YES lastAdjustment:&_lastAdjustmentForScroller];
1338 - (void)_keyboardWillChangeFrame:(NSNotification *)notification
1340 if ([_contentView isAssistingNode])
1341 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1344 - (void)_keyboardDidChangeFrame:(NSNotification *)notification
1346 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:NO];
1349 - (void)_keyboardWillShow:(NSNotification *)notification
1351 if ([_contentView isAssistingNode])
1352 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1355 - (void)_keyboardWillHide:(NSNotification *)notification
1357 // Ignore keyboard will hide notifications sent during rotation. They're just there for
1358 // backwards compatibility reasons and processing the will hide notification would
1359 // temporarily screw up the the unobscured view area.
1360 if ([[UIPeripheralHost sharedInstance] rotationState])
1363 [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1366 - (void)_windowDidRotate:(NSNotification *)notification
1368 if (!_overridesInterfaceOrientation)
1369 _page->setDeviceOrientation(deviceOrientation());
1372 - (void)_contentSizeCategoryDidChange:(NSNotification *)notification
1374 _page->contentSizeCategoryDidChange([self _contentSizeCategory]);
1377 - (NSString *)_contentSizeCategory
1379 return [[UIApplication sharedApplication] preferredContentSizeCategory];
1382 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1384 if (_allowsBackForwardNavigationGestures == allowsBackForwardNavigationGestures)
1387 _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
1389 if (allowsBackForwardNavigationGestures) {
1390 if (!_gestureController) {
1391 _gestureController = std::make_unique<WebKit::ViewGestureController>(*_page);
1392 _gestureController->installSwipeHandler(self, [self scrollView]);
1395 _gestureController = nullptr;
1397 _page->setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures);
1400 - (BOOL)allowsBackForwardNavigationGestures
1402 return _allowsBackForwardNavigationGestures;
1407 #pragma mark OS X-specific methods
1411 - (void)resizeSubviewsWithOldSize:(NSSize)oldSize
1413 [_wkView setFrame:self.bounds];
1416 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1418 [_wkView setAllowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures];
1421 - (BOOL)allowsBackForwardNavigationGestures
1423 return [_wkView allowsBackForwardNavigationGestures];
1426 - (void)setAllowsMagnification:(BOOL)allowsMagnification
1428 [_wkView setAllowsMagnification:allowsMagnification];
1431 - (BOOL)allowsMagnification
1433 return [_wkView allowsMagnification];
1436 - (void)setMagnification:(CGFloat)magnification
1438 [_wkView setMagnification:magnification];
1441 - (CGFloat)magnification
1443 return [_wkView magnification];
1446 - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point
1448 [_wkView setMagnification:magnification centeredAtPoint:NSPointFromCGPoint(point)];
1455 @implementation WKWebView (WKPrivate)
1457 - (_WKRemoteObjectRegistry *)_remoteObjectRegistry
1459 if (!_remoteObjectRegistry) {
1460 _remoteObjectRegistry = adoptNS([[_WKRemoteObjectRegistry alloc] _initWithMessageSender:*_page]);
1461 _page->process().context().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID(), [_remoteObjectRegistry remoteObjectRegistry]);
1464 return _remoteObjectRegistry.get();
1467 - (WKBrowsingContextHandle *)_handle
1469 return [[[WKBrowsingContextHandle alloc] _initWithPageID:_page->pageID()] autorelease];
1472 - (_WKRenderingProgressEvents)_observedRenderingProgressEvents
1474 return _observedRenderingProgressEvents;
1477 - (id <WKHistoryDelegatePrivate>)_historyDelegate
1479 return _navigationState->historyDelegate().autorelease();
1482 - (void)_setHistoryDelegate:(id <WKHistoryDelegatePrivate>)historyDelegate
1484 _navigationState->setHistoryDelegate(historyDelegate);
1487 - (NSURL *)_unreachableURL
1489 return [NSURL _web_URLWithWTFString:_page->pageLoadState().unreachableURL()];
1492 - (void)_loadAlternateHTMLString:(NSString *)string baseURL:(NSURL *)baseURL forUnreachableURL:(NSURL *)unreachableURL
1494 _page->loadAlternateHTMLString(string, [baseURL _web_originalDataAsWTFString], [unreachableURL _web_originalDataAsWTFString]);
1497 - (WKNavigation *)_reload
1499 return [self reload];
1502 - (NSArray *)_certificateChain
1504 if (WebKit::WebFrameProxy* mainFrame = _page->mainFrame())
1505 return mainFrame->certificateInfo() ? (NSArray *)mainFrame->certificateInfo()->certificateInfo().certificateChain() : nil;
1510 - (NSURL *)_committedURL
1512 return [NSURL _web_URLWithWTFString:_page->pageLoadState().url()];
1515 - (NSString *)_MIMEType
1517 if (_page->mainFrame())
1518 return _page->mainFrame()->mimeType();
1523 - (NSString *)_applicationNameForUserAgent
1525 return _page->applicationNameForUserAgent();
1528 - (void)_setApplicationNameForUserAgent:(NSString *)applicationNameForUserAgent
1530 _page->setApplicationNameForUserAgent(applicationNameForUserAgent);
1533 - (NSString *)_customUserAgent
1535 return _page->customUserAgent();
1538 - (void)_setCustomUserAgent:(NSString *)_customUserAgent
1540 _page->setCustomUserAgent(_customUserAgent);
1543 - (pid_t)_webProcessIdentifier
1545 return _page->isValid() ? _page->processIdentifier() : 0;
1548 - (void)_killWebContentProcess
1550 if (!_page->isValid())
1553 _page->process().terminate();
1556 - (void)_didRelaunchProcess
1559 WebCore::FloatSize boundsSize(self.bounds.size);
1560 _page->setViewportConfigurationMinimumLayoutSize(_overridesMinimumLayoutSize ? WebCore::FloatSize(_minimumLayoutSizeOverride) : boundsSize);
1561 _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(_overridesMinimumLayoutSizeForMinimalUI ? WebCore::FloatSize(_minimumLayoutSizeOverrideForMinimalUI) : boundsSize);
1562 _page->setMaximumUnobscuredSize(_overridesMaximumUnobscuredSize ? WebCore::FloatSize(_maximumUnobscuredSizeOverride) : boundsSize);
1566 - (NSData *)_sessionState
1568 return [wrapper(*_page->sessionStateData(nullptr, nullptr).leakRef()) autorelease];
1571 static void releaseNSData(unsigned char*, const void* data)
1573 [(NSData *)data release];
1576 - (void)_restoreFromSessionState:(NSData *)sessionState
1578 [sessionState retain];
1579 _page->restoreFromSessionStateData(API::Data::createWithoutCopying((const unsigned char*)sessionState.bytes, sessionState.length, releaseNSData, sessionState).get());
1587 - (BOOL)_allowsRemoteInspection
1589 #if ENABLE(REMOTE_INSPECTOR)
1590 return _page->allowsRemoteInspection();
1596 - (void)_setAllowsRemoteInspection:(BOOL)allow
1598 #if ENABLE(REMOTE_INSPECTOR)
1599 _page->setAllowsRemoteInspection(allow);
1603 - (BOOL)_addsVisitedLinks
1605 return _page->addsVisitedLinks();
1608 - (void)_setAddsVisitedLinks:(BOOL)addsVisitedLinks
1610 _page->setAddsVisitedLinks(addsVisitedLinks);
1613 static inline WebCore::LayoutMilestones layoutMilestones(_WKRenderingProgressEvents events)
1615 WebCore::LayoutMilestones milestones = 0;
1617 if (events & _WKRenderingProgressEventFirstLayout)
1618 milestones |= WebCore::DidFirstLayout;
1620 if (events & _WKRenderingProgressEventFirstPaintWithSignificantArea)
1621 milestones |= WebCore::DidHitRelevantRepaintedObjectsAreaThreshold;
1626 - (void)_setObservedRenderingProgressEvents:(_WKRenderingProgressEvents)observedRenderingProgressEvents
1628 _observedRenderingProgressEvents = observedRenderingProgressEvents;
1629 _page->listenForLayoutMilestones(layoutMilestones(observedRenderingProgressEvents));
1632 - (void)_getMainResourceDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
1634 auto handler = adoptNS([completionHandler copy]);
1636 _page->getMainResourceDataOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
1637 void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
1638 if (error != WebKit::CallbackBase::Error::None) {
1639 // FIXME: Pipe a proper error in from the WebPageProxy.
1640 RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
1641 completionHandlerBlock(nil, error.get());
1643 completionHandlerBlock(wrapper(*data), nil);
1647 - (void)_getWebArchiveDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
1649 auto handler = adoptNS([completionHandler copy]);
1651 _page->getWebArchiveOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
1652 void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
1653 if (error != WebKit::CallbackBase::Error::None) {
1654 // FIXME: Pipe a proper error in from the WebPageProxy.
1655 RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
1656 completionHandlerBlock(nil, error.get());
1658 completionHandlerBlock(wrapper(*data), nil);
1662 - (_WKPaginationMode)_paginationMode
1664 switch (_page->paginationMode()) {
1665 case WebCore::Pagination::Unpaginated:
1666 return _WKPaginationModeUnpaginated;
1667 case WebCore::Pagination::LeftToRightPaginated:
1668 return _WKPaginationModeLeftToRight;
1669 case WebCore::Pagination::RightToLeftPaginated:
1670 return _WKPaginationModeRightToLeft;
1671 case WebCore::Pagination::TopToBottomPaginated:
1672 return _WKPaginationModeTopToBottom;
1673 case WebCore::Pagination::BottomToTopPaginated:
1674 return _WKPaginationModeBottomToTop;
1677 ASSERT_NOT_REACHED();
1678 return _WKPaginationModeUnpaginated;
1681 - (void)_setPaginationMode:(_WKPaginationMode)paginationMode
1683 WebCore::Pagination::Mode mode;
1684 switch (paginationMode) {
1685 case _WKPaginationModeUnpaginated:
1686 mode = WebCore::Pagination::Unpaginated;
1688 case _WKPaginationModeLeftToRight:
1689 mode = WebCore::Pagination::LeftToRightPaginated;
1691 case _WKPaginationModeRightToLeft:
1692 mode = WebCore::Pagination::RightToLeftPaginated;
1694 case _WKPaginationModeTopToBottom:
1695 mode = WebCore::Pagination::TopToBottomPaginated;
1697 case _WKPaginationModeBottomToTop:
1698 mode = WebCore::Pagination::BottomToTopPaginated;
1704 _page->setPaginationMode(mode);
1707 - (BOOL)_paginationBehavesLikeColumns
1709 return _page->paginationBehavesLikeColumns();
1712 - (void)_setPaginationBehavesLikeColumns:(BOOL)behavesLikeColumns
1714 _page->setPaginationBehavesLikeColumns(behavesLikeColumns);
1717 - (CGFloat)_pageLength
1719 return _page->pageLength();
1722 - (void)_setPageLength:(CGFloat)pageLength
1724 _page->setPageLength(pageLength);
1727 - (CGFloat)_gapBetweenPages
1729 return _page->gapBetweenPages();
1732 - (void)_setGapBetweenPages:(CGFloat)gapBetweenPages
1734 _page->setGapBetweenPages(gapBetweenPages);
1737 - (NSUInteger)_pageCount
1739 return _page->pageCount();
1742 - (BOOL)_supportsTextZoom
1744 return _page->supportsTextZoom();
1747 - (double)_textZoomFactor
1749 return _page->textZoomFactor();
1752 - (void)_setTextZoomFactor:(double)zoomFactor
1754 _page->setTextZoomFactor(zoomFactor);
1757 - (double)_pageZoomFactor
1759 return _page->pageZoomFactor();
1762 - (void)_setPageZoomFactor:(double)zoomFactor
1764 _page->setPageZoomFactor(zoomFactor);
1767 - (id <_WKFindDelegate>)_findDelegate
1769 return [static_cast<WebKit::FindClient&>(_page->findClient()).delegate().leakRef() autorelease];
1772 - (void)_setFindDelegate:(id<_WKFindDelegate>)findDelegate
1774 static_cast<WebKit::FindClient&>(_page->findClient()).setDelegate(findDelegate);
1777 static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
1779 unsigned findOptions = 0;
1781 if (wkFindOptions & _WKFindOptionsCaseInsensitive)
1782 findOptions |= WebKit::FindOptionsCaseInsensitive;
1783 if (wkFindOptions & _WKFindOptionsAtWordStarts)
1784 findOptions |= WebKit::FindOptionsAtWordStarts;
1785 if (wkFindOptions & _WKFindOptionsTreatMedialCapitalAsWordStart)
1786 findOptions |= WebKit::FindOptionsTreatMedialCapitalAsWordStart;
1787 if (wkFindOptions & _WKFindOptionsBackwards)
1788 findOptions |= WebKit::FindOptionsBackwards;
1789 if (wkFindOptions & _WKFindOptionsWrapAround)
1790 findOptions |= WebKit::FindOptionsWrapAround;
1791 if (wkFindOptions & _WKFindOptionsShowOverlay)
1792 findOptions |= WebKit::FindOptionsShowOverlay;
1793 if (wkFindOptions & _WKFindOptionsShowFindIndicator)
1794 findOptions |= WebKit::FindOptionsShowFindIndicator;
1795 if (wkFindOptions & _WKFindOptionsShowHighlight)
1796 findOptions |= WebKit::FindOptionsShowHighlight;
1797 if (wkFindOptions & _WKFindOptionsDetermineMatchIndex)
1798 findOptions |= WebKit::FindOptionsDetermineMatchIndex;
1800 return static_cast<WebKit::FindOptions>(findOptions);
1803 - (void)_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
1805 _page->countStringMatches(string, toFindOptions(options), maxCount);
1808 - (void)_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
1810 _page->findString(string, toFindOptions(options), maxCount);
1815 _page->hideFindUI();
1818 - (id <_WKFormDelegate>)_formDelegate
1820 return _formDelegate.getAutoreleased();
1823 - (void)_setFormDelegate:(id <_WKFormDelegate>)formDelegate
1825 _formDelegate = formDelegate;
1827 class FormClient : public API::FormClient {
1829 explicit FormClient(WKWebView *webView)
1830 : m_webView(webView)
1834 virtual ~FormClient() { }
1836 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
1838 if (userData && userData->type() != API::Object::Type::Data) {
1839 ASSERT(!userData || userData->type() == API::Object::Type::Data);
1840 m_webView->_page->process().connection()->markCurrentlyDispatchedMessageAsInvalid();
1844 auto formDelegate = m_webView->_formDelegate.get();
1846 if (![formDelegate respondsToSelector:@selector(_webView:willSubmitFormValues:userObject:submissionHandler:)])
1849 auto valueMap = adoptNS([[NSMutableDictionary alloc] initWithCapacity:textFieldValues.size()]);
1850 for (const auto& pair : textFieldValues)
1851 [valueMap setObject:pair.second forKey:pair.first];
1853 NSObject <NSSecureCoding> *userObject = nil;
1854 if (API::Data* data = static_cast<API::Data*>(userData)) {
1855 auto nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:const_cast<void*>(static_cast<const void*>(data->bytes())) length:data->size() freeWhenDone:NO]);
1856 auto unarchiver = adoptNS([[NSKeyedUnarchiver alloc] initForReadingWithData:nsData.get()]);
1857 [unarchiver setRequiresSecureCoding:YES];
1859 userObject = [unarchiver decodeObjectOfClass:[NSObject class] forKey:@"userObject"];
1860 } @catch (NSException *exception) {
1861 LOG_ERROR("Failed to decode user data: %@", exception);
1865 [formDelegate _webView:m_webView willSubmitFormValues:valueMap.get() userObject:userObject submissionHandler:^{
1866 listener->continueSubmission();
1872 WKWebView *m_webView;
1876 _page->setFormClient(std::make_unique<FormClient>(self));
1878 _page->setFormClient(nullptr);
1881 - (BOOL)_isDisplayingStandaloneImageDocument
1883 if (auto* mainFrame = _page->mainFrame())
1884 return mainFrame->isDisplayingStandaloneImageDocument();
1888 #pragma mark iOS-specific methods
1892 - (CGSize)_minimumLayoutSizeOverride
1894 ASSERT(_overridesMinimumLayoutSize);
1895 return _minimumLayoutSizeOverride;
1898 - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride
1900 _overridesMinimumLayoutSize = YES;
1901 if (CGSizeEqualToSize(_minimumLayoutSizeOverride, minimumLayoutSizeOverride))
1904 _minimumLayoutSizeOverride = minimumLayoutSizeOverride;
1905 if (!_isAnimatingResize)
1906 _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(minimumLayoutSizeOverride));
1909 - (CGSize)_minimumLayoutSizeOverrideForMinimalUI
1911 ASSERT(_overridesMinimumLayoutSizeForMinimalUI);
1912 return _minimumLayoutSizeOverrideForMinimalUI;
1915 - (void)_setMinimumLayoutSizeOverrideForMinimalUI:(CGSize)size
1917 _overridesMinimumLayoutSizeForMinimalUI = YES;
1918 if (CGSizeEqualToSize(_minimumLayoutSizeOverrideForMinimalUI, size))
1921 _minimumLayoutSizeOverrideForMinimalUI = size;
1922 if (!_isAnimatingResize)
1923 _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(WebCore::FloatSize(size));
1926 - (UIEdgeInsets)_obscuredInsets
1928 return _obscuredInsets;
1931 - (void)_setObscuredInsets:(UIEdgeInsets)obscuredInsets
1933 ASSERT(obscuredInsets.top >= 0);
1934 ASSERT(obscuredInsets.left >= 0);
1935 ASSERT(obscuredInsets.bottom >= 0);
1936 ASSERT(obscuredInsets.right >= 0);
1938 if (UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, obscuredInsets))
1941 _obscuredInsets = obscuredInsets;
1943 [self _updateVisibleContentRects];
1946 - (void)_setInterfaceOrientationOverride:(UIInterfaceOrientation)interfaceOrientation
1948 if (!_overridesInterfaceOrientation)
1949 [[NSNotificationCenter defaultCenter] removeObserver:self name:UIWindowDidRotateNotification object:nil];
1951 _overridesInterfaceOrientation = YES;
1953 if (interfaceOrientation == _interfaceOrientationOverride)
1956 _interfaceOrientationOverride = interfaceOrientation;
1958 if (!_isAnimatingResize)
1959 _page->setDeviceOrientation(deviceOrientationForUIInterfaceOrientation(_interfaceOrientationOverride));
1962 - (UIInterfaceOrientation)_interfaceOrientationOverride
1964 ASSERT(_overridesInterfaceOrientation);
1965 return _interfaceOrientationOverride;
1968 - (CGSize)_maximumUnobscuredSizeOverride
1970 ASSERT(_overridesMaximumUnobscuredSize);
1971 return _maximumUnobscuredSizeOverride;
1974 - (void)_setMaximumUnobscuredSizeOverride:(CGSize)size
1976 ASSERT(size.width <= self.bounds.size.width && size.height <= self.bounds.size.height);
1977 _overridesMaximumUnobscuredSize = YES;
1978 if (CGSizeEqualToSize(_maximumUnobscuredSizeOverride, size))
1981 _maximumUnobscuredSizeOverride = size;
1982 if (!_isAnimatingResize)
1983 _page->setMaximumUnobscuredSize(WebCore::FloatSize(size));
1986 - (void)_setBackgroundExtendsBeyondPage:(BOOL)backgroundExtends
1988 _page->setBackgroundExtendsBeyondPage(backgroundExtends);
1991 - (BOOL)_backgroundExtendsBeyondPage
1993 return _page->backgroundExtendsBeyondPage();
1996 - (void)_beginInteractiveObscuredInsetsChange
1998 ASSERT(!_isChangingObscuredInsetsInteractively);
1999 _isChangingObscuredInsetsInteractively = YES;
2002 - (void)_endInteractiveObscuredInsetsChange
2004 ASSERT(_isChangingObscuredInsetsInteractively);
2005 _isChangingObscuredInsetsInteractively = NO;
2006 [self _updateVisibleContentRects];
2009 - (void)_beginAnimatedResizeWithUpdates:(void (^)(void))updateBlock
2011 _isAnimatingResize = YES;
2013 if (_customContentView) {
2018 _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2020 NSUInteger indexOfContentView = [[_scrollView subviews] indexOfObject:_contentView.get()];
2021 _resizeAnimationView = adoptNS([[UIView alloc] init]);
2022 [_scrollView insertSubview:_resizeAnimationView.get() atIndex:indexOfContentView];
2023 [_resizeAnimationView addSubview:_contentView.get()];
2024 WebCore::FloatRect oldUnobscuredContentRect = _page->unobscuredContentRect();
2028 CGRect newBounds = self.bounds;
2029 CGSize newMinimumLayoutSize = newBounds.size;
2031 CGSize contentSizeInContentViewCoordinates = [_contentView bounds].size;
2032 [_scrollView setMinimumZoomScale:std::min(newMinimumLayoutSize.width / contentSizeInContentViewCoordinates.width, [_scrollView minimumZoomScale])];
2033 [_scrollView setMaximumZoomScale:std::max(newMinimumLayoutSize.width / contentSizeInContentViewCoordinates.width, [_scrollView maximumZoomScale])];
2035 // Compute the new scale to keep the current content width in the scrollview.
2036 CGFloat oldWebViewWidthInContentViewCoordinates = oldUnobscuredContentRect.width();
2037 CGFloat visibleContentViewWidthInContentCoordinates = std::min(contentSizeInContentViewCoordinates.width, oldWebViewWidthInContentViewCoordinates);
2038 CGFloat targetScale = newMinimumLayoutSize.width / visibleContentViewWidthInContentCoordinates;
2039 CGFloat resizeAnimationViewAnimationScale = targetScale / contentZoomScale(self);
2040 [_resizeAnimationView setTransform:CGAffineTransformMakeScale(resizeAnimationViewAnimationScale, resizeAnimationViewAnimationScale)];
2042 // Compute a new position to keep the content centered.
2043 CGPoint originalContentCenter = oldUnobscuredContentRect.center();
2044 CGPoint originalContentCenterInSelfCoordinates = [self convertPoint:originalContentCenter fromView:_contentView.get()];
2045 CGRect futureUnobscuredRectInSelfCoordinates = UIEdgeInsetsInsetRect(newBounds, _obscuredInsets);
2046 CGPoint futureUnobscuredRectCenterInSelfCoordinates = CGPointMake(futureUnobscuredRectInSelfCoordinates.origin.x + futureUnobscuredRectInSelfCoordinates.size.width / 2, futureUnobscuredRectInSelfCoordinates.origin.y + futureUnobscuredRectInSelfCoordinates.size.height / 2);
2048 CGPoint originalContentOffset = [_scrollView contentOffset];
2049 CGPoint contentOffset = originalContentOffset;
2050 contentOffset.x += (originalContentCenterInSelfCoordinates.x - futureUnobscuredRectCenterInSelfCoordinates.x);
2051 contentOffset.y += (originalContentCenterInSelfCoordinates.y - futureUnobscuredRectCenterInSelfCoordinates.y);
2053 // Limit the new offset within the scrollview, we do not want to rubber band programmatically.
2054 CGSize futureContentSizeInSelfCoordinates = CGSizeMake(contentSizeInContentViewCoordinates.width * targetScale, contentSizeInContentViewCoordinates.height * targetScale);
2055 CGFloat maxHorizontalOffset = futureContentSizeInSelfCoordinates.width - newBounds.size.width + _obscuredInsets.right;
2056 contentOffset.x = std::min(contentOffset.x, maxHorizontalOffset);
2057 CGFloat maxVerticalOffset = futureContentSizeInSelfCoordinates.height - newBounds.size.height + _obscuredInsets.bottom;
2058 contentOffset.y = std::min(contentOffset.y, maxVerticalOffset);
2060 contentOffset.x = std::max(contentOffset.x, -_obscuredInsets.left);
2061 contentOffset.y = std::max(contentOffset.y, -_obscuredInsets.top);
2063 // Make the top/bottom edges "sticky" within 1 pixel.
2064 if (oldUnobscuredContentRect.maxY() > contentSizeInContentViewCoordinates.height - 1)
2065 contentOffset.y = maxVerticalOffset;
2066 if (oldUnobscuredContentRect.y() < 1)
2067 contentOffset.y = -_obscuredInsets.top;
2069 // FIXME: if we have content centered after double tap to zoom, we should also try to keep that rect in view.
2070 [_scrollView setContentSize:roundScrollViewContentSize(*_page, futureContentSizeInSelfCoordinates)];
2071 [_scrollView setContentOffset:contentOffset];
2073 CGRect visibleRectInContentCoordinates = [self convertRect:newBounds toView:_contentView.get()];
2074 CGRect unobscuredRectInContentCoordinates = [self convertRect:futureUnobscuredRectInSelfCoordinates toView:_contentView.get()];
2076 CGSize minimumLayoutSize = newBounds.size;
2077 if (_overridesMinimumLayoutSize)
2078 minimumLayoutSize = _minimumLayoutSizeOverride;
2080 CGSize minimumLayoutSizeForMinimalUI = minimumLayoutSize;
2081 if (_overridesMinimumLayoutSizeForMinimalUI)
2082 minimumLayoutSizeForMinimalUI = _minimumLayoutSizeOverrideForMinimalUI;
2084 CGSize maximumUnobscuredSize = newBounds.size;
2085 if (_overridesMaximumUnobscuredSize)
2086 maximumUnobscuredSize = _maximumUnobscuredSizeOverride;
2088 int32_t orientation;
2089 if (_overridesInterfaceOrientation)
2090 orientation = deviceOrientationForUIInterfaceOrientation(_interfaceOrientationOverride);
2092 orientation = _page->deviceOrientation();
2094 _page->dynamicViewportSizeUpdate(WebCore::FloatSize(minimumLayoutSize), WebCore::FloatSize(minimumLayoutSizeForMinimalUI), WebCore::FloatSize(maximumUnobscuredSize), visibleRectInContentCoordinates, unobscuredRectInContentCoordinates, futureUnobscuredRectInSelfCoordinates, targetScale, orientation);
2097 - (void)_endAnimatedResize
2099 _page->synchronizeDynamicViewportUpdate();
2101 if (!_customContentView && _isAnimatingResize) {
2102 NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
2103 [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
2105 CALayer *contentViewLayer = [_contentView layer];
2106 CATransform3D resizeAnimationTransformAdjustements = _resizeAnimationTransformAdjustments;
2107 CGFloat adjustmentScale = resizeAnimationTransformAdjustements.m11;
2108 contentViewLayer.sublayerTransform = CATransform3DIdentity;
2110 CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
2111 CALayer *contentLayer = [_contentView layer];
2112 CATransform3D contentLayerTransform = contentLayer.transform;
2113 CGFloat currentScale = [[_resizeAnimationView layer] transform].m11 * contentLayerTransform.m11;
2115 // We cannot use [UIScrollView setZoomScale:] directly because the UIScrollView delegate would get a callback with
2116 // an invalid contentOffset. The real content offset is only set below.
2117 // Since there is no public API for setting both the zoomScale and the contentOffset, we set the zoomScale manually
2118 // on the zoom layer and then only change the contentOffset.
2119 CGFloat adjustedScale = adjustmentScale * currentScale;
2120 contentLayerTransform.m11 = adjustedScale;
2121 contentLayerTransform.m22 = adjustedScale;
2122 contentLayer.transform = contentLayerTransform;
2124 CGPoint currentScrollOffset = [_scrollView contentOffset];
2125 double horizontalScrollAdjustement = _resizeAnimationTransformAdjustments.m41 * animatingScaleTarget;
2126 double verticalScrollAdjustment = _resizeAnimationTransformAdjustments.m42 * animatingScaleTarget;
2128 [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
2129 [_scrollView setContentOffset:CGPointMake(currentScrollOffset.x - horizontalScrollAdjustement, currentScrollOffset.y - verticalScrollAdjustment)];
2131 [_resizeAnimationView removeFromSuperview];
2132 _resizeAnimationView = nil;
2135 _isAnimatingResize = NO;
2136 _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2138 [self _updateVisibleContentRects];
2141 - (void)_setOverlaidAccessoryViewsInset:(CGSize)inset
2143 [_customContentView web_setOverlaidAccessoryViewsInset:inset];
2146 - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler
2148 CGRect snapshotRectInContentCoordinates = [self convertRect:rectInViewCoordinates toView:_contentView.get()];
2149 CGFloat imageHeight = imageWidth / snapshotRectInContentCoordinates.size.width * snapshotRectInContentCoordinates.size.height;
2150 CGSize imageSize = CGSizeMake(imageWidth, imageHeight);
2152 void(^copiedCompletionHandler)(CGImageRef) = [completionHandler copy];
2153 _page->takeSnapshot(WebCore::enclosingIntRect(snapshotRectInContentCoordinates), WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebKit::SnapshotOptionsExcludeDeviceScaleFactor, [=](const WebKit::ShareableBitmap::Handle& imageHandle, WebKit::CallbackBase::Error) {
2154 if (imageHandle.isNull()) {
2155 copiedCompletionHandler(nullptr);
2156 [copiedCompletionHandler release];
2160 RefPtr<WebKit::ShareableBitmap> bitmap = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::ReadOnly);
2163 copiedCompletionHandler(nullptr);
2164 [copiedCompletionHandler release];
2168 RetainPtr<CGImageRef> cgImage;
2169 cgImage = bitmap->makeCGImage();
2170 copiedCompletionHandler(cgImage.get());
2171 [copiedCompletionHandler release];
2175 - (void)_overrideLayoutParametersWithMinimumLayoutSize:(CGSize)minimumLayoutSize minimumLayoutSizeForMinimalUI:(CGSize)minimumLayoutSizeForMinimalUI maximumUnobscuredSizeOverride:(CGSize)maximumUnobscuredSizeOverride
2177 // FIXME: After Safari is updated to use this function instead of setting the parameters separately, we should remove
2178 // the individual setters and send a single message to send everything at once to the WebProcess.
2179 self._minimumLayoutSizeOverride = minimumLayoutSize;
2180 self._minimumLayoutSizeOverrideForMinimalUI = minimumLayoutSizeForMinimalUI;
2181 self._maximumUnobscuredSizeOverride = maximumUnobscuredSizeOverride;
2184 - (UIView *)_viewForFindUI
2186 return [self viewForZoomingInScrollView:[self scrollView]];
2189 - (BOOL)_isDisplayingPDF
2191 return [_customContentView isKindOfClass:[WKPDFView class]];
2194 - (NSData *)_dataForDisplayedPDF
2196 if (![self _isDisplayingPDF])
2198 CGPDFDocumentRef pdfDocument = [(WKPDFView *)_customContentView pdfDocument];
2199 return [(NSData *)CGDataProviderCopyData(CGPDFDocumentGetDataProvider(pdfDocument)) autorelease];
2202 - (NSString *)_suggestedFilenameForDisplayedPDF
2204 if (![self _isDisplayingPDF])
2206 return [(WKPDFView *)_customContentView.get() suggestedFilename];
2209 - (CGFloat)_viewportMetaTagWidth
2211 return _viewportMetaTagWidth;
2214 - (_WKWebViewPrintFormatter *)_webViewPrintFormatter
2216 UIViewPrintFormatter *viewPrintFormatter = self.viewPrintFormatter;
2217 ASSERT([viewPrintFormatter isKindOfClass:[_WKWebViewPrintFormatter class]]);
2218 return (_WKWebViewPrintFormatter *)viewPrintFormatter;
2223 #pragma mark - OS X-specific methods
2225 - (NSColor *)_pageExtendedBackgroundColor
2227 WebCore::Color color = _page->pageExtendedBackgroundColor();
2228 if (!color.isValid())
2231 return nsColor(color);
2234 - (BOOL)_drawsTransparentBackground
2236 return _page->drawsTransparentBackground();
2239 - (void)_setDrawsTransparentBackground:(BOOL)drawsTransparentBackground
2241 _page->setDrawsTransparentBackground(drawsTransparentBackground);
2244 - (void)_setTopContentInset:(CGFloat)contentInset
2246 _page->setTopContentInset(contentInset);
2249 - (CGFloat)_topContentInset
2251 return _page->topContentInset();
2258 #if !TARGET_OS_IPHONE
2260 @implementation WKWebView (WKIBActions)
2262 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
2264 SEL action = item.action;
2266 if (action == @selector(goBack:))
2267 return !!_page->backForwardList().backItem();
2269 if (action == @selector(goForward:))
2270 return !!_page->backForwardList().forwardItem();
2272 if (action == @selector(stopLoading:)) {
2273 // FIXME: Return no if we're stopped.
2277 if (action == @selector(reload:) || action == @selector(reloadFromOrigin:)) {
2278 // FIXME: Return no if we're loading.
2285 - (IBAction)goBack:(id)sender
2290 - (IBAction)goForward:(id)sender
2295 - (IBAction)reload:(id)sender
2300 - (IBAction)reloadFromOrigin:(id)sender
2302 [self reloadFromOrigin];
2305 - (IBAction)stopLoading:(id)sender
2307 _page->stopLoading();
2315 @implementation WKWebView (_WKWebViewPrintFormatter)
2317 - (Class)_printFormatterClass
2319 return [_WKWebViewPrintFormatter class];
2322 - (NSInteger)_computePageCountAndStartDrawingToPDFForFrame:(_WKFrameHandle *)frame printInfo:(const WebKit::PrintInfo&)printInfo firstPage:(uint32_t)firstPage computedTotalScaleFactor:(double&)totalScaleFactor
2324 if ([self _isDisplayingPDF])
2325 return CGPDFDocumentGetNumberOfPages([(WKPDFView *)_customContentView pdfDocument]);
2327 _pageIsPrintingToPDF = YES;
2328 Vector<WebCore::IntRect> pageRects;
2329 uint64_t frameID = frame ? frame._frameID : _page->mainFrame()->frameID();
2330 if (!_page->sendSync(Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF(frameID, printInfo, firstPage), Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF::Reply(pageRects, totalScaleFactor)))
2332 return pageRects.size();
2335 - (void)_endPrinting
2337 _pageIsPrintingToPDF = NO;
2338 _printedDocument = nullptr;
2339 _page->send(Messages::WebPage::EndPrinting());
2342 // FIXME: milliseconds::max() overflows when converted to nanoseconds, causing condition_variable::wait_for() to believe
2343 // a timeout occurred on any spurious wakeup. Use nanoseconds::max() (converted to ms) to avoid this. We should perhaps
2344 // change waitForAndDispatchImmediately() to take nanoseconds to avoid this issue.
2345 static constexpr std::chrono::milliseconds didFinishLoadingTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds::max());
2347 - (CGPDFDocumentRef)_printedDocument
2349 if ([self _isDisplayingPDF]) {
2350 ASSERT(!_pageIsPrintingToPDF);
2351 return [(WKPDFView *)_customContentView pdfDocument];
2354 if (_pageIsPrintingToPDF) {
2355 if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::DidFinishDrawingPagesToPDF>(_page->pageID(), didFinishLoadingTimeout)) {
2356 ASSERT_NOT_REACHED();
2359 ASSERT(!_pageIsPrintingToPDF);
2361 return _printedDocument.get();
2364 - (void)_setPrintedDocument:(CGPDFDocumentRef)printedDocument
2366 if (!_pageIsPrintingToPDF)
2368 ASSERT(![self _isDisplayingPDF]);
2369 _printedDocument = printedDocument;
2370 _pageIsPrintingToPDF = NO;
2376 #endif // WK_API_ENABLED