Nearly everything in the UIProcess "leaks" when WKWebView is torn down
[WebKit-https.git] / Source / WebKit2 / UIProcess / API / Cocoa / WKWebView.mm
1 /*
2  * Copyright (C) 2014 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 "FindClient.h"
33 #import "LegacySessionStateCoding.h"
34 #import "NavigationState.h"
35 #import "RemoteLayerTreeTransaction.h"
36 #import "RemoteObjectRegistry.h"
37 #import "RemoteObjectRegistryMessages.h"
38 #import "UIDelegate.h"
39 #import "ViewGestureController.h"
40 #import "ViewSnapshotStore.h"
41 #import "WKBackForwardListInternal.h"
42 #import "WKBackForwardListItemInternal.h"
43 #import "WKBrowsingContextHandleInternal.h"
44 #import "WKErrorInternal.h"
45 #import "WKHistoryDelegatePrivate.h"
46 #import "WKNSData.h"
47 #import "WKNSURLExtras.h"
48 #import "WKNavigationDelegate.h"
49 #import "WKNavigationInternal.h"
50 #import "WKPreferencesInternal.h"
51 #import "WKProcessPoolInternal.h"
52 #import "WKUIDelegate.h"
53 #import "WKUIDelegatePrivate.h"
54 #import "WKUserContentControllerInternal.h"
55 #import "WKWebViewConfigurationInternal.h"
56 #import "WKWebViewContentProvider.h"
57 #import "WebBackForwardList.h"
58 #import "WebCertificateInfo.h"
59 #import "WebContext.h"
60 #import "WebFormSubmissionListenerProxy.h"
61 #import "WebKitSystemInterface.h"
62 #import "WebPageGroup.h"
63 #import "WebPageProxy.h"
64 #import "WebPreferencesKeys.h"
65 #import "WebProcessProxy.h"
66 #import "WebSerializedScriptValue.h"
67 #import "_WKFindDelegate.h"
68 #import "_WKFormDelegate.h"
69 #import "_WKRemoteObjectRegistryInternal.h"
70 #import "_WKSessionStateInternal.h"
71 #import "_WKVisitedLinkProviderInternal.h"
72 #import "_WKWebsiteDataStoreInternal.h"
73 #import <JavaScriptCore/JSContext.h>
74 #import <JavaScriptCore/JSValue.h>
75 #import <wtf/HashMap.h>
76 #import <wtf/NeverDestroyed.h>
77 #import <wtf/RetainPtr.h>
78
79 #if PLATFORM(IOS)
80 #import "_WKFrameHandleInternal.h"
81 #import "_WKWebViewPrintFormatter.h"
82 #import "PrintInfo.h"
83 #import "ProcessThrottler.h"
84 #import "RemoteLayerTreeDrawingAreaProxy.h"
85 #import "WKPDFView.h"
86 #import "WKScrollView.h"
87 #import "WKWebViewContentProviderRegistry.h"
88 #import "WebPageMessages.h"
89 #import <CoreGraphics/CGFloat.h>
90 #import <CoreGraphics/CGPDFDocumentPrivate.h>
91 #import <UIKit/UIApplication.h>
92 #import <UIKit/UIDevice_Private.h>
93 #import <UIKit/UIPeripheralHost_Private.h>
94 #import <UIKit/UIWindow_Private.h>
95 #import <QuartzCore/CARenderServer.h>
96 #import <QuartzCore/QuartzCorePrivate.h>
97 #import <WebCore/InspectorOverlay.h>
98
99 @interface UIScrollView (UIScrollViewInternal)
100 - (void)_adjustForAutomaticKeyboardInfo:(NSDictionary*)info animated:(BOOL)animated lastAdjustment:(CGFloat*)lastAdjustment;
101 - (BOOL)_isScrollingToTop;
102 @end
103
104 @interface UIPeripheralHost(UIKitInternal)
105 - (CGFloat)getVerticalOverlapForView:(UIView *)view usingKeyboardInfo:(NSDictionary *)info;
106 @end
107
108 @interface UIView (UIViewInternal)
109 - (UIViewController *)_viewControllerForAncestor;
110 @end
111
112 @interface UIWindow (UIWindowInternal)
113 - (BOOL)_isHostedInAnotherProcess;
114 @end
115
116 @interface UIViewController (UIViewControllerInternal)
117 - (UIViewController *)_rootAncestorViewController;
118 - (UIViewController *)_viewControllerForSupportedInterfaceOrientations;
119 @end
120 #endif
121
122 #if PLATFORM(MAC)
123 #import "WKViewInternal.h"
124 #import <WebCore/ColorMac.h>
125 #endif
126
127
128 static HashMap<WebKit::WebPageProxy*, WKWebView *>& pageToViewMap()
129 {
130     static NeverDestroyed<HashMap<WebKit::WebPageProxy*, WKWebView *>> map;
131     return map;
132 }
133
134 WKWebView* fromWebPageProxy(WebKit::WebPageProxy& page)
135 {
136     return pageToViewMap().get(&page);
137 }
138
139 @implementation WKWebView {
140     std::unique_ptr<WebKit::NavigationState> _navigationState;
141     std::unique_ptr<WebKit::UIDelegate> _uiDelegate;
142
143     RetainPtr<_WKRemoteObjectRegistry> _remoteObjectRegistry;
144     _WKRenderingProgressEvents _observedRenderingProgressEvents;
145
146     WebKit::WeakObjCPtr<id <_WKFormDelegate>> _formDelegate;
147 #if PLATFORM(IOS)
148     RetainPtr<WKScrollView> _scrollView;
149     RetainPtr<WKContentView> _contentView;
150
151     BOOL _overridesMinimumLayoutSize;
152     CGSize _minimumLayoutSizeOverride;
153     BOOL _overridesMinimumLayoutSizeForMinimalUI;
154     CGSize _minimumLayoutSizeOverrideForMinimalUI;
155     BOOL _overridesMaximumUnobscuredSize;
156     CGSize _maximumUnobscuredSizeOverride;
157     BOOL _usesMinimalUI;
158     BOOL _needsToNotifyDelegateAboutMinimalUI;
159     CGRect _inputViewBounds;
160     CGFloat _viewportMetaTagWidth;
161
162     UIEdgeInsets _obscuredInsets;
163     bool _isChangingObscuredInsetsInteractively;
164
165     UIInterfaceOrientation _interfaceOrientationOverride;
166     BOOL _overridesInterfaceOrientation;
167
168     BOOL _needsResetViewStateAfterCommitLoadForMainFrame;
169     uint64_t _firstPaintAfterCommitLoadTransactionID;
170     BOOL _isAnimatingResize;
171     CATransform3D _resizeAnimationTransformAdjustments;
172     RetainPtr<UIView> _resizeAnimationView;
173     CGFloat _lastAdjustmentForScroller;
174
175     BOOL _needsToRestoreExposedRect;
176     WebCore::FloatRect _exposedRectToRestore;
177     BOOL _needsToRestoreUnobscuredCenter;
178     WebCore::FloatPoint _unobscuredCenterToRestore;
179     uint64_t _firstTransactionIDAfterPageRestore;
180     double _scaleToRestore;
181
182     std::unique_ptr<WebKit::ViewGestureController> _gestureController;
183     BOOL _allowsBackForwardNavigationGestures;
184
185     RetainPtr<UIView <WKWebViewContentProvider>> _customContentView;
186     RetainPtr<UIView> _customContentFixedOverlayView;
187
188     WebCore::Color _scrollViewBackgroundColor;
189
190     BOOL _delayUpdateVisibleContentRects;
191     BOOL _hadDelayedUpdateVisibleContentRects;
192
193     BOOL _pageIsPrintingToPDF;
194     RetainPtr<CGPDFDocumentRef> _printedDocument;
195 #endif
196 #if PLATFORM(MAC)
197     RetainPtr<WKView> _wkView;
198 #endif
199 }
200
201 - (instancetype)initWithFrame:(CGRect)frame
202 {
203     return [self initWithFrame:frame configuration:adoptNS([[WKWebViewConfiguration alloc] init]).get()];
204 }
205
206 #if PLATFORM(IOS)
207 static int32_t deviceOrientationForUIInterfaceOrientation(UIInterfaceOrientation orientation)
208 {
209     switch (orientation) {
210     case UIInterfaceOrientationUnknown:
211     case UIInterfaceOrientationPortrait:
212         return 0;
213     case UIInterfaceOrientationPortraitUpsideDown:
214         return 180;
215     case UIInterfaceOrientationLandscapeLeft:
216         return -90;
217     case UIInterfaceOrientationLandscapeRight:
218         return 90;
219     }
220 }
221
222 static int32_t deviceOrientation()
223 {
224     return deviceOrientationForUIInterfaceOrientation([[UIApplication sharedApplication] statusBarOrientation]);
225 }
226 #endif
227
228 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
229 {
230     if (!(self = [super initWithFrame:frame]))
231         return nil;
232
233     _configuration = adoptNS([configuration copy]);
234
235     if (WKWebView *relatedWebView = [_configuration _relatedWebView]) {
236         WKProcessPool *processPool = [_configuration processPool];
237         WKProcessPool *relatedWebViewProcessPool = [relatedWebView->_configuration processPool];
238         if (processPool && processPool != relatedWebViewProcessPool)
239             [NSException raise:NSInvalidArgumentException format:@"Related web view %@ has process pool %@ but configuration specifies a different process pool %@", relatedWebView, relatedWebViewProcessPool, configuration.processPool];
240
241         [_configuration setProcessPool:relatedWebViewProcessPool];
242     }
243
244     [_configuration _validate];
245
246     CGRect bounds = self.bounds;
247
248     WebKit::WebContext& context = *[_configuration processPool]->_context;
249
250     WebKit::WebPageConfiguration webPageConfiguration;
251     webPageConfiguration.preferences = [_configuration preferences]->_preferences.get();
252     if (WKWebView *relatedWebView = [_configuration _relatedWebView])
253         webPageConfiguration.relatedPage = relatedWebView->_page.get();
254
255     webPageConfiguration.userContentController = [_configuration userContentController]->_userContentControllerProxy.get();
256     webPageConfiguration.visitedLinkProvider = [_configuration _visitedLinkProvider]->_visitedLinkProvider.get();
257     webPageConfiguration.session = [_configuration _websiteDataStore]->_session.get();
258     
259     RefPtr<WebKit::WebPageGroup> pageGroup;
260     NSString *groupIdentifier = configuration._groupIdentifier;
261     if (groupIdentifier.length) {
262         pageGroup = WebKit::WebPageGroup::create(configuration._groupIdentifier);
263         webPageConfiguration.pageGroup = pageGroup.get();
264     }
265
266     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::suppressesIncrementalRenderingKey(), WebKit::WebPreferencesStore::Value(!![_configuration suppressesIncrementalRendering]));
267
268 #if PLATFORM(IOS)
269     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::mediaPlaybackAllowsInlineKey(), WebKit::WebPreferencesStore::Value(!![_configuration allowsInlineMediaPlayback]));
270     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::mediaPlaybackRequiresUserGestureKey(), WebKit::WebPreferencesStore::Value(!![_configuration mediaPlaybackRequiresUserAction]));
271     webPageConfiguration.preferenceValues.set(WebKit::WebPreferencesKey::mediaPlaybackAllowsAirPlayKey(), WebKit::WebPreferencesStore::Value(!![_configuration mediaPlaybackAllowsAirPlay]));
272 #endif
273
274 #if PLATFORM(IOS)
275     _scrollView = adoptNS([[WKScrollView alloc] initWithFrame:bounds]);
276     [_scrollView setInternalDelegate:self];
277     [_scrollView setBouncesZoom:YES];
278
279     [self addSubview:_scrollView.get()];
280     [_scrollView setBackgroundColor:[UIColor whiteColor]];
281
282     _contentView = adoptNS([[WKContentView alloc] initWithFrame:bounds context:context configuration:WTF::move(webPageConfiguration) webView:self]);
283
284     _page = [_contentView page];
285     _page->setApplicationNameForUserAgent([@"Mobile/" stringByAppendingString:[UIDevice currentDevice].buildVersion]);
286     _page->setDeviceOrientation(deviceOrientation());
287
288     [_contentView layer].anchorPoint = CGPointZero;
289     [_contentView setFrame:bounds];
290     [_scrollView addSubview:_contentView.get()];
291     _viewportMetaTagWidth = -1;
292
293     [self _frameOrBoundsChanged];
294
295     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
296     [center addObserver:self selector:@selector(_keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
297     [center addObserver:self selector:@selector(_keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];
298     [center addObserver:self selector:@selector(_keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
299     [center addObserver:self selector:@selector(_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
300     [center addObserver:self selector:@selector(_windowDidRotate:) name:UIWindowDidRotateNotification object:nil];
301     [center addObserver:self selector:@selector(_contentSizeCategoryDidChange:) name:UIContentSizeCategoryDidChangeNotification object:nil];
302     _page->contentSizeCategoryDidChange([self _contentSizeCategory]);
303
304     [[_configuration _contentProviderRegistry] addPage:*_page];
305 #endif
306
307 #if PLATFORM(MAC)
308     _wkView = adoptNS([[WKView alloc] initWithFrame:bounds context:context configuration:WTF::move(webPageConfiguration) webView:self]);
309     [self addSubview:_wkView.get()];
310     _page = WebKit::toImpl([_wkView pageRef]);
311
312 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
313     [_wkView _setAutomaticallyAdjustsContentInsets:YES];
314 #endif
315 #endif
316
317     _page->setBackgroundExtendsBeyondPage(true);
318
319     _navigationState = std::make_unique<WebKit::NavigationState>(self);
320     _page->setPolicyClient(_navigationState->createPolicyClient());
321     _page->setLoaderClient(_navigationState->createLoaderClient());
322
323     _uiDelegate = std::make_unique<WebKit::UIDelegate>(self);
324     _page->setUIClient(_uiDelegate->createUIClient());
325
326     _page->setFindClient(std::make_unique<WebKit::FindClient>(self));
327
328     pageToViewMap().add(_page.get(), self);
329
330     return self;
331 }
332
333 - (instancetype)initWithCoder:(NSCoder *)coder
334 {
335     [self release];
336     return nil;
337 }
338
339 - (void)dealloc
340 {
341     _page->close();
342
343     [_remoteObjectRegistry _invalidate];
344 #if PLATFORM(IOS)
345     [[_configuration _contentProviderRegistry] removePage:*_page];
346     [[NSNotificationCenter defaultCenter] removeObserver:self];
347 #endif
348
349     pageToViewMap().remove(_page.get());
350
351     [super dealloc];
352 }
353
354 - (WKWebViewConfiguration *)configuration
355 {
356     return [[_configuration copy] autorelease];
357 }
358
359 - (WKBackForwardList *)backForwardList
360 {
361     return wrapper(_page->backForwardList());
362 }
363
364 - (id <WKNavigationDelegate>)navigationDelegate
365 {
366     return _navigationState->navigationDelegate().autorelease();
367 }
368
369 - (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate
370 {
371     _navigationState->setNavigationDelegate(navigationDelegate);
372 }
373
374 - (id <WKUIDelegate>)UIDelegate
375 {
376     return _uiDelegate->delegate().autorelease();
377 }
378
379 - (void)setUIDelegate:(id<WKUIDelegate>)UIDelegate
380 {
381     _uiDelegate->setDelegate(UIDelegate);
382 }
383
384 - (WKNavigation *)loadRequest:(NSURLRequest *)request
385 {
386     uint64_t navigationID = _page->loadRequest(request);
387     auto navigation = _navigationState->createLoadRequestNavigation(navigationID, request);
388
389     return navigation.autorelease();
390 }
391
392 - (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL
393 {
394     uint64_t navigationID = _page->loadHTMLString(string, baseURL.absoluteString);
395     auto navigation = _navigationState->createLoadDataNavigation(navigationID);
396
397     return navigation.autorelease();
398 }
399
400 - (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item
401 {
402     uint64_t navigationID = _page->goToBackForwardItem(&item._item);
403
404     auto navigation = _navigationState->createBackForwardNavigation(navigationID, item._item);
405
406     return navigation.autorelease();
407 }
408
409 - (NSString *)title
410 {
411     return _page->pageLoadState().title();
412 }
413
414 - (NSURL *)URL
415 {
416     return [NSURL _web_URLWithWTFString:_page->pageLoadState().activeURL()];
417 }
418
419 - (BOOL)isLoading
420 {
421     return _page->pageLoadState().isLoading();
422 }
423
424 - (double)estimatedProgress
425 {
426     return _page->pageLoadState().estimatedProgress();
427 }
428
429 - (BOOL)hasOnlySecureContent
430 {
431     return _page->pageLoadState().hasOnlySecureContent();
432 }
433
434 - (BOOL)canGoBack
435 {
436     return _page->pageLoadState().canGoBack();
437 }
438
439 - (BOOL)canGoForward
440 {
441     return _page->pageLoadState().canGoForward();
442 }
443
444 - (WKNavigation *)goBack
445 {
446     uint64_t navigationID = _page->goBack();
447     if (!navigationID)
448         return nil;
449
450     ASSERT(_page->backForwardList().currentItem());
451     auto navigation = _navigationState->createBackForwardNavigation(navigationID, *_page->backForwardList().currentItem());
452
453     return navigation.autorelease();
454 }
455
456 - (WKNavigation *)goForward
457 {
458     uint64_t navigationID = _page->goForward();
459     if (!navigationID)
460         return nil;
461
462     ASSERT(_page->backForwardList().currentItem());
463     auto navigation = _navigationState->createBackForwardNavigation(navigationID, *_page->backForwardList().currentItem());
464
465     return navigation.autorelease();
466 }
467
468 - (WKNavigation *)reload
469 {
470     uint64_t navigationID = _page->reload(false);
471     ASSERT(navigationID);
472
473     auto navigation = _navigationState->createReloadNavigation(navigationID);
474     return navigation.autorelease();
475 }
476
477 - (WKNavigation *)reloadFromOrigin
478 {
479     uint64_t navigationID = _page->reload(true);
480     ASSERT(navigationID);
481
482     auto navigation = _navigationState->createReloadNavigation(navigationID);
483     return navigation.autorelease();
484 }
485
486 - (void)stopLoading
487 {
488     _page->stopLoading();
489 }
490
491 static WKErrorCode callbackErrorCode(WebKit::CallbackBase::Error error)
492 {
493     switch (error) {
494     case WebKit::CallbackBase::Error::None:
495         ASSERT_NOT_REACHED();
496         return WKErrorUnknown;
497
498     case WebKit::CallbackBase::Error::Unknown:
499         return WKErrorUnknown;
500
501     case WebKit::CallbackBase::Error::ProcessExited:
502         return WKErrorWebContentProcessTerminated;
503
504     case WebKit::CallbackBase::Error::OwnerWasInvalidated:
505         return WKErrorWebViewInvalidated;
506     }
507 }
508
509 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler
510 {
511     auto handler = adoptNS([completionHandler copy]);
512
513     _page->runJavaScriptInMainFrame(javaScriptString, [handler](WebKit::WebSerializedScriptValue* serializedScriptValue, WebKit::ScriptValueCallback::Error errorCode) {
514         if (!handler)
515             return;
516
517         auto completionHandler = (void (^)(id, NSError *))handler.get();
518
519         if (errorCode != WebKit::ScriptValueCallback::Error::None) {
520             auto error = createNSError(callbackErrorCode(errorCode));
521             if (errorCode == WebKit::ScriptValueCallback::Error::OwnerWasInvalidated) {
522                 // The OwnerWasInvalidated callback is synchronous. We don't want to call the block from within it
523                 // because that can trigger re-entrancy bugs in WebKit.
524                 // FIXME: It would be even better if GenericCallback did this for us.
525                 dispatch_async(dispatch_get_main_queue(), [completionHandler, error] {
526                     completionHandler(nil, error.get());
527                 });
528                 return;
529             }
530
531             completionHandler(nil, error.get());
532             return;
533         }
534
535         if (!serializedScriptValue) {
536             completionHandler(nil, createNSError(WKErrorJavaScriptExceptionOccurred).get());
537             return;
538         }
539
540         auto context = adoptNS([[JSContext alloc] init]);
541         JSValueRef valueRef = serializedScriptValue->deserialize([context JSGlobalContextRef], 0);
542         JSValue *value = [JSValue valueWithJSValueRef:valueRef inContext:context.get()];
543
544         completionHandler([value toObject], nil);
545     });
546 }
547
548 #pragma mark iOS-specific methods
549
550 #if PLATFORM(IOS)
551 - (void)setFrame:(CGRect)frame
552 {
553     CGRect oldFrame = self.frame;
554     [super setFrame:frame];
555
556     if (!CGSizeEqualToSize(oldFrame.size, frame.size))
557         [self _frameOrBoundsChanged];
558 }
559
560 - (void)setBounds:(CGRect)bounds
561 {
562     CGRect oldBounds = self.bounds;
563     [super setBounds:bounds];
564     [_customContentFixedOverlayView setFrame:self.bounds];
565
566     if (!CGSizeEqualToSize(oldBounds.size, bounds.size))
567         [self _frameOrBoundsChanged];
568 }
569
570 - (UIScrollView *)scrollView
571 {
572     return _scrollView.get();
573 }
574
575 - (WKBrowsingContextController *)browsingContextController
576 {
577     return [_contentView browsingContextController];
578 }
579
580 static inline CGFloat floorToDevicePixel(CGFloat input, float deviceScaleFactor)
581 {
582     return CGFloor(input * deviceScaleFactor) / deviceScaleFactor;
583 }
584
585 static CGSize roundScrollViewContentSize(const WebKit::WebPageProxy& page, CGSize contentSize)
586 {
587     float deviceScaleFactor = page.deviceScaleFactor();
588     return CGSizeMake(floorToDevicePixel(contentSize.width, deviceScaleFactor), floorToDevicePixel(contentSize.height, deviceScaleFactor));
589 }
590
591 - (UIView *)_currentContentView
592 {
593     return _customContentView ? _customContentView.get() : _contentView.get();
594 }
595
596 - (void)_setHasCustomContentView:(BOOL)pageHasCustomContentView loadedMIMEType:(const WTF::String&)mimeType
597 {
598     if (pageHasCustomContentView) {
599         [_customContentView removeFromSuperview];
600         [_customContentFixedOverlayView removeFromSuperview];
601
602         Class representationClass = [[_configuration _contentProviderRegistry] providerForMIMEType:mimeType];
603         ASSERT(representationClass);
604         _customContentView = adoptNS([[representationClass alloc] web_initWithFrame:self.bounds webView:self]);
605         _customContentFixedOverlayView = adoptNS([[UIView alloc] initWithFrame:self.bounds]);
606         [_customContentFixedOverlayView setUserInteractionEnabled:NO];
607
608         [_contentView removeFromSuperview];
609         [_scrollView addSubview:_customContentView.get()];
610         [self addSubview:_customContentFixedOverlayView.get()];
611
612         [_customContentView web_setMinimumSize:self.bounds.size];
613         [_customContentView web_setFixedOverlayView:_customContentFixedOverlayView.get()];
614     } else if (_customContentView) {
615         [_customContentView removeFromSuperview];
616         _customContentView = nullptr;
617
618         [_customContentFixedOverlayView removeFromSuperview];
619         _customContentFixedOverlayView = nullptr;
620
621         [_scrollView addSubview:_contentView.get()];
622         [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
623
624         [_customContentFixedOverlayView setFrame:self.bounds];
625         [self addSubview:_customContentFixedOverlayView.get()];
626     }
627 }
628
629 - (void)_didFinishLoadingDataForCustomContentProviderWithSuggestedFilename:(const String&)suggestedFilename data:(NSData *)data
630 {
631     ASSERT(_customContentView);
632     [_customContentView web_setContentProviderData:data suggestedFilename:suggestedFilename];
633 }
634
635 - (void)_setViewportMetaTagWidth:(float)newWidth
636 {
637     _viewportMetaTagWidth = newWidth;
638 }
639
640 - (void)_willInvokeUIScrollViewDelegateCallback
641 {
642     _delayUpdateVisibleContentRects = YES;
643 }
644
645 - (void)_didInvokeUIScrollViewDelegateCallback
646 {
647     _delayUpdateVisibleContentRects = NO;
648     if (_hadDelayedUpdateVisibleContentRects) {
649         _hadDelayedUpdateVisibleContentRects = NO;
650         [self _updateVisibleContentRects];
651     }
652 }
653
654 static CGFloat contentZoomScale(WKWebView* webView)
655 {
656     CGFloat scale = [[webView._currentContentView layer] affineTransform].a;
657     ASSERT(scale == [webView->_scrollView zoomScale]);
658     return scale;
659 }
660
661 - (void)_updateScrollViewBackground
662 {
663     WebCore::Color color;
664     if (_customContentView)
665         color = [_customContentView backgroundColor].CGColor;
666     else
667         color = _page->pageExtendedBackgroundColor();
668
669     CGFloat zoomScale = contentZoomScale(self);
670     CGFloat minimumZoomScale = [_scrollView minimumZoomScale];
671     if (zoomScale < minimumZoomScale) {
672         CGFloat slope = 12;
673         CGFloat opacity = std::max<CGFloat>(1 - slope * (minimumZoomScale - zoomScale), 0);
674         color = WebCore::colorWithOverrideAlpha(color.rgb(), opacity);
675     }
676
677     if (_scrollViewBackgroundColor == color)
678         return;
679
680     _scrollViewBackgroundColor = color;
681
682     auto uiBackgroundColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)]);
683     [_scrollView setBackgroundColor:uiBackgroundColor.get()];
684 }
685
686 - (void)_setUsesMinimalUI:(BOOL)usesMinimalUI
687 {
688     _usesMinimalUI = usesMinimalUI;
689     _needsToNotifyDelegateAboutMinimalUI = YES;
690 }
691
692 - (BOOL)_usesMinimalUI
693 {
694     return _usesMinimalUI;
695 }
696
697 - (CGPoint)_adjustedContentOffset:(CGPoint)point
698 {
699     CGPoint result = point;
700     UIEdgeInsets contentInset = [self _computedContentInset];
701
702     result.x -= contentInset.left;
703     result.y -= contentInset.top;
704
705     return result;
706 }
707
708 - (UIEdgeInsets)_computedContentInset
709 {
710     if (!UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, UIEdgeInsetsZero))
711         return _obscuredInsets;
712
713     return [_scrollView contentInset];
714 }
715
716 - (void)_processDidExit
717 {
718     if (!_customContentView && _isAnimatingResize) {
719         NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
720         [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
721         [_resizeAnimationView removeFromSuperview];
722         _resizeAnimationView = nil;
723
724         _isAnimatingResize = NO;
725         _resizeAnimationTransformAdjustments = CATransform3DIdentity;
726     }
727     [_contentView setFrame:self.bounds];
728     [_scrollView setBackgroundColor:[UIColor whiteColor]];
729     [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
730     [_scrollView setZoomScale:1];
731
732     _viewportMetaTagWidth = -1;
733     _needsResetViewStateAfterCommitLoadForMainFrame = NO;
734     _needsToRestoreExposedRect = NO;
735     _needsToRestoreUnobscuredCenter = NO;
736     _scrollViewBackgroundColor = WebCore::Color();
737     _delayUpdateVisibleContentRects = NO;
738     _hadDelayedUpdateVisibleContentRects = NO;
739 }
740
741 - (void)_didCommitLoadForMainFrame
742 {
743     _firstPaintAfterCommitLoadTransactionID = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
744
745     _needsResetViewStateAfterCommitLoadForMainFrame = YES;
746     _usesMinimalUI = NO;
747 }
748
749 static void changeContentOffsetBoundedInValidRange(UIScrollView *scrollView, WebCore::FloatPoint contentOffset)
750 {
751     UIEdgeInsets contentInsets = scrollView.contentInset;
752     CGSize contentSize = scrollView.contentSize;
753     CGSize scrollViewSize = scrollView.bounds.size;
754
755     float maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
756     if (contentOffset.x() > maxHorizontalOffset)
757         contentOffset.setX(maxHorizontalOffset);
758     float maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
759     if (contentOffset.y() > maxVerticalOffset)
760         contentOffset.setY(maxVerticalOffset);
761     if (contentOffset.x() < -contentInsets.left)
762         contentOffset.setX(-contentInsets.left);
763     if (contentOffset.y() < -contentInsets.top)
764         contentOffset.setY(-contentInsets.top);
765     scrollView.contentOffset = contentOffset;
766 }
767
768 - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
769 {
770     if (_customContentView)
771         return;
772
773     if (_isAnimatingResize) {
774         [_resizeAnimationView layer].sublayerTransform = _resizeAnimationTransformAdjustments;
775         return;
776     }
777
778     CGSize newContentSize = roundScrollViewContentSize(*_page, [_contentView frame].size);
779     [_scrollView _setContentSizePreservingContentOffsetDuringRubberband:newContentSize];
780
781     [_scrollView setMinimumZoomScale:layerTreeTransaction.minimumScaleFactor()];
782     [_scrollView setMaximumZoomScale:layerTreeTransaction.maximumScaleFactor()];
783     [_scrollView setZoomEnabled:layerTreeTransaction.allowsUserScaling()];
784     if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom])
785         [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()];
786
787     [self _updateScrollViewBackground];
788
789     if (_gestureController)
790         _gestureController->setRenderTreeSize(layerTreeTransaction.renderTreeSize());
791
792     if (_needsToNotifyDelegateAboutMinimalUI || _needsResetViewStateAfterCommitLoadForMainFrame) {
793         _needsToNotifyDelegateAboutMinimalUI = NO;
794
795         auto delegate = _uiDelegate->delegate();
796         if ([delegate respondsToSelector:@selector(_webView:usesMinimalUI:)])
797             [static_cast<id <WKUIDelegatePrivate>>(delegate.get()) _webView:self usesMinimalUI:_usesMinimalUI];
798     }
799
800     if (_needsResetViewStateAfterCommitLoadForMainFrame && layerTreeTransaction.transactionID() >= _firstPaintAfterCommitLoadTransactionID) {
801         _needsResetViewStateAfterCommitLoadForMainFrame = NO;
802         [_scrollView setContentOffset:[self _adjustedContentOffset:CGPointZero]];
803         [self _updateVisibleContentRects];
804     }
805
806     if (_needsToRestoreExposedRect && layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore) {
807         _needsToRestoreExposedRect = NO;
808
809         if (withinEpsilon(contentZoomScale(self), _scaleToRestore)) {
810             WebCore::FloatPoint exposedPosition = _exposedRectToRestore.location();
811             exposedPosition.scale(_scaleToRestore, _scaleToRestore);
812
813             changeContentOffsetBoundedInValidRange(_scrollView.get(), exposedPosition);
814         }
815         [self _updateVisibleContentRects];
816     }
817
818     if (_needsToRestoreUnobscuredCenter && layerTreeTransaction.transactionID() >= _firstTransactionIDAfterPageRestore) {
819         _needsToRestoreUnobscuredCenter = NO;
820
821         if (withinEpsilon(contentZoomScale(self), _scaleToRestore)) {
822             CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, _obscuredInsets);
823             WebCore::FloatSize unobscuredContentSizeAtNewScale(unobscuredRect.size.width / _scaleToRestore, unobscuredRect.size.height / _scaleToRestore);
824             WebCore::FloatPoint topLeftInDocumentCoordinate(_unobscuredCenterToRestore.x() - unobscuredContentSizeAtNewScale.width() / 2, _unobscuredCenterToRestore.y() - unobscuredContentSizeAtNewScale.height() / 2);
825
826             topLeftInDocumentCoordinate.scale(_scaleToRestore, _scaleToRestore);
827             topLeftInDocumentCoordinate.moveBy(WebCore::FloatPoint(-_obscuredInsets.left, -_obscuredInsets.top));
828
829             changeContentOffsetBoundedInValidRange(_scrollView.get(), topLeftInDocumentCoordinate);
830         }
831         [self _updateVisibleContentRects];
832     }
833 }
834
835 - (void)_dynamicViewportUpdateChangedTargetToScale:(double)newScale position:(CGPoint)newScrollPosition
836 {
837     if (_isAnimatingResize) {
838         CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
839         double currentTargetScale = animatingScaleTarget * [[_contentView layer] transform].m11;
840         double scale = newScale / currentTargetScale;
841         _resizeAnimationTransformAdjustments = CATransform3DMakeScale(scale, scale, 0);
842
843         CGPoint newContentOffset = [self _adjustedContentOffset:CGPointMake(newScrollPosition.x * newScale, newScrollPosition.y * newScale)];
844         CGPoint currentContentOffset = [_scrollView contentOffset];
845
846         _resizeAnimationTransformAdjustments.m41 = (currentContentOffset.x - newContentOffset.x) / animatingScaleTarget;
847         _resizeAnimationTransformAdjustments.m42 = (currentContentOffset.y - newContentOffset.y) / animatingScaleTarget;
848     }
849 }
850
851 - (void)_restorePageStateToExposedRect:(WebCore::FloatRect)exposedRect scale:(double)scale
852 {
853     if (_isAnimatingResize)
854         return;
855
856     if (_customContentView)
857         return;
858
859     _needsToRestoreUnobscuredCenter = NO;
860     _needsToRestoreExposedRect = YES;
861     _firstTransactionIDAfterPageRestore = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
862     _exposedRectToRestore = exposedRect;
863     _scaleToRestore = scale;
864 }
865
866 - (void)_restorePageStateToUnobscuredCenter:(WebCore::FloatPoint)center scale:(double)scale
867 {
868     if (_isAnimatingResize)
869         return;
870
871     if (_customContentView)
872         return;
873
874     _needsToRestoreExposedRect = NO;
875     _needsToRestoreUnobscuredCenter = YES;
876     _firstTransactionIDAfterPageRestore = toRemoteLayerTreeDrawingAreaProxy(_page->drawingArea())->nextLayerTreeTransactionID();
877     _unobscuredCenterToRestore = center;
878     _scaleToRestore = scale;
879 }
880
881 - (WebKit::ViewSnapshot)_takeViewSnapshot
882 {
883     float deviceScale = WKGetScreenScaleFactor();
884     CGSize snapshotSize = self.bounds.size;
885     snapshotSize.width *= deviceScale;
886     snapshotSize.height *= deviceScale;
887
888     WebKit::ViewSnapshot snapshot;
889     snapshot.slotID = [WebKit::ViewSnapshotStore::snapshottingContext() createImageSlot:snapshotSize hasAlpha:YES];
890
891     CATransform3D transform = CATransform3DMakeScale(deviceScale, deviceScale, 1);
892     CARenderServerCaptureLayerWithTransform(MACH_PORT_NULL, self.layer.context.contextId, (uint64_t)self.layer, snapshot.slotID, 0, 0, &transform);
893
894     snapshot.size = WebCore::expandedIntSize(WebCore::FloatSize(snapshotSize));
895     snapshot.imageSizeInBytes = snapshotSize.width * snapshotSize.height * 4;
896     snapshot.backgroundColor = _page->pageExtendedBackgroundColor();
897
898     return snapshot;
899 }
900
901 - (void)_zoomToPoint:(WebCore::FloatPoint)point atScale:(double)scale
902 {
903     double maximumZoomDuration = 0.4;
904     double minimumZoomDuration = 0.1;
905     double zoomDurationFactor = 0.3;
906
907     CGFloat zoomScale = contentZoomScale(self);
908     CFTimeInterval duration = std::min(fabs(log(zoomScale) - log(scale)) * zoomDurationFactor + minimumZoomDuration, maximumZoomDuration);
909
910     if (scale != zoomScale)
911         _page->willStartUserTriggeredZooming();
912
913     [_scrollView _zoomToCenter:point scale:scale duration:duration];
914 }
915
916 - (void)_zoomToRect:(WebCore::FloatRect)targetRect atScale:(double)scale origin:(WebCore::FloatPoint)origin
917 {
918     // FIMXE: Some of this could be shared with _scrollToRect.
919     const double visibleRectScaleChange = contentZoomScale(self) / scale;
920     const WebCore::FloatRect visibleRect([self convertRect:self.bounds toView:self._currentContentView]);
921     const WebCore::FloatRect unobscuredRect([self _contentRectForUserInteraction]);
922
923     const WebCore::FloatSize topLeftObscuredInsetAfterZoom((unobscuredRect.minXMinYCorner() - visibleRect.minXMinYCorner()) * visibleRectScaleChange);
924     const WebCore::FloatSize bottomRightObscuredInsetAfterZoom((visibleRect.maxXMaxYCorner() - unobscuredRect.maxXMaxYCorner()) * visibleRectScaleChange);
925
926     const WebCore::FloatSize unobscuredRectSizeAfterZoom(unobscuredRect.size() * visibleRectScaleChange);
927
928     // Center to the target rect.
929     WebCore::FloatPoint unobscuredRectLocationAfterZoom = targetRect.location() - (unobscuredRectSizeAfterZoom - targetRect.size()) * 0.5;
930
931     // Center to the tap point instead in case the target rect won't fit in a direction.
932     if (targetRect.width() > unobscuredRectSizeAfterZoom.width())
933         unobscuredRectLocationAfterZoom.setX(origin.x() - unobscuredRectSizeAfterZoom.width() / 2);
934     if (targetRect.height() > unobscuredRectSizeAfterZoom.height())
935         unobscuredRectLocationAfterZoom.setY(origin.y() - unobscuredRectSizeAfterZoom.height() / 2);
936
937     // We have computed where we want the unobscured rect to be. Now adjust for the obscuring insets.
938     WebCore::FloatRect visibleRectAfterZoom(unobscuredRectLocationAfterZoom, unobscuredRectSizeAfterZoom);
939     visibleRectAfterZoom.move(-topLeftObscuredInsetAfterZoom);
940     visibleRectAfterZoom.expand(topLeftObscuredInsetAfterZoom + bottomRightObscuredInsetAfterZoom);
941
942     [self _zoomToPoint:visibleRectAfterZoom.center() atScale:scale];
943 }
944
945 static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOffset, WebCore::FloatSize contentSize, WebCore::FloatSize unobscuredContentSize)
946 {
947     WebCore::FloatSize maximumContentOffset = contentSize - unobscuredContentSize;
948     contentOffset = contentOffset.shrunkTo(WebCore::FloatPoint(maximumContentOffset.width(), maximumContentOffset.height()));
949     contentOffset = contentOffset.expandedTo(WebCore::FloatPoint());
950     return contentOffset;
951 }
952
953 - (void)_scrollToContentOffset:(WebCore::FloatPoint)contentOffset
954 {
955     if (_isAnimatingResize)
956         return;
957
958     [_scrollView _stopScrollingAndZoomingAnimations];
959
960     WebCore::FloatPoint scaledOffset = contentOffset;
961     CGFloat zoomScale = contentZoomScale(self);
962     scaledOffset.scale(zoomScale, zoomScale);
963
964     [_scrollView setContentOffset:[self _adjustedContentOffset:scaledOffset]];
965 }
966
967 - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance
968 {
969     WebCore::FloatRect unobscuredContentRect([self _contentRectForUserInteraction]);
970     WebCore::FloatPoint unobscuredContentOffset = unobscuredContentRect.location();
971     WebCore::FloatSize contentSize([self._currentContentView bounds].size);
972
973     // Center the target rect in the scroll view.
974     // If the target doesn't fit in the scroll view, center on the gesture location instead.
975     WebCore::FloatPoint newUnobscuredContentOffset;
976     if (targetRect.width() <= unobscuredContentRect.width())
977         newUnobscuredContentOffset.setX(targetRect.x() - (unobscuredContentRect.width() - targetRect.width()) / 2);
978     else
979         newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
980     if (targetRect.height() <= unobscuredContentRect.height())
981         newUnobscuredContentOffset.setY(targetRect.y() - (unobscuredContentRect.height() - targetRect.height()) / 2);
982     else
983         newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
984     newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
985
986     if (unobscuredContentOffset == newUnobscuredContentOffset) {
987         if (targetRect.width() > unobscuredContentRect.width())
988             newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
989         if (targetRect.height() > unobscuredContentRect.height())
990             newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
991         newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
992     }
993
994     WebCore::FloatSize scrollViewOffsetDelta = newUnobscuredContentOffset - unobscuredContentOffset;
995     scrollViewOffsetDelta.scale(contentZoomScale(self));
996
997     float scrollDistance = scrollViewOffsetDelta.diagonalLength();
998     if (scrollDistance < minimumScrollDistance)
999         return false;
1000
1001     [_scrollView setContentOffset:([_scrollView contentOffset] + scrollViewOffsetDelta) animated:YES];
1002     return true;
1003 }
1004
1005 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin
1006 {
1007     [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale]];
1008 }
1009
1010 // focusedElementRect and selectionRect are both in document coordinates.
1011 - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRectInDocumentCoordinates selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll
1012 {
1013     const double WKWebViewStandardFontSize = 16;
1014     const double kMinimumHeightToShowContentAboveKeyboard = 106;
1015     const CFTimeInterval UIWebFormAnimationDuration = 0.25;
1016     const double CaretOffsetFromWindowEdge = 20;
1017
1018     // Zoom around the element's bounding frame. We use a "standard" size to determine the proper frame.
1019     double scale = allowScaling ? std::min(std::max(WKWebViewStandardFontSize / fontSize, minimumScale), maximumScale) : contentZoomScale(self);
1020     CGFloat documentWidth = [_contentView bounds].size.width;
1021     scale = CGRound(documentWidth * scale) / documentWidth;
1022
1023     UIWindow *window = [_scrollView window];
1024
1025     WebCore::FloatRect focusedElementRectInNewScale = focusedElementRectInDocumentCoordinates;
1026     focusedElementRectInNewScale.scale(scale);
1027     focusedElementRectInNewScale.moveBy([_contentView frame].origin);
1028
1029     // Find the portion of the view that is visible on the screen.
1030     UIViewController *topViewController = [[[_scrollView _viewControllerForAncestor] _rootAncestorViewController] _viewControllerForSupportedInterfaceOrientations];
1031     UIView *fullScreenView = topViewController.view;
1032     if (!fullScreenView)
1033         fullScreenView = window;
1034
1035     CGRect unobscuredScrollViewRectInWebViewCoordinates = UIEdgeInsetsInsetRect([self bounds], _obscuredInsets);
1036     CGRect visibleScrollViewBoundsInWebViewCoordinates = CGRectIntersection(unobscuredScrollViewRectInWebViewCoordinates, [fullScreenView convertRect:[fullScreenView bounds] toView:self]);
1037     CGRect formAssistantFrameInWebViewCoordinates = [window convertRect:_inputViewBounds toView:self];
1038     CGRect intersectionBetweenScrollViewAndFormAssistant = CGRectIntersection(visibleScrollViewBoundsInWebViewCoordinates, formAssistantFrameInWebViewCoordinates);
1039     CGSize visibleSize = visibleScrollViewBoundsInWebViewCoordinates.size;
1040
1041     CGFloat visibleOffsetFromTop = 0;
1042     if (!CGRectIsEmpty(intersectionBetweenScrollViewAndFormAssistant)) {
1043         CGFloat heightVisibleAboveFormAssistant = CGRectGetMinY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1044         CGFloat heightVisibleBelowFormAssistant = CGRectGetMaxY(visibleScrollViewBoundsInWebViewCoordinates) - CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant);
1045
1046         if (heightVisibleAboveFormAssistant >= kMinimumHeightToShowContentAboveKeyboard || heightVisibleBelowFormAssistant < heightVisibleAboveFormAssistant)
1047             visibleSize.height = heightVisibleAboveFormAssistant;
1048         else {
1049             visibleSize.height = heightVisibleBelowFormAssistant;
1050             visibleOffsetFromTop = CGRectGetMaxY(intersectionBetweenScrollViewAndFormAssistant) - CGRectGetMinY(visibleScrollViewBoundsInWebViewCoordinates);
1051         }
1052     }
1053
1054     BOOL selectionRectIsNotNull = !selectionRectInDocumentCoordinates.isZero();
1055     if (!forceScroll) {
1056         CGRect currentlyVisibleRegionInWebViewCoordinates;
1057         currentlyVisibleRegionInWebViewCoordinates.origin = unobscuredScrollViewRectInWebViewCoordinates.origin;
1058         currentlyVisibleRegionInWebViewCoordinates.origin.y += visibleOffsetFromTop;
1059         currentlyVisibleRegionInWebViewCoordinates.size = visibleSize;
1060
1061         // Don't bother scrolling if the entire node is already visible, whether or not we got a selectionRect.
1062         if (CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:focusedElementRectInDocumentCoordinates fromView:_contentView.get()]))
1063             return;
1064
1065         // Don't bother scrolling if we have a valid selectionRect and it is already visible.
1066         if (selectionRectIsNotNull && CGRectContainsRect(currentlyVisibleRegionInWebViewCoordinates, [self convertRect:selectionRectInDocumentCoordinates fromView:_contentView.get()]))
1067             return;
1068     }
1069
1070     // We want to zoom to the left/top corner of the DOM node, with as much spacing on all sides as we
1071     // can get based on the visible area after zooming (workingFrame).  The spacing in either dimension is half the
1072     // difference between the size of the DOM node and the size of the visible frame.
1073     CGFloat horizontalSpaceInWebViewCoordinates = std::max((visibleSize.width - focusedElementRectInNewScale.width()) / 2.0, 0.0);
1074     CGFloat verticalSpaceInWebViewCoordinates = std::max((visibleSize.height - focusedElementRectInNewScale.height()) / 2.0, 0.0);
1075
1076     CGPoint topLeft;
1077     topLeft.x = focusedElementRectInNewScale.x() - horizontalSpaceInWebViewCoordinates;
1078     topLeft.y = focusedElementRectInNewScale.y() - verticalSpaceInWebViewCoordinates - visibleOffsetFromTop;
1079
1080     CGFloat minimumAllowableHorizontalOffsetInWebViewCoordinates = -INFINITY;
1081     CGFloat minimumAllowableVerticalOffsetInWebViewCoordinates = -INFINITY;
1082     if (selectionRectIsNotNull) {
1083         WebCore::FloatRect selectionRectInNewScale = selectionRectInDocumentCoordinates;
1084         selectionRectInNewScale.scale(scale);
1085         selectionRectInNewScale.moveBy([_contentView frame].origin);
1086         minimumAllowableHorizontalOffsetInWebViewCoordinates = CGRectGetMaxX(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.width;
1087         minimumAllowableVerticalOffsetInWebViewCoordinates = CGRectGetMaxY(selectionRectInNewScale) + CaretOffsetFromWindowEdge - visibleSize.height - visibleOffsetFromTop;
1088     }
1089
1090     WebCore::FloatRect documentBoundsInNewScale = [_contentView bounds];
1091     documentBoundsInNewScale.scale(scale);
1092     documentBoundsInNewScale.moveBy([_contentView frame].origin);
1093
1094     // Constrain the left edge in document coordinates so that:
1095     //  - it isn't so small that the scrollVisibleRect isn't visible on the screen
1096     //  - it isn't so great that the document's right edge is less than the right edge of the screen
1097     if (selectionRectIsNotNull && topLeft.x < minimumAllowableHorizontalOffsetInWebViewCoordinates)
1098         topLeft.x = minimumAllowableHorizontalOffsetInWebViewCoordinates;
1099     else {
1100         CGFloat maximumAllowableHorizontalOffset = CGRectGetMaxX(documentBoundsInNewScale) - visibleSize.width;
1101         if (topLeft.x > maximumAllowableHorizontalOffset)
1102             topLeft.x = maximumAllowableHorizontalOffset;
1103     }
1104
1105     // Constrain the top edge in document coordinates so that:
1106     //  - it isn't so small that the scrollVisibleRect isn't visible on the screen
1107     //  - it isn't so great that the document's bottom edge is higher than the top of the form assistant
1108     if (selectionRectIsNotNull && topLeft.y < minimumAllowableVerticalOffsetInWebViewCoordinates)
1109         topLeft.y = minimumAllowableVerticalOffsetInWebViewCoordinates;
1110     else {
1111         CGFloat maximumAllowableVerticalOffset = CGRectGetMaxY(documentBoundsInNewScale) - visibleSize.height;
1112         if (topLeft.y > maximumAllowableVerticalOffset)
1113             topLeft.y = maximumAllowableVerticalOffset;
1114     }
1115
1116     WebCore::FloatPoint newCenter = CGPointMake(topLeft.x + unobscuredScrollViewRectInWebViewCoordinates.size.width / 2.0, topLeft.y + unobscuredScrollViewRectInWebViewCoordinates.size.height / 2.0);
1117
1118     if (scale != contentZoomScale(self))
1119         _page->willStartUserTriggeredZooming();
1120
1121     // The newCenter has been computed in the new scale, but _zoomToCenter expected the center to be in the original scale.
1122     newCenter.scale(1 / scale, 1 / scale);
1123     [_scrollView _zoomToCenter:newCenter
1124                         scale:scale
1125                      duration:UIWebFormAnimationDuration
1126                         force:YES];
1127 }
1128
1129 - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
1130 {
1131     const float maximumScaleFactorDeltaForPanScroll = 0.02;
1132
1133     double currentScale = contentZoomScale(self);
1134
1135     WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
1136     double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width();
1137     double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height();
1138
1139     horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale);
1140     verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale);
1141
1142     double targetScale = fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
1143     if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) {
1144         if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance])
1145             return true;
1146     } else if (targetScale != currentScale) {
1147         [self _zoomToRect:targetRect atScale:targetScale origin:origin];
1148         return true;
1149     }
1150     
1151     return false;
1152 }
1153
1154 - (void)didMoveToWindow
1155 {
1156     _page->viewStateDidChange(WebCore::ViewState::IsInWindow);
1157 }
1158
1159 #pragma mark - UIScrollViewDelegate
1160
1161 - (BOOL)usesStandardContentView
1162 {
1163     return !_customContentView;
1164 }
1165
1166 - (CGSize)scrollView:(UIScrollView*)scrollView contentSizeForZoomScale:(CGFloat)scale withProposedSize:(CGSize)proposedSize
1167 {
1168     return roundScrollViewContentSize(*_page, proposedSize);
1169 }
1170
1171 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
1172 {
1173     ASSERT(_scrollView == scrollView);
1174
1175     if (_customContentView)
1176         return _customContentView.get();
1177
1178     return _contentView.get();
1179 }
1180
1181 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
1182 {
1183     if (![self usesStandardContentView])
1184         return;
1185
1186     if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan) {
1187         _page->willStartUserTriggeredZooming();
1188         [_contentView scrollViewWillStartPanOrPinchGesture];
1189     }
1190     [_contentView willStartZoomOrScroll];
1191 }
1192
1193 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
1194 {
1195     if (![self usesStandardContentView])
1196         return;
1197
1198     if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
1199         [_contentView scrollViewWillStartPanOrPinchGesture];
1200     [_contentView willStartZoomOrScroll];
1201 }
1202
1203 - (void)_didFinishScrolling
1204 {
1205     if (![self usesStandardContentView])
1206         return;
1207
1208     [self _updateVisibleContentRects];
1209     [_contentView didFinishScrolling];
1210 }
1211
1212 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
1213 {
1214     // Work around <rdar://problem/16374753> by avoiding deceleration while
1215     // zooming. We'll animate to the right place once the zoom finishes.
1216     if ([scrollView isZooming])
1217         *targetContentOffset = [scrollView contentOffset];
1218 }
1219
1220 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
1221 {
1222     // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called.
1223     if (!decelerate)
1224         [self _didFinishScrolling];
1225 }
1226
1227 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
1228 {
1229     [self _didFinishScrolling];
1230 }
1231
1232 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
1233 {
1234     [self _didFinishScrolling];
1235 }
1236
1237 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
1238 {
1239     if (![self usesStandardContentView])
1240         [_customContentView scrollViewDidScroll:(UIScrollView *)scrollView];
1241
1242     [self _updateVisibleContentRects];
1243 }
1244
1245 - (void)scrollViewDidZoom:(UIScrollView *)scrollView
1246 {
1247     [self _updateScrollViewBackground];
1248     [self _updateVisibleContentRects];
1249 }
1250
1251 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
1252 {
1253     ASSERT(scrollView == _scrollView);
1254     [self _updateVisibleContentRects];
1255     [_contentView didZoomToScale:scale];
1256 }
1257
1258 - (void)_frameOrBoundsChanged
1259 {
1260     CGRect bounds = self.bounds;
1261     if (!_isAnimatingResize) {
1262         if (!_overridesMinimumLayoutSize)
1263             _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(bounds.size));
1264         if (!_overridesMinimumLayoutSizeForMinimalUI)
1265             _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(WebCore::FloatSize(bounds.size));
1266         if (!_overridesMaximumUnobscuredSize)
1267             _page->setMaximumUnobscuredSize(WebCore::FloatSize(bounds.size));
1268     }
1269
1270     [_scrollView setFrame:bounds];
1271     [_contentView setMinimumSize:bounds.size];
1272     [_customContentView web_setMinimumSize:bounds.size];
1273     [self _updateVisibleContentRects];
1274 }
1275
1276 // 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.
1277 - (CGRect)_contentRectForUserInteraction
1278 {
1279     // FIXME: handle split keyboard.
1280     UIEdgeInsets obscuredInsets = _obscuredInsets;
1281     obscuredInsets.bottom = std::max(_obscuredInsets.bottom, _inputViewBounds.size.height);
1282     CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, obscuredInsets);
1283     return [self convertRect:unobscuredRect toView:self._currentContentView];
1284 }
1285
1286 - (void)_updateVisibleContentRects
1287 {
1288     if (![self usesStandardContentView]) {
1289         [_customContentView web_computedContentInsetDidChange];
1290         return;
1291     }
1292
1293     if (_delayUpdateVisibleContentRects) {
1294         _hadDelayedUpdateVisibleContentRects = YES;
1295         return;
1296     }
1297
1298     if (_isAnimatingResize)
1299         return;
1300
1301     if (_needsResetViewStateAfterCommitLoadForMainFrame)
1302         return;
1303
1304     CGRect fullViewRect = self.bounds;
1305     CGRect visibleRectInContentCoordinates = [self convertRect:fullViewRect toView:_contentView.get()];
1306
1307     CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, [self _computedContentInset]);
1308     CGRect unobscuredRectInContentCoordinates = [self convertRect:unobscuredRect toView:_contentView.get()];
1309
1310     CGFloat scaleFactor = contentZoomScale(self);
1311
1312     BOOL isStableState = !(_isChangingObscuredInsetsInteractively || [_scrollView isDragging] || [_scrollView isDecelerating] || [_scrollView isZooming] || [_scrollView isZoomBouncing] || [_scrollView _isAnimatingZoom] || [_scrollView _isScrollingToTop]);
1313     [_contentView didUpdateVisibleRect:visibleRectInContentCoordinates
1314         unobscuredRect:unobscuredRectInContentCoordinates
1315         unobscuredRectInScrollViewCoordinates:unobscuredRect
1316         scale:scaleFactor minimumScale:[_scrollView minimumZoomScale]
1317         inStableState:isStableState isChangingObscuredInsetsInteractively:_isChangingObscuredInsetsInteractively];
1318 }
1319
1320 - (void)_keyboardChangedWithInfo:(NSDictionary *)keyboardInfo adjustScrollView:(BOOL)adjustScrollView
1321 {
1322     NSValue *endFrameValue = [keyboardInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
1323     if (!endFrameValue)
1324         return;
1325
1326     // The keyboard rect is always in screen coordinates. In the view services case the window does not
1327     // have the interface orientation rotation transformation; its host does. So, it makes no sense to
1328     // clip the keyboard rect against its screen.
1329     if ([[self window] _isHostedInAnotherProcess])
1330         _inputViewBounds = [self.window convertRect:[endFrameValue CGRectValue] fromWindow:nil];
1331     else
1332         _inputViewBounds = [self.window convertRect:CGRectIntersection([endFrameValue CGRectValue], self.window.screen.bounds) fromWindow:nil];
1333
1334     [self _updateVisibleContentRects];
1335
1336     if (adjustScrollView)
1337         [_scrollView _adjustForAutomaticKeyboardInfo:keyboardInfo animated:YES lastAdjustment:&_lastAdjustmentForScroller];
1338 }
1339
1340 - (void)_keyboardWillChangeFrame:(NSNotification *)notification
1341 {
1342     if ([_contentView isAssistingNode])
1343         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1344 }
1345
1346 - (void)_keyboardDidChangeFrame:(NSNotification *)notification
1347 {
1348     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:NO];
1349 }
1350
1351 - (void)_keyboardWillShow:(NSNotification *)notification
1352 {
1353     if ([_contentView isAssistingNode])
1354         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1355 }
1356
1357 - (void)_keyboardWillHide:(NSNotification *)notification
1358 {
1359     // Ignore keyboard will hide notifications sent during rotation. They're just there for
1360     // backwards compatibility reasons and processing the will hide notification would
1361     // temporarily screw up the the unobscured view area.
1362     if ([[UIPeripheralHost sharedInstance] rotationState])
1363         return;
1364
1365     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
1366 }
1367
1368 - (void)_windowDidRotate:(NSNotification *)notification
1369 {
1370     if (!_overridesInterfaceOrientation)
1371         _page->setDeviceOrientation(deviceOrientation());
1372 }
1373
1374 - (void)_contentSizeCategoryDidChange:(NSNotification *)notification
1375 {
1376     _page->contentSizeCategoryDidChange([self _contentSizeCategory]);
1377 }
1378
1379 - (NSString *)_contentSizeCategory
1380 {
1381     return [[UIApplication sharedApplication] preferredContentSizeCategory];
1382 }
1383
1384 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1385 {
1386     if (_allowsBackForwardNavigationGestures == allowsBackForwardNavigationGestures)
1387         return;
1388
1389     _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
1390
1391     if (allowsBackForwardNavigationGestures) {
1392         if (!_gestureController) {
1393             _gestureController = std::make_unique<WebKit::ViewGestureController>(*_page);
1394             _gestureController->installSwipeHandler(self, [self scrollView]);
1395         }
1396     } else
1397         _gestureController = nullptr;
1398
1399     _page->setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures);
1400 }
1401
1402 - (BOOL)allowsBackForwardNavigationGestures
1403 {
1404     return _allowsBackForwardNavigationGestures;
1405 }
1406
1407 #endif
1408
1409 #pragma mark OS X-specific methods
1410
1411 #if PLATFORM(MAC)
1412
1413 - (void)resizeSubviewsWithOldSize:(NSSize)oldSize
1414 {
1415     [_wkView setFrame:self.bounds];
1416 }
1417
1418 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
1419 {
1420     [_wkView setAllowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures];
1421 }
1422
1423 - (BOOL)allowsBackForwardNavigationGestures
1424 {
1425     return [_wkView allowsBackForwardNavigationGestures];
1426 }
1427
1428 - (void)setAllowsMagnification:(BOOL)allowsMagnification
1429 {
1430     [_wkView setAllowsMagnification:allowsMagnification];
1431 }
1432
1433 - (BOOL)allowsMagnification
1434 {
1435     return [_wkView allowsMagnification];
1436 }
1437
1438 - (void)setMagnification:(CGFloat)magnification
1439 {
1440     [_wkView setMagnification:magnification];
1441 }
1442
1443 - (CGFloat)magnification
1444 {
1445     return [_wkView magnification];
1446 }
1447
1448 - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point
1449 {
1450     [_wkView setMagnification:magnification centeredAtPoint:NSPointFromCGPoint(point)];
1451 }
1452
1453 #endif
1454
1455 @end
1456
1457 @implementation WKWebView (WKPrivate)
1458
1459 - (_WKRemoteObjectRegistry *)_remoteObjectRegistry
1460 {
1461     if (!_remoteObjectRegistry) {
1462         _remoteObjectRegistry = adoptNS([[_WKRemoteObjectRegistry alloc] _initWithMessageSender:*_page]);
1463         _page->process().context().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID(), [_remoteObjectRegistry remoteObjectRegistry]);
1464     }
1465
1466     return _remoteObjectRegistry.get();
1467 }
1468
1469 - (WKBrowsingContextHandle *)_handle
1470 {
1471     return [[[WKBrowsingContextHandle alloc] _initWithPageID:_page->pageID()] autorelease];
1472 }
1473
1474 - (_WKRenderingProgressEvents)_observedRenderingProgressEvents
1475 {
1476     return _observedRenderingProgressEvents;
1477 }
1478
1479 - (id <WKHistoryDelegatePrivate>)_historyDelegate
1480 {
1481     return _navigationState->historyDelegate().autorelease();
1482 }
1483
1484 - (void)_setHistoryDelegate:(id <WKHistoryDelegatePrivate>)historyDelegate
1485 {
1486     _navigationState->setHistoryDelegate(historyDelegate);
1487 }
1488
1489 - (NSURL *)_unreachableURL
1490 {
1491     return [NSURL _web_URLWithWTFString:_page->pageLoadState().unreachableURL()];
1492 }
1493
1494 - (void)_loadAlternateHTMLString:(NSString *)string baseURL:(NSURL *)baseURL forUnreachableURL:(NSURL *)unreachableURL
1495 {
1496     _page->loadAlternateHTMLString(string, [baseURL _web_originalDataAsWTFString], [unreachableURL _web_originalDataAsWTFString]);
1497 }
1498
1499 - (NSArray *)_certificateChain
1500 {
1501     if (WebKit::WebFrameProxy* mainFrame = _page->mainFrame())
1502         return mainFrame->certificateInfo() ? (NSArray *)mainFrame->certificateInfo()->certificateInfo().certificateChain() : nil;
1503
1504     return nil;
1505 }
1506
1507 - (NSURL *)_committedURL
1508 {
1509     return [NSURL _web_URLWithWTFString:_page->pageLoadState().url()];
1510 }
1511
1512 - (NSString *)_MIMEType
1513 {
1514     if (_page->mainFrame())
1515         return _page->mainFrame()->mimeType();
1516
1517     return nil;
1518 }
1519
1520 - (NSString *)_applicationNameForUserAgent
1521 {
1522     return _page->applicationNameForUserAgent();
1523 }
1524
1525 - (void)_setApplicationNameForUserAgent:(NSString *)applicationNameForUserAgent
1526 {
1527     _page->setApplicationNameForUserAgent(applicationNameForUserAgent);
1528 }
1529
1530 - (NSString *)_customUserAgent
1531 {
1532     return _page->customUserAgent();
1533 }
1534
1535 - (void)_setCustomUserAgent:(NSString *)_customUserAgent
1536 {
1537     _page->setCustomUserAgent(_customUserAgent);
1538 }
1539
1540 - (pid_t)_webProcessIdentifier
1541 {
1542     return _page->isValid() ? _page->processIdentifier() : 0;
1543 }
1544
1545 - (void)_killWebContentProcess
1546 {
1547     if (!_page->isValid())
1548         return;
1549
1550     _page->process().terminate();
1551 }
1552
1553 #if PLATFORM(IOS)
1554 static WebCore::FloatSize activeMinimumLayoutSize(WKWebView *webView, const CGRect& bounds)
1555 {
1556     return WebCore::FloatSize(webView->_overridesMinimumLayoutSize ? webView->_minimumLayoutSizeOverride : bounds.size);
1557 }
1558
1559 static WebCore::FloatSize activeMinimumLayoutSizeForMinimalUI(WKWebView *webView, WebCore::FloatSize minimumLayoutSize)
1560 {
1561     return webView->_overridesMinimumLayoutSizeForMinimalUI ? WebCore::FloatSize(webView->_minimumLayoutSizeOverrideForMinimalUI) : minimumLayoutSize;
1562 }
1563
1564 static WebCore::FloatSize activeMaximumUnobscuredSize(WKWebView *webView, const CGRect& bounds)
1565 {
1566     return WebCore::FloatSize(webView->_overridesMaximumUnobscuredSize ? webView->_maximumUnobscuredSizeOverride : bounds.size);
1567 }
1568
1569 static int32_t activeOrientation(WKWebView *webView)
1570 {
1571     return webView->_overridesInterfaceOrientation ? deviceOrientationForUIInterfaceOrientation(webView->_interfaceOrientationOverride) : webView->_page->deviceOrientation();
1572 }
1573 #endif
1574
1575 - (void)_didRelaunchProcess
1576 {
1577 #if PLATFORM(IOS)
1578     CGRect bounds = self.bounds;
1579     WebCore::FloatSize minimalLayoutSize = activeMinimumLayoutSize(self, bounds);
1580     _page->setViewportConfigurationMinimumLayoutSize(minimalLayoutSize);
1581     _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(activeMinimumLayoutSizeForMinimalUI(self, minimalLayoutSize));
1582     _page->setMaximumUnobscuredSize(activeMaximumUnobscuredSize(self, bounds));
1583 #endif
1584 }
1585
1586 - (NSData *)_sessionStateData
1587 {
1588     WebKit::SessionState sessionState = _page->sessionState();
1589
1590     // FIXME: This should not use the legacy session state encoder.
1591     return [wrapper(*WebKit::encodeLegacySessionState(sessionState).release().leakRef()) autorelease];
1592 }
1593
1594 // FIXME: This should return a _WKSessionState object.
1595 - (id)_sessionState
1596 {
1597     return adoptNS([[_WKSessionState alloc] _initWithSessionState:_page->sessionState()]).autorelease();
1598 }
1599
1600 - (void)_restoreFromSessionStateData:(NSData *)sessionStateData
1601 {
1602     // FIXME: This should not use the legacy session state decoder.
1603     WebKit::SessionState sessionState;
1604     if (!WebKit::decodeLegacySessionState(static_cast<const uint8_t*>(sessionStateData.bytes), sessionStateData.length, sessionState))
1605         return;
1606
1607     if (uint64_t navigationID = _page->restoreFromSessionState(WTF::move(sessionState), true)) {
1608         // FIXME: This is not necessarily always a reload navigation.
1609         _navigationState->createReloadNavigation(navigationID);
1610     }
1611 }
1612
1613 // FIXME: Remove this once nobody is using it.
1614 - (void)_restoreFromSessionState:(id)sessionState
1615 {
1616     [self _restoreSessionState:sessionState andNavigate:YES];
1617 }
1618
1619 - (WKNavigation *)_restoreSessionState:(_WKSessionState *)sessionState andNavigate:(BOOL)navigate
1620 {
1621     if (uint64_t navigationID = _page->restoreFromSessionState(sessionState->_sessionState, navigate)) {
1622         // FIXME: This is not necessarily always a reload navigation.
1623         return _navigationState->createReloadNavigation(navigationID).autorelease();
1624     }
1625
1626     return nil;
1627 }
1628
1629 - (void)_close
1630 {
1631     _page->close();
1632 }
1633
1634 - (BOOL)_allowsRemoteInspection
1635 {
1636 #if ENABLE(REMOTE_INSPECTOR)
1637     return _page->allowsRemoteInspection();
1638 #else
1639     return NO;
1640 #endif
1641 }
1642
1643 - (void)_setAllowsRemoteInspection:(BOOL)allow
1644 {
1645 #if ENABLE(REMOTE_INSPECTOR)
1646     _page->setAllowsRemoteInspection(allow);
1647 #endif
1648 }
1649
1650 - (BOOL)_addsVisitedLinks
1651 {
1652     return _page->addsVisitedLinks();
1653 }
1654
1655 - (void)_setAddsVisitedLinks:(BOOL)addsVisitedLinks
1656 {
1657     _page->setAddsVisitedLinks(addsVisitedLinks);
1658 }
1659
1660 - (BOOL)_networkRequestsInProgress
1661 {
1662     return _page->pageLoadState().networkRequestsInProgress();
1663 }
1664
1665 static inline WebCore::LayoutMilestones layoutMilestones(_WKRenderingProgressEvents events)
1666 {
1667     WebCore::LayoutMilestones milestones = 0;
1668
1669     if (events & _WKRenderingProgressEventFirstLayout)
1670         milestones |= WebCore::DidFirstLayout;
1671
1672     if (events & _WKRenderingProgressEventFirstPaintWithSignificantArea)
1673         milestones |= WebCore::DidHitRelevantRepaintedObjectsAreaThreshold;
1674
1675     return milestones;
1676 }
1677
1678 - (void)_setObservedRenderingProgressEvents:(_WKRenderingProgressEvents)observedRenderingProgressEvents
1679 {
1680     _observedRenderingProgressEvents = observedRenderingProgressEvents;
1681     _page->listenForLayoutMilestones(layoutMilestones(observedRenderingProgressEvents));
1682 }
1683
1684 - (void)_getMainResourceDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
1685 {
1686     auto handler = adoptNS([completionHandler copy]);
1687
1688     _page->getMainResourceDataOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
1689         void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
1690         if (error != WebKit::CallbackBase::Error::None) {
1691             // FIXME: Pipe a proper error in from the WebPageProxy.
1692             RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
1693             completionHandlerBlock(nil, error.get());
1694         } else
1695             completionHandlerBlock(wrapper(*data), nil);
1696     });
1697 }
1698
1699 - (void)_getWebArchiveDataWithCompletionHandler:(void (^)(NSData *, NSError *))completionHandler
1700 {
1701     auto handler = adoptNS([completionHandler copy]);
1702
1703     _page->getWebArchiveOfFrame(_page->mainFrame(), [handler](API::Data* data, WebKit::CallbackBase::Error error) {
1704         void (^completionHandlerBlock)(NSData *, NSError *) = (void (^)(NSData *, NSError *))handler.get();
1705         if (error != WebKit::CallbackBase::Error::None) {
1706             // FIXME: Pipe a proper error in from the WebPageProxy.
1707             RetainPtr<NSError> error = adoptNS([[NSError alloc] init]);
1708             completionHandlerBlock(nil, error.get());
1709         } else
1710             completionHandlerBlock(wrapper(*data), nil);
1711     });
1712 }
1713
1714 - (_WKPaginationMode)_paginationMode
1715 {
1716     switch (_page->paginationMode()) {
1717     case WebCore::Pagination::Unpaginated:
1718         return _WKPaginationModeUnpaginated;
1719     case WebCore::Pagination::LeftToRightPaginated:
1720         return _WKPaginationModeLeftToRight;
1721     case WebCore::Pagination::RightToLeftPaginated:
1722         return _WKPaginationModeRightToLeft;
1723     case WebCore::Pagination::TopToBottomPaginated:
1724         return _WKPaginationModeTopToBottom;
1725     case WebCore::Pagination::BottomToTopPaginated:
1726         return _WKPaginationModeBottomToTop;
1727     }
1728
1729     ASSERT_NOT_REACHED();
1730     return _WKPaginationModeUnpaginated;
1731 }
1732
1733 - (void)_setPaginationMode:(_WKPaginationMode)paginationMode
1734 {
1735     WebCore::Pagination::Mode mode;
1736     switch (paginationMode) {
1737     case _WKPaginationModeUnpaginated:
1738         mode = WebCore::Pagination::Unpaginated;
1739         break;
1740     case _WKPaginationModeLeftToRight:
1741         mode = WebCore::Pagination::LeftToRightPaginated;
1742         break;
1743     case _WKPaginationModeRightToLeft:
1744         mode = WebCore::Pagination::RightToLeftPaginated;
1745         break;
1746     case _WKPaginationModeTopToBottom:
1747         mode = WebCore::Pagination::TopToBottomPaginated;
1748         break;
1749     case _WKPaginationModeBottomToTop:
1750         mode = WebCore::Pagination::BottomToTopPaginated;
1751         break;
1752     default:
1753         return;
1754     }
1755
1756     _page->setPaginationMode(mode);
1757 }
1758
1759 - (BOOL)_paginationBehavesLikeColumns
1760 {
1761     return _page->paginationBehavesLikeColumns();
1762 }
1763
1764 - (void)_setPaginationBehavesLikeColumns:(BOOL)behavesLikeColumns
1765 {
1766     _page->setPaginationBehavesLikeColumns(behavesLikeColumns);
1767 }
1768
1769 - (CGFloat)_pageLength
1770 {
1771     return _page->pageLength();
1772 }
1773
1774 - (void)_setPageLength:(CGFloat)pageLength
1775 {
1776     _page->setPageLength(pageLength);
1777 }
1778
1779 - (CGFloat)_gapBetweenPages
1780 {
1781     return _page->gapBetweenPages();
1782 }
1783
1784 - (void)_setGapBetweenPages:(CGFloat)gapBetweenPages
1785 {
1786     _page->setGapBetweenPages(gapBetweenPages);
1787 }
1788
1789 - (NSUInteger)_pageCount
1790 {
1791     return _page->pageCount();
1792 }
1793
1794 - (BOOL)_supportsTextZoom
1795 {
1796     return _page->supportsTextZoom();
1797 }
1798
1799 - (double)_textZoomFactor
1800 {
1801     return _page->textZoomFactor();
1802 }
1803
1804 - (void)_setTextZoomFactor:(double)zoomFactor
1805 {
1806     _page->setTextZoomFactor(zoomFactor);
1807 }
1808
1809 - (double)_pageZoomFactor
1810 {
1811     return _page->pageZoomFactor();
1812 }
1813
1814 - (void)_setPageZoomFactor:(double)zoomFactor
1815 {
1816     _page->setPageZoomFactor(zoomFactor);
1817 }
1818
1819 - (id <_WKFindDelegate>)_findDelegate
1820 {
1821     return [static_cast<WebKit::FindClient&>(_page->findClient()).delegate().leakRef() autorelease];
1822 }
1823
1824 - (void)_setFindDelegate:(id<_WKFindDelegate>)findDelegate
1825 {
1826     static_cast<WebKit::FindClient&>(_page->findClient()).setDelegate(findDelegate);
1827 }
1828
1829 static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
1830 {
1831     unsigned findOptions = 0;
1832
1833     if (wkFindOptions & _WKFindOptionsCaseInsensitive)
1834         findOptions |= WebKit::FindOptionsCaseInsensitive;
1835     if (wkFindOptions & _WKFindOptionsAtWordStarts)
1836         findOptions |= WebKit::FindOptionsAtWordStarts;
1837     if (wkFindOptions & _WKFindOptionsTreatMedialCapitalAsWordStart)
1838         findOptions |= WebKit::FindOptionsTreatMedialCapitalAsWordStart;
1839     if (wkFindOptions & _WKFindOptionsBackwards)
1840         findOptions |= WebKit::FindOptionsBackwards;
1841     if (wkFindOptions & _WKFindOptionsWrapAround)
1842         findOptions |= WebKit::FindOptionsWrapAround;
1843     if (wkFindOptions & _WKFindOptionsShowOverlay)
1844         findOptions |= WebKit::FindOptionsShowOverlay;
1845     if (wkFindOptions & _WKFindOptionsShowFindIndicator)
1846         findOptions |= WebKit::FindOptionsShowFindIndicator;
1847     if (wkFindOptions & _WKFindOptionsShowHighlight)
1848         findOptions |= WebKit::FindOptionsShowHighlight;
1849     if (wkFindOptions & _WKFindOptionsDetermineMatchIndex)
1850         findOptions |= WebKit::FindOptionsDetermineMatchIndex;
1851
1852     return static_cast<WebKit::FindOptions>(findOptions);
1853 }
1854
1855 - (void)_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
1856 {
1857     _page->countStringMatches(string, toFindOptions(options), maxCount);
1858 }
1859
1860 - (void)_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
1861 {
1862     _page->findString(string, toFindOptions(options), maxCount);
1863 }
1864
1865 - (void)_hideFindUI
1866 {
1867     _page->hideFindUI();
1868 }
1869
1870 - (id <_WKFormDelegate>)_formDelegate
1871 {
1872     return _formDelegate.getAutoreleased();
1873 }
1874
1875 - (void)_setFormDelegate:(id <_WKFormDelegate>)formDelegate
1876 {
1877     _formDelegate = formDelegate;
1878
1879     class FormClient : public API::FormClient {
1880     public:
1881         explicit FormClient(WKWebView *webView)
1882             : m_webView(webView)
1883         {
1884         }
1885
1886         virtual ~FormClient() { }
1887
1888         virtual bool willSubmitForm(WebKit::WebPageProxy*, WebKit::WebFrameProxy*, WebKit::WebFrameProxy* sourceFrame, const Vector<std::pair<WTF::String, WTF::String>>& textFieldValues, API::Object* userData, WebKit::WebFormSubmissionListenerProxy* listener) override
1889         {
1890             if (userData && userData->type() != API::Object::Type::Data) {
1891                 ASSERT(!userData || userData->type() == API::Object::Type::Data);
1892                 m_webView->_page->process().connection()->markCurrentlyDispatchedMessageAsInvalid();
1893                 return false;
1894             }
1895
1896             auto formDelegate = m_webView->_formDelegate.get();
1897
1898             if (![formDelegate respondsToSelector:@selector(_webView:willSubmitFormValues:userObject:submissionHandler:)])
1899                 return false;
1900
1901             auto valueMap = adoptNS([[NSMutableDictionary alloc] initWithCapacity:textFieldValues.size()]);
1902             for (const auto& pair : textFieldValues)
1903                 [valueMap setObject:pair.second forKey:pair.first];
1904
1905             NSObject <NSSecureCoding> *userObject = nil;
1906             if (API::Data* data = static_cast<API::Data*>(userData)) {
1907                 auto nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:const_cast<void*>(static_cast<const void*>(data->bytes())) length:data->size() freeWhenDone:NO]);
1908                 auto unarchiver = adoptNS([[NSKeyedUnarchiver alloc] initForReadingWithData:nsData.get()]);
1909                 [unarchiver setRequiresSecureCoding:YES];
1910                 @try {
1911                     userObject = [unarchiver decodeObjectOfClass:[NSObject class] forKey:@"userObject"];
1912                 } @catch (NSException *exception) {
1913                     LOG_ERROR("Failed to decode user data: %@", exception);
1914                 }
1915             }
1916
1917             [formDelegate _webView:m_webView willSubmitFormValues:valueMap.get() userObject:userObject submissionHandler:^{
1918                 listener->continueSubmission();
1919             }];
1920             return true;
1921         }
1922
1923     private:
1924         WKWebView *m_webView;
1925     };
1926
1927     if (formDelegate)
1928         _page->setFormClient(std::make_unique<FormClient>(self));
1929     else
1930         _page->setFormClient(nullptr);
1931 }
1932
1933 - (BOOL)_isDisplayingStandaloneImageDocument
1934 {
1935     if (auto* mainFrame = _page->mainFrame())
1936         return mainFrame->isDisplayingStandaloneImageDocument();
1937     return NO;
1938 }
1939
1940 #pragma mark iOS-specific methods
1941
1942 #if PLATFORM(IOS)
1943
1944 - (CGSize)_minimumLayoutSizeOverride
1945 {
1946     ASSERT(_overridesMinimumLayoutSize);
1947     return _minimumLayoutSizeOverride;
1948 }
1949
1950 - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride
1951 {
1952     _overridesMinimumLayoutSize = YES;
1953     if (CGSizeEqualToSize(_minimumLayoutSizeOverride, minimumLayoutSizeOverride))
1954         return;
1955
1956     _minimumLayoutSizeOverride = minimumLayoutSizeOverride;
1957     if (!_isAnimatingResize)
1958         _page->setViewportConfigurationMinimumLayoutSize(WebCore::FloatSize(minimumLayoutSizeOverride));
1959 }
1960
1961 - (CGSize)_minimumLayoutSizeOverrideForMinimalUI
1962 {
1963     ASSERT(_overridesMinimumLayoutSizeForMinimalUI);
1964     return _minimumLayoutSizeOverrideForMinimalUI;
1965 }
1966
1967 - (void)_setMinimumLayoutSizeOverrideForMinimalUI:(CGSize)size
1968 {
1969     _overridesMinimumLayoutSizeForMinimalUI = YES;
1970     if (CGSizeEqualToSize(_minimumLayoutSizeOverrideForMinimalUI, size))
1971         return;
1972
1973     _minimumLayoutSizeOverrideForMinimalUI = size;
1974     if (!_isAnimatingResize)
1975         _page->setViewportConfigurationMinimumLayoutSizeForMinimalUI(WebCore::FloatSize(size));
1976 }
1977
1978 - (UIEdgeInsets)_obscuredInsets
1979 {
1980     return _obscuredInsets;
1981 }
1982
1983 - (void)_setObscuredInsets:(UIEdgeInsets)obscuredInsets
1984 {
1985     ASSERT(obscuredInsets.top >= 0);
1986     ASSERT(obscuredInsets.left >= 0);
1987     ASSERT(obscuredInsets.bottom >= 0);
1988     ASSERT(obscuredInsets.right >= 0);
1989
1990     if (UIEdgeInsetsEqualToEdgeInsets(_obscuredInsets, obscuredInsets))
1991         return;
1992
1993     _obscuredInsets = obscuredInsets;
1994
1995     [self _updateVisibleContentRects];
1996 }
1997
1998 - (void)_setInterfaceOrientationOverride:(UIInterfaceOrientation)interfaceOrientation
1999 {
2000     if (!_overridesInterfaceOrientation)
2001         [[NSNotificationCenter defaultCenter] removeObserver:self name:UIWindowDidRotateNotification object:nil];
2002
2003     _overridesInterfaceOrientation = YES;
2004
2005     if (interfaceOrientation == _interfaceOrientationOverride)
2006         return;
2007
2008     _interfaceOrientationOverride = interfaceOrientation;
2009
2010     if (!_isAnimatingResize)
2011         _page->setDeviceOrientation(deviceOrientationForUIInterfaceOrientation(_interfaceOrientationOverride));
2012 }
2013
2014 - (UIInterfaceOrientation)_interfaceOrientationOverride
2015 {
2016     ASSERT(_overridesInterfaceOrientation);
2017     return _interfaceOrientationOverride;
2018 }
2019
2020 - (CGSize)_maximumUnobscuredSizeOverride
2021 {
2022     ASSERT(_overridesMaximumUnobscuredSize);
2023     return _maximumUnobscuredSizeOverride;
2024 }
2025
2026 - (void)_setMaximumUnobscuredSizeOverride:(CGSize)size
2027 {
2028     ASSERT(size.width <= self.bounds.size.width && size.height <= self.bounds.size.height);
2029     _overridesMaximumUnobscuredSize = YES;
2030     if (CGSizeEqualToSize(_maximumUnobscuredSizeOverride, size))
2031         return;
2032
2033     _maximumUnobscuredSizeOverride = size;
2034     if (!_isAnimatingResize)
2035         _page->setMaximumUnobscuredSize(WebCore::FloatSize(size));
2036 }
2037
2038 - (void)_setBackgroundExtendsBeyondPage:(BOOL)backgroundExtends
2039 {
2040     _page->setBackgroundExtendsBeyondPage(backgroundExtends);
2041 }
2042
2043 - (BOOL)_backgroundExtendsBeyondPage
2044 {
2045     return _page->backgroundExtendsBeyondPage();
2046 }
2047
2048 - (void)_beginInteractiveObscuredInsetsChange
2049 {
2050     ASSERT(!_isChangingObscuredInsetsInteractively);
2051     _isChangingObscuredInsetsInteractively = YES;
2052 }
2053
2054 - (void)_endInteractiveObscuredInsetsChange
2055 {
2056     ASSERT(_isChangingObscuredInsetsInteractively);
2057     _isChangingObscuredInsetsInteractively = NO;
2058     [self _updateVisibleContentRects];
2059 }
2060
2061 - (void)_beginAnimatedResizeWithUpdates:(void (^)(void))updateBlock
2062 {
2063     if (_customContentView) {
2064         updateBlock();
2065         return;
2066     }
2067
2068     _isAnimatingResize = YES;
2069
2070     CGRect oldBounds = self.bounds;
2071     WebCore::FloatSize oldMinimumLayoutSize = activeMinimumLayoutSize(self, oldBounds);
2072     WebCore::FloatSize oldMinimumLayoutSizeForMinimalUI = activeMinimumLayoutSizeForMinimalUI(self, oldMinimumLayoutSize);
2073     WebCore::FloatSize oldMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, oldBounds);
2074     int32_t oldOrientation = activeOrientation(self);
2075     UIEdgeInsets oldObscuredInsets = _obscuredInsets;
2076     WebCore::FloatRect oldUnobscuredContentRect = _page->unobscuredContentRect();
2077
2078     updateBlock();
2079
2080     CGRect newBounds = self.bounds;
2081     WebCore::FloatSize newMinimumLayoutSize = activeMinimumLayoutSize(self, newBounds);
2082     WebCore::FloatSize newMinimumLayoutSizeForMinimalUI = activeMinimumLayoutSizeForMinimalUI(self, newMinimumLayoutSize);
2083     WebCore::FloatSize newMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, newBounds);
2084     int32_t newOrientation = activeOrientation(self);
2085     UIEdgeInsets newObscuredInsets = _obscuredInsets;
2086
2087     if (CGRectEqualToRect(oldBounds, newBounds)
2088         && oldMinimumLayoutSize == newMinimumLayoutSize
2089         && oldMinimumLayoutSizeForMinimalUI == newMinimumLayoutSizeForMinimalUI
2090         && oldMaximumUnobscuredSize == newMaximumUnobscuredSize
2091         && oldOrientation == newOrientation
2092         && UIEdgeInsetsEqualToEdgeInsets(oldObscuredInsets, newObscuredInsets)) {
2093         _isAnimatingResize = NO;
2094         [self _updateVisibleContentRects];
2095         return;
2096     }
2097
2098     _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2099
2100     NSUInteger indexOfContentView = [[_scrollView subviews] indexOfObject:_contentView.get()];
2101     _resizeAnimationView = adoptNS([[UIView alloc] init]);
2102     [_scrollView insertSubview:_resizeAnimationView.get() atIndex:indexOfContentView];
2103     [_resizeAnimationView addSubview:_contentView.get()];
2104
2105     CGSize contentSizeInContentViewCoordinates = [_contentView bounds].size;
2106     [_scrollView setMinimumZoomScale:std::min(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView minimumZoomScale])];
2107     [_scrollView setMaximumZoomScale:std::max(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView maximumZoomScale])];
2108
2109     // Compute the new scale to keep the current content width in the scrollview.
2110     CGFloat oldWebViewWidthInContentViewCoordinates = oldUnobscuredContentRect.width();
2111     CGFloat visibleContentViewWidthInContentCoordinates = std::min(contentSizeInContentViewCoordinates.width, oldWebViewWidthInContentViewCoordinates);
2112     CGFloat targetScale = newMinimumLayoutSize.width() / visibleContentViewWidthInContentCoordinates;
2113     CGFloat resizeAnimationViewAnimationScale = targetScale / contentZoomScale(self);
2114     [_resizeAnimationView setTransform:CGAffineTransformMakeScale(resizeAnimationViewAnimationScale, resizeAnimationViewAnimationScale)];
2115
2116     // Compute a new position to keep the content centered.
2117     CGPoint originalContentCenter = oldUnobscuredContentRect.center();
2118     CGPoint originalContentCenterInSelfCoordinates = [self convertPoint:originalContentCenter fromView:_contentView.get()];
2119     CGRect futureUnobscuredRectInSelfCoordinates = UIEdgeInsetsInsetRect(newBounds, _obscuredInsets);
2120     CGPoint futureUnobscuredRectCenterInSelfCoordinates = CGPointMake(futureUnobscuredRectInSelfCoordinates.origin.x + futureUnobscuredRectInSelfCoordinates.size.width / 2, futureUnobscuredRectInSelfCoordinates.origin.y + futureUnobscuredRectInSelfCoordinates.size.height / 2);
2121
2122     CGPoint originalContentOffset = [_scrollView contentOffset];
2123     CGPoint contentOffset = originalContentOffset;
2124     contentOffset.x += (originalContentCenterInSelfCoordinates.x - futureUnobscuredRectCenterInSelfCoordinates.x);
2125     contentOffset.y += (originalContentCenterInSelfCoordinates.y - futureUnobscuredRectCenterInSelfCoordinates.y);
2126
2127     // Limit the new offset within the scrollview, we do not want to rubber band programmatically.
2128     CGSize futureContentSizeInSelfCoordinates = CGSizeMake(contentSizeInContentViewCoordinates.width * targetScale, contentSizeInContentViewCoordinates.height * targetScale);
2129     CGFloat maxHorizontalOffset = futureContentSizeInSelfCoordinates.width - newBounds.size.width + _obscuredInsets.right;
2130     contentOffset.x = std::min(contentOffset.x, maxHorizontalOffset);
2131     CGFloat maxVerticalOffset = futureContentSizeInSelfCoordinates.height - newBounds.size.height + _obscuredInsets.bottom;
2132     contentOffset.y = std::min(contentOffset.y, maxVerticalOffset);
2133
2134     contentOffset.x = std::max(contentOffset.x, -_obscuredInsets.left);
2135     contentOffset.y = std::max(contentOffset.y, -_obscuredInsets.top);
2136
2137     // Make the top/bottom edges "sticky" within 1 pixel.
2138     if (oldUnobscuredContentRect.maxY() > contentSizeInContentViewCoordinates.height - 1)
2139         contentOffset.y = maxVerticalOffset;
2140     if (oldUnobscuredContentRect.y() < 1)
2141         contentOffset.y = -_obscuredInsets.top;
2142
2143     // FIXME: if we have content centered after double tap to zoom, we should also try to keep that rect in view.
2144     [_scrollView setContentSize:roundScrollViewContentSize(*_page, futureContentSizeInSelfCoordinates)];
2145     [_scrollView setContentOffset:contentOffset];
2146
2147     CGRect visibleRectInContentCoordinates = [self convertRect:newBounds toView:_contentView.get()];
2148     CGRect unobscuredRectInContentCoordinates = [self convertRect:futureUnobscuredRectInSelfCoordinates toView:_contentView.get()];
2149
2150     _page->dynamicViewportSizeUpdate(newMinimumLayoutSize, newMinimumLayoutSizeForMinimalUI, newMaximumUnobscuredSize, visibleRectInContentCoordinates, unobscuredRectInContentCoordinates, futureUnobscuredRectInSelfCoordinates, targetScale, newOrientation);
2151 }
2152
2153 - (void)_endAnimatedResize
2154 {
2155     if (!_isAnimatingResize)
2156         return;
2157
2158     _page->synchronizeDynamicViewportUpdate();
2159
2160     NSUInteger indexOfResizeAnimationView = [[_scrollView subviews] indexOfObject:_resizeAnimationView.get()];
2161     [_scrollView insertSubview:_contentView.get() atIndex:indexOfResizeAnimationView];
2162
2163     CALayer *contentViewLayer = [_contentView layer];
2164     CGFloat adjustmentScale = _resizeAnimationTransformAdjustments.m11;
2165     contentViewLayer.sublayerTransform = CATransform3DIdentity;
2166
2167     CGFloat animatingScaleTarget = [[_resizeAnimationView layer] transform].m11;
2168     CALayer *contentLayer = [_contentView layer];
2169     CATransform3D contentLayerTransform = contentLayer.transform;
2170     CGFloat currentScale = [[_resizeAnimationView layer] transform].m11 * contentLayerTransform.m11;
2171
2172     // We cannot use [UIScrollView setZoomScale:] directly because the UIScrollView delegate would get a callback with
2173     // an invalid contentOffset. The real content offset is only set below.
2174     // Since there is no public API for setting both the zoomScale and the contentOffset, we set the zoomScale manually
2175     // on the zoom layer and then only change the contentOffset.
2176     CGFloat adjustedScale = adjustmentScale * currentScale;
2177     contentLayerTransform.m11 = adjustedScale;
2178     contentLayerTransform.m22 = adjustedScale;
2179     contentLayer.transform = contentLayerTransform;
2180
2181     CGPoint currentScrollOffset = [_scrollView contentOffset];
2182     double horizontalScrollAdjustement = _resizeAnimationTransformAdjustments.m41 * animatingScaleTarget;
2183     double verticalScrollAdjustment = _resizeAnimationTransformAdjustments.m42 * animatingScaleTarget;
2184
2185     [_scrollView setContentSize:roundScrollViewContentSize(*_page, [_contentView frame].size)];
2186     [_scrollView setContentOffset:CGPointMake(currentScrollOffset.x - horizontalScrollAdjustement, currentScrollOffset.y - verticalScrollAdjustment)];
2187
2188     [_resizeAnimationView removeFromSuperview];
2189     _resizeAnimationView = nil;
2190     _resizeAnimationTransformAdjustments = CATransform3DIdentity;
2191
2192     _isAnimatingResize = NO;
2193     [self _updateVisibleContentRects];
2194 }
2195
2196 - (void)_setOverlaidAccessoryViewsInset:(CGSize)inset
2197 {
2198     [_customContentView web_setOverlaidAccessoryViewsInset:inset];
2199 }
2200
2201 - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler
2202 {
2203     CGRect snapshotRectInContentCoordinates = [self convertRect:rectInViewCoordinates toView:_contentView.get()];
2204     CGFloat imageHeight = imageWidth / snapshotRectInContentCoordinates.size.width * snapshotRectInContentCoordinates.size.height;
2205     CGSize imageSize = CGSizeMake(imageWidth, imageHeight);
2206     
2207     void(^copiedCompletionHandler)(CGImageRef) = [completionHandler copy];
2208     _page->takeSnapshot(WebCore::enclosingIntRect(snapshotRectInContentCoordinates), WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebKit::SnapshotOptionsExcludeDeviceScaleFactor, [=](const WebKit::ShareableBitmap::Handle& imageHandle, WebKit::CallbackBase::Error) {
2209         if (imageHandle.isNull()) {
2210             copiedCompletionHandler(nullptr);
2211             [copiedCompletionHandler release];
2212             return;
2213         }
2214
2215         RefPtr<WebKit::ShareableBitmap> bitmap = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::ReadOnly);
2216
2217         if (!bitmap) {
2218             copiedCompletionHandler(nullptr);
2219             [copiedCompletionHandler release];
2220             return;
2221         }
2222
2223         RetainPtr<CGImageRef> cgImage;
2224         cgImage = bitmap->makeCGImage();
2225         copiedCompletionHandler(cgImage.get());
2226         [copiedCompletionHandler release];
2227     });
2228 }
2229
2230 - (void)_overrideLayoutParametersWithMinimumLayoutSize:(CGSize)minimumLayoutSize minimumLayoutSizeForMinimalUI:(CGSize)minimumLayoutSizeForMinimalUI maximumUnobscuredSizeOverride:(CGSize)maximumUnobscuredSizeOverride
2231 {
2232     // FIXME: After Safari is updated to use this function instead of setting the parameters separately, we should remove
2233     // the individual setters and send a single message to send everything at once to the WebProcess.
2234     self._minimumLayoutSizeOverride = minimumLayoutSize;
2235     self._minimumLayoutSizeOverrideForMinimalUI = minimumLayoutSizeForMinimalUI;
2236     self._maximumUnobscuredSizeOverride = maximumUnobscuredSizeOverride;
2237 }
2238
2239 - (UIView *)_viewForFindUI
2240 {
2241     return [self viewForZoomingInScrollView:[self scrollView]];
2242 }
2243
2244 - (BOOL)_isDisplayingPDF
2245 {
2246     return [_customContentView isKindOfClass:[WKPDFView class]];
2247 }
2248
2249 - (NSData *)_dataForDisplayedPDF
2250 {
2251     if (![self _isDisplayingPDF])
2252         return nil;
2253     CGPDFDocumentRef pdfDocument = [(WKPDFView *)_customContentView pdfDocument];
2254     return [(NSData *)CGDataProviderCopyData(CGPDFDocumentGetDataProvider(pdfDocument)) autorelease];
2255 }
2256
2257 - (NSString *)_suggestedFilenameForDisplayedPDF
2258 {
2259     if (![self _isDisplayingPDF])
2260         return nil;
2261     return [(WKPDFView *)_customContentView.get() suggestedFilename];
2262 }
2263
2264 - (CGFloat)_viewportMetaTagWidth
2265 {
2266     return _viewportMetaTagWidth;
2267 }
2268
2269 - (_WKWebViewPrintFormatter *)_webViewPrintFormatter
2270 {
2271     UIViewPrintFormatter *viewPrintFormatter = self.viewPrintFormatter;
2272     ASSERT([viewPrintFormatter isKindOfClass:[_WKWebViewPrintFormatter class]]);
2273     return (_WKWebViewPrintFormatter *)viewPrintFormatter;
2274 }
2275
2276 #else
2277
2278 #pragma mark - OS X-specific methods
2279
2280 - (NSColor *)_pageExtendedBackgroundColor
2281 {
2282     WebCore::Color color = _page->pageExtendedBackgroundColor();
2283     if (!color.isValid())
2284         return nil;
2285
2286     return nsColor(color);
2287 }
2288
2289 - (BOOL)_drawsTransparentBackground
2290 {
2291     return _page->drawsTransparentBackground();
2292 }
2293
2294 - (void)_setDrawsTransparentBackground:(BOOL)drawsTransparentBackground
2295 {
2296     _page->setDrawsTransparentBackground(drawsTransparentBackground);
2297 }
2298
2299 - (void)_setTopContentInset:(CGFloat)contentInset
2300 {
2301     [_wkView _setTopContentInset:contentInset];
2302 }
2303
2304 - (CGFloat)_topContentInset
2305 {
2306     return [_wkView _topContentInset];
2307 }
2308
2309 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
2310
2311 - (void)_setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets
2312 {
2313     [_wkView _setAutomaticallyAdjustsContentInsets:automaticallyAdjustsContentInsets];
2314 }
2315
2316 - (BOOL)_automaticallyAdjustsContentInsets
2317 {
2318     return [_wkView _automaticallyAdjustsContentInsets];
2319 }
2320
2321 #endif
2322
2323 #endif
2324
2325 @end
2326
2327 #if !TARGET_OS_IPHONE
2328
2329 @implementation WKWebView (WKIBActions)
2330
2331 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
2332 {
2333     SEL action = item.action;
2334
2335     if (action == @selector(goBack:))
2336         return !!_page->backForwardList().backItem();
2337
2338     if (action == @selector(goForward:))
2339         return !!_page->backForwardList().forwardItem();
2340
2341     if (action == @selector(stopLoading:)) {
2342         // FIXME: Return no if we're stopped.
2343         return YES;
2344     }
2345
2346     if (action == @selector(reload:) || action == @selector(reloadFromOrigin:)) {
2347         // FIXME: Return no if we're loading.
2348         return YES;
2349     }
2350
2351     return NO;
2352 }
2353
2354 - (IBAction)goBack:(id)sender
2355 {
2356     [self goBack];
2357 }
2358
2359 - (IBAction)goForward:(id)sender
2360 {
2361     [self goForward];
2362 }
2363
2364 - (IBAction)reload:(id)sender
2365 {
2366     [self reload];
2367 }
2368
2369 - (IBAction)reloadFromOrigin:(id)sender
2370 {
2371     [self reloadFromOrigin];
2372 }
2373
2374 - (IBAction)stopLoading:(id)sender
2375 {
2376     _page->stopLoading();
2377 }
2378
2379 @end
2380
2381 #endif
2382
2383 #if PLATFORM(IOS)
2384 @implementation WKWebView (_WKWebViewPrintFormatter)
2385
2386 - (Class)_printFormatterClass
2387 {
2388     return [_WKWebViewPrintFormatter class];
2389 }
2390
2391 - (NSInteger)_computePageCountAndStartDrawingToPDFForFrame:(_WKFrameHandle *)frame printInfo:(const WebKit::PrintInfo&)printInfo firstPage:(uint32_t)firstPage computedTotalScaleFactor:(double&)totalScaleFactor
2392 {
2393     if ([self _isDisplayingPDF])
2394         return CGPDFDocumentGetNumberOfPages([(WKPDFView *)_customContentView pdfDocument]);
2395
2396     _pageIsPrintingToPDF = YES;
2397     Vector<WebCore::IntRect> pageRects;
2398     uint64_t frameID = frame ? frame._frameID : _page->mainFrame()->frameID();
2399     if (!_page->sendSync(Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF(frameID, printInfo, firstPage), Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF::Reply(pageRects, totalScaleFactor)))
2400         return 0;
2401     return pageRects.size();
2402 }
2403
2404 - (void)_endPrinting
2405 {
2406     _pageIsPrintingToPDF = NO;
2407     _printedDocument = nullptr;
2408     _page->send(Messages::WebPage::EndPrinting());
2409 }
2410
2411 // FIXME: milliseconds::max() overflows when converted to nanoseconds, causing condition_variable::wait_for() to believe
2412 // a timeout occurred on any spurious wakeup. Use nanoseconds::max() (converted to ms) to avoid this. We should perhaps
2413 // change waitForAndDispatchImmediately() to take nanoseconds to avoid this issue.
2414 static constexpr std::chrono::milliseconds didFinishLoadingTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds::max());
2415
2416 - (CGPDFDocumentRef)_printedDocument
2417 {
2418     if ([self _isDisplayingPDF]) {
2419         ASSERT(!_pageIsPrintingToPDF);
2420         return [(WKPDFView *)_customContentView pdfDocument];
2421     }
2422
2423     if (_pageIsPrintingToPDF) {
2424         if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::DidFinishDrawingPagesToPDF>(_page->pageID(), didFinishLoadingTimeout)) {
2425             ASSERT_NOT_REACHED();
2426             return nullptr;
2427         }
2428         ASSERT(!_pageIsPrintingToPDF);
2429     }
2430     return _printedDocument.get();
2431 }
2432
2433 - (void)_setPrintedDocument:(CGPDFDocumentRef)printedDocument
2434 {
2435     if (!_pageIsPrintingToPDF)
2436         return;
2437     ASSERT(![self _isDisplayingPDF]);
2438     _printedDocument = printedDocument;
2439     _pageIsPrintingToPDF = NO;
2440 }
2441
2442 @end
2443 #endif
2444
2445 #endif // WK_API_ENABLED