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