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