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