5441953c4873dc417e6556896f0491c855e67d48
[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 "NavigationState.h"
32 #import "RemoteLayerTreeTransaction.h"
33 #import "RemoteObjectRegistry.h"
34 #import "RemoteObjectRegistryMessages.h"
35 #import "UIClient.h"
36 #import "ViewGestureController.h"
37 #import "WKBackForwardListInternal.h"
38 #import "WKBackForwardListItemInternal.h"
39 #import "WKBrowsingContextHandleInternal.h"
40 #import "WKHistoryDelegatePrivate.h"
41 #import "WKNSData.h"
42 #import "WKNSURLExtras.h"
43 #import "WKNavigationDelegate.h"
44 #import "WKNavigationInternal.h"
45 #import "WKPreferencesInternal.h"
46 #import "WKProcessPoolInternal.h"
47 #import "WKRemoteObjectRegistryInternal.h"
48 #import "WKUIDelegate.h"
49 #import "WKWebViewConfigurationInternal.h"
50 #import "WKWebViewContentProvider.h"
51 #import "WebBackForwardList.h"
52 #import "WebCertificateInfo.h"
53 #import "WebContext.h"
54 #import "WebPageGroup.h"
55 #import "WebPageProxy.h"
56 #import "WebProcessProxy.h"
57 #import "_WKVisitedLinkProviderInternal.h"
58 #import <wtf/RetainPtr.h>
59
60 #if PLATFORM(IOS)
61 #import "WKScrollView.h"
62 #import "WKWebViewContentProviderRegistry.h"
63 #import <UIKit/UIPeripheralHost_Private.h>
64
65 @interface UIScrollView (UIScrollViewInternal)
66 - (void)_adjustForAutomaticKeyboardInfo:(NSDictionary*)info animated:(BOOL)animated lastAdjustment:(CGFloat*)lastAdjustment;
67 @end
68
69 @interface UIPeripheralHost(UIKitInternal)
70 - (CGFloat)getVerticalOverlapForView:(UIView *)view usingKeyboardInfo:(NSDictionary *)info;
71 @end
72 #endif
73
74 #if PLATFORM(MAC)
75 #import "WKViewInternal.h"
76 #import <WebCore/ColorMac.h>
77 #endif
78
79 @implementation WKWebView {
80     std::unique_ptr<WebKit::NavigationState> _navigationState;
81
82     RetainPtr<WKRemoteObjectRegistry> _remoteObjectRegistry;
83     _WKRenderingProgressEvents _observedRenderingProgressEvents;
84
85 #if PLATFORM(IOS)
86     RetainPtr<WKScrollView> _scrollView;
87     RetainPtr<WKContentView> _contentView;
88
89     BOOL _isWaitingForNewLayerTreeAfterDidCommitLoad;
90     BOOL _hasStaticMinimumLayoutSize;
91     CGSize _minimumLayoutSizeOverride;
92
93     UIEdgeInsets _obscuredInsets;
94     bool _isChangingObscuredInsetsInteractively;
95     CGFloat _lastAdjustmentForScroller;
96     CGFloat _keyboardVerticalOverlap;
97
98     std::unique_ptr<WebKit::ViewGestureController> _gestureController;
99     BOOL _allowsBackForwardNavigationGestures;
100
101     RetainPtr<UIView <WKWebViewContentProvider>> _customContentView;
102 #endif
103 #if PLATFORM(MAC)
104     RetainPtr<WKView> _wkView;
105 #endif
106 }
107
108 - (instancetype)initWithFrame:(CGRect)frame
109 {
110     return [self initWithFrame:frame configuration:adoptNS([[WKWebViewConfiguration alloc] init]).get()];
111 }
112
113 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
114 {
115     if (!(self = [super initWithFrame:frame]))
116         return nil;
117
118     _configuration = adoptNS([configuration copy]);
119
120     if (WKWebView *relatedWebView = [_configuration _relatedWebView]) {
121         WKProcessPool *processPool = [_configuration processPool];
122         WKProcessPool *relatedWebViewProcessPool = [relatedWebView->_configuration processPool];
123         if (processPool && processPool != relatedWebViewProcessPool)
124             [NSException raise:NSInvalidArgumentException format:@"Related web view %@ has process pool %@ but configuration specifies a different process pool %@", relatedWebView, relatedWebViewProcessPool, configuration.processPool];
125
126         [_configuration setProcessPool:relatedWebViewProcessPool];
127     }
128
129     if (![_configuration processPool])
130         [_configuration setProcessPool:adoptNS([[WKProcessPool alloc] init]).get()];
131
132     if (![_configuration preferences])
133         [_configuration setPreferences:adoptNS([[WKPreferences alloc] init]).get()];
134
135     if (![_configuration _visitedLinkProvider])
136         [_configuration _setVisitedLinkProvider:adoptNS([[_WKVisitedLinkProvider alloc] init]).get()];
137
138 #if PLATFORM(IOS)
139     if (![_configuration _contentProviderRegistry])
140         [_configuration _setContentProviderRegistry:adoptNS([[WKWebViewContentProviderRegistry alloc] init]).get()];
141 #endif
142
143     CGRect bounds = self.bounds;
144
145     WebKit::WebContext& context = *[_configuration processPool]->_context;
146
147     WebKit::WebPageConfiguration webPageConfiguration;
148     webPageConfiguration.preferences = [_configuration preferences]->_preferences.get();
149     if (WKWebView *relatedWebView = [_configuration _relatedWebView])
150         webPageConfiguration.relatedPage = relatedWebView->_page.get();
151
152     webPageConfiguration.visitedLinkProvider = [_configuration _visitedLinkProvider]->_visitedLinkProvider.get();
153
154     RefPtr<WebKit::WebPageGroup> pageGroup;
155     NSString *groupIdentifier = configuration._groupIdentifier;
156     if (groupIdentifier.length) {
157         pageGroup = WebKit::WebPageGroup::create(configuration._groupIdentifier);
158         webPageConfiguration.pageGroup = pageGroup.get();
159     }
160
161 #if PLATFORM(IOS)
162     _scrollView = adoptNS([[WKScrollView alloc] initWithFrame:bounds]);
163     [_scrollView setInternalDelegate:self];
164     [_scrollView setBouncesZoom:YES];
165
166     [self addSubview:_scrollView.get()];
167
168     _contentView = adoptNS([[WKContentView alloc] initWithFrame:bounds context:context configuration:std::move(webPageConfiguration) webView:self]);
169     _page = [_contentView page];
170     [_contentView layer].anchorPoint = CGPointZero;
171     [_contentView setFrame:bounds];
172     [_scrollView addSubview:_contentView.get()];
173
174     [self _frameOrBoundsChanged];
175
176     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
177     [center addObserver:self selector:@selector(_keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
178     [center addObserver:self selector:@selector(_keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];
179     [center addObserver:self selector:@selector(_keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
180     [center addObserver:self selector:@selector(_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
181
182     [[_configuration _contentProviderRegistry] addPage:*_page];
183 #endif
184
185 #if PLATFORM(MAC)
186     _wkView = [[WKView alloc] initWithFrame:bounds context:context configuration:std::move(webPageConfiguration)];
187     [self addSubview:_wkView.get()];
188     _page = WebKit::toImpl([_wkView pageRef]);
189 #endif
190
191     _navigationState = std::make_unique<WebKit::NavigationState>(self);
192     _page->setPolicyClient(_navigationState->createPolicyClient());
193     _page->setLoaderClient(_navigationState->createLoaderClient());
194
195     _page->setUIClient(std::make_unique<WebKit::UIClient>(self));
196
197     return self;
198 }
199
200 - (void)dealloc
201 {
202     [_remoteObjectRegistry _invalidate];
203 #if PLATFORM(IOS)
204     [[_configuration _contentProviderRegistry] removePage:*_page];
205     [[NSNotificationCenter defaultCenter] removeObserver:self];
206 #endif
207
208     [super dealloc];
209 }
210
211 - (WKWebViewConfiguration *)configuration
212 {
213     return [[_configuration copy] autorelease];
214 }
215
216 - (WKBackForwardList *)backForwardList
217 {
218     return wrapper(_page->backForwardList());
219 }
220
221 - (id <WKNavigationDelegate>)navigationDelegate
222 {
223     return [_navigationState->navigationDelegate().leakRef() autorelease];
224 }
225
226 - (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate
227 {
228     _navigationState->setNavigationDelegate(navigationDelegate);
229 }
230
231 - (id <WKUIDelegate>)UIDelegate
232 {
233     return [static_cast<WebKit::UIClient&>(_page->uiClient()).delegate().leakRef() autorelease];
234 }
235
236 - (void)setUIDelegate:(id<WKUIDelegate>)UIDelegate
237 {
238     static_cast<WebKit::UIClient&>(_page->uiClient()).setDelegate(UIDelegate);
239 }
240
241 - (WKNavigation *)loadRequest:(NSURLRequest *)request
242 {
243     uint64_t navigationID = _page->loadRequest(request);
244     auto navigation = _navigationState->createLoadRequestNavigation(navigationID, request);
245
246     return [navigation.leakRef() autorelease];
247 }
248
249 - (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item
250 {
251     _page->goToBackForwardItem(&item._item);
252
253     // FIXME: return a WKNavigation object.
254     return nil;
255 }
256
257 - (NSString *)title
258 {
259     return _page->pageLoadState().title();
260 }
261
262 - (NSURL *)activeURL
263 {
264     return [NSURL _web_URLWithWTFString:_page->pageLoadState().activeURL()];
265 }
266
267 - (BOOL)isLoading
268 {
269     return _page->pageLoadState().isLoading();
270 }
271
272 - (double)estimatedProgress
273 {
274     return _page->pageLoadState().estimatedProgress();
275 }
276
277 - (BOOL)hasOnlySecureContent
278 {
279     return _page->pageLoadState().hasOnlySecureContent();
280 }
281
282 // FIXME: This should be KVO compliant.
283 - (BOOL)canGoBack
284 {
285     return !!_page->backForwardList().backItem();
286 }
287
288 // FIXME: This should be KVO compliant.
289 - (BOOL)canGoForward
290 {
291     return !!_page->backForwardList().forwardItem();
292 }
293
294 - (WKNavigation *)goBack
295 {
296     _page->goBack();
297
298     // FIXME: Return a navigation object.
299     return nil;
300 }
301
302 - (WKNavigation *)goForward
303 {
304     _page->goForward();
305
306     // FIXME: Return a navigation object.
307     return nil;
308 }
309
310 - (IBAction)stopLoading:(id)sender
311 {
312     _page->stopLoading();
313 }
314
315 #pragma mark iOS-specific methods
316
317 #if PLATFORM(IOS)
318 - (void)setFrame:(CGRect)frame
319 {
320     CGRect oldFrame = self.frame;
321     [super setFrame:frame];
322
323     if (!CGSizeEqualToSize(oldFrame.size, frame.size))
324         [self _frameOrBoundsChanged];
325 }
326
327 - (void)setBounds:(CGRect)bounds
328 {
329     CGRect oldBounds = self.bounds;
330     [super setBounds:bounds];
331
332     if (!CGSizeEqualToSize(oldBounds.size, bounds.size))
333         [self _frameOrBoundsChanged];
334 }
335
336 - (UIScrollView *)scrollView
337 {
338     return _scrollView.get();
339 }
340
341 - (WKBrowsingContextController *)browsingContextController
342 {
343     return [_contentView browsingContextController];
344 }
345
346 - (void)_setHasCustomContentView:(BOOL)pageHasCustomContentView loadedMIMEType:(const WTF::String&)mimeType
347 {
348     if (pageHasCustomContentView) {
349         [_customContentView removeFromSuperview];
350
351         Class representationClass = [[_configuration _contentProviderRegistry] providerForMIMEType:mimeType];
352         ASSERT(representationClass);
353         _customContentView = adoptNS([[representationClass alloc] init]);
354
355         [_contentView removeFromSuperview];
356         [_scrollView addSubview:_customContentView.get()];
357
358         [_customContentView web_setMinimumSize:self.bounds.size];
359         [_customContentView web_setScrollView:_scrollView.get()];
360     } else if (_customContentView) {
361         [_customContentView removeFromSuperview];
362         _customContentView = nullptr;
363
364         [_scrollView addSubview:_contentView.get()];
365         [_scrollView setContentSize:[_contentView frame].size];
366     }
367 }
368
369 - (void)_didFinishLoadingDataForCustomContentProviderWithSuggestedFilename:(const String&)suggestedFilename data:(NSData *)data
370 {
371     ASSERT(_customContentView);
372     [_customContentView web_setContentProviderData:data];
373 }
374
375 - (void)_didCommitLoadForMainFrame
376 {
377     _isWaitingForNewLayerTreeAfterDidCommitLoad = YES;
378 }
379
380 // This is a convenience method that will convert _page->pageExtendedBackgroundColor() from a WebCore::Color to a UIColor *.
381 - (UIColor *)pageExtendedBackgroundColor
382 {
383     WebCore::Color color = _page->pageExtendedBackgroundColor();
384     if (!color.isValid())
385         return nil;
386
387     return [UIColor colorWithRed:(color.red() / 255.0) green:(color.green() / 255.0) blue:(color.blue() / 255.0) alpha:(color.alpha() / 255.0)];
388 }
389
390 static CGFloat contentZoomScale(WKWebView* webView)
391 {
392     UIView *zoomView;
393     if (webView->_customContentView)
394         zoomView = webView->_customContentView.get();
395     else
396         zoomView = webView->_contentView.get();
397
398     CGFloat scale = [[zoomView layer] affineTransform].a;
399     ASSERT(scale == [webView->_scrollView zoomScale]);
400     return scale;
401 }
402
403 - (void)_updateScrollViewBackground
404 {
405     UIColor *pageExtendedBackgroundColor = [self pageExtendedBackgroundColor];
406
407     CGFloat zoomScale = contentZoomScale(self);
408     CGFloat minimumZoomScale = [_scrollView minimumZoomScale];
409     if (zoomScale < minimumZoomScale) {
410         CGFloat slope = 12;
411         CGFloat opacity = std::max(1 - slope * (minimumZoomScale - zoomScale), static_cast<CGFloat>(0));
412         pageExtendedBackgroundColor = [pageExtendedBackgroundColor colorWithAlphaComponent:opacity];
413     }
414
415     [_scrollView setBackgroundColor:pageExtendedBackgroundColor];
416 }
417
418 - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
419 {
420     ASSERT(!_customContentView);
421
422     [_scrollView setContentSize:[_contentView frame].size];
423     [_scrollView setMinimumZoomScale:layerTreeTransaction.minimumScaleFactor()];
424     [_scrollView setMaximumZoomScale:layerTreeTransaction.maximumScaleFactor()];
425     [_scrollView setZoomEnabled:layerTreeTransaction.allowsUserScaling()];
426     if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom])
427         [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()];
428     
429     [self _updateScrollViewBackground];
430
431     if (_gestureController)
432         _gestureController->setRenderTreeSize(layerTreeTransaction.renderTreeSize());
433
434     if (_isWaitingForNewLayerTreeAfterDidCommitLoad) {
435         UIEdgeInsets inset = [_scrollView contentInset];
436         [_scrollView setContentOffset:CGPointMake(-inset.left, -inset.top)];
437         _isWaitingForNewLayerTreeAfterDidCommitLoad = NO;
438     }
439
440 }
441
442 - (RetainPtr<CGImageRef>)_takeViewSnapshot
443 {
444     // FIXME: We should be able to use acquire an IOSurface directly, instead of going to CGImage here and back in ViewSnapshotStore.
445     UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, self.window.screen.scale);
446     [self drawViewHierarchyInRect:[self bounds] afterScreenUpdates:NO];
447     UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
448     UIGraphicsEndImageContext();
449     return image.CGImage;
450 }
451
452 - (void)_zoomToPoint:(WebCore::FloatPoint)point atScale:(double)scale
453 {
454     double maximumZoomDuration = 0.4;
455     double minimumZoomDuration = 0.1;
456     double zoomDurationFactor = 0.3;
457
458     CGFloat zoomScale = contentZoomScale(self);
459     CFTimeInterval duration = std::min(fabs(log(zoomScale) - log(scale)) * zoomDurationFactor + minimumZoomDuration, maximumZoomDuration);
460
461     if (scale != zoomScale)
462         [_contentView willStartUserTriggeredZoom];
463
464     [_scrollView _zoomToCenter:point scale:scale duration:duration];
465 }
466
467 - (void)_zoomToRect:(WebCore::FloatRect)targetRect atScale:(double)scale origin:(WebCore::FloatPoint)origin
468 {
469     WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
470     WebCore::FloatSize targetRectSizeAfterZoom = targetRect.size();
471     targetRectSizeAfterZoom.scale(scale);
472
473     // Center the target rect in the scroll view.
474     // If the target doesn't fit in the scroll view, center on the gesture location instead.
475     WebCore::FloatPoint zoomCenter = targetRect.center();
476
477     if (targetRectSizeAfterZoom.width() > unobscuredContentSize.width())
478         zoomCenter.setX(origin.x());
479     if (targetRectSizeAfterZoom.height() > unobscuredContentSize.height())
480         zoomCenter.setY(origin.y());
481
482     [self _zoomToPoint:zoomCenter atScale:scale];
483 }
484
485 static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOffset, WebCore::FloatSize contentSize, WebCore::FloatSize unobscuredContentSize)
486 {
487     WebCore::FloatSize maximumContentOffset = contentSize - unobscuredContentSize;
488     contentOffset = contentOffset.shrunkTo(WebCore::FloatPoint(maximumContentOffset.width(), maximumContentOffset.height()));
489     contentOffset = contentOffset.expandedTo(WebCore::FloatPoint());
490     return contentOffset;
491 }
492
493 - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance
494 {
495     WebCore::FloatRect unobscuredContentRect([self _contentRectForUserInteraction]);
496     WebCore::FloatPoint unobscuredContentOffset = unobscuredContentRect.location();
497     WebCore::FloatSize contentSize([_contentView bounds].size);
498
499     // Center the target rect in the scroll view.
500     // If the target doesn't fit in the scroll view, center on the gesture location instead.
501     WebCore::FloatPoint newUnobscuredContentOffset;
502     if (targetRect.width() <= unobscuredContentRect.width())
503         newUnobscuredContentOffset.setX(targetRect.x() - (unobscuredContentRect.width() - targetRect.width()) / 2);
504     else
505         newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
506     if (targetRect.height() <= unobscuredContentRect.height())
507         newUnobscuredContentOffset.setY(targetRect.y() - (unobscuredContentRect.height() - targetRect.height()) / 2);
508     else
509         newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
510     newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
511
512     if (unobscuredContentOffset == newUnobscuredContentOffset) {
513         if (targetRect.width() > unobscuredContentRect.width())
514             newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
515         if (targetRect.height() > unobscuredContentRect.height())
516             newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
517         newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
518     }
519
520     WebCore::FloatSize scrollViewOffsetDelta = newUnobscuredContentOffset - unobscuredContentOffset;
521     scrollViewOffsetDelta.scale(contentZoomScale(self));
522
523     float scrollDistance = scrollViewOffsetDelta.diagonalLength();
524     if (scrollDistance < minimumScrollDistance)
525         return false;
526
527     [_scrollView setContentOffset:([_scrollView contentOffset] + scrollViewOffsetDelta) animated:YES];
528     return true;
529 }
530
531 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin
532 {
533     [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale]];
534 }
535
536 - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
537 {
538     const float maximumScaleFactorDeltaForPanScroll = 0.02;
539
540     double currentScale = contentZoomScale(self);
541
542     WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
543     double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width();
544     double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height();
545
546     horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale);
547     verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale);
548
549     double targetScale = fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
550     if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) {
551         if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance])
552             return true;
553     } else if (targetScale != currentScale) {
554         [self _zoomToRect:targetRect atScale:targetScale origin:origin];
555         return true;
556     }
557     
558     return false;
559 }
560
561 #pragma mark - UIScrollViewDelegate
562
563 - (BOOL)usesStandardContentView
564 {
565     return !_customContentView;
566 }
567
568 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
569 {
570     ASSERT(_scrollView == scrollView);
571
572     if (_customContentView)
573         return _customContentView.get();
574
575     return _contentView.get();
576 }
577
578 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
579 {
580     if (![self usesStandardContentView])
581         return;
582
583     if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan)
584         [_contentView willStartUserTriggeredZoom];
585     [_contentView willStartZoomOrScroll];
586 }
587
588 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
589 {
590     if (![self usesStandardContentView])
591         return;
592
593     if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
594         [_contentView willStartUserTriggeredScroll];
595     [_contentView willStartZoomOrScroll];
596 }
597
598 - (void)_didFinishScrolling
599 {
600     if (![self usesStandardContentView])
601         return;
602
603     [self _updateVisibleContentRects];
604     [_contentView didFinishScrolling];
605 }
606
607 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
608 {
609     // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called.
610     if (!decelerate)
611         [self _didFinishScrolling];
612 }
613
614 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
615 {
616     [self _didFinishScrolling];
617 }
618
619 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
620 {
621     [self _didFinishScrolling];
622 }
623
624 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
625 {
626     [self _updateVisibleContentRects];
627 }
628
629 - (void)scrollViewDidZoom:(UIScrollView *)scrollView
630 {
631     [self _updateScrollViewBackground];
632     [self _updateVisibleContentRects];
633 }
634
635 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
636 {
637     ASSERT(scrollView == _scrollView);
638     [self _updateVisibleContentRects];
639     [_contentView didZoomToScale:scale];
640 }
641
642 - (void)_frameOrBoundsChanged
643 {
644     CGRect bounds = self.bounds;
645
646     if (!_hasStaticMinimumLayoutSize)
647         [_contentView setMinimumLayoutSize:bounds.size];
648     [_scrollView setFrame:bounds];
649     [_contentView setMinimumSize:bounds.size];
650     [_customContentView web_setMinimumSize:bounds.size];
651     [self _updateVisibleContentRects];
652 }
653
654 // 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.
655 - (CGRect)_contentRectForUserInteraction
656 {
657     // FIXME: handle split keyboard.
658     UIEdgeInsets obscuredInsets = _obscuredInsets;
659     obscuredInsets.bottom = std::max(_obscuredInsets.bottom, _keyboardVerticalOverlap);
660     CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, obscuredInsets);
661     return [self convertRect:unobscuredRect toView:_contentView.get()];
662 }
663
664 - (void)_updateVisibleContentRects
665 {
666     if (![self usesStandardContentView])
667         return;
668
669     CGRect fullViewRect = self.bounds;
670     CGRect visibleRectInContentCoordinates = [self convertRect:fullViewRect toView:_contentView.get()];
671
672     CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, _obscuredInsets);
673     CGRect unobscuredRectInContentCoordinates = [self convertRect:unobscuredRect toView:_contentView.get()];
674
675     CGFloat scaleFactor = contentZoomScale(self);
676
677     BOOL isStableState = !(_isChangingObscuredInsetsInteractively || [_scrollView isDragging] || [_scrollView isDecelerating] || [_scrollView isZooming] || [_scrollView isZoomBouncing] || [_scrollView _isAnimatingZoom]);
678     [_contentView didUpdateVisibleRect:visibleRectInContentCoordinates unobscuredRect:unobscuredRectInContentCoordinates scale:scaleFactor inStableState:isStableState];
679 }
680
681 - (void)_keyboardChangedWithInfo:(NSDictionary *)keyboardInfo adjustScrollView:(BOOL)adjustScrollView
682 {
683     _keyboardVerticalOverlap = [[UIPeripheralHost sharedInstance] getVerticalOverlapForView:self usingKeyboardInfo:keyboardInfo];
684     [self _updateVisibleContentRects];
685
686     if (adjustScrollView)
687         [_scrollView _adjustForAutomaticKeyboardInfo:keyboardInfo animated:YES lastAdjustment:&_lastAdjustmentForScroller];
688 }
689
690 - (void)_keyboardWillChangeFrame:(NSNotification *)notification
691 {
692     if ([_contentView isAssistingNode])
693         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
694 }
695
696 - (void)_keyboardDidChangeFrame:(NSNotification *)notification
697 {
698     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:NO];
699 }
700
701 - (void)_keyboardWillShow:(NSNotification *)notification
702 {
703     if ([_contentView isAssistingNode])
704         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
705 }
706
707 - (void)_keyboardWillHide:(NSNotification *)notification
708 {
709     // Ignore keyboard will hide notifications sent during rotation. They're just there for
710     // backwards compatibility reasons and processing the will hide notification would
711     // temporarily screw up the the unobscured view area.
712     if ([[UIPeripheralHost sharedInstance] rotationState])
713         return;
714
715     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
716 }
717
718 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
719 {
720     if (_allowsBackForwardNavigationGestures == allowsBackForwardNavigationGestures)
721         return;
722
723     _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
724
725     if (allowsBackForwardNavigationGestures) {
726         if (!_gestureController) {
727             _gestureController = std::make_unique<WebKit::ViewGestureController>(*_page);
728             _gestureController->installSwipeHandler(self, [self scrollView]);
729         }
730     } else
731         _gestureController = nullptr;
732
733     _page->setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures);
734 }
735
736 - (BOOL)allowsBackForwardNavigationGestures
737 {
738     return _allowsBackForwardNavigationGestures;
739 }
740
741 #endif
742
743 #pragma mark OS X-specific methods
744
745 #if PLATFORM(MAC)
746
747 - (void)resizeSubviewsWithOldSize:(NSSize)oldSize
748 {
749     [_wkView setFrame:self.bounds];
750 }
751
752 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
753 {
754     [_wkView setAllowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures];
755 }
756
757 - (BOOL)allowsBackForwardNavigationGestures
758 {
759     return [_wkView allowsBackForwardNavigationGestures];
760 }
761
762 - (void)setAllowsMagnification:(BOOL)allowsMagnification
763 {
764     [_wkView setAllowsMagnification:allowsMagnification];
765 }
766
767 - (BOOL)allowsMagnification
768 {
769     return [_wkView allowsMagnification];
770 }
771
772 - (void)setMagnification:(CGFloat)magnification
773 {
774     [_wkView setMagnification:magnification];
775 }
776
777 - (CGFloat)magnification
778 {
779     return [_wkView magnification];
780 }
781
782 - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point
783 {
784     [_wkView setMagnification:magnification centeredAtPoint:NSPointFromCGPoint(point)];
785 }
786
787 #endif
788
789 @end
790
791 @implementation WKWebView (WKPrivate)
792
793 - (WKRemoteObjectRegistry *)_remoteObjectRegistry
794 {
795     if (!_remoteObjectRegistry) {
796         _remoteObjectRegistry = adoptNS([[WKRemoteObjectRegistry alloc] _initWithMessageSender:*_page]);
797         _page->process().context().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID(), [_remoteObjectRegistry remoteObjectRegistry]);
798     }
799
800     return _remoteObjectRegistry.get();
801 }
802
803 - (WKBrowsingContextHandle *)_handle
804 {
805     return [[[WKBrowsingContextHandle alloc] _initWithPageID:_page->pageID()] autorelease];
806 }
807
808 - (_WKRenderingProgressEvents)_observedRenderingProgressEvents
809 {
810     return _observedRenderingProgressEvents;
811 }
812
813 - (id <WKHistoryDelegatePrivate>)_historyDelegate
814 {
815     return [_navigationState->historyDelegate().leakRef() autorelease];
816 }
817
818 - (void)_setHistoryDelegate:(id <WKHistoryDelegatePrivate>)historyDelegate
819 {
820     _navigationState->setHistoryDelegate(historyDelegate);
821 }
822
823 - (NSURL *)_unreachableURL
824 {
825     return [NSURL _web_URLWithWTFString:_page->pageLoadState().unreachableURL()];
826 }
827
828 - (void)_loadAlternateHTMLString:(NSString *)string baseURL:(NSURL *)baseURL forUnreachableURL:(NSURL *)unreachableURL
829 {
830     _page->loadAlternateHTMLString(string, [baseURL _web_originalDataAsWTFString], [unreachableURL _web_originalDataAsWTFString]);
831 }
832
833 - (WKNavigation *)_reload
834 {
835     _page->reload(false);
836
837     // FIXME: return a WKNavigation object.
838     return nil;
839 }
840
841 - (NSArray *)_certificateChain
842 {
843     if (WebKit::WebFrameProxy* mainFrame = _page->mainFrame())
844         return mainFrame->certificateInfo() ? (NSArray *)mainFrame->certificateInfo()->certificateInfo().certificateChain() : nil;
845
846     return nil;
847 }
848
849 - (NSURL *)_committedURL
850 {
851     return [NSURL _web_URLWithWTFString:_page->pageLoadState().url()];
852 }
853
854 - (NSString *)_applicationNameForUserAgent
855 {
856     return _page->applicationNameForUserAgent();
857 }
858
859 - (void)_setApplicationNameForUserAgent:(NSString *)applicationNameForUserAgent
860 {
861     _page->setApplicationNameForUserAgent(applicationNameForUserAgent);
862 }
863
864 - (NSString *)_customUserAgent
865 {
866     return _page->customUserAgent();
867 }
868
869 - (void)_setCustomUserAgent:(NSString *)_customUserAgent
870 {
871     _page->setCustomUserAgent(_customUserAgent);
872 }
873
874 - (pid_t)_webProcessIdentifier
875 {
876     return _page->processIdentifier();
877 }
878
879 - (NSData *)_sessionState
880 {
881     return [wrapper(*_page->sessionStateData(nullptr, nullptr).leakRef()) autorelease];
882 }
883
884 static void releaseNSData(unsigned char*, const void* data)
885 {
886     [(NSData *)data release];
887 }
888
889 - (void)_restoreFromSessionState:(NSData *)sessionState
890 {
891     [sessionState retain];
892     _page->restoreFromSessionStateData(API::Data::createWithoutCopying((const unsigned char*)sessionState.bytes, sessionState.length, releaseNSData, sessionState).get());
893 }
894
895 - (void)_close
896 {
897     _page->close();
898 }
899
900 - (BOOL)_privateBrowsingEnabled
901 {
902     return [_configuration preferences]->_preferences->privateBrowsingEnabled();
903 }
904
905 - (void)_setPrivateBrowsingEnabled:(BOOL)privateBrowsingEnabled
906 {
907     [_configuration preferences]->_preferences->setPrivateBrowsingEnabled(privateBrowsingEnabled);
908 }
909
910 - (BOOL)_allowsRemoteInspection
911 {
912 #if ENABLE(REMOTE_INSPECTOR)
913     return _page->allowsRemoteInspection();
914 #else
915     return NO;
916 #endif
917 }
918
919 - (void)_setAllowsRemoteInspection:(BOOL)allow
920 {
921 #if ENABLE(REMOTE_INSPECTOR)
922     _page->setAllowsRemoteInspection(allow);
923 #endif
924 }
925
926 static inline WebCore::LayoutMilestones layoutMilestones(_WKRenderingProgressEvents events)
927 {
928     WebCore::LayoutMilestones milestones = 0;
929
930     if (events & _WKRenderingProgressEventFirstLayout)
931         milestones |= WebCore::DidFirstLayout;
932
933     if (events & _WKRenderingProgressEventFirstPaintWithSignificantArea)
934         milestones |= WebCore::DidHitRelevantRepaintedObjectsAreaThreshold;
935
936     return milestones;
937 }
938
939 - (void)_setObservedRenderingProgressEvents:(_WKRenderingProgressEvents)observedRenderingProgressEvents
940 {
941     _observedRenderingProgressEvents = observedRenderingProgressEvents;
942     _page->listenForLayoutMilestones(layoutMilestones(observedRenderingProgressEvents));
943 }
944
945 - (void)_runJavaScriptInMainFrame:(NSString *)scriptString
946 {
947     _page->runJavaScriptInMainFrame(scriptString, WebKit::ScriptValueCallback::create([](bool, WebKit::WebSerializedScriptValue*){}));
948 }
949
950 - (_WKPaginationMode)_paginationMode
951 {
952     switch (_page->paginationMode()) {
953     case WebCore::Pagination::Unpaginated:
954         return _WKPaginationModeUnpaginated;
955     case WebCore::Pagination::LeftToRightPaginated:
956         return _WKPaginationModeLeftToRight;
957     case WebCore::Pagination::RightToLeftPaginated:
958         return _WKPaginationModeRightToLeft;
959     case WebCore::Pagination::TopToBottomPaginated:
960         return _WKPaginationModeTopToBottom;
961     case WebCore::Pagination::BottomToTopPaginated:
962         return _WKPaginationModeBottomToTop;
963     }
964
965     ASSERT_NOT_REACHED();
966     return _WKPaginationModeUnpaginated;
967 }
968
969 - (void)_setPaginationMode:(_WKPaginationMode)paginationMode
970 {
971     WebCore::Pagination::Mode mode;
972     switch (paginationMode) {
973     case _WKPaginationModeUnpaginated:
974         mode = WebCore::Pagination::Unpaginated;
975         break;
976     case _WKPaginationModeLeftToRight:
977         mode = WebCore::Pagination::LeftToRightPaginated;
978         break;
979     case _WKPaginationModeRightToLeft:
980         mode = WebCore::Pagination::RightToLeftPaginated;
981         break;
982     case _WKPaginationModeTopToBottom:
983         mode = WebCore::Pagination::TopToBottomPaginated;
984         break;
985     case _WKPaginationModeBottomToTop:
986         mode = WebCore::Pagination::BottomToTopPaginated;
987         break;
988     default:
989         return;
990     }
991
992     _page->setPaginationMode(mode);
993 }
994
995 - (BOOL)_paginationBehavesLikeColumns
996 {
997     return _page->paginationBehavesLikeColumns();
998 }
999
1000 - (void)_setPaginationBehavesLikeColumns:(BOOL)behavesLikeColumns
1001 {
1002     _page->setPaginationBehavesLikeColumns(behavesLikeColumns);
1003 }
1004
1005 - (CGFloat)_pageLength
1006 {
1007     return _page->pageLength();
1008 }
1009
1010 - (void)_setPageLength:(CGFloat)pageLength
1011 {
1012     _page->setPageLength(pageLength);
1013 }
1014
1015 - (CGFloat)_gapBetweenPages
1016 {
1017     return _page->gapBetweenPages();
1018 }
1019
1020 - (void)_setGapBetweenPages:(CGFloat)gapBetweenPages
1021 {
1022     _page->setGapBetweenPages(gapBetweenPages);
1023 }
1024
1025 - (NSUInteger)_pageCount
1026 {
1027     return _page->pageCount();
1028 }
1029
1030 - (BOOL)_supportsTextZoom
1031 {
1032     return _page->supportsTextZoom();
1033 }
1034
1035 - (double)_textZoomFactor
1036 {
1037     return _page->textZoomFactor();
1038 }
1039
1040 - (void)_setTextZoomFactor:(double)zoomFactor
1041 {
1042     _page->setTextZoomFactor(zoomFactor);
1043 }
1044
1045 - (double)_pageZoomFactor
1046 {
1047     return _page->pageZoomFactor();
1048 }
1049
1050 - (void)_setPageZoomFactor:(double)zoomFactor
1051 {
1052     _page->setPageZoomFactor(zoomFactor);
1053 }
1054
1055 #pragma mark iOS-specific methods
1056
1057 #if PLATFORM(IOS)
1058
1059 - (CGSize)_minimumLayoutSizeOverride
1060 {
1061     ASSERT(_hasStaticMinimumLayoutSize);
1062     return _minimumLayoutSizeOverride;
1063 }
1064
1065 - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride
1066 {
1067     _hasStaticMinimumLayoutSize = YES;
1068     _minimumLayoutSizeOverride = minimumLayoutSizeOverride;
1069     [_contentView setMinimumLayoutSize:minimumLayoutSizeOverride];
1070 }
1071
1072 - (UIEdgeInsets)_obscuredInsets
1073 {
1074     return _obscuredInsets;
1075 }
1076
1077 - (void)_setObscuredInsets:(UIEdgeInsets)obscuredInsets
1078 {
1079     ASSERT(obscuredInsets.top >= 0);
1080     ASSERT(obscuredInsets.left >= 0);
1081     ASSERT(obscuredInsets.bottom >= 0);
1082     ASSERT(obscuredInsets.right >= 0);
1083     _obscuredInsets = obscuredInsets;
1084     [self _updateVisibleContentRects];
1085 }
1086
1087 - (UIColor *)_pageExtendedBackgroundColor
1088 {
1089     // This is deprecated.
1090     return nil;
1091 }
1092
1093 - (void)_setBackgroundExtendsBeyondPage:(BOOL)backgroundExtends
1094 {
1095     _page->setBackgroundExtendsBeyondPage(backgroundExtends);
1096 }
1097
1098 - (BOOL)_backgroundExtendsBeyondPage
1099 {
1100     return _page->backgroundExtendsBeyondPage();
1101 }
1102
1103 - (void)_beginInteractiveObscuredInsetsChange
1104 {
1105     ASSERT(!_isChangingObscuredInsetsInteractively);
1106     _isChangingObscuredInsetsInteractively = YES;
1107 }
1108
1109 - (void)_endInteractiveObscuredInsetsChange
1110 {
1111     ASSERT(_isChangingObscuredInsetsInteractively);
1112     _isChangingObscuredInsetsInteractively = NO;
1113     [self _updateVisibleContentRects];
1114 }
1115
1116 - (void)_showInspectorIndication
1117 {
1118     [_contentView setShowingInspectorIndication:YES];
1119 }
1120
1121 - (void)_hideInspectorIndication
1122 {
1123     [_contentView setShowingInspectorIndication:NO];
1124 }
1125
1126 - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler
1127 {
1128     CGRect snapshotRectInContentCoordinates = [self convertRect:rectInViewCoordinates toView:_contentView.get()];
1129     CGFloat imageHeight = imageWidth / snapshotRectInContentCoordinates.size.width * snapshotRectInContentCoordinates.size.height;
1130     CGSize imageSize = CGSizeMake(imageWidth, imageHeight);
1131
1132     void(^copiedCompletionHandler)(CGImageRef) = [completionHandler copy];
1133     _page->takeSnapshot(WebCore::enclosingIntRect(snapshotRectInContentCoordinates), WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebKit::SnapshotOptionsExcludeDeviceScaleFactor, [copiedCompletionHandler](bool, const WebKit::ShareableBitmap::Handle& imageHandle) {
1134         if (imageHandle.isNull()) {
1135             copiedCompletionHandler(nullptr);
1136             [copiedCompletionHandler release];
1137             return;
1138         }
1139
1140         RefPtr<WebKit::ShareableBitmap> bitmap = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::ReadOnly);
1141
1142         if (!bitmap) {
1143             copiedCompletionHandler(nullptr);
1144             [copiedCompletionHandler release];
1145             return;
1146         }
1147
1148         RetainPtr<CGImageRef> cgImage;
1149         cgImage = bitmap->makeCGImage();
1150         copiedCompletionHandler(cgImage.get());
1151         [copiedCompletionHandler release];
1152     });
1153 }
1154
1155 #else
1156
1157 #pragma mark - OS X-specific methods
1158
1159 - (NSColor *)_pageExtendedBackgroundColor
1160 {
1161     WebCore::Color color = _page->pageExtendedBackgroundColor();
1162     if (!color.isValid())
1163         return nil;
1164
1165     return nsColor(color);
1166 }
1167
1168 - (BOOL)_drawsTransparentBackground
1169 {
1170     return _page->drawsTransparentBackground();
1171 }
1172
1173 - (void)_setDrawsTransparentBackground:(BOOL)drawsTransparentBackground
1174 {
1175     _page->setDrawsTransparentBackground(drawsTransparentBackground);
1176 }
1177
1178 - (void)_setTopContentInset:(CGFloat)contentInset
1179 {
1180     _page->setTopContentInset(contentInset);
1181 }
1182
1183 - (CGFloat)_topContentInset
1184 {
1185     return _page->topContentInset();
1186 }
1187
1188 #endif
1189
1190 @end
1191
1192 #if !TARGET_OS_IPHONE
1193
1194 @implementation WKWebView (WKIBActions)
1195
1196 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
1197 {
1198     SEL action = item.action;
1199
1200     if (action == @selector(goBack:))
1201         return !!_page->backForwardList().backItem();
1202
1203     if (action == @selector(goForward:))
1204         return !!_page->backForwardList().forwardItem();
1205
1206     if (action == @selector(stopLoading:)) {
1207         // FIXME: Return no if we're stopped.
1208         return YES;
1209     }
1210
1211     return NO;
1212 }
1213
1214 - (IBAction)goBack:(id)sender
1215 {
1216     [self goBack];
1217 }
1218
1219 - (IBAction)goForward:(id)sender
1220 {
1221     [self goForward];
1222 }
1223
1224 @end
1225
1226 #endif
1227
1228 #endif // WK_API_ENABLED