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