ad4e2e27d3c508fea40712c805690fc70cf394c1
[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 - (void)stopLoading
311 {
312     _page->stopLoading();
313 }
314
315 - (IBAction)stopLoading:(id)sender
316 {
317     _page->stopLoading();
318 }
319
320 #pragma mark iOS-specific methods
321
322 #if PLATFORM(IOS)
323 - (void)setFrame:(CGRect)frame
324 {
325     CGRect oldFrame = self.frame;
326     [super setFrame:frame];
327
328     if (!CGSizeEqualToSize(oldFrame.size, frame.size))
329         [self _frameOrBoundsChanged];
330 }
331
332 - (void)setBounds:(CGRect)bounds
333 {
334     CGRect oldBounds = self.bounds;
335     [super setBounds:bounds];
336
337     if (!CGSizeEqualToSize(oldBounds.size, bounds.size))
338         [self _frameOrBoundsChanged];
339 }
340
341 - (UIScrollView *)scrollView
342 {
343     return _scrollView.get();
344 }
345
346 - (WKBrowsingContextController *)browsingContextController
347 {
348     return [_contentView browsingContextController];
349 }
350
351 - (void)_setHasCustomContentView:(BOOL)pageHasCustomContentView loadedMIMEType:(const WTF::String&)mimeType
352 {
353     if (pageHasCustomContentView) {
354         [_customContentView removeFromSuperview];
355
356         Class representationClass = [[_configuration _contentProviderRegistry] providerForMIMEType:mimeType];
357         ASSERT(representationClass);
358         _customContentView = adoptNS([[representationClass alloc] init]);
359
360         [_contentView removeFromSuperview];
361         [_scrollView addSubview:_customContentView.get()];
362
363         [_customContentView web_setMinimumSize:self.bounds.size];
364         [_customContentView web_setScrollView:_scrollView.get()];
365     } else if (_customContentView) {
366         [_customContentView removeFromSuperview];
367         _customContentView = nullptr;
368
369         [_scrollView addSubview:_contentView.get()];
370         [_scrollView setContentSize:[_contentView frame].size];
371     }
372 }
373
374 - (void)_didFinishLoadingDataForCustomContentProviderWithSuggestedFilename:(const String&)suggestedFilename data:(NSData *)data
375 {
376     ASSERT(_customContentView);
377     [_customContentView web_setContentProviderData:data];
378 }
379
380 - (void)_didCommitLoadForMainFrame
381 {
382     _isWaitingForNewLayerTreeAfterDidCommitLoad = YES;
383 }
384
385 // This is a convenience method that will convert _page->pageExtendedBackgroundColor() from a WebCore::Color to a UIColor *.
386 - (UIColor *)pageExtendedBackgroundColor
387 {
388     WebCore::Color color = _page->pageExtendedBackgroundColor();
389     if (!color.isValid())
390         return nil;
391
392     return [UIColor colorWithRed:(color.red() / 255.0) green:(color.green() / 255.0) blue:(color.blue() / 255.0) alpha:(color.alpha() / 255.0)];
393 }
394
395 static CGFloat contentZoomScale(WKWebView* webView)
396 {
397     UIView *zoomView;
398     if (webView->_customContentView)
399         zoomView = webView->_customContentView.get();
400     else
401         zoomView = webView->_contentView.get();
402
403     CGFloat scale = [[zoomView layer] affineTransform].a;
404     ASSERT(scale == [webView->_scrollView zoomScale]);
405     return scale;
406 }
407
408 - (void)_updateScrollViewBackground
409 {
410     UIColor *pageExtendedBackgroundColor = [self pageExtendedBackgroundColor];
411
412     CGFloat zoomScale = contentZoomScale(self);
413     CGFloat minimumZoomScale = [_scrollView minimumZoomScale];
414     if (zoomScale < minimumZoomScale) {
415         CGFloat slope = 12;
416         CGFloat opacity = std::max(1 - slope * (minimumZoomScale - zoomScale), static_cast<CGFloat>(0));
417         pageExtendedBackgroundColor = [pageExtendedBackgroundColor colorWithAlphaComponent:opacity];
418     }
419
420     [_scrollView setBackgroundColor:pageExtendedBackgroundColor];
421 }
422
423 - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
424 {
425     ASSERT(!_customContentView);
426
427     [_scrollView setContentSize:[_contentView frame].size];
428     [_scrollView setMinimumZoomScale:layerTreeTransaction.minimumScaleFactor()];
429     [_scrollView setMaximumZoomScale:layerTreeTransaction.maximumScaleFactor()];
430     [_scrollView setZoomEnabled:layerTreeTransaction.allowsUserScaling()];
431     if (!layerTreeTransaction.scaleWasSetByUIProcess() && ![_scrollView isZooming] && ![_scrollView isZoomBouncing] && ![_scrollView _isAnimatingZoom])
432         [_scrollView setZoomScale:layerTreeTransaction.pageScaleFactor()];
433     
434     [self _updateScrollViewBackground];
435
436     if (_gestureController)
437         _gestureController->setRenderTreeSize(layerTreeTransaction.renderTreeSize());
438
439     if (_isWaitingForNewLayerTreeAfterDidCommitLoad) {
440         UIEdgeInsets inset = [_scrollView contentInset];
441         [_scrollView setContentOffset:CGPointMake(-inset.left, -inset.top)];
442         _isWaitingForNewLayerTreeAfterDidCommitLoad = NO;
443     }
444
445 }
446
447 - (RetainPtr<CGImageRef>)_takeViewSnapshot
448 {
449     // FIXME: We should be able to use acquire an IOSurface directly, instead of going to CGImage here and back in ViewSnapshotStore.
450     UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, self.window.screen.scale);
451     [self drawViewHierarchyInRect:[self bounds] afterScreenUpdates:NO];
452     UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
453     UIGraphicsEndImageContext();
454     return image.CGImage;
455 }
456
457 - (void)_zoomToPoint:(WebCore::FloatPoint)point atScale:(double)scale
458 {
459     double maximumZoomDuration = 0.4;
460     double minimumZoomDuration = 0.1;
461     double zoomDurationFactor = 0.3;
462
463     CGFloat zoomScale = contentZoomScale(self);
464     CFTimeInterval duration = std::min(fabs(log(zoomScale) - log(scale)) * zoomDurationFactor + minimumZoomDuration, maximumZoomDuration);
465
466     if (scale != zoomScale)
467         [_contentView willStartUserTriggeredZoom];
468
469     [_scrollView _zoomToCenter:point scale:scale duration:duration];
470 }
471
472 - (void)_zoomToRect:(WebCore::FloatRect)targetRect atScale:(double)scale origin:(WebCore::FloatPoint)origin
473 {
474     WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
475     WebCore::FloatSize targetRectSizeAfterZoom = targetRect.size();
476     targetRectSizeAfterZoom.scale(scale);
477
478     // Center the target rect in the scroll view.
479     // If the target doesn't fit in the scroll view, center on the gesture location instead.
480     WebCore::FloatPoint zoomCenter = targetRect.center();
481
482     if (targetRectSizeAfterZoom.width() > unobscuredContentSize.width())
483         zoomCenter.setX(origin.x());
484     if (targetRectSizeAfterZoom.height() > unobscuredContentSize.height())
485         zoomCenter.setY(origin.y());
486
487     [self _zoomToPoint:zoomCenter atScale:scale];
488 }
489
490 static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOffset, WebCore::FloatSize contentSize, WebCore::FloatSize unobscuredContentSize)
491 {
492     WebCore::FloatSize maximumContentOffset = contentSize - unobscuredContentSize;
493     contentOffset = contentOffset.shrunkTo(WebCore::FloatPoint(maximumContentOffset.width(), maximumContentOffset.height()));
494     contentOffset = contentOffset.expandedTo(WebCore::FloatPoint());
495     return contentOffset;
496 }
497
498 - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance
499 {
500     WebCore::FloatRect unobscuredContentRect([self _contentRectForUserInteraction]);
501     WebCore::FloatPoint unobscuredContentOffset = unobscuredContentRect.location();
502     WebCore::FloatSize contentSize([_contentView bounds].size);
503
504     // Center the target rect in the scroll view.
505     // If the target doesn't fit in the scroll view, center on the gesture location instead.
506     WebCore::FloatPoint newUnobscuredContentOffset;
507     if (targetRect.width() <= unobscuredContentRect.width())
508         newUnobscuredContentOffset.setX(targetRect.x() - (unobscuredContentRect.width() - targetRect.width()) / 2);
509     else
510         newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
511     if (targetRect.height() <= unobscuredContentRect.height())
512         newUnobscuredContentOffset.setY(targetRect.y() - (unobscuredContentRect.height() - targetRect.height()) / 2);
513     else
514         newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
515     newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
516
517     if (unobscuredContentOffset == newUnobscuredContentOffset) {
518         if (targetRect.width() > unobscuredContentRect.width())
519             newUnobscuredContentOffset.setX(origin.x() - unobscuredContentRect.width() / 2);
520         if (targetRect.height() > unobscuredContentRect.height())
521             newUnobscuredContentOffset.setY(origin.y() - unobscuredContentRect.height() / 2);
522         newUnobscuredContentOffset = constrainContentOffset(newUnobscuredContentOffset, contentSize, unobscuredContentRect.size());
523     }
524
525     WebCore::FloatSize scrollViewOffsetDelta = newUnobscuredContentOffset - unobscuredContentOffset;
526     scrollViewOffsetDelta.scale(contentZoomScale(self));
527
528     float scrollDistance = scrollViewOffsetDelta.diagonalLength();
529     if (scrollDistance < minimumScrollDistance)
530         return false;
531
532     [_scrollView setContentOffset:([_scrollView contentOffset] + scrollViewOffsetDelta) animated:YES];
533     return true;
534 }
535
536 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin
537 {
538     [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale]];
539 }
540
541 - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance
542 {
543     const float maximumScaleFactorDeltaForPanScroll = 0.02;
544
545     double currentScale = contentZoomScale(self);
546
547     WebCore::FloatSize unobscuredContentSize([self _contentRectForUserInteraction].size);
548     double horizontalScale = unobscuredContentSize.width() * currentScale / targetRect.width();
549     double verticalScale = unobscuredContentSize.height() * currentScale / targetRect.height();
550
551     horizontalScale = std::min(std::max(horizontalScale, minimumScale), maximumScale);
552     verticalScale = std::min(std::max(verticalScale, minimumScale), maximumScale);
553
554     double targetScale = fitEntireRect ? std::min(horizontalScale, verticalScale) : horizontalScale;
555     if (fabs(targetScale - currentScale) < maximumScaleFactorDeltaForPanScroll) {
556         if ([self _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance])
557             return true;
558     } else if (targetScale != currentScale) {
559         [self _zoomToRect:targetRect atScale:targetScale origin:origin];
560         return true;
561     }
562     
563     return false;
564 }
565
566 #pragma mark - UIScrollViewDelegate
567
568 - (BOOL)usesStandardContentView
569 {
570     return !_customContentView;
571 }
572
573 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
574 {
575     ASSERT(_scrollView == scrollView);
576
577     if (_customContentView)
578         return _customContentView.get();
579
580     return _contentView.get();
581 }
582
583 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
584 {
585     if (![self usesStandardContentView])
586         return;
587
588     if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan)
589         [_contentView willStartUserTriggeredZoom];
590     [_contentView willStartZoomOrScroll];
591 }
592
593 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
594 {
595     if (![self usesStandardContentView])
596         return;
597
598     if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
599         [_contentView willStartUserTriggeredScroll];
600     [_contentView willStartZoomOrScroll];
601 }
602
603 - (void)_didFinishScrolling
604 {
605     if (![self usesStandardContentView])
606         return;
607
608     [self _updateVisibleContentRects];
609     [_contentView didFinishScrolling];
610 }
611
612 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
613 {
614     // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called.
615     if (!decelerate)
616         [self _didFinishScrolling];
617 }
618
619 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
620 {
621     [self _didFinishScrolling];
622 }
623
624 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
625 {
626     [self _didFinishScrolling];
627 }
628
629 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
630 {
631     [self _updateVisibleContentRects];
632 }
633
634 - (void)scrollViewDidZoom:(UIScrollView *)scrollView
635 {
636     [self _updateScrollViewBackground];
637     [self _updateVisibleContentRects];
638 }
639
640 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
641 {
642     ASSERT(scrollView == _scrollView);
643     [self _updateVisibleContentRects];
644     [_contentView didZoomToScale:scale];
645 }
646
647 - (void)_frameOrBoundsChanged
648 {
649     CGRect bounds = self.bounds;
650
651     if (!_hasStaticMinimumLayoutSize)
652         [_contentView setMinimumLayoutSize:bounds.size];
653     [_scrollView setFrame:bounds];
654     [_contentView setMinimumSize:bounds.size];
655     [_customContentView web_setMinimumSize:bounds.size];
656     [self _updateVisibleContentRects];
657 }
658
659 // 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.
660 - (CGRect)_contentRectForUserInteraction
661 {
662     // FIXME: handle split keyboard.
663     UIEdgeInsets obscuredInsets = _obscuredInsets;
664     obscuredInsets.bottom = std::max(_obscuredInsets.bottom, _keyboardVerticalOverlap);
665     CGRect unobscuredRect = UIEdgeInsetsInsetRect(self.bounds, obscuredInsets);
666     return [self convertRect:unobscuredRect toView:_contentView.get()];
667 }
668
669 - (void)_updateVisibleContentRects
670 {
671     if (![self usesStandardContentView])
672         return;
673
674     CGRect fullViewRect = self.bounds;
675     CGRect visibleRectInContentCoordinates = [self convertRect:fullViewRect toView:_contentView.get()];
676
677     CGRect unobscuredRect = UIEdgeInsetsInsetRect(fullViewRect, _obscuredInsets);
678     CGRect unobscuredRectInContentCoordinates = [self convertRect:unobscuredRect toView:_contentView.get()];
679
680     CGFloat scaleFactor = contentZoomScale(self);
681
682     BOOL isStableState = !(_isChangingObscuredInsetsInteractively || [_scrollView isDragging] || [_scrollView isDecelerating] || [_scrollView isZooming] || [_scrollView isZoomBouncing] || [_scrollView _isAnimatingZoom]);
683     [_contentView didUpdateVisibleRect:visibleRectInContentCoordinates unobscuredRect:unobscuredRectInContentCoordinates scale:scaleFactor inStableState:isStableState];
684 }
685
686 - (void)_keyboardChangedWithInfo:(NSDictionary *)keyboardInfo adjustScrollView:(BOOL)adjustScrollView
687 {
688     _keyboardVerticalOverlap = [[UIPeripheralHost sharedInstance] getVerticalOverlapForView:self usingKeyboardInfo:keyboardInfo];
689     [self _updateVisibleContentRects];
690
691     if (adjustScrollView)
692         [_scrollView _adjustForAutomaticKeyboardInfo:keyboardInfo animated:YES lastAdjustment:&_lastAdjustmentForScroller];
693 }
694
695 - (void)_keyboardWillChangeFrame:(NSNotification *)notification
696 {
697     if ([_contentView isAssistingNode])
698         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
699 }
700
701 - (void)_keyboardDidChangeFrame:(NSNotification *)notification
702 {
703     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:NO];
704 }
705
706 - (void)_keyboardWillShow:(NSNotification *)notification
707 {
708     if ([_contentView isAssistingNode])
709         [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
710 }
711
712 - (void)_keyboardWillHide:(NSNotification *)notification
713 {
714     // Ignore keyboard will hide notifications sent during rotation. They're just there for
715     // backwards compatibility reasons and processing the will hide notification would
716     // temporarily screw up the the unobscured view area.
717     if ([[UIPeripheralHost sharedInstance] rotationState])
718         return;
719
720     [self _keyboardChangedWithInfo:notification.userInfo adjustScrollView:YES];
721 }
722
723 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
724 {
725     if (_allowsBackForwardNavigationGestures == allowsBackForwardNavigationGestures)
726         return;
727
728     _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
729
730     if (allowsBackForwardNavigationGestures) {
731         if (!_gestureController) {
732             _gestureController = std::make_unique<WebKit::ViewGestureController>(*_page);
733             _gestureController->installSwipeHandler(self, [self scrollView]);
734         }
735     } else
736         _gestureController = nullptr;
737
738     _page->setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures);
739 }
740
741 - (BOOL)allowsBackForwardNavigationGestures
742 {
743     return _allowsBackForwardNavigationGestures;
744 }
745
746 #endif
747
748 #pragma mark OS X-specific methods
749
750 #if PLATFORM(MAC)
751
752 - (void)resizeSubviewsWithOldSize:(NSSize)oldSize
753 {
754     [_wkView setFrame:self.bounds];
755 }
756
757 - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures
758 {
759     [_wkView setAllowsBackForwardNavigationGestures:allowsBackForwardNavigationGestures];
760 }
761
762 - (BOOL)allowsBackForwardNavigationGestures
763 {
764     return [_wkView allowsBackForwardNavigationGestures];
765 }
766
767 - (void)setAllowsMagnification:(BOOL)allowsMagnification
768 {
769     [_wkView setAllowsMagnification:allowsMagnification];
770 }
771
772 - (BOOL)allowsMagnification
773 {
774     return [_wkView allowsMagnification];
775 }
776
777 - (void)setMagnification:(CGFloat)magnification
778 {
779     [_wkView setMagnification:magnification];
780 }
781
782 - (CGFloat)magnification
783 {
784     return [_wkView magnification];
785 }
786
787 - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point
788 {
789     [_wkView setMagnification:magnification centeredAtPoint:NSPointFromCGPoint(point)];
790 }
791
792 #endif
793
794 @end
795
796 @implementation WKWebView (WKPrivate)
797
798 - (WKRemoteObjectRegistry *)_remoteObjectRegistry
799 {
800     if (!_remoteObjectRegistry) {
801         _remoteObjectRegistry = adoptNS([[WKRemoteObjectRegistry alloc] _initWithMessageSender:*_page]);
802         _page->process().context().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _page->pageID(), [_remoteObjectRegistry remoteObjectRegistry]);
803     }
804
805     return _remoteObjectRegistry.get();
806 }
807
808 - (WKBrowsingContextHandle *)_handle
809 {
810     return [[[WKBrowsingContextHandle alloc] _initWithPageID:_page->pageID()] autorelease];
811 }
812
813 - (_WKRenderingProgressEvents)_observedRenderingProgressEvents
814 {
815     return _observedRenderingProgressEvents;
816 }
817
818 - (id <WKHistoryDelegatePrivate>)_historyDelegate
819 {
820     return [_navigationState->historyDelegate().leakRef() autorelease];
821 }
822
823 - (void)_setHistoryDelegate:(id <WKHistoryDelegatePrivate>)historyDelegate
824 {
825     _navigationState->setHistoryDelegate(historyDelegate);
826 }
827
828 - (NSURL *)_unreachableURL
829 {
830     return [NSURL _web_URLWithWTFString:_page->pageLoadState().unreachableURL()];
831 }
832
833 - (void)_loadAlternateHTMLString:(NSString *)string baseURL:(NSURL *)baseURL forUnreachableURL:(NSURL *)unreachableURL
834 {
835     _page->loadAlternateHTMLString(string, [baseURL _web_originalDataAsWTFString], [unreachableURL _web_originalDataAsWTFString]);
836 }
837
838 - (WKNavigation *)_reload
839 {
840     _page->reload(false);
841
842     // FIXME: return a WKNavigation object.
843     return nil;
844 }
845
846 - (NSArray *)_certificateChain
847 {
848     if (WebKit::WebFrameProxy* mainFrame = _page->mainFrame())
849         return mainFrame->certificateInfo() ? (NSArray *)mainFrame->certificateInfo()->certificateInfo().certificateChain() : nil;
850
851     return nil;
852 }
853
854 - (NSURL *)_committedURL
855 {
856     return [NSURL _web_URLWithWTFString:_page->pageLoadState().url()];
857 }
858
859 - (NSString *)_applicationNameForUserAgent
860 {
861     return _page->applicationNameForUserAgent();
862 }
863
864 - (void)_setApplicationNameForUserAgent:(NSString *)applicationNameForUserAgent
865 {
866     _page->setApplicationNameForUserAgent(applicationNameForUserAgent);
867 }
868
869 - (NSString *)_customUserAgent
870 {
871     return _page->customUserAgent();
872 }
873
874 - (void)_setCustomUserAgent:(NSString *)_customUserAgent
875 {
876     _page->setCustomUserAgent(_customUserAgent);
877 }
878
879 - (pid_t)_webProcessIdentifier
880 {
881     return _page->processIdentifier();
882 }
883
884 - (NSData *)_sessionState
885 {
886     return [wrapper(*_page->sessionStateData(nullptr, nullptr).leakRef()) autorelease];
887 }
888
889 static void releaseNSData(unsigned char*, const void* data)
890 {
891     [(NSData *)data release];
892 }
893
894 - (void)_restoreFromSessionState:(NSData *)sessionState
895 {
896     [sessionState retain];
897     _page->restoreFromSessionStateData(API::Data::createWithoutCopying((const unsigned char*)sessionState.bytes, sessionState.length, releaseNSData, sessionState).get());
898 }
899
900 - (void)_close
901 {
902     _page->close();
903 }
904
905 - (BOOL)_privateBrowsingEnabled
906 {
907     return [_configuration preferences]->_preferences->privateBrowsingEnabled();
908 }
909
910 - (void)_setPrivateBrowsingEnabled:(BOOL)privateBrowsingEnabled
911 {
912     [_configuration preferences]->_preferences->setPrivateBrowsingEnabled(privateBrowsingEnabled);
913 }
914
915 - (BOOL)_allowsRemoteInspection
916 {
917 #if ENABLE(REMOTE_INSPECTOR)
918     return _page->allowsRemoteInspection();
919 #else
920     return NO;
921 #endif
922 }
923
924 - (void)_setAllowsRemoteInspection:(BOOL)allow
925 {
926 #if ENABLE(REMOTE_INSPECTOR)
927     _page->setAllowsRemoteInspection(allow);
928 #endif
929 }
930
931 static inline WebCore::LayoutMilestones layoutMilestones(_WKRenderingProgressEvents events)
932 {
933     WebCore::LayoutMilestones milestones = 0;
934
935     if (events & _WKRenderingProgressEventFirstLayout)
936         milestones |= WebCore::DidFirstLayout;
937
938     if (events & _WKRenderingProgressEventFirstPaintWithSignificantArea)
939         milestones |= WebCore::DidHitRelevantRepaintedObjectsAreaThreshold;
940
941     return milestones;
942 }
943
944 - (void)_setObservedRenderingProgressEvents:(_WKRenderingProgressEvents)observedRenderingProgressEvents
945 {
946     _observedRenderingProgressEvents = observedRenderingProgressEvents;
947     _page->listenForLayoutMilestones(layoutMilestones(observedRenderingProgressEvents));
948 }
949
950 - (void)_runJavaScriptInMainFrame:(NSString *)scriptString
951 {
952     _page->runJavaScriptInMainFrame(scriptString, WebKit::ScriptValueCallback::create([](bool, WebKit::WebSerializedScriptValue*){}));
953 }
954
955 - (_WKPaginationMode)_paginationMode
956 {
957     switch (_page->paginationMode()) {
958     case WebCore::Pagination::Unpaginated:
959         return _WKPaginationModeUnpaginated;
960     case WebCore::Pagination::LeftToRightPaginated:
961         return _WKPaginationModeLeftToRight;
962     case WebCore::Pagination::RightToLeftPaginated:
963         return _WKPaginationModeRightToLeft;
964     case WebCore::Pagination::TopToBottomPaginated:
965         return _WKPaginationModeTopToBottom;
966     case WebCore::Pagination::BottomToTopPaginated:
967         return _WKPaginationModeBottomToTop;
968     }
969
970     ASSERT_NOT_REACHED();
971     return _WKPaginationModeUnpaginated;
972 }
973
974 - (void)_setPaginationMode:(_WKPaginationMode)paginationMode
975 {
976     WebCore::Pagination::Mode mode;
977     switch (paginationMode) {
978     case _WKPaginationModeUnpaginated:
979         mode = WebCore::Pagination::Unpaginated;
980         break;
981     case _WKPaginationModeLeftToRight:
982         mode = WebCore::Pagination::LeftToRightPaginated;
983         break;
984     case _WKPaginationModeRightToLeft:
985         mode = WebCore::Pagination::RightToLeftPaginated;
986         break;
987     case _WKPaginationModeTopToBottom:
988         mode = WebCore::Pagination::TopToBottomPaginated;
989         break;
990     case _WKPaginationModeBottomToTop:
991         mode = WebCore::Pagination::BottomToTopPaginated;
992         break;
993     default:
994         return;
995     }
996
997     _page->setPaginationMode(mode);
998 }
999
1000 - (BOOL)_paginationBehavesLikeColumns
1001 {
1002     return _page->paginationBehavesLikeColumns();
1003 }
1004
1005 - (void)_setPaginationBehavesLikeColumns:(BOOL)behavesLikeColumns
1006 {
1007     _page->setPaginationBehavesLikeColumns(behavesLikeColumns);
1008 }
1009
1010 - (CGFloat)_pageLength
1011 {
1012     return _page->pageLength();
1013 }
1014
1015 - (void)_setPageLength:(CGFloat)pageLength
1016 {
1017     _page->setPageLength(pageLength);
1018 }
1019
1020 - (CGFloat)_gapBetweenPages
1021 {
1022     return _page->gapBetweenPages();
1023 }
1024
1025 - (void)_setGapBetweenPages:(CGFloat)gapBetweenPages
1026 {
1027     _page->setGapBetweenPages(gapBetweenPages);
1028 }
1029
1030 - (NSUInteger)_pageCount
1031 {
1032     return _page->pageCount();
1033 }
1034
1035 - (BOOL)_supportsTextZoom
1036 {
1037     return _page->supportsTextZoom();
1038 }
1039
1040 - (double)_textZoomFactor
1041 {
1042     return _page->textZoomFactor();
1043 }
1044
1045 - (void)_setTextZoomFactor:(double)zoomFactor
1046 {
1047     _page->setTextZoomFactor(zoomFactor);
1048 }
1049
1050 - (double)_pageZoomFactor
1051 {
1052     return _page->pageZoomFactor();
1053 }
1054
1055 - (void)_setPageZoomFactor:(double)zoomFactor
1056 {
1057     _page->setPageZoomFactor(zoomFactor);
1058 }
1059
1060 #pragma mark iOS-specific methods
1061
1062 #if PLATFORM(IOS)
1063
1064 - (CGSize)_minimumLayoutSizeOverride
1065 {
1066     ASSERT(_hasStaticMinimumLayoutSize);
1067     return _minimumLayoutSizeOverride;
1068 }
1069
1070 - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride
1071 {
1072     _hasStaticMinimumLayoutSize = YES;
1073     _minimumLayoutSizeOverride = minimumLayoutSizeOverride;
1074     [_contentView setMinimumLayoutSize:minimumLayoutSizeOverride];
1075 }
1076
1077 - (UIEdgeInsets)_obscuredInsets
1078 {
1079     return _obscuredInsets;
1080 }
1081
1082 - (void)_setObscuredInsets:(UIEdgeInsets)obscuredInsets
1083 {
1084     ASSERT(obscuredInsets.top >= 0);
1085     ASSERT(obscuredInsets.left >= 0);
1086     ASSERT(obscuredInsets.bottom >= 0);
1087     ASSERT(obscuredInsets.right >= 0);
1088     _obscuredInsets = obscuredInsets;
1089     [self _updateVisibleContentRects];
1090 }
1091
1092 - (UIColor *)_pageExtendedBackgroundColor
1093 {
1094     // This is deprecated.
1095     return nil;
1096 }
1097
1098 - (void)_setBackgroundExtendsBeyondPage:(BOOL)backgroundExtends
1099 {
1100     _page->setBackgroundExtendsBeyondPage(backgroundExtends);
1101 }
1102
1103 - (BOOL)_backgroundExtendsBeyondPage
1104 {
1105     return _page->backgroundExtendsBeyondPage();
1106 }
1107
1108 - (void)_beginInteractiveObscuredInsetsChange
1109 {
1110     ASSERT(!_isChangingObscuredInsetsInteractively);
1111     _isChangingObscuredInsetsInteractively = YES;
1112 }
1113
1114 - (void)_endInteractiveObscuredInsetsChange
1115 {
1116     ASSERT(_isChangingObscuredInsetsInteractively);
1117     _isChangingObscuredInsetsInteractively = NO;
1118     [self _updateVisibleContentRects];
1119 }
1120
1121 - (void)_showInspectorIndication
1122 {
1123     [_contentView setShowingInspectorIndication:YES];
1124 }
1125
1126 - (void)_hideInspectorIndication
1127 {
1128     [_contentView setShowingInspectorIndication:NO];
1129 }
1130
1131 - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler
1132 {
1133     CGRect snapshotRectInContentCoordinates = [self convertRect:rectInViewCoordinates toView:_contentView.get()];
1134     CGFloat imageHeight = imageWidth / snapshotRectInContentCoordinates.size.width * snapshotRectInContentCoordinates.size.height;
1135     CGSize imageSize = CGSizeMake(imageWidth, imageHeight);
1136
1137     void(^copiedCompletionHandler)(CGImageRef) = [completionHandler copy];
1138     _page->takeSnapshot(WebCore::enclosingIntRect(snapshotRectInContentCoordinates), WebCore::expandedIntSize(WebCore::FloatSize(imageSize)), WebKit::SnapshotOptionsExcludeDeviceScaleFactor, [copiedCompletionHandler](bool, const WebKit::ShareableBitmap::Handle& imageHandle) {
1139         if (imageHandle.isNull()) {
1140             copiedCompletionHandler(nullptr);
1141             [copiedCompletionHandler release];
1142             return;
1143         }
1144
1145         RefPtr<WebKit::ShareableBitmap> bitmap = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::ReadOnly);
1146
1147         if (!bitmap) {
1148             copiedCompletionHandler(nullptr);
1149             [copiedCompletionHandler release];
1150             return;
1151         }
1152
1153         RetainPtr<CGImageRef> cgImage;
1154         cgImage = bitmap->makeCGImage();
1155         copiedCompletionHandler(cgImage.get());
1156         [copiedCompletionHandler release];
1157     });
1158 }
1159
1160 #else
1161
1162 #pragma mark - OS X-specific methods
1163
1164 - (NSColor *)_pageExtendedBackgroundColor
1165 {
1166     WebCore::Color color = _page->pageExtendedBackgroundColor();
1167     if (!color.isValid())
1168         return nil;
1169
1170     return nsColor(color);
1171 }
1172
1173 - (BOOL)_drawsTransparentBackground
1174 {
1175     return _page->drawsTransparentBackground();
1176 }
1177
1178 - (void)_setDrawsTransparentBackground:(BOOL)drawsTransparentBackground
1179 {
1180     _page->setDrawsTransparentBackground(drawsTransparentBackground);
1181 }
1182
1183 - (void)_setTopContentInset:(CGFloat)contentInset
1184 {
1185     _page->setTopContentInset(contentInset);
1186 }
1187
1188 - (CGFloat)_topContentInset
1189 {
1190     return _page->topContentInset();
1191 }
1192
1193 #endif
1194
1195 @end
1196
1197 #if !TARGET_OS_IPHONE
1198
1199 @implementation WKWebView (WKIBActions)
1200
1201 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
1202 {
1203     SEL action = item.action;
1204
1205     if (action == @selector(goBack:))
1206         return !!_page->backForwardList().backItem();
1207
1208     if (action == @selector(goForward:))
1209         return !!_page->backForwardList().forwardItem();
1210
1211     if (action == @selector(stopLoading:)) {
1212         // FIXME: Return no if we're stopped.
1213         return YES;
1214     }
1215
1216     return NO;
1217 }
1218
1219 - (IBAction)goBack:(id)sender
1220 {
1221     [self goBack];
1222 }
1223
1224 - (IBAction)goForward:(id)sender
1225 {
1226     [self goForward];
1227 }
1228
1229 @end
1230
1231 #endif
1232
1233 #endif // WK_API_ENABLED