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