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