Implement visual-viewport based position:fixed handling for Mac async scrolling
[WebKit-https.git] / Source / WebKit2 / UIProcess / Scrolling / ios / ScrollingTreeOverflowScrollingNodeIOS.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 "ScrollingTreeOverflowScrollingNodeIOS.h"
28
29 #if PLATFORM(IOS)
30 #if ENABLE(ASYNC_SCROLLING)
31
32 #import <QuartzCore/QuartzCore.h>
33 #import <WebCore/ScrollingStateOverflowScrollingNode.h>
34 #import <WebCore/ScrollingTree.h>
35 #import <UIKit/UIPanGestureRecognizer.h>
36 #import <UIKit/UIScrollView.h>
37 #import <wtf/BlockObjCExceptions.h>
38 #import <wtf/TemporaryChange.h>
39
40 #if ENABLE(CSS_SCROLL_SNAP)
41 #import <WebCore/AxisScrollSnapOffsets.h>
42 #endif
43
44 using namespace WebCore;
45
46 @interface WKOverflowScrollViewDelegate : NSObject <UIScrollViewDelegate> {
47     WebKit::ScrollingTreeOverflowScrollingNodeIOS* _scrollingTreeNode;
48 }
49
50 @property (nonatomic, getter=_isInUserInteraction) BOOL inUserInteraction;
51
52 - (instancetype)initWithScrollingTreeNode:(WebKit::ScrollingTreeOverflowScrollingNodeIOS*)node;
53
54 @end
55
56 @implementation WKOverflowScrollViewDelegate
57
58 - (instancetype)initWithScrollingTreeNode:(WebKit::ScrollingTreeOverflowScrollingNodeIOS*)node
59 {
60     if ((self = [super init]))
61         _scrollingTreeNode = node;
62
63     return self;
64 }
65
66 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
67 {
68     _scrollingTreeNode->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction);
69 }
70
71 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
72 {
73     _inUserInteraction = YES;
74
75     if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
76         _scrollingTreeNode->overflowScrollViewWillStartPanGesture();
77     _scrollingTreeNode->overflowScrollWillStart();
78 }
79
80 #if ENABLE(CSS_SCROLL_SNAP)
81 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
82 {
83     CGFloat horizontalTarget = targetContentOffset->x;
84     CGFloat verticalTarget = targetContentOffset->y;
85
86     unsigned originalHorizontalSnapPosition = _scrollingTreeNode->currentHorizontalSnapPointIndex();
87     unsigned originalVerticalSnapPosition = _scrollingTreeNode->currentVerticalSnapPointIndex();
88
89     if (!_scrollingTreeNode->horizontalSnapOffsets().isEmpty()) {
90         unsigned index;
91         float potentialSnapPosition = closestSnapOffset<float, CGFloat>(_scrollingTreeNode->horizontalSnapOffsets(), horizontalTarget, velocity.x, index);
92         _scrollingTreeNode->setCurrentHorizontalSnapPointIndex(index);
93         if (horizontalTarget >= 0 && horizontalTarget <= scrollView.contentSize.width)
94             targetContentOffset->x = potentialSnapPosition;
95     }
96
97     if (!_scrollingTreeNode->verticalSnapOffsets().isEmpty()) {
98         unsigned index;
99         float potentialSnapPosition = closestSnapOffset<float, CGFloat>(_scrollingTreeNode->verticalSnapOffsets(), verticalTarget, velocity.y, index);
100         _scrollingTreeNode->setCurrentVerticalSnapPointIndex(index);
101         if (verticalTarget >= 0 && verticalTarget <= scrollView.contentSize.height)
102             targetContentOffset->y = potentialSnapPosition;
103     }
104
105     if (originalHorizontalSnapPosition != _scrollingTreeNode->currentHorizontalSnapPointIndex()
106         || originalVerticalSnapPosition != _scrollingTreeNode->currentVerticalSnapPointIndex()) {
107         _scrollingTreeNode->currentSnapPointIndicesDidChange(_scrollingTreeNode->currentHorizontalSnapPointIndex(), _scrollingTreeNode->currentVerticalSnapPointIndex());
108     }
109 }
110 #endif
111
112 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)willDecelerate
113 {
114     if (_inUserInteraction && !willDecelerate) {
115         _inUserInteraction = NO;
116         _scrollingTreeNode->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction);
117         _scrollingTreeNode->overflowScrollDidEnd();
118     }
119 }
120
121 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
122 {
123     if (_inUserInteraction) {
124         _inUserInteraction = NO;
125         _scrollingTreeNode->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction);
126         _scrollingTreeNode->overflowScrollDidEnd();
127     }
128 }
129
130 @end
131
132 namespace WebKit {
133
134 Ref<ScrollingTreeOverflowScrollingNodeIOS> ScrollingTreeOverflowScrollingNodeIOS::create(WebCore::ScrollingTree& scrollingTree, WebCore::ScrollingNodeID nodeID)
135 {
136     return adoptRef(*new ScrollingTreeOverflowScrollingNodeIOS(scrollingTree, nodeID));
137 }
138
139 ScrollingTreeOverflowScrollingNodeIOS::ScrollingTreeOverflowScrollingNodeIOS(WebCore::ScrollingTree& scrollingTree, WebCore::ScrollingNodeID nodeID)
140     : ScrollingTreeOverflowScrollingNode(scrollingTree, nodeID)
141     , m_updatingFromStateNode(false)
142 {
143 }
144
145 ScrollingTreeOverflowScrollingNodeIOS::~ScrollingTreeOverflowScrollingNodeIOS()
146 {
147     BEGIN_BLOCK_OBJC_EXCEPTIONS
148     if (UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate]) {
149         ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
150         // The scrollView may have been adopted by another node, so only clear the delegate if it's ours.
151         if (scrollView.delegate == m_scrollViewDelegate.get())
152             scrollView.delegate = nil;
153     }
154     END_BLOCK_OBJC_EXCEPTIONS
155 }
156
157 void ScrollingTreeOverflowScrollingNodeIOS::commitStateBeforeChildren(const WebCore::ScrollingStateNode& stateNode)
158 {
159     if (stateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollLayer)) {
160         BEGIN_BLOCK_OBJC_EXCEPTIONS
161         if (UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate]) {
162             ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
163             scrollView.delegate = nil;
164         }
165         END_BLOCK_OBJC_EXCEPTIONS
166     }
167
168     ScrollingTreeOverflowScrollingNode::commitStateBeforeChildren(stateNode);
169
170     const auto& scrollingStateNode = downcast<ScrollingStateOverflowScrollingNode>(stateNode);
171     if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer))
172         m_scrollLayer = scrollingStateNode.layer();
173
174     if (scrollingStateNode.hasChangedProperty(ScrollingStateOverflowScrollingNode::ScrolledContentsLayer))
175         m_scrolledContentsLayer = scrollingStateNode.scrolledContentsLayer();
176 }
177
178 void ScrollingTreeOverflowScrollingNodeIOS::commitStateAfterChildren(const ScrollingStateNode& stateNode)
179 {
180     ScrollingTreeOverflowScrollingNode::commitStateAfterChildren(stateNode);
181
182     TemporaryChange<bool> updatingChange(m_updatingFromStateNode, true);
183
184     const auto& scrollingStateNode = downcast<ScrollingStateOverflowScrollingNode>(stateNode);
185
186     if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollLayer)
187         || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize)
188         || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ReachableContentsSize)
189         || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollPosition)
190         || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollOrigin)) {
191         BEGIN_BLOCK_OBJC_EXCEPTIONS
192         UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate];
193         ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
194
195         if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollLayer)) {
196             if (!m_scrollViewDelegate)
197                 m_scrollViewDelegate = adoptNS([[WKOverflowScrollViewDelegate alloc] initWithScrollingTreeNode:this]);
198
199             scrollView.scrollsToTop = NO;
200             scrollView.delegate = m_scrollViewDelegate.get();
201         }
202
203         bool recomputeInsets = scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize);
204         if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ReachableContentsSize)) {
205             scrollView.contentSize = scrollingStateNode.reachableContentsSize();
206             recomputeInsets = true;
207         }
208
209         if ((scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollPosition)
210             || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollOrigin))
211             && ![m_scrollViewDelegate _isInUserInteraction]) {
212             scrollView.contentOffset = scrollingStateNode.scrollPosition() + scrollOrigin();
213             recomputeInsets = true;
214         }
215
216         if (recomputeInsets) {
217             UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
218             // With RTL or bottom-to-top scrolling (non-zero origin), we need extra space on the left or top.
219             if (scrollOrigin().x())
220                 insets.left = reachableContentsSize().width() - totalContentsSize().width();
221
222             if (scrollOrigin().y())
223                 insets.top = reachableContentsSize().height() - totalContentsSize().height();
224
225             scrollView.contentInset = insets;
226         }
227
228 #if ENABLE(CSS_SCROLL_SNAP)
229         // FIXME: If only one axis snaps in 2D scrolling, the other axis will decelerate fast as well. Is this what we want?
230         if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::HorizontalSnapOffsets) || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::VerticalSnapOffsets))
231             scrollView.decelerationRate = horizontalSnapOffsets().size() || verticalSnapOffsets().size() ? UIScrollViewDecelerationRateFast : UIScrollViewDecelerationRateNormal;
232 #endif
233         END_BLOCK_OBJC_EXCEPTIONS
234     }
235 }
236
237 void ScrollingTreeOverflowScrollingNodeIOS::updateLayersAfterAncestorChange(const ScrollingTreeNode& changedNode, const FloatRect& fixedPositionRect, const FloatSize& cumulativeDelta)
238 {
239     if (!m_children)
240         return;
241
242     FloatSize scrollDelta = lastCommittedScrollPosition() - scrollPosition();
243
244     for (auto& child : *m_children)
245         child->updateLayersAfterAncestorChange(changedNode, fixedPositionRect, cumulativeDelta + scrollDelta);
246 }
247
248 FloatPoint ScrollingTreeOverflowScrollingNodeIOS::scrollPosition() const
249 {
250     BEGIN_BLOCK_OBJC_EXCEPTIONS
251     UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate];
252     ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
253     return [scrollView contentOffset];
254     END_BLOCK_OBJC_EXCEPTIONS
255 }
256
257 void ScrollingTreeOverflowScrollingNodeIOS::setScrollLayerPosition(const FloatPoint& scrollPosition, const FloatRect&)
258 {
259     [m_scrollLayer setPosition:CGPointMake(-scrollPosition.x() + scrollOrigin().x(), -scrollPosition.y() + scrollOrigin().y())];
260
261     updateChildNodesAfterScroll(scrollPosition);
262 }
263
264 void ScrollingTreeOverflowScrollingNodeIOS::updateLayersAfterDelegatedScroll(const FloatPoint& scrollPosition)
265 {
266     updateChildNodesAfterScroll(scrollPosition);
267 }
268
269 void ScrollingTreeOverflowScrollingNodeIOS::updateChildNodesAfterScroll(const FloatPoint& scrollPosition)
270 {
271     if (!m_children)
272         return;
273
274     FloatRect fixedPositionRect = scrollingTree().fixedPositionRect();
275     FloatSize scrollDelta = lastCommittedScrollPosition() - scrollPosition;
276
277     for (auto& child : *m_children)
278         child->updateLayersAfterAncestorChange(*this, fixedPositionRect, scrollDelta);
279 }
280
281 void ScrollingTreeOverflowScrollingNodeIOS::overflowScrollWillStart()
282 {
283     scrollingTree().scrollingTreeNodeWillStartScroll();
284 }
285
286 void ScrollingTreeOverflowScrollingNodeIOS::overflowScrollDidEnd()
287 {
288     scrollingTree().scrollingTreeNodeDidEndScroll();
289 }
290
291 void ScrollingTreeOverflowScrollingNodeIOS::overflowScrollViewWillStartPanGesture()
292 {
293     scrollingTree().scrollingTreeNodeWillStartPanGesture();
294 }
295
296 void ScrollingTreeOverflowScrollingNodeIOS::scrollViewDidScroll(const FloatPoint& scrollPosition, bool inUserInteration)
297 {
298     if (m_updatingFromStateNode)
299         return;
300
301     scrollingTree().scrollPositionChangedViaDelegatedScrolling(scrollingNodeID(), scrollPosition, inUserInteration);
302 }
303
304 void ScrollingTreeOverflowScrollingNodeIOS::currentSnapPointIndicesDidChange(unsigned horizontal, unsigned vertical)
305 {
306     if (m_updatingFromStateNode)
307         return;
308     
309     scrollingTree().currentSnapPointIndicesDidChange(scrollingNodeID(), horizontal, vertical);
310 }
311
312 } // namespace WebCore
313
314 #endif // ENABLE(ASYNC_SCROLLING)
315 #endif // PLATFORM(IOS)