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