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