60ee01033c716cffa8dbaf26f3f4eef0bbbce7f9
[WebKit-https.git] / Source / WebKit2 / UIProcess / API / Cocoa / WKWebView.mm
1 /*
2  * Copyright (C) 2014, 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #import "config.h"
27 #import "WKWebViewInternal.h"
28
29 #if WK_API_ENABLED
30
31 #import "APIFormClient.h"
32 #import "APISerializedScriptValue.h"
33 #import "CompletionHandlerCallChecker.h"
34 #import "DiagnosticLoggingClient.h"
35 #import "FindClient.h"
36 #import "LegacySessionStateCoding.h"
37 #import "NavigationState.h"
38 #import "RemoteLayerTreeScrollingPerformanceData.h"
39 #import "RemoteLayerTreeTransaction.h"
40 #import "RemoteObjectRegistry.h"
41 #import "RemoteObjectRegistryMessages.h"
42 #import "UIDelegate.h"
43 #import "ViewGestureController.h"
44 #import "ViewSnapshotStore.h"
45 #import "WKBackForwardListInternal.h"
46 #import "WKBackForwardListItemInternal.h"
47 #import "WKBrowsingContextHandleInternal.h"
48 #import "WKErrorInternal.h"
49 #import "WKHistoryDelegatePrivate.h"
50 #import "WKLayoutMode.h"
51 #import "WKNSData.h"
52 #import "WKNSURLExtras.h"
53 #import "WKNavigationDelegate.h"
54 #import "WKNavigationInternal.h"
55 #import "WKPreferencesInternal.h"
56 #import "WKProcessPoolInternal.h"
57 #import "WKSharedAPICast.h"
58 #import "WKUIDelegate.h"
59 #import "WKUIDelegatePrivate.h"
60 #import "WKUserContentControllerInternal.h"
61 #import "WKWebViewConfigurationInternal.h"
62 #import "WKWebViewContentProvider.h"
63 #import "WKWebsiteDataStoreInternal.h"
64 #import "WebBackForwardList.h"
65 #import "WebCertificateInfo.h"
66 #import "WebFormSubmissionListenerProxy.h"
67 #import "WebKitSystemInterface.h"
68 #import "WebPageGroup.h"
69 #import "WebPageProxy.h"
70 #import "WebPreferencesKeys.h"
71 #import "WebProcessPool.h"
72 #import "WebProcessProxy.h"
73 #import "_WKDiagnosticLoggingDelegate.h"
74 #import "_WKFindDelegate.h"
75 #import "_WKFormDelegate.h"
76 #import "_WKRemoteObjectRegistryInternal.h"
77 #import "_WKSessionStateInternal.h"
78 #import "_WKVisitedLinkProviderInternal.h"
79 #import <JavaScriptCore/JSContext.h>
80 #import <JavaScriptCore/JSValue.h>
81 #import <WebCore/IOSurface.h>
82 #import <WebCore/WebCoreSystemInterface.h>
83 #import <wtf/HashMap.h>
84 #import <wtf/MathExtras.h>
85 #import <wtf/NeverDestroyed.h>
86 #import <wtf/RetainPtr.h>
87
88 #if PLATFORM(IOS)
89 #import "_WKFrameHandleInternal.h"
90 #import "_WKWebViewPrintFormatter.h"
91 #import "PrintInfo.h"
92 #import "ProcessThrottler.h"
93 #import "RemoteLayerTreeDrawingAreaProxy.h"
94 #import "RemoteScrollingCoordinatorProxy.h"
95 #import "UIKitSPI.h"
96 #import "WKContentViewInteraction.h"
97 #import "WKPDFView.h"
98 #import "WKScrollView.h"
99 #import "WKWebViewContentProviderRegistry.h"
100 #import "WebPageMessages.h"
101 #import "WebVideoFullscreenManagerProxy.h"
102 #import <UIKit/UIApplication.h>
103 #import <WebCore/CoreGraphicsSPI.h>
104 #import <WebCore/FrameLoaderTypes.h>
105 #import <WebCore/InspectorOverlay.h>
106 #import <WebCore/QuartzCoreSPI.h>
107
108 @interface UIScrollView (UIScrollViewInternal)
109 - (void)_adjustForAutomaticKeyboardInfo:(NSDictionary*)info animated:(BOOL)animated lastAdjustment:(CGFloat*)lastAdjustment;
110 - (BOOL)_isScrollingToTop;
111 - (BOOL)_isInterruptingDeceleration;
112 @end
113
114 @interface UIPeripheralHost(UIKitInternal)
115 - (CGFloat)getVerticalOverlapForView:(UIView *)view usingKeyboardInfo:(NSDictionary *)info;
116 @end
117
118 @interface UIView (UIViewInternal)
119 - (UIViewController *)_viewControllerForAncestor;
120 @end
121
122 @interface UIWindow (UIWindowInternal)
123 - (BOOL)_isHostedInAnotherProcess;
124 @end
125
126 @interface UIViewController (UIViewControllerInternal)
127 - (UIViewController *)_rootAncestorViewController;
128 - (UIViewController *)_viewControllerForSupportedInterfaceOrientations;
129 @end
130
131 enum class DynamicViewportUpdateMode {
132     NotResizing,
133     ResizingWithAnimation,
134     ResizingWithDocumentHidden,
135 };
136
137 #endif
138
139 #if PLATFORM(MAC)
140 #import "WKViewInternal.h"
141 #import <WebCore/ColorMac.h>
142 #endif
143
144 NSString * const _WKShouldOpenExternalURLsKey = @"_WKShouldOpenExternalURLsKey";
145
146 static HashMap<WebKit::WebPageProxy*, WKWebView *>& pageToViewMap()
147 {
148     static NeverDestroyed<HashMap<WebKit::WebPageProxy*, WKWebView *>> map;
149     return map;
150 }
151
152 WKWebView* fromWebPageProxy(WebKit::WebPageProxy& page)
153 {
154     return pageToViewMap().get(&page);
155 }
156
157 @implementation WKWebView {
158     std::unique_ptr<WebKit::NavigationState> _navigationState;
159     std::unique_ptr<WebKit::UIDelegate> _uiDelegate;
160
161     RetainPtr<_WKRemoteObjectRegistry> _remoteObjectRegistry;
162     _WKRenderingProgressEvents _observedRenderingProgressEvents;
163
164     WebKit::WeakObjCPtr<id <_WKFormDelegate>> _formDelegate;
165 #if PLATFORM(IOS)
166     RetainPtr<WKScrollView> _scrollView;
167     RetainPtr<WKContentView> _contentView;
168
169     BOOL _overridesMinimumLayoutSize;
170     CGSize _minimumLayoutSizeOverride;
171     BOOL _overridesMaximumUnobscuredSize;
172     CGSize _maximumUnobscuredSizeOverride;
173     CGRect _inputViewBounds;
174     CGFloat _viewportMetaTagWidth;
175     BOOL _allowsLinkPreview;
176
177     UIEdgeInsets _obscuredInsets;
178     BOOL _haveSetObscuredInsets;
179     BOOL _isChangingObscuredInsetsInteractively;
180
181     UIInterfaceOrientation _interfaceOrientationOverride;
182     BOOL _overridesInterfaceOrientation;
183
184     BOOL _hasCommittedLoadForMainFrame;
185     BOOL _needsResetViewStateAfterCommitLoadForMainFrame;
186     uint64_t _firstPaintAfterCommitLoadTransactionID;
187     DynamicViewportUpdateMode _dynamicViewportUpdateMode;
188     CATransform3D _resizeAnimationTransformAdjustments;
189     uint64_t _resizeAnimationTransformTransactionID;
190     RetainPtr<UIView> _resizeAnimationView;
191     CGFloat _lastAdjustmentForScroller;
192
193     BOOL _needsToRestoreExposedRect;
194     WebCore::FloatRect _exposedRectToRestore;
195     BOOL _needsToRestoreUnobscuredCenter;
196     WebCore::FloatPoint _unobscuredCenterToRestore;
197     uint64_t _firstTransactionIDAfterPageRestore;
198     double _scaleToRestore;
199
200     std::unique_ptr<WebKit::ViewGestureController> _gestureController;
201     BOOL _allowsBackForwardNavigationGestures;
202
203     RetainPtr<UIView <WKWebViewContentProvider>> _customContentView;
204     RetainPtr<UIView> _customContentFixedOverlayView;
205
206     WebCore::Color _scrollViewBackgroundColor;
207
208     BOOL _delayUpdateVisibleContentRects;
209     BOOL _hadDelayedUpdateVisibleContentRects;
210
211     BOOL _pageIsPrintingToPDF;
212     RetainPtr<CGPDFDocumentRef> _printedDocument;
213 #endif
214 #if PLATFORM(MAC)
215     RetainPtr<WKView> _wkView;
216 #endif
217 }
218
219 - (instancetype)initWithFrame:(CGRect)frame
220 {
221     return [self initWithFrame:frame configuration:adoptNS([[WKWebViewConfiguration alloc] init]).get()];
222 }
223
224 #if PLATFORM(IOS)
225 static int32_t deviceOrientationForUIInterfaceOrientation(UIInterfaceOrientation orientation)
226 {
227     switch (orientation) {
228     case UIInterfaceOrientationUnknown:
229     case UIInterfaceOrientationPortrait:
230         return 0;
231     case UIInterfaceOrientationPortraitUpsideDown:
232         return 180;
233     case UIInterfaceOrientationLandscapeLeft:
234         return -90;
235     case UIInterfaceOrientationLandscapeRight:
236         return 90;
237     }
238 }
239
240 static int32_t deviceOrientation()
241 {
242     return deviceOrientationForUIInterfaceOrientation([[UIApplication sharedApplication] statusBarOrientation]);
243 }
244
245 - (BOOL)_isShowingVideoOptimized
246 {
247     if (!_page || !_page->videoFullscreenManager())
248         return false;
249     
250     return _page->videoFullscreenManager()->hasMode(WebCore::HTMLMediaElementEnums::VideoFullscreenModeOptimized);
251 }
252
253 - (BOOL)_mayAutomaticallyShowVideoOptimized
254 {
255 #if (__IPHONE_OS_VERSION_MIN_REQUIRED <= 80200) || !HAVE(AVKIT)
256     return false;
257 #else
258     if (!_page || !_page->videoFullscreenManager())
259         return false;
260
261     return _page->videoFullscreenManager()->mayAutomaticallyShowVideoOptimized();
262 #endif
263 }
264
265 static bool shouldAllowAlternateFullscreen()
266 {
267 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000
268     static bool shouldAllowAlternateFullscreen = iosExecutableWasLinkedOnOrAfterVersion(wkIOSSystemVersion_9_0);
269     return shouldAllowAlternateFullscreen;
270 #else
271     return false;
272 #endif
273 }
274
275 #endif
276
277 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
278 {
279     if (!(self = [super initWithFrame:frame]))
280         return nil;
281
282     if (!configuration)
283         [NSException raise:NSInvalidArgumentException format:@"Configuration cannot be nil"];
284
285     _configuration = adoptNS([configuration copy]);
286
287     if (WKWebView *relatedWebView = [_configuration _relatedWebView]) {
288         WKProcessPool *processPool = [_configuration processPool];
289         WKProcessPool *relatedWebViewProcessPool = [relatedWebView->_configuration processPool];
290         if (processPool && processPool != relatedWebViewProcessPool)
291             [NSException raise:NSInvalidArgumentException format:@"Related web view %@ has process pool %@ but configuration specifies a different process pool %@", relatedWebView, relatedWebViewProcessPool, configuration.processPool];
292
293         [_configuration setProcessPool:relatedWebViewProcessPool];
294     }
295
296     [_configuration _validate];
297
298     CGRect bounds = self.bounds;
299
300     WebKit::WebProcessPool& processPool = *[_configuration processPool]->_processPool;
301
302     WebKit::WebPageConfiguration webPageConfiguration;
303     webPageConfiguration.preferences = [_configuration preferences]->_preferences.get();
304     if (WKWebView *relatedWebView = [_configuration _relatedWebView])
305         webPageConfiguration.relatedPage = relatedWebView->_page.get();
306
307     webPageConfiguration.userContentController = [_configuration userContentController]->_userContentControllerProxy.get();
308     webPageConfiguration.visitedLinkProvider = [_configuration _visitedLinkProvider]->_visitedLinkProvider.get();
309     webPageConfiguration.websiteDataStore = &[_configuration websiteDataStore]->_websiteDataStore->websiteDataStore();
310     webPageConfiguration.sessionID = webPageConfiguration.websiteDataStore->sessionID();
311     webPageConfiguration.treatsSHA1SignedCertificatesAsInsecure = [_configuration _treatsSHA1SignedCertificatesAsInsecure];
312
313     RefPtr<WebKit::WebPageGroup> pageGroup;
314     NSString *groupIdentifier = configuration._groupIdentifier;
315     if (groupIdentifier.length) {
316         pageGroup = WebKit::WebPageGroup::create(configuration._groupIdentifier);
317         webPageConfiguration.pageGroup = pageGroup.get();
318     }
319
320     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::suppressesIncrementalRenderingKey(), WebKit::WebPreferencesStore::Value(!![_configuration suppressesIncrementalRendering]));
321
322 #if PLATFORM(IOS)
323     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::allowsInlineMediaPlaybackKey(), WebKit::WebPreferencesStore::Value(!![_configuration allowsInlineMediaPlayback]));
324     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::allowsAlternateFullscreenKey(), WebKit::WebPreferencesStore::Value(!![_configuration _allowsAlternateFullscreen] && shouldAllowAlternateFullscreen()));
325     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::requiresUserGestureForMediaPlaybackKey(), WebKit::WebPreferencesStore::Value(!![_configuration requiresUserActionForMediaPlayback]));
326 #endif
327 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
328     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::allowsAirPlayForMediaPlaybackKey(), WebKit::WebPreferencesStore::Value(!![_configuration allowsAirPlayForMediaPlayback]));
329 #endif
330
331 #if PLATFORM(IOS)
332     _scrollView = adoptNS([[WKScrollView alloc] initWithFrame:bounds]);
333     [_scrollView setInternalDelegate:self];
334     [_scrollView setBouncesZoom:YES];
335
336     [self addSubview:_scrollView.get()];
337
338     _contentView = adoptNS([[WKContentView alloc] initWithFrame:bounds processPool:processPool configuration:WTF::move(webPageConfiguration) webView:self]);
339
340     _page = [_contentView page];
341     _page->setDeviceOrientation(deviceOrientation());
342
343     [_contentView layer].anchorPoint = CGPointZero;
344     [_contentView setFrame:bounds];
345     [_scrollView addSubview:_contentView.get()];
346     [_scrollView addSubview:[_contentView unscaledView]];
347     [self _updateScrollViewBackground];
348
349     _viewportMetaTagWidth = -1;
350     _allowsLinkPreview = YES;
351
352     [self _frameOrBoundsChanged];
353
354     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
355     [center addObserver:self selector:@selector(_keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
356     [center addObserver:self selector:@selector(_keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];
357     [center addObserver:self selector:@selector(_keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
358     [center addObserver:self selector:@selector(_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
359     [center addObserver:self selector:@selector(_windowDidRotate:) name:UIWindowDidRotateNotification object:nil];
360     [center addObserver:self selector:@selector(_contentSizeCategoryDidChange:) name:UIContentSizeCategoryDidChangeNotification object:nil];
361     _page->contentSizeCategoryDidChange([self _contentSizeCategory]);
362
363     [[_configuration _contentProviderRegistry] addPage:*_page];
364 #endif
365
366 #if PLATFORM(MAC)
367     _wkView = adoptNS([[WKView alloc] initWithFrame:bounds processPool:processPool configuration:WTF::move(webPageConfiguration) webView:self]);
368     [self addSubview:_wkView.get()];
369     _page = WebKit::toImpl([_wkView pageRef]);
370
371 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
372     [_wkView _setAutomaticallyAdjustsContentInsets:YES];
373 #endif
374 #endif
375
376     _page->setBackgroundExtendsBeyondPage(true);
377
378     if (NSString *applicationNameForUserAgent = configuration.applicationNameForUserAgent)
379         _page->setApplicationNameForUserAgent(applicationNameForUserAgent);
380
381     _navigationState = std::make_unique<WebKit::NavigationState>(self);
382     _uiDelegate = std::make_unique<WebKit::UIDelegate>(self);
383     _page->setFindClient(std::make_unique<WebKit::FindClient>(self));
384     _page->setDiagnosticLoggingClient(std::make_unique<WebKit::DiagnosticLoggingClient>(self));
385
386     pageToViewMap().add(_page.get(), self);
387
388     return self;
389 }
390
391 - (instancetype)initWithCoder:(NSCoder *)coder
392 {
393     [self release];
394     return nil;
395 }
396
397 - (void)dealloc
398 {
399     if (_remoteObjectRegistry)
400         _page->process().processPool().removeMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID());
401
402     _page->close();
403
404     [_remoteObjectRegistry _invalidate];
405 #if PLATFORM(IOS)
406     [[_configuration _contentProviderRegistry] removePage:*_page];
407     [[NSNotificationCenter defaultCenter] removeObserver:self];
408     [_scrollView setInternalDelegate:nil];
409 #endif
410
411     pageToViewMap().remove(_page.get());
412
413     [super dealloc];
414 }
415
416 - (WKWebViewConfiguration *)configuration
417 {
418     return [[_configuration copy] autorelease];
419 }
420
421 - (WKBackForwardList *)backForwardList
422 {
423     return wrapper(_page->backForwardList());
424 }
425
426 - (id <WKNavigationDelegate>)navigationDelegate
427 {
428     return _navigationState->navigationDelegate().autorelease();
429 }
430
431 - (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate
432 {
433     _page->setNavigationClient(_navigationState->createNavigationClient());
434     _navigationState->setNavigationDelegate(navigationDelegate);
435 }
436
437 - (id <WKUIDelegate>)UIDelegate
438 {
439     return _uiDelegate->delegate().autorelease();
440 }
441
442 - (void)setUIDelegate:(id<WKUIDelegate>)UIDelegate
443 {
444     _page->setUIClient(_uiDelegate->createUIClient());
445     _uiDelegate->setDelegate(UIDelegate);
446 }
447
448 - (WKNavigation *)loadRequest:(NSURLRequest *)request
449 {
450     return [self _loadRequest:request withOptions:nil];
451 }
452
453 - (WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL
454 {
455     if (![URL isFileURL])
456         [NSException raise:NSInvalidArgumentException format:@"%@ is not a file URL", URL];
457
458     if (![readAccessURL isFileURL])
459         [NSException raise:NSInvalidArgumentException format:@"%@ is not a file URL", readAccessURL];
460
461     auto navigation = _page->loadFile([URL _web_originalDataAsWTFString], [readAccessURL _web_originalDataAsWTFString]);
462     if (!navigation)
463         return nil;
464
465     return [wrapper(*navigation.release().leakRef()) autorelease];
466 }
467
468 - (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL
469 {
470     NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
471
472     return [self loadData:data MIMEType:@"text/html" characterEncodingName:@"UTF-8" baseURL:baseURL];
473 }
474
475 - (WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL
476 {
477     auto navigation = _page->loadData(API::Data::createWithoutCopying(data).ptr(), MIMEType, characterEncodingName, baseURL.absoluteString);
478     if (!navigation)
479         return nil;
480
481     return [wrapper(*navigation.release().leakRef()) autorelease];
482 }
483
484 - (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item
485 {
486     auto navigation = _page->goToBackForwardItem(&item._item);
487     if (!navigation)
488         return nil;
489
490     return [wrapper(*navigation.release().leakRef()) autorelease];
491 }
492
493 - (NSString *)title
494 {
495     return _page->pageLoadState().title();
496 }
497
498 - (NSURL *)URL
499 {
500     return [NSURL _web_URLWithWTFString:_page->pageLoadState().activeURL()];
501 }
502
503 - (BOOL)isLoading
504 {
505     return _page->pageLoadState().isLoading();
506 }
507
508 - (double)estimatedProgress
509 {
510     return _page->pageLoadState().estimatedProgress();
511 }
512
513 - (BOOL)hasOnlySecureContent
514 {
515     return _page->pageLoadState().hasOnlySecureContent();
516 }
517
518 - (BOOL)canGoBack
519 {
520     return _page->pageLoadState().canGoBack();
521 }
522
523 - (BOOL)canGoForward
524 {
525     return _page->pageLoadState().canGoForward();
526 }
527
528 - (WKNavigation *)goBack
529 {
530     auto navigation = _page->goBack();
531     if (!navigation)
532         return nil;
533  
534     return [wrapper(*navigation.release().leakRef()) autorelease];
535 }
536
537 - (WKNavigation *)goForward
538 {
539     auto navigation = _page->goForward();
540     if (!navigation)
541         return nil;
542
543     return [wrapper(*navigation.release().leakRef()) autorelease];
544 }
545
546 - (WKNavigation *)reload
547 {
548     auto navigation = _page->reload(false);
549     if (!navigation)
550         return nil;
551
552     return [wrapper(*navigation.release().leakRef()) autorelease];
553 }
554
555 - (WKNavigation *)reloadFromOrigin
556 {
557     auto navigation = _page->reload(true);
558     if (!navigation)
559         return nil;
560
561     return [wrapper(*navigation.release().leakRef()) autorelease];
562 }
563
564 - (void)stopLoading
565 {
566     _page->stopLoading();
567 }
568
569 static WKErrorCode callbackErrorCode(WebKit::CallbackBase::Error error)
570 {
571     switch (error) {
572     case WebKit::CallbackBase::Error::None:
573         ASSERT_NOT_REACHED();
574         return WKErrorUnknown;
575
576     case WebKit::CallbackBase::Error::Unknown:
577         return WKErrorUnknown;
578
579     case WebKit::CallbackBase::Error::ProcessExited:
580         return WKErrorWebContentProcessTerminated;
581
582     case WebKit::CallbackBase::Error::OwnerWasInvalidated:
583         return WKErrorWebViewInvalidated;
584     }
585 }
586
587 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler
588 {
589     auto handler = adoptNS([completionHandler copy]);
590
591     _page->runJavaScriptInMainFrame(javaScriptString, [handler](API::SerializedScriptValue* serializedScriptValue, WebKit::ScriptValueCallback::Error errorCode) {
592         if (!handler)
593             return;
594
595         if (errorCode != WebKit::ScriptValueCallback::Error::None) {
596             auto error = createNSError(callbackErrorCode(errorCode));
597             if (errorCode == WebKit::ScriptValueCallback::Error::OwnerWasInvalidated) {
598                 // The OwnerWasInvalidated callback is synchronous. We don't want to call the block from within it
599                 // because that can trigger re-entrancy bugs in WebKit.
600                 // FIXME: It would be even better if GenericCallback did this for us.
601                 dispatch_async(dispatch_get_main_queue(), [handler, error] {
602                     auto rawHandler = (void (^)(id, NSError *))handler.get();
603                     rawHandler(nil, error.get());
604                 });
605                 return;
606             }
607
608             auto rawHandler = (void (^)(id, NSError *))handler.get();
609             rawHandler(nil, error.get());
610             return;
611         }
612
613         auto rawHandler = (void (^)(id, NSError *))handler.get();
614         if (!serializedScriptValue) {
615             rawHandler(nil, createNSError(WKErrorJavaScriptExceptionOccurred).get());
616             return;
617         }
618
619         auto context = adoptNS([[JSContext alloc] init]);
620         JSValueRef valueRef = serializedScriptValue->deserialize([context JSGlobalContextRef], 0);
621         JSValue *value = [JSValue valueWithJSValueRef:valueRef inContext:context.get()];
622
623         rawHandler([value toObject], nil);
624     });
625 }
626
627 - (NSString *)customUserAgent
628 {
629     return _page->customUserAgent();
630 }
631
632 - (void)setCustomUserAgent:(NSString *)customUserAgent
633 {
634     _page->setCustomUserAgent(customUserAgent);
635 }
636
637 - (WKPageRef)_pageForTesting
638 {
639     return toAPI(_page.get());
640 }
641
642 #pragma mark iOS-specific methods
643
644 #if PLATFORM(IOS)
645 - (void)setFrame:(CGRect)frame
646 {
647     CGRect oldFrame = self.frame;
648     [super setFrame:frame];
649
650     if (!CGSizeEqualToSize(oldFrame.size, frame.size))
651         [self _frameOrBoundsChanged];
652 }
653
654 - (void)setBounds:(CGRect)bounds
655 {
656     CGRect oldBounds = self.bounds;
657     [super setBounds:bounds];
658     [_customContentFixedOverlayView setFrame:self.bounds];
659
660     if (!CGSizeEqualToSize(oldBounds.size, bounds.size))
661         [self _frameOrBoundsChanged];
662 }
663
664 - (UIScrollView *)scrollView
665 {
666     return _scrollView.get();
667 }
668
669 - (WKBrowsingContextController *)browsingContextController
670 {
671     return [_contentView browsingContextController];
672 }
673
674 - (BOOL)becomeFirstResponder
675 {
676     return [_contentView becomeFirstResponder] || [super becomeFirstResponder];
677 }
678
679 static inline CGFloat floorToDevicePixel(CGFloat input, float deviceScaleFactor)
680 {
681     return CGFloor(input * deviceScaleFactor) / deviceScaleFactor;
682 }
683
684 static CGSize roundScrollViewContentSize(const WebKit::WebPageProxy& page, CGSize contentSize)
685 {
686     float deviceScaleFactor = page.deviceScaleFactor();
687     return CGSizeMake(floorToDevicePixel(contentSize.width, deviceScaleFactor), floorToDevicePixel(contentSize.height, deviceScaleFactor));
688 }
689
690 - (UIView *)_currentContentView
691 {
692     return _customContentView ? _customContentView.get() : _contentView.get();
693 }
694
695 - (void)_setHasCustomContentView:(BOOL)pageHasCustomContentView loadedMIMEType:(const WTF::String&)mimeType
696 {
697     if (pageHasCustomContentView) {
698         [_customContentView removeFromSuperview];
699         [_customContentFixedOverlayView removeFromSuperview];
700
701         Class representationClass = [[_configuration _contentProviderRegistry] providerForMIMEType:mimeType];
702         ASSERT(representationClass);
703         _customContentView = adoptNS([[representationClass alloc] web_initWithFrame:self.bounds webView:self]);
704         _customContentFixedOverlayView = adoptNS([[UIView alloc] initWithFrame:self.bounds]);
705         [_customContentFixedOverlayView setUserInteractionEnabled:NO];
706
707         [[_contentView unscaledView] removeFromSuperview];
708         [_contentView removeFromSuperview];
709         [_scrollView addSubview:_customContentView.get()];
710         [self addSubview:_customContentFixedOverlayView.get()];
711
712         [_customContentView web_setMinimumSize:self.bounds.size];
713         [_customContentView web_setFixedOverlayView:_customContentFixedOverlayView.get()];
714     } else if (_customContentView) {
715         [_customContentView removeFromSuperview];
716         _customContentView = nullptr;
717
718         [_customContentFixedOverlayView removeFromSuperview];
719         _customContentFixedOverlayView = nullptr;
720
721         [_scrollView addSubview:_contentView.get()];
722         [_scrollView addSubview:[_contentView unscaledView]];
723         [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
724
725         [_customContentFixedOverlayView setFrame:self.bounds];
726         [self addSubview:_customContentFixedOverlayView.get()];
727     }
728 }
729
730 - (void)_didFinishLoadingDataForCustomContentProviderWithSuggestedFilename:(const String&)suggestedFilename data:(NSData *)data
731 {
732     ASSERT(_customContentView);
733     [_customContentView web_setContentProviderData:data suggestedFilename:suggestedFilename];
734
735     // FIXME: It may make more sense for custom content providers to invoke this when they're ready,
736     // because there's no guarantee that all custom content providers will lay out synchronously.
737     _page->didLayoutForCustomContentProvider();
738 }
739
740 - (void)_setViewportMetaTagWidth:(float)newWidth
741 {
742     _viewportMetaTagWidth = newWidth;
743 }
744
745 - (void)_willInvokeUIScrollViewDelegateCallback
746 {
747     _delayUpdateVisibleContentRects = YES;
748 }
749
750 - (void)_didInvokeUIScrollViewDelegateCallback
751 {
752     _delayUpdateVisibleContentRects = NO;
753     if (_hadDelayedUpdateVisibleContentRects) {
754         _hadDelayedUpdateVisibleContentRects = NO;
755         [self _updateVisibleContentRects];
756     }
757 }
758
759 static CGFloat contentZoomScale(WKWebView *webView)
760 {
761     CGFloat scale = webView._currentContentView.layer.affineTransform.a;
762     ASSERT(scale == [webView->_scrollView zoomScale]);
763     return scale;
764 }
765
766 static WebCore::Color baseScrollViewBackgroundColor(WKWebView *webView)
767 {
768     if (webView->_customContentView)
769         return [webView->_customContentView backgroundColor].CGColor;
770
771     if (webView->_gestureController) {
772         WebCore::Color color = webView->_gestureController->backgroundColorForCurrentSnapshot();
773         if (color.isValid())
774             return color;
775     }
776
777     return webView->_page->pageExtendedBackgroundColor();
778 }
779
780 static WebCore::Color scrollViewBackgroundColor(WKWebView *webView)
781 {
782     if (!webView.opaque)
783         return WebCore::Color::transparent;
784
785     WebCore::Color color = baseScrollViewBackgroundColor(webView);
786
787     if (!color.isValid())
788         color = WebCore::Color::white;
789
790     CGFloat zoomScale = contentZoomScale(webView);
791     CGFloat minimumZoomScale = [webView->_scrollView minimumZoomScale];
792     if (zoomScale < minimumZoomScale) {
793         CGFloat slope = 12;
794         CGFloat opacity = std::max<CGFloat>(1 - slope * (minimumZoomScale - zoomScale), 0);
795         color = WebCore::colorWithOverrideAlpha(color.rgb(), opacity);
796     }
797
798     return color;
799 }
800
801 - (void)_updateScrollViewBackground
802 {
803     WebCore::Color color = scrollViewBackgroundColor(self);
804
805     if (_scrollViewBackgroundColor == color)
806         return;
807
808     _scrollViewBackgroundColor = color;
809
810     auto uiBackgroundColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)]);
811     [_scrollView setBackgroundColor:uiBackgroundColor.get()];
812
813     // Update the indicator style based on the lightness/darkness of the background color.
814     double hue, saturation, lightness;
815     color.getHSL(hue, saturation, lightness);
816     if (lightness <= .5 && color.alpha() > 0)
817         [_scrollView setIndicatorStyle:UIScrollViewIndicatorStyleWhite];
818     else
819         [_scrollView setIndicatorStyle:UIScrollViewIndicatorStyleDefault];
820 }
821
822 - (CGPoint)_adjustedContentOffset:(CGPoint)point
823 {
824     CGPoint result = point;
825     UIEdgeInsets contentInset = [self _computedContentInset];
826
827     result.x -= contentInset.left;
828     result.y -= contentInset.top;
829
830     return result;
831 }
832
833 - (UIEdgeInsets)_computedContentInset
834 {
835     if (_haveSetObscuredInsets)
836         return _obscuredInsets;
837
838     return [_scrollView contentInset];
839 }
840
841 - (void)_processDidExit
842 {
843     if (!_customContentView && _dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) {
844         NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
845         [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
846         [_scrollView insertSubview:[_contentView unscaledView] atIndex:indexOfResizeAnimationView + 1];
847         [_resizeAnimationView removeFromSuperview];
848         _resizeAnimationView = nil;
849
850         _resizeAnimationTransformAdjustments = CATransform3DIdentity;
851     }
852     [_contentView setFrame:self.bounds];
853     [_scrollView setBackgroundColor:[UIColor whiteColor]];
854     [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
855     [_scrollView setZoomScale:1];
856
857     _viewportMetaTagWidth = -1;
858     _hasCommittedLoadForMainFrame = NO;
859     _needsResetViewStateAfterCommitLoadForMainFrame = NO;
860     _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing;
861     [_contentView setHidden:NO];
862     _needsToRestoreExposedRect = NO;
863     _needsToRestoreUnobscuredCenter = NO;
864     _scrollViewBackgroundColor = WebCore::Color();
865     _delayUpdateVisibleContentRects = NO;
866     _hadDelayedUpdateVisibleContentRects = NO;
867 }
868
869 - (void)_didCommitLoadForMainFrame
870 {
871     _firstPaintAfterCommitLoadTransactionID = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).nextLayerTreeTransactionID();
872
873     _hasCommittedLoadForMainFrame = YES;
874     _needsResetViewStateAfterCommitLoadForMainFrame = YES;
875 }
876
877 static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset)
878 {
879     UIEdgeInsets contentInsets = scrollView.contentInset;
880     CGSize contentSize = scrollView.contentSize;
881     CGSize scrollViewSize = scrollView.bounds.size;
882
883     CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
884     contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x);
885     contentOffset.x = std::max(-contentInsets.left, contentOffset.x);
886
887     CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
888     contentOffset.y = std::min(maxVerticalOffset, contentOffset.y);
889     contentOffset.y = std::max(-contentInsets.top, contentOffset.y);
890     return contentOffset;
891 }
892
893 static void changeContentOffsetBoundedInValidRange(UIScrollView *scrollView, WebCore::FloatPoint contentOffset)
894 {
895     scrollView.contentOffset = contentOffsetBoundedInValidRange(scrollView, contentOffset);
896 }
897
898 - (WebCore::FloatRect)visibleRectInViewCoordinates
899 {
900     WebCore::FloatRect bounds = self.bounds;
901     bounds.moveBy([_scrollView contentOffset]);
902     WebCore::FloatRect contentViewBounds = [_contentView bounds];
903     bounds.intersect(contentViewBounds);
904     return bounds;
905 }
906
907 // WebCore stores the page scale factor as float instead of double. When we get a scale from WebCore,
908 // we need to ignore differences that are within a small rounding error on floats.
909 static inline bool areEssentiallyEqualAsFloat(float a, float b)
910 {
911     return WTF::areEssentiallyEqual(a, b);
912 }
913
914 - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
915 {
916     if (_customContentView)
917         return;
918
919     if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) {
920         if (layerTreeTransaction.transactionID() >= _resizeAnimationTransformTransactionID) {
921             [_resizeAnimationView layer].sublayerTransform = _resizeAnimationTransformAdjustments;
922             if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::ResizingWithDocumentHidden) {
923                 [_contentView setHidden:NO];
924                 [self _endAnimatedResize];
925             }
926         }
927         return;
928     }
929
930     CGSize newContentSize = roundScrollViewContentSize(*_page, [_contentView frame].size);
931     [_scrollView _setContentSizePreservingContentOffsetDuringRubberband:newContentSize];
932
933     [_scrollView setMinimumZoomScale:layerTreeTransaction.minimumScaleFactor()];
934     [_scrollView setMaximumZoomScale:layerTreeTransaction.maximumScaleFactor()];
935     [_scrollView setZoomEnabled:layerTreeTransaction.allowsUserScaling()];
936     if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom])
937         [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()];
938
939     [self _updateScrollViewBackground];
940
941     if (_gestureController)
942         _gestureController->setRenderTreeSize(layerTreeTransaction.renderTreeSize());
943
944     if (_needsResetViewStateAfterCommitLoadForMainFrame && layerTreeTransaction.transactionID() >= _firstPaintAfterCommitLoadTransactionID) {
945         _needsResetViewStateAfterCommitLoadForMainFrame = NO;
946         [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
947         [self _updateVisibleContentRects];
948     }
949
950     bool isTransactionAfterPageRestore = layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore;
951
952     if (_needsToRestoreExposedRect && isTransactionAfterPageRestore) {
953         _needsToRestoreExposedRect = NO;
954
955         if (areEssentiallyEqualAsFloat(contentZoomScale(self), _scaleToRestore)) {
956             WebCore::FloatPoint exposedPosition = _exposedRectToRestore.location();
957             exposedPosition.scale(_scaleToRestore, _scaleToRestore);
958
959             changeContentOffsetBoundedInValidRange(_scrollView.get(), exposedPosition);
960             _gestureController->didRestoreScrollPosition();
961         }
962         [self _updateVisibleContentRects];
963     }
964
965     if (_needsToRestoreUnobscuredCenter && isTransactionAfterPageRestore) {
966         _needsToRestoreUnobscuredCenter = NO;
967
968         if (areEssentiallyEqualAsFloat(contentZoomScale(self), _scaleToRestore)) {
969             CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, _obscuredInsets);
970             WebCore::FloatSize unobscuredContentSizeAtNewScale(unobscuredRect.size.width / _scaleToRestore, unobscuredRect.size.height / _scaleToRestore);
971             WebCore::FloatPoint topLeftInDocumentCoordinate(_unobscuredCenterToRestore.x() - unobscuredContentSizeAtNewScale.width() / 2, _unobscuredCenterToRestore.y() - unobscuredContentSizeAtNewScale.height() / 2);
972
973             topLeftInDocumentCoordinate.scale(_scaleToRestore, _scaleToRestore);
974             topLeftInDocumentCoordinate.moveBy(WebCore::FloatPoint(-_obscuredInsets.left, -_obscuredInsets.top));
975
976             changeContentOffsetBoundedInValidRange(_scrollView.get(), topLeftInDocumentCoordinate);
977             _gestureController->didRestoreScrollPosition();
978         }
979         [self _updateVisibleContentRects];
980     }
981     
982     if (WebKit::RemoteLayerTreeScrollingPerformanceData* scrollPerfData = _page->scrollingPerformanceData())
983         scrollPerfData->didCommitLayerTree([self visibleRectInViewCoordinates]);
984 }
985
986 - (void)_dynamicViewportUpdateChangedTargetToScale:(double)newScale position:(CGPoint)newScrollPosition nextValidLayerTreeTransactionID:(uint64_t)nextValidLayerTreeTransactionID
987 {
988     if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing) {
989         CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
990         double currentTargetScale = animatingScaleTarget * [[_contentView layer] transform].m11;
991         double scale = newScale / currentTargetScale;
992         _resizeAnimationTransformAdjustments = CATransform3DMakeScale(scale, scale, 1);
993
994         CGPoint newContentOffset = [self _adjustedContentOffset:CGPointMake(newScrollPosition.x * newScale, newScrollPosition.y * newScale)];
995         CGPoint currentContentOffset = [_scrollView contentOffset];
996
997         _resizeAnimationTransformAdjustments.m41 = (currentContentOffset.x - newContentOffset.x) / animatingScaleTarget;
998         _resizeAnimationTransformAdjustments.m42 = (currentContentOffset.y - newContentOffset.y) / animatingScaleTarget;
999         _resizeAnimationTransformTransactionID = nextValidLayerTreeTransactionID;
1000     }
1001 }
1002
1003 - (void)_restorePageStateToExposedRect:(WebCore::FloatRect)exposedRect scale:(double)scale
1004 {
1005     if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing)
1006         return;
1007
1008     if (_customContentView)
1009         return;
1010
1011     _needsToRestoreUnobscuredCenter = NO;
1012     _needsToRestoreExposedRect = YES;
1013     _firstTransactionIDAfterPageRestore = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).nextLayerTreeTransactionID();
1014     _exposedRectToRestore = exposedRect;
1015     _scaleToRestore = scale;
1016 }
1017
1018 - (void)_restorePageStateToUnobscuredCenter:(WebCore::FloatPoint)center scale:(double)scale
1019 {
1020     if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing)
1021         return;
1022
1023     if (_customContentView)
1024         return;
1025
1026     _needsToRestoreExposedRect = NO;
1027     _needsToRestoreUnobscuredCenter = YES;
1028     _firstTransactionIDAfterPageRestore = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).nextLayerTreeTransactionID();
1029     _unobscuredCenterToRestore = center;
1030     _scaleToRestore = scale;
1031 }
1032
1033 - (PassRefPtr<WebKit::ViewSnapshot>)_takeViewSnapshot
1034 {
1035     float deviceScale = WKGetScreenScaleFactor();
1036     CGSize snapshotSize = self.bounds.size;
1037     snapshotSize.width *= deviceScale;
1038     snapshotSize.height *= deviceScale;
1039
1040     uint32_t slotID = [WebKit::ViewSnapshotStore::snapshottingContext() createImageSlot:snapshotSize hasAlpha:YES];
1041
1042     if (!slotID)
1043         return nullptr;
1044
1045     CATransform3D transform = CATransform3DMakeScale(deviceScale, deviceScale, 1);
1046     CARenderServerCaptureLayerWithTransform(MACH_PORT_NULL, self.layer.context.contextId, (uint64_t)self.layer, slotID, 0, 0, &transform);
1047
1048     WebCore::IntSize imageSize = WebCore::expandedIntSize(WebCore::FloatSize(snapshotSize));
1049     return WebKit::ViewSnapshot::create(slotID, imageSize, imageSize.width() * imageSize.height() * 4);
1050 }
1051
1052 - (void)_zoomToPoint:(WebCore::FloatPoint)point atScale:(double)scale animated:(BOOL)animated
1053 {
1054     CFTimeInterval duration = 0;
1055     CGFloat zoomScale = contentZoomScale(self);
1056
1057     if (animated) {
1058         const double maximumZoomDuration = 0.4;
1059         const double minimumZoomDuration = 0.1;
1060         const double zoomDurationFactor = 0.3;
1061
1062         duration = std::min(fabs(log(zoomScale) - log(scale)) * zoomDurationFactor + minimumZoomDuration, maximumZoomDuration);
1063     }
1064
1065     if (scale != zoomScale)
1066         _page->willStartUserTriggeredZooming();
1067
1068     [_scrollView _zoomToCenter:point scale:scale duration:duration];
1069 }
1070
1071 - (void)_zoomToRect:(WebCore::FloatRect)targetRect atScale:(double)scale origin:(WebCore::FloatPoint)origin animated:(BOOL)animated
1072 {
1073     // FIXME: Some of this could be shared with _scrollToRect.
1074     const double visibleRectScaleChange = contentZoomScale(self) / scale;
1075     const WebCore::FloatRect visibleRect([self convertRect:self.bounds toView:self._currentContentView]);
1076     const WebCore::FloatRect unobscuredRect([self _contentRectForUserInteraction]);
1077
1078     const WebCore::FloatSize topLeftObscuredInsetAfterZoom((unobscuredRect.minXMinYCorner() - visibleRect.minXMinYCorner()) * visibleRectScaleChange);
1079     const WebCore::FloatSize bottomRightObscuredInsetAfterZoom((visibleRect.maxXMaxYCorner() - unobscuredRect.maxXMaxYCorner()) * visibleRectScaleChange);
1080
1081     const WebCore::FloatSize unobscuredRectSizeAfterZoom(unobscuredRect.size() * visibleRectScaleChange);
1082
1083     // Center to the target rect.
1084     WebCore::FloatPoint unobscuredRectLocationAfterZoom = targetRect.location() - (unobscuredRectSizeAfterZoom - targetRect.size()) * 0.5;
1085
1086     // Center to the tap point instead in case the target rect won't fit in a direction.
1087     if (targetRect.width() > unobscuredRectSizeAfterZoom.width())
1088         unobscuredRectLocationAfterZoom.setX(origin.x() - unobscuredRectSizeAfterZoom.width() / 2);
1089     if (targetRect.height() > unobscuredRectSizeAfterZoom.height())
1090         unobscuredRectLocationAfterZoom.setY(origin.y() - unobscuredRectSizeAfterZoom.height() / 2);
1091
1092     // We have computed where we want the unobscured rect to be. Now adjust for the obscuring insets.
1093     WebCore::FloatRect visibleRectAfterZoom(unobscuredRectLocationAfterZoom, unobscuredRectSizeAfterZoom);
1094     visibleRectAfterZoom.move(-topLeftObscuredInsetAfterZoom);
1095     visibleRectAfterZoom.expand(topLeftObscuredInsetAfterZoom + bottomRightObscuredInsetAfterZoom);
1096
1097     [self _zoomToPoint:visibleRectAfterZoom.center() atScale:scale animated:animated];
1098 }
1099
1100 static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOffset, WebCore::FloatSize contentSize, WebCore::FloatSize unobscuredContentSize)
1101 {
1102     WebCore::FloatSize maximumContentOffset = contentSize - unobscuredContentSize;
1103     contentOffset = contentOffset.shrunkTo(WebCore::FloatPoint(maximumContentOffset.width(), maximumContentOffset.height()));
1104     contentOffset = contentOffset.expandedTo(WebCore::FloatPoint());
1105     return contentOffset;
1106 }
1107
1108 - (void)_scrollToContentOffset:(WebCore::FloatPoint)contentOffsetInPageCoordinates
1109 {
1110     if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing)
1111         return;
1112
1113     WebCore::FloatPoint scaledOffset = contentOffsetInPageCoordinates;
1114     CGFloat zoomScale = contentZoomScale(self);
1115     scaledOffset.scale(zoomScale, zoomScale);
1116
1117     CGPoint contentOffsetInScrollViewCoordinates = [self _adjustedContentOffset:scaledOffset];
1118     contentOffsetInScrollViewCoordinates = contentOffsetBoundedInValidRange(_scrollView.get(), contentOffsetInScrollViewCoordinates);
1119
1120     [_scrollView _stopScrollingAndZoomingAnimations];
1121
1122     if (!CGPointEqualToPoint(contentOffsetInScrollViewCoordinates, [_scrollView contentOffset]))
1123         [_scrollView setContentOffset:contentOffsetInScrollViewCoordinates];
1124     else {
1125         // If we haven't changed anything, there would not be any VisibleContentRect update sent to the content.
1126         // The WebProcess would keep the invalid contentOffset as its scroll position.
1127         // To synchronize the WebProcess with what is on screen, we send the VisibleContentRect again.
1128         _page->resendLastVisibleContentRects();
1129     }
1130 }
1131
1132 - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance
1133 {
1134     WebCore::FloatRect unobscuredContentRect([self _contentRectForUserInteraction]);
1135     WebCore::FloatPoint unobscuredContentOffset = unobscuredContentRect.location();
1136     WebCore::FloatSize contentSize([self._currentContentView bounds].size);
1137
1138     // Center the target rect in the scroll view.
1139     // If the target doesn't fit in the scroll view, center on the gesture location instead.
1140     WebCore::FloatPoint newUnobscuredContentOffset;
1141     if (targetRect.width() <= unobscuredContentRect.width())
1142         newUnobscuredContentOffset.setX(targetRect.x() - (unobscuredContentRect.width() - targetRect.width()) / 2);
1143     else
1144         newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
1145     if (targetRect.height() <= unobscuredContentRect.height())
1146         newUnobscuredContentOffset.setY(targetRect.y() - (unobscuredContentRect.height() - targetRect.height()) / 2);
1147     else
1148         newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
1149     newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
1150
1151     if (unobscuredContentOffset == newUnobscuredContentOffset) {
1152         if (targetRect.width() > unobscuredContentRect.width())
1153             newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
1154         if (targetRect.height() > unobscuredContentRect.height())
1155             newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
1156         newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
1157     }
1158
1159     WebCore::FloatSize scrollViewOffsetDelta = newUnobscuredContentOffset - unobscuredContentOffset;
1160     scrollViewOffsetDelta.scale(contentZoomScale(self));
1161
1162     float scrollDistance = scrollViewOffsetDelta.diagonalLength();
1163     if (scrollDistance < minimumScrollDistance)
1164         return false;
1165
1166     [_contentView willStartZoomOrScroll];
1167
1168     [_scrollView setContentOffset:([_scrollView contentOffset] + scrollViewOffsetDelta) animated:YES];
1169     return true;
1170 }
1171
1172 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin animated:(BOOL)animated
1173 {
1174     [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale] animated:animated];
1175 }
1176
1177 // focusedElementRect and selectionRect are both in document coordinates.
1178 - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRectInDocumentCoordinates selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll
1179 {
1180     const double WKWebViewStandardFontSize = 16;
1181     const double kMinimumHeightToShowContentAboveKeyboard = 106;
1182     const CFTimeInterval UIWebFormAnimationDuration = 0.25;
1183     const double CaretOffsetFromWindowEdge = 20;
1184
1185     // Zoom around the element's bounding frame. We use a "standard" size to determine the proper frame.
1186     double scale = allowScaling ? std::min(std::max(WKWebViewStandardFontSize / fontSize, minimumScale), maximumScale) : contentZoomScale(self);
1187     CGFloat documentWidth = [_contentView bounds].size.width;
1188     scale = CGRound(documentWidth * scale) / documentWidth;
1189
1190     UIWindow *window = [_scrollView window];
1191
1192     WebCore::FloatRect focusedElementRectInNewScale = focusedElementRectInDocumentCoordinates;
1193     focusedElementRectInNewScale.scale(scale);
1194     focusedElementRectInNewScale.moveBy([_contentView frame].origin);
1195
1196     // Find the portion of the view that is visible on the screen.
1197     UIViewController *topViewController = [[[_scrollView _viewControllerForAncestor] _rootAncestorViewController] _viewControllerForSupportedInterfaceOrientations];
1198     UIView *fullScreenView = topViewController.view;
1199     if (!fullScreenView)
1200         fullScreenView = window;
1201
1202     CGRect unobscuredScrollViewRectInWebViewCoordinates = UIEdgeInsetsInsetRect([self bounds], _obscuredInsets);
1203     CGRect visibleScrollViewBoundsInWebViewCoordinates = CGRectIntersection(unobscuredScrollViewRectInWebViewCoordinates, [fullScreenView convertRect:[fullScreenView bounds] toView:self]);
1204     CGRect formAssistantFrameInWebViewCoordinates = [window convertRect:_inputViewBounds toView:self];
1205     CGRect intersectionBetweenScrollViewAndFormAssistant = CGRectIntersection(visibleScrollViewBoundsInWebViewCoordinates, formAssistantFrameInWebViewCoordinates);
1206     CGSize visibleSize = visibleScrollViewBoundsInWebViewCoordinates.size;
1207
1208     CGFloat visibleOffsetFromTop = 0;
1209     if (!CGRectIsEmpty(intersectionBetweenScrollViewAndFormAssistant)) {
1210         CGFloat heightVisibleAboveFormAssistant = CGRectGetMinY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1211         CGFloat heightVisibleBelowFormAssistant = CGRectGetMaxY(visibleScrollViewBoundsInWebViewCoordinates) - CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant);
1212
1213         if (heightVisibleAboveFormAssistant >= kMinimumHeightToShowContentAboveKeyboard || heightVisibleBelowFormAssistant < heightVisibleAboveFormAssistant)
1214             visibleSize.height = heightVisibleAboveFormAssistant;
1215         else {
1216             visibleSize.height = heightVisibleBelowFormAssistant;
1217             visibleOffsetFromTop = CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1218         }
1219     }
1220
1221     BOOL selectionRectIsNotNull = !selectionRectInDocumentCoordinates.isZero();
1222     if (!forceScroll) {
1223         CGRect currentlyVisibleRegionInWebViewCoordinates;
1224         currentlyVisibleRegionInWebViewCoordinates.origin = unobscuredScrollViewRectInWebViewCoordinates.origin;
1225         currentlyVisibleRegionInWebViewCoordinates.origin.y += visibleOffsetFromTop;
1226         currentlyVisibleRegionInWebViewCoordinates.size = visibleSize;
1227
1228         // Don't bother scrolling if the entire node is already visible, whether or not we got a selectionRect.
1229         if (CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:focusedElementRectInDocumentCoordinates fromView:_contentView.get()]))
1230             return;
1231
1232         // Don't bother scrolling if we have a valid selectionRect and it is already visible.
1233         if (selectionRectIsNotNull && CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:selectionRectInDocumentCoordinates fromView:_contentView.get()]))
1234             return;
1235     }
1236
1237     // We want to zoom to the left/top corner of the DOM node, with as much spacing on all sides as we
1238     // can get based on the visible area after zooming (workingFrame).  The spacing in either dimension is half the
1239     // difference between the size of the DOM node and the size of the visible frame.
1240     CGFloat horizontalSpaceInWebViewCoordinates = std::max((visibleSize.width - focusedElementRectInNewScale.width()) / 2.0, 0.0);
1241     CGFloat verticalSpaceInWebViewCoordinates = std::max((visibleSize.height - focusedElementRectInNewScale.height()) / 2.0, 0.0);
1242
1243     CGPoint topLeft;
1244     topLeft.x = focusedElementRectInNewScale.x() - horizontalSpaceInWebViewCoordinates;
1245     topLeft.y = focusedElementRectInNewScale.y() - verticalSpaceInWebViewCoordinates - visibleOffsetFromTop;
1246
1247     CGFloat minimumAllowableHorizontalOffsetInWebViewCoordinates = -INFINITY;
1248     CGFloat minimumAllowableVerticalOffsetInWebViewCoordinates = -INFINITY;
1249     if (selectionRectIsNotNull) {
1250         WebCore::FloatRect selectionRectInNewScale = selectionRectInDocumentCoordinates;
1251         selectionRectInNewScale.scale(scale);
1252         selectionRectInNewScale.moveBy([_contentView frame].origin);
1253         minimumAllowableHorizontalOffsetInWebViewCoordinates = CGRectGetMaxX(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.width;
1254         minimumAllowableVerticalOffsetInWebViewCoordinates = CGRectGetMaxY(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.height - visibleOffsetFromTop;
1255     }
1256
1257     WebCore::FloatRect documentBoundsInNewScale = [_contentView bounds];
1258     documentBoundsInNewScale.scale(scale);
1259     documentBoundsInNewScale.moveBy([_contentView frame].origin);
1260
1261     // Constrain the left edge in document coordinates so that:
1262     //  - it isn't so small that the scrollVisibleRect isn't visible on the screen
1263     //  - it isn't so great that the document's right edge is less than the right edge of the screen
1264     if (selectionRectIsNotNull && topLeft.x < minimumAllowableHorizontalOffsetInWebViewCoordinates)
1265         topLeft.x = minimumAllowableHorizontalOffsetInWebViewCoordinates;
1266     else {
1267         CGFloat maximumAllowableHorizontalOffset = CGRectGetMaxX(documentBoundsInNewScale) - visibleSize.width;
1268         if (topLeft.x > maximumAllowableHorizontalOffset)
1269             topLeft.x = maximumAllowableHorizontalOffset;
1270     }
1271
1272     // Constrain the top edge in document coordinates so that:
1273     //  - it isn't so small that the scrollVisibleRect isn't visible on the screen
1274     //  - it isn't so great that the document's bottom edge is higher than the top of the form assistant
1275     if (selectionRectIsNotNull && topLeft.y < minimumAllowableVerticalOffsetInWebViewCoordinates)
1276         topLeft.y = minimumAllowableVerticalOffsetInWebViewCoordinates;
1277     else {
1278         CGFloat maximumAllowableVerticalOffset = CGRectGetMaxY(documentBoundsInNewScale) - visibleSize.height;
1279         if (topLeft.y > maximumAllowableVerticalOffset)
1280             topLeft.y = maximumAllowableVerticalOffset;
1281     }
1282
1283     WebCore::FloatPoint newCenter = CGPointMake(topLeft.x + unobscuredScrollViewRectInWebViewCoordinates.size.width / 2.0, topLeft.y + unobscuredScrollViewRectInWebViewCoordinates.size.height / 2.0);
1284
1285     if (scale != contentZoomScale(self))
1286         _page->willStartUserTriggeredZooming();
1287
1288     // The newCenter has been computed in the new scale, but _zoomToCenter expected the center to be in the original scale.
1289     newCenter.scale(1 / scale, 1 / scale);
1290     [_scrollView _zoomToCenter:newCenter
1291                         scale:scale
1292                      duration:UIWebFormAnimationDuration
1293                         force:YES];
1294 }
1295
1296 - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
1297 {
1298     const float maximumScaleFactorDeltaForPanScroll = 0.02;
1299
1300     double currentScale = contentZoomScale(self);
1301
1302     WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
1303     double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width();
1304     double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height();
1305
1306     horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale);
1307     verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale);
1308
1309     double targetScale = fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
1310     if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) {
1311         if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance])
1312             return true;
1313     } else if (targetScale != currentScale) {
1314         [self _zoomToRect:targetRect atScale:targetScale origin:origin animated:YES];
1315         return true;
1316     }
1317     
1318     return false;
1319 }
1320
1321 - (void)didMoveToWindow
1322 {
1323     _page->viewStateDidChange(WebCore::ViewState::AllFlags);
1324 }
1325
1326 - (void)setOpaque:(BOOL)opaque
1327 {
1328     BOOL oldOpaque = self.opaque;
1329
1330     [super setOpaque:opaque];
1331     [_contentView setOpaque:opaque];
1332
1333     if (oldOpaque == opaque)
1334         return;
1335
1336     _page->setDrawsBackground(opaque);
1337     [self _updateScrollViewBackground];
1338 }
1339
1340 - (void)setBackgroundColor:(UIColor *)backgroundColor
1341 {
1342     [super setBackgroundColor:backgroundColor];
1343     [_contentView setBackgroundColor:backgroundColor];
1344 }
1345
1346 #pragma mark - UIScrollViewDelegate
1347
1348 - (BOOL)usesStandardContentView
1349 {
1350     return !_customContentView;
1351 }
1352
1353 - (CGSize)scrollView:(UIScrollView*)scrollView contentSizeForZoomScale:(CGFloat)scale withProposedSize:(CGSize)proposedSize
1354 {
1355     return roundScrollViewContentSize(*_page, proposedSize);
1356 }
1357
1358 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
1359 {
1360     ASSERT(_scrollView == scrollView);
1361
1362     if (_customContentView)
1363         return _customContentView.get();
1364
1365     return _contentView.get();
1366 }
1367
1368 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
1369 {
1370     if (![self usesStandardContentView])
1371         return;
1372
1373     if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan) {
1374         _page->willStartUserTriggeredZooming();
1375         [_contentView scrollViewWillStartPanOrPinchGesture];
1376     }
1377     [_contentView willStartZoomOrScroll];
1378 }
1379
1380 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
1381 {
1382     if (![self usesStandardContentView])
1383         return;
1384
1385     if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
1386         [_contentView scrollViewWillStartPanOrPinchGesture];
1387
1388     [_contentView willStartZoomOrScroll];
1389 #if ENABLE(CSS_SCROLL_SNAP) && ENABLE(ASYNC_SCROLLING)
1390     // FIXME: We will want to detect whether snapping will occur before beginning to drag. See WebPageProxy::didCommitLayerTree.
1391     WebKit::RemoteScrollingCoordinatorProxy* coordinator = _page->scrollingCoordinatorProxy();
1392     ASSERT(scrollView == _scrollView.get());
1393     scrollView.decelerationRate = (coordinator && coordinator->shouldSetScrollViewDecelerationRateFast()) ? UIScrollViewDecelerationRateFast : [_scrollView preferredScrollDecelerationFactor];
1394 #endif
1395 }
1396
1397 - (void)_didFinishScrolling
1398 {
1399     if (![self usesStandardContentView])
1400         return;
1401
1402     [self _updateVisibleContentRects];
1403     [_contentView didFinishScrolling];
1404 }
1405
1406 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
1407 {
1408     // Work around <rdar://problem/16374753> by avoiding deceleration while
1409     // zooming. We'll animate to the right place once the zoom finishes.
1410     if ([scrollView isZooming])
1411         *targetContentOffset = [scrollView contentOffset];
1412 #if ENABLE(CSS_SCROLL_SNAP) && ENABLE(ASYNC_SCROLLING)
1413     if (WebKit::RemoteScrollingCoordinatorProxy* coordinator = _page->scrollingCoordinatorProxy()) {
1414         // FIXME: Here, I'm finding the maximum horizontal/vertical scroll offsets. There's probably a better way to do this.
1415         CGSize maxScrollOffsets = CGSizeMake(scrollView.contentSize.width - scrollView.bounds.size.width, scrollView.contentSize.height - scrollView.bounds.size.height);
1416         
1417         CGRect fullViewRect = self.bounds;
1418         CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]);
1419         
1420         coordinator->adjustTargetContentOffsetForSnapping(maxScrollOffsets, velocity, unobscuredRect.origin.y, targetContentOffset);
1421     }
1422 #endif
1423 }
1424
1425 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
1426 {
1427     // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called.
1428     if (!decelerate)
1429         [self _didFinishScrolling];
1430 }
1431
1432 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
1433 {
1434     [self _didFinishScrolling];
1435 }
1436
1437 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
1438 {
1439     [self _didFinishScrolling];
1440 }
1441
1442 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
1443 {
1444     if (![self usesStandardContentView])
1445         [_customContentView scrollViewDidScroll:(UIScrollView *)scrollView];
1446
1447     [self _updateVisibleContentRects];
1448     
1449     if (WebKit::RemoteLayerTreeScrollingPerformanceData* scrollPerfData = _page->scrollingPerformanceData())
1450         scrollPerfData->didScroll([self visibleRectInViewCoordinates]);
1451 }
1452
1453 - (void)scrollViewDidZoom:(UIScrollView *)scrollView
1454 {
1455     [self _updateScrollViewBackground];
1456     [self _updateVisibleContentRects];
1457 }
1458
1459 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
1460 {
1461     ASSERT(scrollView == _scrollView);
1462     [self _updateVisibleContentRects];
1463     [_contentView didZoomToScale:scale];
1464 }
1465
1466 - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
1467 {
1468     [self _didFinishScrolling];
1469 }
1470
1471 - (void)_scrollViewDidInterruptDecelerating:(UIScrollView *)scrollView
1472 {
1473     if (![self usesStandardContentView])
1474         return;
1475
1476     [_contentView didInterruptScrolling];
1477     [self _updateVisibleContentRects];
1478 }
1479
1480 - (void)_frameOrBoundsChanged
1481 {
1482     CGRect bounds = self.bounds;
1483     [_scrollView setFrame:bounds];
1484
1485     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing) {
1486         if (!_overridesMinimumLayoutSize)
1487             _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(bounds.size));
1488         if (!_overridesMaximumUnobscuredSize)
1489             _page->setMaximumUnobscuredSize(WebCore::FloatSize(bounds.size));
1490         if (WebKit::DrawingAreaProxy* drawingArea = _page->drawingArea())
1491             drawingArea->setSize(WebCore::IntSize(bounds.size), WebCore::IntSize(), WebCore::IntSize());
1492     }
1493
1494     [_customContentView web_setMinimumSize:bounds.size];
1495     [self _updateVisibleContentRects];
1496 }
1497
1498 // 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.
1499 - (CGRect)_contentRectForUserInteraction
1500 {
1501     // FIXME: handle split keyboard.
1502     UIEdgeInsets obscuredInsets = _obscuredInsets;
1503     obscuredInsets.bottom = std::max(_obscuredInsets.bottom, _inputViewBounds.size.height);
1504     CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, obscuredInsets);
1505     return [self convertRect:unobscuredRect toView:self._currentContentView];
1506 }
1507
1508 - (void)_updateVisibleContentRects
1509 {
1510     if (![self usesStandardContentView]) {
1511         [_customContentView web_computedContentInsetDidChange];
1512         return;
1513     }
1514
1515     if (_delayUpdateVisibleContentRects) {
1516         _hadDelayedUpdateVisibleContentRects = YES;
1517         return;
1518     }
1519
1520     if (_dynamicViewportUpdateMode != DynamicViewportUpdateMode::NotResizing)
1521         return;
1522
1523     if (_needsResetViewStateAfterCommitLoadForMainFrame)
1524         return;
1525
1526     CGRect fullViewRect = self.bounds;
1527     CGRect visibleRectInContentCoordinates = [self convertRect:fullViewRect toView:_contentView.get()];
1528
1529     CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]);
1530     CGRect unobscuredRectInContentCoordinates = [self convertRect:unobscuredRect toView:_contentView.get()];
1531
1532     CGFloat scaleFactor = contentZoomScale(self);
1533
1534     BOOL isStableState = !(_isChangingObscuredInsetsInteractively || [_scrollView isDragging] || [_scrollView isDecelerating] || [_scrollView isZooming] || [_scrollView isZoomBouncing] || [_scrollView _isAnimatingZoom] || [_scrollView _isScrollingToTop]);
1535
1536     // FIXME: this can be made static after we stop supporting iOS 8.x.
1537     if (isStableState && [_scrollView respondsToSelector:@selector(_isInterruptingDeceleration)])
1538         isStableState = ![_scrollView performSelector:@selector(_isInterruptingDeceleration)];
1539
1540 #if ENABLE(CSS_SCROLL_SNAP) && ENABLE(ASYNC_SCROLLING)
1541     if (isStableState) {
1542         WebKit::RemoteScrollingCoordinatorProxy* coordinator = _page->scrollingCoordinatorProxy();
1543         if (coordinator && coordinator->hasActiveSnapPoint()) {
1544             CGRect fullViewRect = self.bounds;
1545             CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]);
1546             
1547             CGPoint currentPoint = [_scrollView contentOffset];
1548             CGPoint activePoint = coordinator->nearestActiveContentInsetAdjustedSnapPoint(unobscuredRect.origin.y, currentPoint);
1549
1550             if (!CGPointEqualToPoint(activePoint, currentPoint)) {
1551                 RetainPtr<WKScrollView> strongScrollView = _scrollView;
1552                 dispatch_async(dispatch_get_main_queue(), [strongScrollView, activePoint] {
1553                     [strongScrollView setContentOffset:activePoint animated:NO];
1554                 });
1555             }
1556         }
1557     }
1558 #endif
1559     
1560     [_contentView didUpdateVisibleRect:visibleRectInContentCoordinates
1561         unobscuredRect:unobscuredRectInContentCoordinates
1562         unobscuredRectInScrollViewCoordinates:unobscuredRect
1563         scale:scaleFactor minimumScale:[_scrollView minimumZoomScale]
1564         inStableState:isStableState isChangingObscuredInsetsInteractively:_isChangingObscuredInsetsInteractively];
1565 }
1566
1567 - (void)_didFinishLoadForMainFrame
1568 {
1569     if (_gestureController)
1570         _gestureController->didFinishLoadForMainFrame();
1571 }
1572
1573 - (void)_didSameDocumentNavigationForMainFrame:(WebKit::SameDocumentNavigationType)navigationType
1574 {
1575     [_customContentView web_didSameDocumentNavigation:toAPI(navigationType)];
1576
1577     if (_gestureController)
1578         _gestureController->didSameDocumentNavigationForMainFrame(navigationType);
1579 }
1580
1581 - (void)_keyboardChangedWithInfo:(NSDictionary *)keyboardInfo adjustScrollView:(BOOL)adjustScrollView
1582 {
1583     NSValue *endFrameValue = [keyboardInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
1584     if (!endFrameValue)
1585         return;
1586
1587     // The keyboard rect is always in screen coordinates. In the view services case the window does not
1588     // have the interface orientation rotation transformation; its host does. So, it makes no sense to
1589     // clip the keyboard rect against its screen.
1590     if ([[self window] _isHostedInAnotherProcess])
1591         _inputViewBounds = [self.window convertRect:[endFrameValue CGRectValue] fromWindow:nil];
1592     else
1593         _inputViewBounds = [self.window convertRect:CGRectIntersection([endFrameValue CGRectValue], self.window.screen.bounds) fromWindow:nil];
1594
1595     [self _updateVisibleContentRects];
1596
1597     if (adjustScrollView)
1598         [_scrollView _adjustForAutomaticKeyboardInfo:keyboardInfo animated:YES lastAdjustment:&_lastAdjustmentForScroller];
1599 }
1600
1601 - (BOOL)_shouldUpdateKeyboardWithInfo:(NSDictionary *)keyboardInfo
1602 {
1603     if ([_contentView isAssistingNode])
1604         return YES;
1605
1606     NSNumber *isLocalKeyboard = [keyboardInfo valueForKey:UIKeyboardIsLocalUserInfoKey];
1607     return isLocalKeyboard && !isLocalKeyboard.boolValue;
1608 }
1609
1610 - (void)_keyboardWillChangeFrame:(NSNotification *)notification
1611 {
1612     if ([self _shouldUpdateKeyboardWithInfo:notification.userInfo])
1613         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1614 }
1615
1616 - (void)_keyboardDidChangeFrame:(NSNotification *)notification
1617 {
1618     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:NO];
1619 }
1620
1621 - (void)_keyboardWillShow:(NSNotification *)notification
1622 {
1623     if ([self _shouldUpdateKeyboardWithInfo:notification.userInfo])
1624         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1625 }
1626
1627 - (void)_keyboardWillHide:(NSNotification *)notification
1628 {
1629     // Ignore keyboard will hide notifications sent during rotation. They're just there for
1630     // backwards compatibility reasons and processing the will hide notification would
1631     // temporarily screw up the the unobscured view area.
1632     if ([[UIPeripheralHost sharedInstance] rotationState])
1633         return;
1634
1635     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1636 }
1637
1638 - (void)_windowDidRotate:(NSNotification *)notification
1639 {
1640     if (!_overridesInterfaceOrientation)
1641         _page->setDeviceOrientation(deviceOrientation());
1642 }
1643
1644 - (void)_contentSizeCategoryDidChange:(NSNotification *)notification
1645 {
1646     _page->contentSizeCategoryDidChange([self _contentSizeCategory]);
1647 }
1648
1649 - (NSString *)_contentSizeCategory
1650 {
1651     return [[UIApplication sharedApplication] preferredContentSizeCategory];
1652 }
1653
1654 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1655 {
1656     if (_allowsBackForwardNavigationGestures == allowsBackForwardNavigationGestures)
1657         return;
1658
1659     _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
1660
1661     if (allowsBackForwardNavigationGestures) {
1662         if (!_gestureController) {
1663             _gestureController = std::make_unique<WebKit::ViewGestureController>(*_page);
1664             _gestureController->installSwipeHandler(self, [self scrollView]);
1665             _gestureController->setAlternateBackForwardListSourceView([_configuration _alternateWebViewForNavigationGestures]);
1666         }
1667     } else
1668         _gestureController = nullptr;
1669
1670     _page->setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures);
1671 }
1672
1673 - (BOOL)allowsBackForwardNavigationGestures
1674 {
1675     return _allowsBackForwardNavigationGestures;
1676 }
1677
1678 #endif
1679
1680 #pragma mark OS X-specific methods
1681
1682 #if PLATFORM(MAC)
1683
1684 - (BOOL)becomeFirstResponder
1685 {
1686     return [[self window] makeFirstResponder: _wkView.get()];
1687 }
1688
1689 - (BOOL)acceptsFirstResponder
1690 {
1691     return [_wkView acceptsFirstResponder];
1692 }
1693
1694 - (void)resizeSubviewsWithOldSize:(NSSize)oldSize
1695 {
1696     [_wkView setFrame:self.bounds];
1697 }
1698
1699 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1700 {
1701     [_wkView setAllowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures];
1702 }
1703
1704 - (BOOL)allowsBackForwardNavigationGestures
1705 {
1706     return [_wkView allowsBackForwardNavigationGestures];
1707 }
1708
1709 - (void)setAllowsMagnification:(BOOL)allowsMagnification
1710 {
1711     [_wkView setAllowsMagnification:allowsMagnification];
1712 }
1713
1714 - (BOOL)allowsMagnification
1715 {
1716     return [_wkView allowsMagnification];
1717 }
1718
1719 - (void)setMagnification:(CGFloat)magnification
1720 {
1721     [_wkView setMagnification:magnification];
1722 }
1723
1724 - (CGFloat)magnification
1725 {
1726     return [_wkView magnification];
1727 }
1728
1729 - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point
1730 {
1731     [_wkView setMagnification:magnification centeredAtPoint:NSPointFromCGPoint(point)];
1732 }
1733
1734 - (BOOL)_ignoresNonWheelEvents
1735 {
1736     return [_wkView _ignoresNonWheelEvents];
1737 }
1738
1739 - (void)_setIgnoresNonWheelEvents:(BOOL)ignoresNonWheelEvents
1740 {
1741     [_wkView _setIgnoresNonWheelEvents:ignoresNonWheelEvents];
1742 }
1743
1744 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
1745 {
1746     return [_wkView draggingEntered:sender];
1747 }
1748
1749 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
1750 {
1751     return [_wkView draggingUpdated:sender];
1752 }
1753
1754 - (void)draggingExited:(id <NSDraggingInfo>)sender
1755 {
1756     [_wkView draggingExited:sender];
1757 }
1758
1759 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
1760 {
1761     return [_wkView prepareForDragOperation:sender];
1762 }
1763
1764 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
1765 {
1766     return [_wkView performDragOperation:sender];
1767 }
1768 #endif
1769
1770 @end
1771
1772 @implementation WKWebView (WKPrivate)
1773
1774 - (WKNavigation *)_loadRequest:(NSURLRequest *)request withOptions:(NSDictionary *)loadOptions
1775 {
1776     bool shouldOpenExternalURLs = [loadOptions[_WKShouldOpenExternalURLsKey] boolValue];
1777     WebCore::ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = shouldOpenExternalURLs ? WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow : WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow;
1778
1779     auto navigation = _page->loadRequest(request, shouldOpenExternalURLsPolicy);
1780     if (!navigation)
1781         return nil;
1782
1783     return [wrapper(*navigation.release().leakRef()) autorelease];
1784 }
1785
1786 - (BOOL)_isEditable
1787 {
1788     return _page->isEditable();
1789 }
1790
1791 - (void)_setEditable:(BOOL)editable
1792 {
1793     _page->setEditable(editable);
1794 #if !PLATFORM(IOS)
1795     [_wkView _addFontPanelObserver];
1796 #endif
1797 }
1798
1799 - (_WKRemoteObjectRegistry *)_remoteObjectRegistry
1800 {
1801     if (!_remoteObjectRegistry) {
1802         _remoteObjectRegistry = adoptNS([[_WKRemoteObjectRegistry alloc] _initWithMessageSender:*_page]);
1803         _page->process().processPool().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID(), [_remoteObjectRegistry remoteObjectRegistry]);
1804     }
1805
1806     return _remoteObjectRegistry.get();
1807 }
1808
1809 - (WKBrowsingContextHandle *)_handle
1810 {
1811     return [[[WKBrowsingContextHandle alloc] _initWithPageID:_page->pageID()] autorelease];
1812 }
1813
1814 - (_WKRenderingProgressEvents)_observedRenderingProgressEvents
1815 {
1816     return _observedRenderingProgressEvents;
1817 }
1818
1819 - (id <WKHistoryDelegatePrivate>)_historyDelegate
1820 {
1821     return _navigationState->historyDelegate().autorelease();
1822 }
1823
1824 - (void)_setHistoryDelegate:(id <WKHistoryDelegatePrivate>)historyDelegate
1825 {
1826     _page->setHistoryClient(_navigationState->createHistoryClient());
1827     _navigationState->setHistoryDelegate(historyDelegate);
1828 }
1829
1830 - (NSURL *)_unreachableURL
1831 {
1832     return [NSURL _web_URLWithWTFString:_page->pageLoadState().unreachableURL()];
1833 }
1834
1835 - (void)_loadAlternateHTMLString:(NSString *)string baseURL:(NSURL *)baseURL forUnreachableURL:(NSURL *)unreachableURL
1836 {
1837     _page->loadAlternateHTMLString(string, [baseURL _web_originalDataAsWTFString], [unreachableURL _web_originalDataAsWTFString]);
1838 }
1839
1840 - (NSArray *)_certificateChain
1841 {
1842     if (WebKit::WebFrameProxy* mainFrame = _page->mainFrame())
1843         return mainFrame->certificateInfo() ? (NSArray *)mainFrame->certificateInfo()->certificateInfo().certificateChain() : nil;
1844
1845     return nil;
1846 }
1847
1848 - (NSURL *)_committedURL
1849 {
1850     return [NSURL _web_URLWithWTFString:_page->pageLoadState().url()];
1851 }
1852
1853 - (NSString *)_MIMEType
1854 {
1855     if (_page->mainFrame())
1856         return _page->mainFrame()->mimeType();
1857
1858     return nil;
1859 }
1860
1861 - (NSString *)_userAgent
1862 {
1863     return _page->userAgent();
1864 }
1865
1866 - (NSString *)_applicationNameForUserAgent
1867 {
1868     return _page->applicationNameForUserAgent();
1869 }
1870
1871 - (void)_setApplicationNameForUserAgent:(NSString *)applicationNameForUserAgent
1872 {
1873     _page->setApplicationNameForUserAgent(applicationNameForUserAgent);
1874 }
1875
1876 - (NSString *)_customUserAgent
1877 {
1878     return self.customUserAgent;
1879
1880 }
1881
1882 - (void)_setCustomUserAgent:(NSString *)customUserAgent
1883 {
1884     self.customUserAgent = customUserAgent;
1885 }
1886
1887 - (pid_t)_webProcessIdentifier
1888 {
1889     return _page->isValid() ? _page->processIdentifier() : 0;
1890 }
1891
1892 - (void)_killWebContentProcess
1893 {
1894     if (!_page->isValid())
1895         return;
1896
1897     _page->process().terminate();
1898 }
1899
1900 #if PLATFORM(IOS)
1901 static WebCore::FloatSize activeMinimumLayoutSize(WKWebView *webView, const CGRect& bounds)
1902 {
1903     return WebCore::FloatSize(webView->_overridesMinimumLayoutSize ? webView->_minimumLayoutSizeOverride : bounds.size);
1904 }
1905
1906 static WebCore::FloatSize activeMaximumUnobscuredSize(WKWebView *webView, const CGRect& bounds)
1907 {
1908     return WebCore::FloatSize(webView->_overridesMaximumUnobscuredSize ? webView->_maximumUnobscuredSizeOverride : bounds.size);
1909 }
1910
1911 static int32_t activeOrientation(WKWebView *webView)
1912 {
1913     return webView->_overridesInterfaceOrientation ? deviceOrientationForUIInterfaceOrientation(webView->_interfaceOrientationOverride) : webView->_page->deviceOrientation();
1914 }
1915
1916 - (void (^)(void))_retainActiveFocusedState
1917 {
1918     ++_activeFocusedStateRetainCount;
1919
1920     // FIXME: Use something like CompletionHandlerCallChecker to ensure that the returned block is called before it's released.
1921     return [[[self] {
1922         --_activeFocusedStateRetainCount;
1923     } copy] autorelease];
1924 }
1925
1926 - (void)_becomeFirstResponderWithSelectionMovingForward:(BOOL)selectingForward completionHandler:(void (^)(BOOL didBecomeFirstResponder))completionHandler
1927 {
1928     typeof(completionHandler) completionHandlerCopy = nil;
1929     if (completionHandler)
1930         completionHandlerCopy = Block_copy(completionHandler);
1931
1932     [_contentView _becomeFirstResponderWithSelectionMovingForward:selectingForward completionHandler:[completionHandlerCopy](BOOL didBecomeFirstResponder) {
1933         if (!completionHandlerCopy)
1934             return;
1935
1936         completionHandlerCopy(didBecomeFirstResponder);
1937         Block_release(completionHandlerCopy);
1938     }];
1939 }
1940
1941 #endif
1942
1943 - (void)_didRelaunchProcess
1944 {
1945 #if PLATFORM(IOS)
1946     CGRect bounds = self.bounds;
1947     WebCore::FloatSize minimalLayoutSize = activeMinimumLayoutSize(self, bounds);
1948     _page->setViewportConfigurationMinimumLayoutSize(minimalLayoutSize);
1949     _page->setMaximumUnobscuredSize(activeMaximumUnobscuredSize(self, bounds));
1950 #endif
1951 }
1952
1953 - (NSData *)_sessionStateData
1954 {
1955     WebKit::SessionState sessionState = _page->sessionState();
1956
1957     // FIXME: This should not use the legacy session state encoder.
1958     return [wrapper(*WebKit::encodeLegacySessionState(sessionState).release().leakRef()) autorelease];
1959 }
1960
1961 - (_WKSessionState *)_sessionState
1962 {
1963     return adoptNS([[_WKSessionState alloc] _initWithSessionState:_page->sessionState()]).autorelease();
1964 }
1965
1966 - (void)_restoreFromSessionStateData:(NSData *)sessionStateData
1967 {
1968     // FIXME: This should not use the legacy session state decoder.
1969     WebKit::SessionState sessionState;
1970     if (!WebKit::decodeLegacySessionState(static_cast<const uint8_t*>(sessionStateData.bytes), sessionStateData.length, sessionState))
1971         return;
1972
1973     _page->restoreFromSessionState(WTF::move(sessionState), true);
1974 }
1975
1976 - (WKNavigation *)_restoreSessionState:(_WKSessionState *)sessionState andNavigate:(BOOL)navigate
1977 {
1978     auto navigation = _page->restoreFromSessionState(sessionState->_sessionState, navigate);
1979     if (!navigation)
1980         return nil;
1981
1982     return [wrapper(*navigation.release().leakRef()) autorelease];
1983 }
1984
1985 - (void)_close
1986 {
1987     _page->close();
1988 }
1989
1990 - (BOOL)_allowsRemoteInspection
1991 {
1992 #if ENABLE(REMOTE_INSPECTOR)
1993     return _page->allowsRemoteInspection();
1994 #else
1995     return NO;
1996 #endif
1997 }
1998
1999 - (void)_setAllowsRemoteInspection:(BOOL)allow
2000 {
2001 #if ENABLE(REMOTE_INSPECTOR)
2002     _page->setAllowsRemoteInspection(allow);
2003 #endif
2004 }
2005
2006 - (BOOL)_addsVisitedLinks
2007 {
2008     return _page->addsVisitedLinks();
2009 }
2010
2011 - (void)_setAddsVisitedLinks:(BOOL)addsVisitedLinks
2012 {
2013     _page->setAddsVisitedLinks(addsVisitedLinks);
2014 }
2015
2016 - (BOOL)_networkRequestsInProgress
2017 {
2018     return _page->pageLoadState().networkRequestsInProgress();
2019 }
2020
2021 static inline WebCore::LayoutMilestones layoutMilestones(_WKRenderingProgressEvents events)
2022 {
2023     WebCore::LayoutMilestones milestones = 0;
2024
2025     if (events & _WKRenderingProgressEventFirstLayout)
2026         milestones |= WebCore::DidFirstLayout;
2027
2028     if (events & _WKRenderingProgressEventFirstVisuallyNonEmptyLayout)
2029         milestones |= WebCore::DidFirstVisuallyNonEmptyLayout;
2030
2031     if (events & _WKRenderingProgressEventFirstPaintWithSignificantArea)
2032         milestones |= WebCore::DidHitRelevantRepaintedObjectsAreaThreshold;
2033
2034     if (events & _WKRenderingProgressEventReachedSessionRestorationRenderTreeSizeThreshold)
2035         milestones |= WebCore::ReachedSessionRestorationRenderTreeSizeThreshold;
2036
2037     return milestones;
2038 }
2039
2040 - (void)_setObservedRenderingProgressEvents:(_WKRenderingProgressEvents)observedRenderingProgressEvents
2041 {
2042     _observedRenderingProgressEvents = observedRenderingProgressEvents;
2043     _page->listenForLayoutMilestones(layoutMilestones(observedRenderingProgressEvents));
2044 }
2045
2046 - (void)_getMainResourceDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
2047 {
2048     auto handler = adoptNS([completionHandler copy]);
2049
2050     _page->getMainResourceDataOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
2051         void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
2052         if (error != WebKit::CallbackBase::Error::None) {
2053             // FIXME: Pipe a proper error in from the WebPageProxy.
2054             RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
2055             completionHandlerBlock(nil, error.get());
2056         } else
2057             completionHandlerBlock(wrapper(*data), nil);
2058     });
2059 }
2060
2061 - (void)_getWebArchiveDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
2062 {
2063     auto handler = adoptNS([completionHandler copy]);
2064
2065     _page->getWebArchiveOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
2066         void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
2067         if (error != WebKit::CallbackBase::Error::None) {
2068             // FIXME: Pipe a proper error in from the WebPageProxy.
2069             RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
2070             completionHandlerBlock(nil, error.get());
2071         } else
2072             completionHandlerBlock(wrapper(*data), nil);
2073     });
2074 }
2075
2076 - (_WKPaginationMode)_paginationMode
2077 {
2078     switch (_page->paginationMode()) {
2079     case WebCore::Pagination::Unpaginated:
2080         return _WKPaginationModeUnpaginated;
2081     case WebCore::Pagination::LeftToRightPaginated:
2082         return _WKPaginationModeLeftToRight;
2083     case WebCore::Pagination::RightToLeftPaginated:
2084         return _WKPaginationModeRightToLeft;
2085     case WebCore::Pagination::TopToBottomPaginated:
2086         return _WKPaginationModeTopToBottom;
2087     case WebCore::Pagination::BottomToTopPaginated:
2088         return _WKPaginationModeBottomToTop;
2089     }
2090
2091     ASSERT_NOT_REACHED();
2092     return _WKPaginationModeUnpaginated;
2093 }
2094
2095 - (void)_setPaginationMode:(_WKPaginationMode)paginationMode
2096 {
2097     WebCore::Pagination::Mode mode;
2098     switch (paginationMode) {
2099     case _WKPaginationModeUnpaginated:
2100         mode = WebCore::Pagination::Unpaginated;
2101         break;
2102     case _WKPaginationModeLeftToRight:
2103         mode = WebCore::Pagination::LeftToRightPaginated;
2104         break;
2105     case _WKPaginationModeRightToLeft:
2106         mode = WebCore::Pagination::RightToLeftPaginated;
2107         break;
2108     case _WKPaginationModeTopToBottom:
2109         mode = WebCore::Pagination::TopToBottomPaginated;
2110         break;
2111     case _WKPaginationModeBottomToTop:
2112         mode = WebCore::Pagination::BottomToTopPaginated;
2113         break;
2114     default:
2115         return;
2116     }
2117
2118     _page->setPaginationMode(mode);
2119 }
2120
2121 - (BOOL)_paginationBehavesLikeColumns
2122 {
2123     return _page->paginationBehavesLikeColumns();
2124 }
2125
2126 - (void)_setPaginationBehavesLikeColumns:(BOOL)behavesLikeColumns
2127 {
2128     _page->setPaginationBehavesLikeColumns(behavesLikeColumns);
2129 }
2130
2131 - (CGFloat)_pageLength
2132 {
2133     return _page->pageLength();
2134 }
2135
2136 - (void)_setPageLength:(CGFloat)pageLength
2137 {
2138     _page->setPageLength(pageLength);
2139 }
2140
2141 - (CGFloat)_gapBetweenPages
2142 {
2143     return _page->gapBetweenPages();
2144 }
2145
2146 - (void)_setGapBetweenPages:(CGFloat)gapBetweenPages
2147 {
2148     _page->setGapBetweenPages(gapBetweenPages);
2149 }
2150
2151 - (NSUInteger)_pageCount
2152 {
2153     return _page->pageCount();
2154 }
2155
2156 - (BOOL)_supportsTextZoom
2157 {
2158     return _page->supportsTextZoom();
2159 }
2160
2161 - (double)_textZoomFactor
2162 {
2163     return _page->textZoomFactor();
2164 }
2165
2166 - (void)_setTextZoomFactor:(double)zoomFactor
2167 {
2168     _page->setTextZoomFactor(zoomFactor);
2169 }
2170
2171 - (double)_pageZoomFactor
2172 {
2173     return _page->pageZoomFactor();
2174 }
2175
2176 - (void)_setPageZoomFactor:(double)zoomFactor
2177 {
2178     _page->setPageZoomFactor(zoomFactor);
2179 }
2180
2181 - (id <_WKDiagnosticLoggingDelegate>)_diagnosticLoggingDelegate
2182 {
2183     return [static_cast<WebKit::DiagnosticLoggingClient&>(_page->diagnosticLoggingClient()).delegate().leakRef() autorelease];
2184 }
2185
2186 - (void)_setDiagnosticLoggingDelegate:(id<_WKDiagnosticLoggingDelegate>)diagnosticLoggingDelegate
2187 {
2188     static_cast<WebKit::DiagnosticLoggingClient&>(_page->diagnosticLoggingClient()).setDelegate(diagnosticLoggingDelegate);
2189 }
2190
2191 - (id <_WKFindDelegate>)_findDelegate
2192 {
2193     return [static_cast<WebKit::FindClient&>(_page->findClient()).delegate().leakRef() autorelease];
2194 }
2195
2196 - (void)_setFindDelegate:(id<_WKFindDelegate>)findDelegate
2197 {
2198     static_cast<WebKit::FindClient&>(_page->findClient()).setDelegate(findDelegate);
2199 }
2200
2201 static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
2202 {
2203     unsigned findOptions = 0;
2204
2205     if (wkFindOptions & _WKFindOptionsCaseInsensitive)
2206         findOptions |= WebKit::FindOptionsCaseInsensitive;
2207     if (wkFindOptions & _WKFindOptionsAtWordStarts)
2208         findOptions |= WebKit::FindOptionsAtWordStarts;
2209     if (wkFindOptions & _WKFindOptionsTreatMedialCapitalAsWordStart)
2210         findOptions |= WebKit::FindOptionsTreatMedialCapitalAsWordStart;
2211     if (wkFindOptions & _WKFindOptionsBackwards)
2212         findOptions |= WebKit::FindOptionsBackwards;
2213     if (wkFindOptions & _WKFindOptionsWrapAround)
2214         findOptions |= WebKit::FindOptionsWrapAround;
2215     if (wkFindOptions & _WKFindOptionsShowOverlay)
2216         findOptions |= WebKit::FindOptionsShowOverlay;
2217     if (wkFindOptions & _WKFindOptionsShowFindIndicator)
2218         findOptions |= WebKit::FindOptionsShowFindIndicator;
2219     if (wkFindOptions & _WKFindOptionsShowHighlight)
2220         findOptions |= WebKit::FindOptionsShowHighlight;
2221     if (wkFindOptions & _WKFindOptionsDetermineMatchIndex)
2222         findOptions |= WebKit::FindOptionsDetermineMatchIndex;
2223
2224     return static_cast<WebKit::FindOptions>(findOptions);
2225 }
2226
2227 - (void)_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
2228 {
2229 #if PLATFORM(IOS)
2230     if (_customContentView) {
2231         [_customContentView web_countStringMatches:string options:options maxCount:maxCount];
2232         return;
2233     }
2234 #endif
2235     _page->countStringMatches(string, toFindOptions(options), maxCount);
2236 }
2237
2238 - (void)_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
2239 {
2240 #if PLATFORM(IOS)
2241     if (_customContentView) {
2242         [_customContentView web_findString:string options:options maxCount:maxCount];
2243         return;
2244     }
2245 #endif
2246     _page->findString(string, toFindOptions(options), maxCount);
2247 }
2248
2249 - (void)_hideFindUI
2250 {
2251 #if PLATFORM(IOS)
2252     if (_customContentView) {
2253         [_customContentView web_hideFindUI];
2254         return;
2255     }
2256 #endif
2257     _page->hideFindUI();
2258 }
2259
2260 - (id <_WKFormDelegate>)_formDelegate
2261 {
2262     return _formDelegate.getAutoreleased();
2263 }
2264
2265 - (void)_setFormDelegate:(id <_WKFormDelegate>)formDelegate
2266 {
2267     _formDelegate = formDelegate;
2268
2269     class FormClient : public API::FormClient {
2270     public:
2271         explicit FormClient(WKWebView *webView)
2272             : m_webView(webView)
2273         {
2274         }
2275
2276         virtual ~FormClient() { }
2277
2278         virtual void willSubmitForm(WebKit::WebPageProxy&, WebKit::WebFrameProxy&, WebKit::WebFrameProxy& sourceFrame, const Vector<std::pair<WTF::String, WTF::String>>& textFieldValues, API::Object* userData, Ref<WebKit::WebFormSubmissionListenerProxy>&& listener) override
2279         {
2280             if (userData && userData->type() != API::Object::Type::Data) {
2281                 ASSERT(!userData || userData->type() == API::Object::Type::Data);
2282                 m_webView->_page->process().connection()->markCurrentlyDispatchedMessageAsInvalid();
2283                 listener->continueSubmission();
2284                 return;
2285             }
2286
2287             auto formDelegate = m_webView->_formDelegate.get();
2288
2289             if (![formDelegate respondsToSelector:@selector(_webView:willSubmitFormValues:userObject:submissionHandler:)]) {
2290                 listener->continueSubmission();
2291                 return;
2292             }
2293
2294             auto valueMap = adoptNS([[NSMutableDictionary alloc] initWithCapacity:textFieldValues.size()]);
2295             for (const auto& pair : textFieldValues)
2296                 [valueMap setObject:pair.second forKey:pair.first];
2297
2298             NSObject <NSSecureCoding> *userObject = nil;
2299             if (API::Data* data = static_cast<API::Data*>(userData)) {
2300                 auto nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:const_cast<void*>(static_cast<const void*>(data->bytes())) length:data->size() freeWhenDone:NO]);
2301                 auto unarchiver = adoptNS([[NSKeyedUnarchiver alloc] initForReadingWithData:nsData.get()]);
2302                 [unarchiver setRequiresSecureCoding:YES];
2303                 @try {
2304                     userObject = [unarchiver decodeObjectOfClass:[NSObject class] forKey:@"userObject"];
2305                 } @catch (NSException *exception) {
2306                     LOG_ERROR("Failed to decode user data: %@", exception);
2307                 }
2308             }
2309
2310             RefPtr<WebKit::WebFormSubmissionListenerProxy> localListener = WTF::move(listener);
2311             RefPtr<WebKit::CompletionHandlerCallChecker> checker = WebKit::CompletionHandlerCallChecker::create(formDelegate.get(), @selector(_webView:willSubmitFormValues:userObject:submissionHandler:));
2312             [formDelegate _webView:m_webView willSubmitFormValues:valueMap.get() userObject:userObject submissionHandler:[localListener, checker] {
2313                 checker->didCallCompletionHandler();
2314                 localListener->continueSubmission();
2315             }];
2316         }
2317
2318     private:
2319         WKWebView *m_webView;
2320     };
2321
2322     if (formDelegate)
2323         _page->setFormClient(std::make_unique<FormClient>(self));
2324     else
2325         _page->setFormClient(nullptr);
2326 }
2327
2328 - (BOOL)_isDisplayingStandaloneImageDocument
2329 {
2330     if (auto* mainFrame = _page->mainFrame())
2331         return mainFrame->isDisplayingStandaloneImageDocument();
2332     return NO;
2333 }
2334
2335 - (BOOL)_isShowingNavigationGestureSnapshot
2336 {
2337     return _page->isShowingNavigationGestureSnapshot();
2338 }
2339
2340 - (_WKLayoutMode)_layoutMode
2341 {
2342 #if PLATFORM(MAC)
2343     switch ([_wkView _layoutMode]) {
2344     case kWKLayoutModeFixedSize:
2345         return _WKLayoutModeFixedSize;
2346     case kWKLayoutModeDynamicSizeComputedFromViewScale:
2347         return _WKLayoutModeDynamicSizeComputedFromViewScale;
2348     case kWKLayoutModeDynamicSizeWithMinimumViewSize:
2349         return _WKLayoutModeDynamicSizeWithMinimumViewSize;
2350     case kWKLayoutModeDynamicSizeComputedFromMinimumDocumentSize:
2351         return _WKLayoutModeDynamicSizeComputedFromMinimumDocumentSize;
2352     case kWKLayoutModeViewSize:
2353     default:
2354         return _WKLayoutModeViewSize;
2355     }
2356 #else
2357     return _page->useFixedLayout() ? _WKLayoutModeFixedSize : _WKLayoutModeViewSize;
2358 #endif
2359 }
2360
2361 - (void)_setLayoutMode:(_WKLayoutMode)layoutMode
2362 {
2363 #if PLATFORM(MAC)
2364     WKLayoutMode wkViewLayoutMode;
2365     switch (layoutMode) {
2366     case _WKLayoutModeFixedSize:
2367         wkViewLayoutMode = kWKLayoutModeFixedSize;
2368         break;
2369     case _WKLayoutModeDynamicSizeComputedFromViewScale:
2370         wkViewLayoutMode = kWKLayoutModeDynamicSizeComputedFromViewScale;
2371         break;
2372     case _WKLayoutModeDynamicSizeWithMinimumViewSize:
2373         wkViewLayoutMode = kWKLayoutModeDynamicSizeWithMinimumViewSize;
2374         break;
2375     case _WKLayoutModeDynamicSizeComputedFromMinimumDocumentSize:
2376         wkViewLayoutMode = kWKLayoutModeDynamicSizeComputedFromMinimumDocumentSize;
2377         break;
2378     case _WKLayoutModeViewSize:
2379     default:
2380         wkViewLayoutMode = kWKLayoutModeViewSize;
2381         break;
2382     }
2383     [_wkView _setLayoutMode:wkViewLayoutMode];
2384 #else
2385     _page->setUseFixedLayout(layoutMode == _WKLayoutModeFixedSize || layoutMode == _WKLayoutModeDynamicSizeComputedFromViewScale);
2386 #endif
2387 }
2388
2389 - (CGSize)_fixedLayoutSize
2390 {
2391     return _page->fixedLayoutSize();
2392 }
2393
2394 - (void)_setFixedLayoutSize:(CGSize)fixedLayoutSize
2395 {
2396     _page->setFixedLayoutSize(WebCore::expandedIntSize(WebCore::FloatSize(fixedLayoutSize)));
2397 }
2398
2399 - (CGFloat)_viewScale
2400 {
2401     return _page->viewScaleFactor();
2402 }
2403
2404 - (void)_setViewScale:(CGFloat)viewScale
2405 {
2406 #if PLATFORM(MAC)
2407     [_wkView _setViewScale:viewScale];
2408 #else
2409     if (viewScale <= 0 || isnan(viewScale) || isinf(viewScale))
2410         [NSException raise:NSInvalidArgumentException format:@"View scale should be a positive number"];
2411
2412     _page->scaleView(viewScale);
2413 #endif
2414 }
2415
2416 - (void)_setMinimumViewSize:(CGSize)minimumViewSize
2417 {
2418 #if PLATFORM(MAC)
2419     [_wkView _setMinimumViewSize:minimumViewSize];
2420 #endif
2421 }
2422
2423 - (CGSize)_minimumViewSize
2424 {
2425 #if PLATFORM(MAC)
2426     return [_wkView _minimumViewSize];
2427 #else
2428     return CGSizeZero;
2429 #endif
2430 }
2431
2432 #pragma mark scrollperf methods
2433
2434 - (void)_setScrollPerformanceDataCollectionEnabled:(BOOL)enabled
2435 {
2436     _page->setScrollPerformanceDataCollectionEnabled(enabled);
2437 }
2438
2439 - (BOOL)_scrollPerformanceDataCollectionEnabled
2440 {
2441     return _page->scrollPerformanceDataCollectionEnabled();
2442 }
2443
2444 - (NSArray *)_scrollPerformanceData
2445 {
2446 #if PLATFORM(IOS)
2447     if (WebKit::RemoteLayerTreeScrollingPerformanceData* scrollPerfData = _page->scrollingPerformanceData())
2448         return scrollPerfData->data();
2449 #endif
2450     return nil;
2451 }
2452
2453 #pragma mark iOS-specific methods
2454
2455 #if PLATFORM(IOS)
2456
2457 - (CGSize)_minimumLayoutSizeOverride
2458 {
2459     ASSERT(_overridesMinimumLayoutSize);
2460     return _minimumLayoutSizeOverride;
2461 }
2462
2463 - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride
2464 {
2465     _overridesMinimumLayoutSize = YES;
2466     if (CGSizeEqualToSize(_minimumLayoutSizeOverride, minimumLayoutSizeOverride))
2467         return;
2468
2469     _minimumLayoutSizeOverride = minimumLayoutSizeOverride;
2470     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing)
2471         _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(minimumLayoutSizeOverride));
2472 }
2473
2474 - (UIEdgeInsets)_obscuredInsets
2475 {
2476     return _obscuredInsets;
2477 }
2478
2479 - (void)_setObscuredInsets:(UIEdgeInsets)obscuredInsets
2480 {
2481     ASSERT(obscuredInsets.top >= 0);
2482     ASSERT(obscuredInsets.left >= 0);
2483     ASSERT(obscuredInsets.bottom >= 0);
2484     ASSERT(obscuredInsets.right >= 0);
2485     
2486     _haveSetObscuredInsets = YES;
2487
2488     if (UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, obscuredInsets))
2489         return;
2490
2491     _obscuredInsets = obscuredInsets;
2492
2493     [self _updateVisibleContentRects];
2494 }
2495
2496 - (void)_setInterfaceOrientationOverride:(UIInterfaceOrientation)interfaceOrientation
2497 {
2498     if (!_overridesInterfaceOrientation)
2499         [[NSNotificationCenter defaultCenter] removeObserver:self name:UIWindowDidRotateNotification object:nil];
2500
2501     _overridesInterfaceOrientation = YES;
2502
2503     if (interfaceOrientation == _interfaceOrientationOverride)
2504         return;
2505
2506     _interfaceOrientationOverride = interfaceOrientation;
2507
2508     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing)
2509         _page->setDeviceOrientation(deviceOrientationForUIInterfaceOrientation(_interfaceOrientationOverride));
2510 }
2511
2512 - (UIInterfaceOrientation)_interfaceOrientationOverride
2513 {
2514     ASSERT(_overridesInterfaceOrientation);
2515     return _interfaceOrientationOverride;
2516 }
2517
2518 - (CGSize)_maximumUnobscuredSizeOverride
2519 {
2520     ASSERT(_overridesMaximumUnobscuredSize);
2521     return _maximumUnobscuredSizeOverride;
2522 }
2523
2524 - (void)_setMaximumUnobscuredSizeOverride:(CGSize)size
2525 {
2526     ASSERT(size.width <= self.bounds.size.width && size.height <= self.bounds.size.height);
2527     _overridesMaximumUnobscuredSize = YES;
2528     if (CGSizeEqualToSize(_maximumUnobscuredSizeOverride, size))
2529         return;
2530
2531     _maximumUnobscuredSizeOverride = size;
2532     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing)
2533         _page->setMaximumUnobscuredSize(WebCore::FloatSize(size));
2534 }
2535
2536 - (void)_setBackgroundExtendsBeyondPage:(BOOL)backgroundExtends
2537 {
2538     _page->setBackgroundExtendsBeyondPage(backgroundExtends);
2539 }
2540
2541 - (BOOL)_backgroundExtendsBeyondPage
2542 {
2543     return _page->backgroundExtendsBeyondPage();
2544 }
2545
2546 - (void)_beginInteractiveObscuredInsetsChange
2547 {
2548     ASSERT(!_isChangingObscuredInsetsInteractively);
2549     _isChangingObscuredInsetsInteractively = YES;
2550 }
2551
2552 - (void)_endInteractiveObscuredInsetsChange
2553 {
2554     ASSERT(_isChangingObscuredInsetsInteractively);
2555     _isChangingObscuredInsetsInteractively = NO;
2556     [self _updateVisibleContentRects];
2557 }
2558
2559 - (void)_beginAnimatedResizeWithUpdates:(void (^)(void))updateBlock
2560 {
2561     CGRect oldBounds = self.bounds;
2562     WebCore::FloatRect oldUnobscuredContentRect = _page->unobscuredContentRect();
2563
2564     if (_customContentView || !_hasCommittedLoadForMainFrame || CGRectIsEmpty(oldBounds) || oldUnobscuredContentRect.isEmpty()) {
2565         updateBlock();
2566         return;
2567     }
2568
2569     _dynamicViewportUpdateMode = DynamicViewportUpdateMode::ResizingWithAnimation;
2570
2571     WebCore::FloatSize oldMinimumLayoutSize = activeMinimumLayoutSize(self, oldBounds);
2572     WebCore::FloatSize oldMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, oldBounds);
2573     int32_t oldOrientation = activeOrientation(self);
2574     UIEdgeInsets oldObscuredInsets = _obscuredInsets;
2575
2576     updateBlock();
2577
2578     CGRect newBounds = self.bounds;
2579     WebCore::FloatSize newMinimumLayoutSize = activeMinimumLayoutSize(self, newBounds);
2580     WebCore::FloatSize newMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, newBounds);
2581     int32_t newOrientation = activeOrientation(self);
2582     UIEdgeInsets newObscuredInsets = _obscuredInsets;
2583     CGRect futureUnobscuredRectInSelfCoordinates = UIEdgeInsetsInsetRect(newBounds, _obscuredInsets);
2584
2585     ASSERT_WITH_MESSAGE(!(_overridesMinimumLayoutSize && newMinimumLayoutSize.isEmpty()), "Clients controlling the layout size should maintain a valid layout size to minimize layouts.");
2586     if (CGRectIsEmpty(newBounds) || newMinimumLayoutSize.isEmpty() || CGRectIsEmpty(futureUnobscuredRectInSelfCoordinates)) {
2587         _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing;
2588         [self _frameOrBoundsChanged];
2589         if (_overridesMinimumLayoutSize)
2590             _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(newMinimumLayoutSize));
2591         if (_overridesMaximumUnobscuredSize)
2592             _page->setMaximumUnobscuredSize(WebCore::FloatSize(newMaximumUnobscuredSize));
2593         if (_overridesInterfaceOrientation)
2594             _page->setDeviceOrientation(newOrientation);
2595         [self _updateVisibleContentRects];
2596         return;
2597     }
2598
2599     if (CGRectEqualToRect(oldBounds, newBounds)
2600         && oldMinimumLayoutSize == newMinimumLayoutSize
2601         && oldMaximumUnobscuredSize == newMaximumUnobscuredSize
2602         && oldOrientation == newOrientation
2603         && UIEdgeInsetsEqualToEdgeInsets(oldObscuredInsets, newObscuredInsets)) {
2604         _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing;
2605         [self _updateVisibleContentRects];
2606         return;
2607     }
2608
2609     _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2610
2611     NSUInteger indexOfContentView = [[_scrollView subviews] indexOfObject:_contentView.get()];
2612     _resizeAnimationView = adoptNS([[UIView alloc] init]);
2613     [_scrollView insertSubview:_resizeAnimationView.get() atIndex:indexOfContentView];
2614     [_resizeAnimationView addSubview:_contentView.get()];
2615     [_resizeAnimationView addSubview:[_contentView unscaledView]];
2616
2617     CGSize contentSizeInContentViewCoordinates = [_contentView bounds].size;
2618     [_scrollView setMinimumZoomScale:std::min(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView minimumZoomScale])];
2619     [_scrollView setMaximumZoomScale:std::max(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView maximumZoomScale])];
2620
2621     // Compute the new scale to keep the current content width in the scrollview.
2622     CGFloat oldWebViewWidthInContentViewCoordinates = oldUnobscuredContentRect.width();
2623     CGFloat visibleContentViewWidthInContentCoordinates = std::min(contentSizeInContentViewCoordinates.width, oldWebViewWidthInContentViewCoordinates);
2624     CGFloat targetScale = newMinimumLayoutSize.width() / visibleContentViewWidthInContentCoordinates;
2625     CGFloat resizeAnimationViewAnimationScale = targetScale / contentZoomScale(self);
2626     [_resizeAnimationView setTransform:CGAffineTransformMakeScale(resizeAnimationViewAnimationScale, resizeAnimationViewAnimationScale)];
2627
2628     // Compute a new position to keep the content centered.
2629     CGPoint originalContentCenter = oldUnobscuredContentRect.center();
2630     CGPoint originalContentCenterInSelfCoordinates = [self convertPoint:originalContentCenter fromView:_contentView.get()];
2631     CGPoint futureUnobscuredRectCenterInSelfCoordinates = CGPointMake(futureUnobscuredRectInSelfCoordinates.origin.x + futureUnobscuredRectInSelfCoordinates.size.width / 2, futureUnobscuredRectInSelfCoordinates.origin.y + futureUnobscuredRectInSelfCoordinates.size.height / 2);
2632
2633     CGPoint originalContentOffset = [_scrollView contentOffset];
2634     CGPoint contentOffset = originalContentOffset;
2635     contentOffset.x += (originalContentCenterInSelfCoordinates.x - futureUnobscuredRectCenterInSelfCoordinates.x);
2636     contentOffset.y += (originalContentCenterInSelfCoordinates.y - futureUnobscuredRectCenterInSelfCoordinates.y);
2637
2638     // Limit the new offset within the scrollview, we do not want to rubber band programmatically.
2639     CGSize futureContentSizeInSelfCoordinates = CGSizeMake(contentSizeInContentViewCoordinates.width * targetScale, contentSizeInContentViewCoordinates.height * targetScale);
2640     CGFloat maxHorizontalOffset = futureContentSizeInSelfCoordinates.width - newBounds.size.width + _obscuredInsets.right;
2641     contentOffset.x = std::min(contentOffset.x, maxHorizontalOffset);
2642     CGFloat maxVerticalOffset = futureContentSizeInSelfCoordinates.height - newBounds.size.height + _obscuredInsets.bottom;
2643     contentOffset.y = std::min(contentOffset.y, maxVerticalOffset);
2644
2645     contentOffset.x = std::max(contentOffset.x, -_obscuredInsets.left);
2646     contentOffset.y = std::max(contentOffset.y, -_obscuredInsets.top);
2647
2648     // Make the top/bottom edges "sticky" within 1 pixel.
2649     if (oldUnobscuredContentRect.maxY() > contentSizeInContentViewCoordinates.height - 1)
2650         contentOffset.y = maxVerticalOffset;
2651     if (oldUnobscuredContentRect.y() < 1)
2652         contentOffset.y = -_obscuredInsets.top;
2653
2654     // FIXME: if we have content centered after double tap to zoom, we should also try to keep that rect in view.
2655     [_scrollView setContentSize:roundScrollViewContentSize(*_page, futureContentSizeInSelfCoordinates)];
2656     [_scrollView setContentOffset:contentOffset];
2657
2658     CGRect visibleRectInContentCoordinates = [self convertRect:newBounds toView:_contentView.get()];
2659     CGRect unobscuredRectInContentCoordinates = [self convertRect:futureUnobscuredRectInSelfCoordinates toView:_contentView.get()];
2660
2661     _page->dynamicViewportSizeUpdate(newMinimumLayoutSize, newMaximumUnobscuredSize, visibleRectInContentCoordinates, unobscuredRectInContentCoordinates, futureUnobscuredRectInSelfCoordinates, targetScale, newOrientation);
2662     if (WebKit::DrawingAreaProxy* drawingArea = _page->drawingArea())
2663         drawingArea->setSize(WebCore::IntSize(newBounds.size), WebCore::IntSize(), WebCore::IntSize());
2664 }
2665
2666 - (void)_endAnimatedResize
2667 {
2668     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing)
2669         return;
2670
2671     _page->synchronizeDynamicViewportUpdate();
2672
2673     NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
2674     [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
2675     [_scrollView insertSubview:[_contentView unscaledView] atIndex:indexOfResizeAnimationView + 1];
2676
2677     CALayer *contentViewLayer = [_contentView layer];
2678     CGFloat adjustmentScale = _resizeAnimationTransformAdjustments.m11;
2679     contentViewLayer.sublayerTransform = CATransform3DIdentity;
2680
2681     CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
2682     CALayer *contentLayer = [_contentView layer];
2683     CATransform3D contentLayerTransform = contentLayer.transform;
2684     CGFloat currentScale = [[_resizeAnimationView layer] transform].m11 * contentLayerTransform.m11;
2685
2686     // We cannot use [UIScrollView setZoomScale:] directly because the UIScrollView delegate would get a callback with
2687     // an invalid contentOffset. The real content offset is only set below.
2688     // Since there is no public API for setting both the zoomScale and the contentOffset, we set the zoomScale manually
2689     // on the zoom layer and then only change the contentOffset.
2690     CGFloat adjustedScale = adjustmentScale * currentScale;
2691     contentLayerTransform.m11 = adjustedScale;
2692     contentLayerTransform.m22 = adjustedScale;
2693     contentLayer.transform = contentLayerTransform;
2694
2695     CGPoint currentScrollOffset = [_scrollView contentOffset];
2696     double horizontalScrollAdjustement = _resizeAnimationTransformAdjustments.m41 * animatingScaleTarget;
2697     double verticalScrollAdjustment = _resizeAnimationTransformAdjustments.m42 * animatingScaleTarget;
2698
2699     [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
2700     [_scrollView setContentOffset:CGPointMake(currentScrollOffset.x - horizontalScrollAdjustement, currentScrollOffset.y - verticalScrollAdjustment)];
2701
2702     [_resizeAnimationView removeFromSuperview];
2703     _resizeAnimationView = nil;
2704     _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2705
2706     _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing;
2707     [_contentView setHidden:NO];
2708     [self _updateVisibleContentRects];
2709 }
2710
2711 - (void)_resizeWhileHidingContentWithUpdates:(void (^)(void))updateBlock
2712 {
2713     [self _beginAnimatedResizeWithUpdates:updateBlock];
2714     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::ResizingWithAnimation) {
2715         [_contentView setHidden:YES];
2716         _dynamicViewportUpdateMode = DynamicViewportUpdateMode::ResizingWithDocumentHidden;
2717     }
2718 }
2719
2720 - (void)_setOverlaidAccessoryViewsInset:(CGSize)inset
2721 {
2722     [_customContentView web_setOverlaidAccessoryViewsInset:inset];
2723 }
2724
2725 - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler
2726 {
2727     CGRect snapshotRectInContentCoordinates = [self convertRect:rectInViewCoordinates toView:self._currentContentView];
2728     CGFloat imageScale = imageWidth / snapshotRectInContentCoordinates.size.width;
2729     CGFloat imageHeight = imageScale * snapshotRectInContentCoordinates.size.height;
2730     CGSize imageSize = CGSizeMake(imageWidth, imageHeight);
2731
2732 #if USE(IOSURFACE)
2733     // If we are parented and thus won't incur a significant penalty from paging in tiles, snapshot the view hierarchy directly.
2734     if (self.window) {
2735         auto surface = WebCore::IOSurface::create(WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebCore::ColorSpaceDeviceRGB);
2736         CGFloat imageScaleInViewCoordinates = imageWidth / rectInViewCoordinates.size.width;
2737         CATransform3D transform = CATransform3DMakeScale(imageScaleInViewCoordinates, imageScaleInViewCoordinates, 1);
2738         transform = CATransform3DTranslate(transform, -rectInViewCoordinates.origin.x, -rectInViewCoordinates.origin.y, 0);
2739         CARenderServerRenderLayerWithTransform(MACH_PORT_NULL, self.layer.context.contextId, reinterpret_cast<uint64_t>(self.layer), surface->surface(), 0, 0, &transform);
2740         completionHandler(surface->createImage().get());
2741
2742         return;
2743     }
2744 #endif
2745     
2746     if (_customContentView) {
2747         UIGraphicsBeginImageContextWithOptions(imageSize, YES, 1);
2748
2749         UIView *customContentView = _customContentView.get();
2750         [customContentView.backgroundColor set];
2751         UIRectFill(CGRectMake(0, 0, imageWidth, imageHeight));
2752
2753         CGContextRef context = UIGraphicsGetCurrentContext();
2754         CGContextTranslateCTM(context, -snapshotRectInContentCoordinates.origin.x * imageScale, -snapshotRectInContentCoordinates.origin.y * imageScale);
2755         CGContextScaleCTM(context, imageScale, imageScale);
2756         [customContentView.layer renderInContext:context];
2757
2758         completionHandler([UIGraphicsGetImageFromCurrentImageContext() CGImage]);
2759
2760         UIGraphicsEndImageContext();
2761         return;
2762     }
2763
2764
2765     void(^copiedCompletionHandler)(CGImageRef) = [completionHandler copy];
2766     _page->takeSnapshot(WebCore::enclosingIntRect(snapshotRectInContentCoordinates), WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebKit::SnapshotOptionsExcludeDeviceScaleFactor, [=](const WebKit::ShareableBitmap::Handle& imageHandle, WebKit::CallbackBase::Error) {
2767         if (imageHandle.isNull()) {
2768             copiedCompletionHandler(nullptr);
2769             [copiedCompletionHandler release];
2770             return;
2771         }
2772
2773         RefPtr<WebKit::ShareableBitmap> bitmap = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::Protection::ReadOnly);
2774
2775         if (!bitmap) {
2776             copiedCompletionHandler(nullptr);
2777             [copiedCompletionHandler release];
2778             return;
2779         }
2780
2781         RetainPtr<CGImageRef> cgImage;
2782         cgImage = bitmap->makeCGImage();
2783         copiedCompletionHandler(cgImage.get());
2784         [copiedCompletionHandler release];
2785     });
2786 }
2787
2788 - (void)_overrideLayoutParametersWithMinimumLayoutSize:(CGSize)minimumLayoutSize minimumLayoutSizeForMinimalUI:(CGSize)minimumLayoutSizeForMinimalUI maximumUnobscuredSizeOverride:(CGSize)maximumUnobscuredSizeOverride
2789 {
2790     UNUSED_PARAM(minimumLayoutSizeForMinimalUI);
2791     [self _overrideLayoutParametersWithMinimumLayoutSize:minimumLayoutSize maximumUnobscuredSizeOverride:maximumUnobscuredSizeOverride];
2792 }
2793
2794 - (void)_overrideLayoutParametersWithMinimumLayoutSize:(CGSize)minimumLayoutSize maximumUnobscuredSizeOverride:(CGSize)maximumUnobscuredSizeOverride
2795 {
2796     [self _setMinimumLayoutSizeOverride:minimumLayoutSize];
2797     [self _setMaximumUnobscuredSizeOverride:maximumUnobscuredSizeOverride];
2798 }
2799
2800 - (UIView *)_viewForFindUI
2801 {
2802     return [self viewForZoomingInScrollView:[self scrollView]];
2803 }
2804
2805 - (BOOL)_isDisplayingPDF
2806 {
2807     return [_customContentView isKindOfClass:[WKPDFView class]];
2808 }
2809
2810 - (NSData *)_dataForDisplayedPDF
2811 {
2812     if (![self _isDisplayingPDF])
2813         return nil;
2814     CGPDFDocumentRef pdfDocument = [(WKPDFView *)_customContentView pdfDocument];
2815     return [(NSData *)CGDataProviderCopyData(CGPDFDocumentGetDataProvider(pdfDocument)) autorelease];
2816 }
2817
2818 - (NSString *)_suggestedFilenameForDisplayedPDF
2819 {
2820     if (![self _isDisplayingPDF])
2821         return nil;
2822     return [(WKPDFView *)_customContentView.get() suggestedFilename];
2823 }
2824
2825 - (CGFloat)_viewportMetaTagWidth
2826 {
2827     return _viewportMetaTagWidth;
2828 }
2829
2830 - (_WKWebViewPrintFormatter *)_webViewPrintFormatter
2831 {
2832     UIViewPrintFormatter *viewPrintFormatter = self.viewPrintFormatter;
2833     ASSERT([viewPrintFormatter isKindOfClass:[_WKWebViewPrintFormatter class]]);
2834     return (_WKWebViewPrintFormatter *)viewPrintFormatter;
2835 }
2836
2837 - (BOOL)_allowsLinkPreview
2838 {
2839     return _allowsLinkPreview;
2840 }
2841
2842 - (void)_setAllowsLinkPreview:(BOOL)allowsLinkPreview
2843 {
2844     if (_allowsLinkPreview == allowsLinkPreview)
2845         return;
2846
2847     _allowsLinkPreview = allowsLinkPreview;
2848 #if HAVE(LINK_PREVIEW)
2849     if (_allowsLinkPreview)
2850         [_contentView _registerPreviewInWindow:[_contentView window]];
2851     else
2852         [_contentView _unregisterPreviewInWindow:[_contentView window]];
2853 #endif
2854 }
2855
2856 #else
2857
2858 #pragma mark - OS X-specific methods
2859
2860 - (NSColor *)_pageExtendedBackgroundColor
2861 {
2862     WebCore::Color color = _page->pageExtendedBackgroundColor();
2863     if (!color.isValid())
2864         return nil;
2865
2866     return nsColor(color);
2867 }
2868
2869 - (BOOL)_drawsTransparentBackground
2870 {
2871     return _page->drawsTransparentBackground();
2872 }
2873
2874 - (void)_setDrawsTransparentBackground:(BOOL)drawsTransparentBackground
2875 {
2876     _page->setDrawsTransparentBackground(drawsTransparentBackground);
2877 }
2878
2879 - (void)_setOverrideDeviceScaleFactor:(CGFloat)deviceScaleFactor
2880 {
2881     [_wkView _setOverrideDeviceScaleFactor:deviceScaleFactor];
2882 }
2883
2884 - (CGFloat)_overrideDeviceScaleFactor
2885 {
2886     return [_wkView _overrideDeviceScaleFactor];
2887 }
2888
2889 - (void)_setTopContentInset:(CGFloat)contentInset
2890 {
2891     [_wkView _setTopContentInset:contentInset];
2892 }
2893
2894 - (CGFloat)_topContentInset
2895 {
2896     return [_wkView _topContentInset];
2897 }
2898
2899 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
2900
2901 - (void)_setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets
2902 {
2903     [_wkView _setAutomaticallyAdjustsContentInsets:automaticallyAdjustsContentInsets];
2904 }
2905
2906 - (BOOL)_automaticallyAdjustsContentInsets
2907 {
2908     return [_wkView _automaticallyAdjustsContentInsets];
2909 }
2910
2911 #endif
2912
2913 #endif
2914
2915 @end
2916
2917 #if !TARGET_OS_IPHONE
2918
2919 @implementation WKWebView (WKIBActions)
2920
2921 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
2922 {
2923     SEL action = item.action;
2924
2925     if (action == @selector(goBack:))
2926         return !!_page->backForwardList().backItem();
2927
2928     if (action == @selector(goForward:))
2929         return !!_page->backForwardList().forwardItem();
2930
2931     if (action == @selector(stopLoading:)) {
2932         // FIXME: Return no if we're stopped.
2933         return YES;
2934     }
2935
2936     if (action == @selector(reload:) || action == @selector(reloadFromOrigin:)) {
2937         // FIXME: Return no if we're loading.
2938         return YES;
2939     }
2940
2941     return NO;
2942 }
2943
2944 - (IBAction)goBack:(id)sender
2945 {
2946     [self goBack];
2947 }
2948
2949 - (IBAction)goForward:(id)sender
2950 {
2951     [self goForward];
2952 }
2953
2954 - (IBAction)reload:(id)sender
2955 {
2956     [self reload];
2957 }
2958
2959 - (IBAction)reloadFromOrigin:(id)sender
2960 {
2961     [self reloadFromOrigin];
2962 }
2963
2964 - (IBAction)stopLoading:(id)sender
2965 {
2966     _page->stopLoading();
2967 }
2968
2969 @end
2970
2971 #endif
2972
2973 #if PLATFORM(IOS)
2974 @implementation WKWebView (_WKWebViewPrintFormatter)
2975
2976 - (Class)_printFormatterClass
2977 {
2978     return [_WKWebViewPrintFormatter class];
2979 }
2980
2981 - (NSInteger)_computePageCountAndStartDrawingToPDFForFrame:(_WKFrameHandle *)frame printInfo:(const WebKit::PrintInfo&)printInfo firstPage:(uint32_t)firstPage computedTotalScaleFactor:(double&)totalScaleFactor
2982 {
2983     if ([self _isDisplayingPDF])
2984         return CGPDFDocumentGetNumberOfPages([(WKPDFView *)_customContentView pdfDocument]);
2985
2986     _pageIsPrintingToPDF = YES;
2987     Vector<WebCore::IntRect> pageRects;
2988     uint64_t frameID = frame ? frame._frameID : _page->mainFrame()->frameID();
2989     if (!_page->sendSync(Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF(frameID, printInfo, firstPage), Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF::Reply(pageRects, totalScaleFactor)))
2990         return 0;
2991     return pageRects.size();
2992 }
2993
2994 - (void)_endPrinting
2995 {
2996     _pageIsPrintingToPDF = NO;
2997     _printedDocument = nullptr;
2998     _page->send(Messages::WebPage::EndPrinting());
2999 }
3000
3001 // FIXME: milliseconds::max() overflows when converted to nanoseconds, causing condition_variable::wait_for() to believe
3002 // a timeout occurred on any spurious wakeup. Use nanoseconds::max() (converted to ms) to avoid this. We should perhaps
3003 // change waitForAndDispatchImmediately() to take nanoseconds to avoid this issue.
3004 static constexpr std::chrono::milliseconds didFinishLoadingTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds::max());
3005
3006 - (CGPDFDocumentRef)_printedDocument
3007 {
3008     if ([self _isDisplayingPDF]) {
3009         ASSERT(!_pageIsPrintingToPDF);
3010         return [(WKPDFView *)_customContentView pdfDocument];
3011     }
3012
3013     if (_pageIsPrintingToPDF) {
3014         if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::DidFinishDrawingPagesToPDF>(_page->pageID(), didFinishLoadingTimeout)) {
3015             ASSERT_NOT_REACHED();
3016             return nullptr;
3017         }
3018         ASSERT(!_pageIsPrintingToPDF);
3019     }
3020     return _printedDocument.get();
3021 }
3022
3023 - (void)_setPrintedDocument:(CGPDFDocumentRef)printedDocument
3024 {
3025     if (!_pageIsPrintingToPDF)
3026         return;
3027     ASSERT(![self _isDisplayingPDF]);
3028     _printedDocument = printedDocument;
3029     _pageIsPrintingToPDF = NO;
3030 }
3031
3032 @end
3033 #endif
3034
3035 #endif // WK_API_ENABLED