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