bf41977e1c4261a8ee67fb8efd9242f55310495c
[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 "WKNavigationDelegate.h"
33 #import "WKNavigationInternal.h"
34 #import "WKProcessClass.h"
35 #import "WKWebViewConfiguration.h"
36 #import "WebBackForwardList.h"
37 #import "WebPageProxy.h"
38 #import <wtf/RetainPtr.h>
39
40 #if PLATFORM(IOS)
41 #import "WKScrollView.h"
42
43 static const float minWebViewScale = 0.25;
44 static const float maxWebViewScale = 5;
45 static _UIWebViewportConfiguration standardViewportConfiguration = { { UIWebViewportStandardViewportWidth, UIWebViewportGrowsAndShrinksToFitHeight }, UIWebViewportScaleForScalesToFit, minWebViewScale, maxWebViewScale, true
46 };
47
48 #endif
49
50 #if PLATFORM(MAC) && !PLATFORM(IOS)
51 #import "WKViewInternal.h"
52 #endif
53
54 @implementation WKWebView {
55     RetainPtr<WKWebViewConfiguration> _configuration;
56     std::unique_ptr<WebKit::NavigationState> _navigationState;
57
58 #if PLATFORM(IOS)
59     RetainPtr<WKScrollView> _scrollView;
60     RetainPtr<WKContentView> _contentView;
61     RetainPtr<_UIWebViewportHandler> _viewportHandler;
62
63     BOOL _userHasChangedPageScale;
64     BOOL _hasStaticMinimumLayoutSize;
65 #endif
66 #if PLATFORM(MAC) && !PLATFORM(IOS)
67     RetainPtr<WKView> _wkView;
68 #endif
69 }
70
71 - (instancetype)initWithFrame:(CGRect)frame
72 {
73     return [self initWithFrame:frame configuration:adoptNS([[WKWebViewConfiguration alloc] init]).get()];
74 }
75
76 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
77 {
78     if (!(self = [super initWithFrame:frame]))
79         return nil;
80
81     _configuration = adoptNS([configuration copy]);
82
83     if (![_configuration processClass])
84         [_configuration setProcessClass:adoptNS([[WKProcessClass alloc] init]).get()];
85
86     CGRect bounds = self.bounds;
87
88 #if PLATFORM(IOS)
89     _scrollView = adoptNS([[WKScrollView alloc] initWithFrame:bounds]);
90     [_scrollView setInternalDelegate:self];
91     [_scrollView setBouncesZoom:YES];
92
93     [self addSubview:_scrollView.get()];
94
95     _contentView = adoptNS([[WKContentView alloc] initWithFrame:bounds configuration:_configuration.get()]);
96     _page = _contentView->_page;
97     [_contentView setDelegate:self];
98     [_contentView layer].anchorPoint = CGPointZero;
99     [_contentView setFrame:bounds];
100     [_scrollView addSubview:_contentView.get()];
101
102     _viewportHandler = adoptNS([[_UIWebViewportHandler alloc] init]);
103     [_viewportHandler setDelegate:self];
104
105     [self _frameOrBoundsChanged];
106 #endif
107
108 #if PLATFORM(MAC) && !PLATFORM(IOS)
109     _wkView = [[WKView alloc] initWithFrame:bounds configuration:_configuration.get()];
110     [self addSubview:_wkView.get()];
111     _page = WebKit::toImpl([_wkView pageRef]);
112 #endif
113
114     _navigationState = std::make_unique<WebKit::NavigationState>(self);
115     _page->setPolicyClient(_navigationState->createPolicyClient());
116     _page->setLoaderClient(_navigationState->createLoaderClient());
117
118     return self;
119 }
120
121 - (WKWebViewConfiguration *)configuration
122 {
123     return [[_configuration copy] autorelease];
124 }
125
126 - (id <WKNavigationDelegate>)navigationDelegate
127 {
128     return [_navigationState->navigationDelegate().leakRef() autorelease];
129 }
130
131 - (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate
132 {
133     _navigationState->setNavigationDelegate(navigationDelegate);
134 }
135
136 - (WKNavigation *)loadRequest:(NSURLRequest *)request
137 {
138     uint64_t navigationID = _page->loadRequest(request);
139     auto navigation = _navigationState->createLoadRequestNavigation(navigationID, request);
140
141     return [navigation.leakRef() autorelease];
142 }
143
144 - (NSString *)title
145 {
146     return _page->pageLoadState().title();
147 }
148
149 - (BOOL)isLoading
150 {
151     return _page->pageLoadState().isLoading();
152 }
153
154 - (BOOL)hasOnlySecureContent
155 {
156     return _page->pageLoadState().hasOnlySecureContent();
157 }
158
159 // FIXME: This should be KVO compliant.
160 - (BOOL)canGoBack
161 {
162     return !!_page->backForwardList().backItem();
163 }
164
165 // FIXME: This should be KVO compliant.
166 - (BOOL)canGoForward
167 {
168     return !!_page->backForwardList().forwardItem();
169 }
170
171 // FIXME: This should return a WKNavigation object.
172 - (void)goBack
173 {
174     _page->goBack();
175 }
176
177 // FIXME: This should return a WKNavigation object.
178 - (void)goForward
179 {
180     _page->goForward();
181 }
182
183 #pragma mark iOS-specific methods
184
185 #if PLATFORM(IOS)
186 - (void)setFrame:(CGRect)frame
187 {
188     CGRect oldFrame = self.frame;
189     [super setFrame:frame];
190
191     if (!CGSizeEqualToSize(oldFrame.size, frame.size))
192         [self _frameOrBoundsChanged];
193 }
194
195 - (void)setBounds:(CGRect)bounds
196 {
197     CGRect oldBounds = self.bounds;
198     [super setBounds:bounds];
199
200     if (!CGSizeEqualToSize(oldBounds.size, bounds.size))
201         [self _frameOrBoundsChanged];
202 }
203
204 - (UIScrollView *)scrollView
205 {
206     return _scrollView.get();
207 }
208
209 - (WKBrowsingContextController *)browsingContextController
210 {
211     return [_contentView browsingContextController];
212 }
213
214 #pragma mark WKContentViewDelegate
215
216 - (void)contentView:(WKContentView *)contentView contentsSizeDidChange:(CGSize)newSize
217 {
218     CGFloat zoomScale = [_scrollView zoomScale];
219     CGSize contentsSizeInScrollViewCoordinates = CGSizeMake(newSize.width * zoomScale, newSize.height * zoomScale);
220     [_scrollView setContentSize:contentsSizeInScrollViewCoordinates];
221
222     [_viewportHandler update:^{
223          [_viewportHandler setDocumentBounds:{CGPointZero, newSize}];
224     }];
225 }
226
227 - (void)contentViewDidCommitLoadForMainFrame:(WKContentView *)contentView
228 {
229     _userHasChangedPageScale = NO;
230
231     WKContentType contentType = [_contentView contentType];
232     [_viewportHandler update:^{
233         [_viewportHandler clearWebKitViewportConfigurationFlags];
234         _UIWebViewportConfiguration configuration = standardViewportConfiguration;
235
236         if (contentType == PlainText) {
237             CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
238             configuration.size.width = screenWidth;
239         } else if (contentType == WKContentType::Image)
240             configuration.minimumScale = 0.01;
241
242         [_viewportHandler resetViewportConfiguration:&configuration];
243     }];
244 }
245
246 - (void)contentViewDidReceiveMobileDocType:(WKContentView *)contentView
247 {
248     [_viewportHandler update:^{
249         _UIWebViewportConfiguration configuration = standardViewportConfiguration;
250         configuration.minimumScale = 1;
251         configuration.size = CGSizeMake(320, UIWebViewportGrowsAndShrinksToFitHeight);
252         [_viewportHandler resetViewportConfiguration:&configuration];
253     }];
254 }
255
256 - (void)contentView:(WKContentView *)contentView didChangeViewportArgumentsSize:(CGSize)newSize initialScale:(float)initialScale minimumScale:(float)minimumScale maximumScale:(float)maximumScale allowsUserScaling:(float)allowsUserScaling
257 {
258     [_viewportHandler update:^{
259         [_viewportHandler applyWebKitViewportArgumentsSize:newSize initialScale:initialScale minimumScale:minimumScale maximumScale:maximumScale allowsUserScaling:allowsUserScaling];
260     }];
261 }
262
263 #pragma mark - _UIWebViewportHandlerDelegate
264
265 - (void)viewportHandlerDidChangeScales:(_UIWebViewportHandler *)viewportHandler
266 {
267     ASSERT(viewportHandler == _viewportHandler);
268     [_scrollView setMinimumZoomScale:viewportHandler.minimumScale];
269     [_scrollView setMaximumZoomScale:viewportHandler.maximumScale];
270     [_scrollView setZoomEnabled:viewportHandler.allowsUserScaling];
271
272     if (!_userHasChangedPageScale)
273         [self _setDocumentScale:viewportHandler.initialScale];
274     else {
275         CGFloat currentScale = [_scrollView zoomScale];
276         CGFloat validScale = std::max(std::min(currentScale, static_cast<CGFloat>(viewportHandler.maximumScale)), static_cast<CGFloat>(viewportHandler.minimumScale));
277         [self _setDocumentScale:validScale];
278     }
279 }
280
281 - (void)viewportHandler:(_UIWebViewportHandler *)viewportHandler didChangeViewportSize:(CGSize)newSize
282 {
283     ASSERT(viewportHandler == _viewportHandler);
284     [_contentView setViewportSize:newSize];
285 }
286
287 #pragma mark - UIScrollViewDelegate
288
289 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
290 {
291     ASSERT(_scrollView == scrollView);
292     return _contentView.get();
293 }
294
295 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
296 {
297     if (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan)
298         _userHasChangedPageScale = YES;
299     [_contentView willStartZoomOrScroll];
300 }
301
302 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
303 {
304     [_contentView willStartZoomOrScroll];
305 }
306
307 - (void)_didFinishScroll
308 {
309     CGPoint position = [_scrollView convertPoint:[_scrollView contentOffset] toView:_contentView.get()];
310     [_contentView didFinishScrollTo:position];
311 }
312
313 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
314 {
315     // If we're decelerating, scroll offset will be updated when scrollViewDidFinishDecelerating: is called.
316     if (!decelerate)
317         [self _didFinishScroll];
318 }
319
320 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
321 {
322     [self _didFinishScroll];
323 }
324
325 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
326 {
327     [self _didFinishScroll];
328 }
329
330 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
331 {
332     CGPoint position = [_scrollView convertPoint:[_scrollView contentOffset] toView:_contentView.get()];
333     [_contentView didScrollTo:position];
334 }
335
336 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
337 {
338     ASSERT(scrollView == _scrollView);
339     [_contentView didZoomToScale:scale];
340 }
341
342 - (void)_frameOrBoundsChanged
343 {
344     CGRect bounds = self.bounds;
345
346     if (!_hasStaticMinimumLayoutSize) {
347         [_viewportHandler update:^{
348             [_viewportHandler setAvailableViewSize:bounds.size];
349         }];
350     }
351     [_scrollView setFrame:bounds];
352     [_contentView setMinimumSize:bounds.size];
353 }
354
355 - (void)_setDocumentScale:(CGFloat)newScale
356 {
357     CGPoint contentOffsetInDocumentCoordinates = [_scrollView convertPoint:[_scrollView contentOffset] toView:_contentView.get()];
358
359     [_scrollView setZoomScale:newScale];
360     [_contentView didZoomToScale:newScale];
361
362     CGPoint contentOffset = [_scrollView convertPoint:contentOffsetInDocumentCoordinates fromView:_contentView.get()];
363     [_scrollView setContentOffset:contentOffset];
364 }
365
366 #pragma mark Private API
367
368 - (CGSize)_minimumLayoutSizeOverride
369 {
370     ASSERT(_hasStaticMinimumLayoutSize);
371     return [_viewportHandler availableViewSize];
372 }
373
374 - (void)_setMinimumLayoutSizeOverride:(CGSize)minimumLayoutSizeOverride
375 {
376     _hasStaticMinimumLayoutSize = YES;
377     [_viewportHandler update:^{
378         [_viewportHandler setAvailableViewSize:minimumLayoutSizeOverride];
379     }];
380 }
381
382 #endif
383
384 #pragma mark OS X-specific methods
385
386 #if PLATFORM(MAC) && !PLATFORM(IOS)
387
388 - (void)resizeSubviewsWithOldSize:(NSSize)oldSize
389 {
390     [_wkView setFrame:self.bounds];
391 }
392
393 #endif
394
395 @end
396
397 #endif // WK_API_ENABLED