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