Cannot change WKWebView scrollView deceleration rate
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / WKScrollView.mm
1 /*
2  * Copyright (C) 2013, 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 "WKScrollView.h"
28
29 #if PLATFORM(IOS)
30
31 #import "WKWebViewInternal.h"
32 #import <WebCore/CoreGraphicsSPI.h>
33
34 @interface UIScrollView (UIScrollViewInternalHack)
35 - (CGFloat)_rubberBandOffsetForOffset:(CGFloat)newOffset maxOffset:(CGFloat)maxOffset minOffset:(CGFloat)minOffset range:(CGFloat)range outside:(BOOL *)outside;
36 @end
37
38 @interface WKScrollViewDelegateForwarder : NSObject <UIScrollViewDelegate>
39
40 - (instancetype)initWithInternalDelegate:(WKWebView *)internalDelegate externalDelegate:(id <UIScrollViewDelegate>)externalDelegate;
41
42 @end
43
44 @implementation WKScrollViewDelegateForwarder {
45     WKWebView *_internalDelegate;
46     id <UIScrollViewDelegate> _externalDelegate;
47 }
48
49 - (instancetype)initWithInternalDelegate:(WKWebView <UIScrollViewDelegate> *)internalDelegate externalDelegate:(id <UIScrollViewDelegate>)externalDelegate
50 {
51     self = [super init];
52     if (!self)
53         return nil;
54     _internalDelegate = internalDelegate;
55     _externalDelegate = externalDelegate;
56     return self;
57 }
58
59 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
60 {
61     NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
62     if (!signature)
63         signature = [(NSObject *)_internalDelegate methodSignatureForSelector:aSelector];
64     if (!signature)
65         signature = [(NSObject *)_externalDelegate methodSignatureForSelector:aSelector];
66     return signature;
67 }
68
69 - (BOOL)respondsToSelector:(SEL)aSelector
70 {
71     return [super respondsToSelector:aSelector] || [_internalDelegate respondsToSelector:aSelector] || [_externalDelegate respondsToSelector:aSelector];
72 }
73
74 - (void)forwardInvocation:(NSInvocation *)anInvocation
75 {
76     SEL aSelector = [anInvocation selector];
77     BOOL internalDelegateWillRespond = [_internalDelegate respondsToSelector:aSelector];
78     BOOL externalDelegateWillRespond = [_externalDelegate respondsToSelector:aSelector];
79
80     if (internalDelegateWillRespond && externalDelegateWillRespond)
81         [_internalDelegate _willInvokeUIScrollViewDelegateCallback];
82
83     if (internalDelegateWillRespond)
84         [anInvocation invokeWithTarget:_internalDelegate];
85     if (externalDelegateWillRespond)
86         [anInvocation invokeWithTarget:_externalDelegate];
87
88     if (internalDelegateWillRespond && externalDelegateWillRespond)
89         [_internalDelegate _didInvokeUIScrollViewDelegateCallback];
90
91     if (!internalDelegateWillRespond && !externalDelegateWillRespond)
92         [super forwardInvocation:anInvocation];
93 }
94
95 - (id)forwardingTargetForSelector:(SEL)aSelector
96 {
97     BOOL internalDelegateWillRespond = [_internalDelegate respondsToSelector:aSelector];
98     BOOL externalDelegateWillRespond = [_externalDelegate respondsToSelector:aSelector];
99
100     if (internalDelegateWillRespond && !externalDelegateWillRespond)
101         return _internalDelegate;
102     if (externalDelegateWillRespond && !internalDelegateWillRespond)
103         return _externalDelegate;
104     return nil;
105 }
106
107 @end
108
109 @implementation WKScrollView {
110     id <UIScrollViewDelegate> _externalDelegate;
111     WKScrollViewDelegateForwarder *_delegateForwarder;
112 }
113
114 - (id)initWithFrame:(CGRect)frame
115 {
116     if (self = [super initWithFrame:frame]) {
117         ASSERT([self verticalScrollDecelerationFactor] == [self horizontalScrollDecelerationFactor]);
118         // FIXME: use UIWebPreferredScrollDecelerationFactor() from UIKit: rdar://problem/18931007.
119         _preferredScrollDecelerationFactor = [self verticalScrollDecelerationFactor];
120     }
121     
122     return self;
123 }
124
125 - (void)setInternalDelegate:(WKWebView <UIScrollViewDelegate> *)internalDelegate
126 {
127     if (internalDelegate == _internalDelegate)
128         return;
129     _internalDelegate = internalDelegate;
130     [self _updateDelegate];
131 }
132
133 - (void)setDelegate:(id <UIScrollViewDelegate>)delegate
134 {
135     if (_externalDelegate == delegate)
136         return;
137     _externalDelegate = delegate;
138     [self _updateDelegate];
139 }
140
141 - (id <UIScrollViewDelegate>)delegate
142 {
143     return _externalDelegate;
144 }
145
146 - (void)_updateDelegate
147 {
148     WKScrollViewDelegateForwarder *oldForwarder = _delegateForwarder;
149     _delegateForwarder = nil;
150     if (!_externalDelegate)
151         [super setDelegate:_internalDelegate];
152     else if (!_internalDelegate)
153         [super setDelegate:_externalDelegate];
154     else {
155         _delegateForwarder = [[WKScrollViewDelegateForwarder alloc] initWithInternalDelegate:_internalDelegate externalDelegate:_externalDelegate];
156         [super setDelegate:_delegateForwarder];
157     }
158     [oldForwarder release];
159 }
160
161 - (void)dealloc
162 {
163     [_delegateForwarder release];
164     [super dealloc];
165 }
166
167 static inline bool valuesAreWithinOnePixel(CGFloat a, CGFloat b)
168 {
169     return CGFAbs(a - b) < 1;
170 }
171
172 - (CGFloat)_rubberBandOffsetForOffset:(CGFloat)newOffset maxOffset:(CGFloat)maxOffset minOffset:(CGFloat)minOffset range:(CGFloat)range outside:(BOOL *)outside
173 {
174     UIEdgeInsets contentInsets = self.contentInset;
175     CGSize contentSize = self.contentSize;
176     CGRect bounds = self.bounds;
177
178     CGFloat minimalHorizontalRange = bounds.size.width - contentInsets.left - contentInsets.right;
179     CGFloat contentWidthAtMinimumScale = contentSize.width * (self.minimumZoomScale / self.zoomScale);
180     if (contentWidthAtMinimumScale < minimalHorizontalRange) {
181         CGFloat unobscuredEmptyHorizontalMarginAtMinimumScale = minimalHorizontalRange - contentWidthAtMinimumScale;
182         minimalHorizontalRange -= unobscuredEmptyHorizontalMarginAtMinimumScale;
183     }
184     if (contentSize.width < minimalHorizontalRange) {
185         if (valuesAreWithinOnePixel(minOffset, -contentInsets.left)
186             && valuesAreWithinOnePixel(maxOffset, contentSize.width + contentInsets.right - bounds.size.width)
187             && valuesAreWithinOnePixel(range, bounds.size.width)) {
188
189             CGFloat emptyHorizontalMargin = (minimalHorizontalRange - contentSize.width) / 2;
190             minOffset -= emptyHorizontalMargin;
191             maxOffset = minOffset;
192         }
193     }
194
195     CGFloat minimalVerticalRange = bounds.size.height - contentInsets.top - contentInsets.bottom;
196     CGFloat contentHeightAtMinimumScale = contentSize.height * (self.minimumZoomScale / self.zoomScale);
197     if (contentHeightAtMinimumScale < minimalVerticalRange) {
198         CGFloat unobscuredEmptyVerticalMarginAtMinimumScale = minimalVerticalRange - contentHeightAtMinimumScale;
199         minimalVerticalRange -= unobscuredEmptyVerticalMarginAtMinimumScale;
200     }
201     if (contentSize.height < minimalVerticalRange) {
202         if (valuesAreWithinOnePixel(minOffset, -contentInsets.top)
203             && valuesAreWithinOnePixel(maxOffset, contentSize.height + contentInsets.bottom - bounds.size.height)
204             && valuesAreWithinOnePixel(range, bounds.size.height)) {
205
206             CGFloat emptyVerticalMargin = (minimalVerticalRange - contentSize.height) / 2;
207             minOffset -= emptyVerticalMargin;
208             maxOffset = minOffset;
209         }
210     }
211
212     return [super _rubberBandOffsetForOffset:newOffset maxOffset:maxOffset minOffset:minOffset range:range outside:outside];
213 }
214
215 - (void)setContentInset:(UIEdgeInsets)contentInset
216 {
217     [super setContentInset:contentInset];
218
219     [_internalDelegate _updateVisibleContentRects];
220 }
221
222 // Fetch top/left rubberband amounts (as negative values).
223 - (CGSize)_currentTopLeftRubberbandAmount
224 {
225     UIEdgeInsets edgeInsets = [self contentInset];
226
227     CGSize rubberbandAmount = CGSizeZero;
228
229     CGPoint contentOffset = [self contentOffset];
230     if (contentOffset.x < -edgeInsets.left)
231         rubberbandAmount.width = std::min<CGFloat>(contentOffset.x + -edgeInsets.left, 0);
232
233     if (contentOffset.y < -edgeInsets.top)
234         rubberbandAmount.height = std::min<CGFloat>(contentOffset.y + edgeInsets.top, 0);
235     
236     return rubberbandAmount;
237 }
238
239 - (void)_restoreContentOffsetWithRubberbandAmount:(CGSize)rubberbandAmount
240 {
241     UIEdgeInsets edgeInsets = [self contentInset];
242     CGPoint adjustedOffset = [self contentOffset];
243
244     if (rubberbandAmount.width < 0)
245         adjustedOffset.x = -edgeInsets.left + rubberbandAmount.width;
246
247     if (rubberbandAmount.height < 0)
248         adjustedOffset.y = -edgeInsets.top + rubberbandAmount.height;
249
250     [self setContentOffset:adjustedOffset];
251 }
252
253 - (void)_setContentSizePreservingContentOffsetDuringRubberband:(CGSize)contentSize
254 {
255     CGSize currentContentSize = [self contentSize];
256
257     if (CGSizeEqualToSize(currentContentSize, CGSizeZero) || CGSizeEqualToSize(currentContentSize, contentSize) || self.zoomScale < self.minimumZoomScale) {
258         [self setContentSize:contentSize];
259         return;
260     }
261
262     CGSize rubberbandAmount = [self _currentTopLeftRubberbandAmount];
263
264     [self setContentSize:contentSize];
265
266     if (!CGSizeEqualToSize(rubberbandAmount, CGSizeZero))
267         [self _restoreContentOffsetWithRubberbandAmount:rubberbandAmount];
268 }
269
270 - (void)setDecelerationRate:(CGFloat)decelerationRate
271 {
272     [super setDecelerationRate:decelerationRate];
273     _preferredScrollDecelerationFactor = decelerationRate;
274 }
275
276 @end
277
278 #endif // PLATFORM(IOS)