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/BlockExceptions.h>
34#import <WebCore/ScrollingStateOverflowScrollingNode.h>
35#import <WebCore/ScrollingTree.h>
36#import <UIKit/UIPanGestureRecognizer.h>
37#import <UIKit/UIScrollView.h>
38#import <wtf/TemporaryChange.h>
39
40using namespace WebCore;
41
42@interface WKOverflowScrollViewDelegate : NSObject <UIScrollViewDelegate> {
43    WebKit::ScrollingTreeOverflowScrollingNodeIOS* _scrollingTreeNode;
44}
45
46@property (nonatomic, getter=_isInUserInteraction) BOOL inUserInteraction;
47
48- (instancetype)initWithScrollingTreeNode:(WebKit::ScrollingTreeOverflowScrollingNodeIOS*)node;
49
50@end
51
52@implementation WKOverflowScrollViewDelegate
53
54- (instancetype)initWithScrollingTreeNode:(WebKit::ScrollingTreeOverflowScrollingNodeIOS*)node
55{
56    if ((self = [super init]))
57        _scrollingTreeNode = node;
58
59    return self;
60}
61
62- (void)scrollViewDidScroll:(UIScrollView *)scrollView
63{
64    _scrollingTreeNode->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction);
65}
66
67- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
68{
69    _inUserInteraction = YES;
70
71    if (scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan)
72        _scrollingTreeNode->overflowScrollViewWillStartPanGesture();
73    _scrollingTreeNode->overflowScrollWillStart();
74}
75
76- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)willDecelerate
77{
78    if (_inUserInteraction && !willDecelerate) {
79        _inUserInteraction = NO;
80        _scrollingTreeNode->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction);
81        _scrollingTreeNode->overflowScrollDidEnd();
82    }
83}
84
85- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
86{
87    if (_inUserInteraction) {
88        _inUserInteraction = NO;
89        _scrollingTreeNode->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction);
90        _scrollingTreeNode->overflowScrollDidEnd();
91    }
92}
93
94@end
95
96namespace WebKit {
97
98PassRefPtr<ScrollingTreeOverflowScrollingNodeIOS> ScrollingTreeOverflowScrollingNodeIOS::create(WebCore::ScrollingTree& scrollingTree, WebCore::ScrollingNodeID nodeID)
99{
100    return adoptRef(new ScrollingTreeOverflowScrollingNodeIOS(scrollingTree, nodeID));
101}
102
103ScrollingTreeOverflowScrollingNodeIOS::ScrollingTreeOverflowScrollingNodeIOS(WebCore::ScrollingTree& scrollingTree, WebCore::ScrollingNodeID nodeID)
104    : ScrollingTreeOverflowScrollingNode(scrollingTree, nodeID)
105    , m_updatingFromStateNode(false)
106{
107}
108
109ScrollingTreeOverflowScrollingNodeIOS::~ScrollingTreeOverflowScrollingNodeIOS()
110{
111    BEGIN_BLOCK_OBJC_EXCEPTIONS
112    if (UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate]) {
113        ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
114        scrollView.delegate = nil;
115    }
116    END_BLOCK_OBJC_EXCEPTIONS
117}
118
119void ScrollingTreeOverflowScrollingNodeIOS::updateBeforeChildren(const WebCore::ScrollingStateNode& stateNode)
120{
121    if (stateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollLayer)) {
122        BEGIN_BLOCK_OBJC_EXCEPTIONS
123        if (UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate]) {
124            ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
125            scrollView.delegate = nil;
126        }
127        END_BLOCK_OBJC_EXCEPTIONS
128    }
129
130    ScrollingTreeOverflowScrollingNode::updateBeforeChildren(stateNode);
131
132    const auto& scrollingStateNode = toScrollingStateOverflowScrollingNode(stateNode);
133    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer))
134        m_scrollLayer = scrollingStateNode.layer();
135
136    if (scrollingStateNode.hasChangedProperty(ScrollingStateOverflowScrollingNode::ScrolledContentsLayer))
137        m_scrolledContentsLayer = scrollingStateNode.scrolledContentsLayer();
138}
139
140void ScrollingTreeOverflowScrollingNodeIOS::updateAfterChildren(const ScrollingStateNode& stateNode)
141{
142    ScrollingTreeOverflowScrollingNode::updateAfterChildren(stateNode);
143
144    TemporaryChange<bool> updatingChange(m_updatingFromStateNode, true);
145
146    const auto& scrollingStateNode = toScrollingStateOverflowScrollingNode(stateNode);
147
148    if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollLayer)
149        || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize)
150        || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ReachableContentsSize)
151        || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollPosition)
152        || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollOrigin)) {
153        BEGIN_BLOCK_OBJC_EXCEPTIONS
154        UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate];
155        ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
156
157        if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollLayer)) {
158            if (!m_scrollViewDelegate)
159                m_scrollViewDelegate = adoptNS([[WKOverflowScrollViewDelegate alloc] initWithScrollingTreeNode:this]);
160
161            scrollView.scrollsToTop = NO;
162            scrollView.delegate = m_scrollViewDelegate.get();
163        }
164
165        bool recomputeInsets = scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize);
166        if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ReachableContentsSize)) {
167            scrollView.contentSize = scrollingStateNode.reachableContentsSize();
168            recomputeInsets = true;
169        }
170
171        if ((scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollPosition)
172            || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollOrigin))
173            && ![m_scrollViewDelegate _isInUserInteraction]) {
174            scrollView.contentOffset = scrollingStateNode.scrollPosition() + scrollOrigin();
175            recomputeInsets = true;
176        }
177
178        if (recomputeInsets) {
179            UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
180            // With RTL or bottom-to-top scrolling (non-zero origin), we need extra space on the left or top.
181            if (scrollOrigin().x())
182                insets.left = reachableContentsSize().width() - totalContentsSize().width();
183
184            if (scrollOrigin().y())
185                insets.top = reachableContentsSize().height() - totalContentsSize().height();
186
187            scrollView.contentInset = insets;
188        }
189
190        END_BLOCK_OBJC_EXCEPTIONS
191    }
192}
193
194void ScrollingTreeOverflowScrollingNodeIOS::updateLayersAfterAncestorChange(const ScrollingTreeNode& changedNode, const FloatRect& fixedPositionRect, const FloatSize& cumulativeDelta)
195{
196    if (!m_children)
197        return;
198
199    FloatSize scrollDelta = lastCommittedScrollPosition() - scrollPosition();
200
201    for (auto& child : *m_children)
202        child->updateLayersAfterAncestorChange(changedNode, fixedPositionRect, cumulativeDelta + scrollDelta);
203}
204
205FloatPoint ScrollingTreeOverflowScrollingNodeIOS::scrollPosition() const
206{
207    BEGIN_BLOCK_OBJC_EXCEPTIONS
208    UIScrollView *scrollView = (UIScrollView *)[scrollLayer() delegate];
209    ASSERT([scrollView isKindOfClass:[UIScrollView self]]);
210    return [scrollView contentOffset];
211    END_BLOCK_OBJC_EXCEPTIONS
212}
213
214void ScrollingTreeOverflowScrollingNodeIOS::setScrollLayerPosition(const FloatPoint& scrollPosition)
215{
216    [m_scrollLayer setPosition:CGPointMake(-scrollPosition.x() + scrollOrigin().x(), -scrollPosition.y() + scrollOrigin().y())];
217
218    updateChildNodesAfterScroll(scrollPosition);
219}
220
221void ScrollingTreeOverflowScrollingNodeIOS::updateLayersAfterDelegatedScroll(const FloatPoint& scrollPosition)
222{
223    updateChildNodesAfterScroll(scrollPosition);
224}
225
226void ScrollingTreeOverflowScrollingNodeIOS::updateChildNodesAfterScroll(const FloatPoint& scrollPosition)
227{
228    if (!m_children)
229        return;
230
231    FloatRect fixedPositionRect = scrollingTree().fixedPositionRect();
232    FloatSize scrollDelta = lastCommittedScrollPosition() - scrollPosition;
233
234    for (auto& child : *m_children)
235        child->updateLayersAfterAncestorChange(*this, fixedPositionRect, scrollDelta);
236}
237
238void ScrollingTreeOverflowScrollingNodeIOS::overflowScrollWillStart()
239{
240    scrollingTree().scrollingTreeNodeWillStartScroll();
241}
242
243void ScrollingTreeOverflowScrollingNodeIOS::overflowScrollDidEnd()
244{
245    scrollingTree().scrollingTreeNodeDidEndScroll();
246}
247
248void ScrollingTreeOverflowScrollingNodeIOS::overflowScrollViewWillStartPanGesture()
249{
250    scrollingTree().scrollingTreeNodeWillStartPanGesture();
251}
252
253void ScrollingTreeOverflowScrollingNodeIOS::scrollViewDidScroll(const FloatPoint& scrollPosition, bool inUserInteration)
254{
255    if (m_updatingFromStateNode)
256        return;
257
258    scrollingTree().scrollPositionChangedViaDelegatedScrolling(scrollingNodeID(), scrollPosition, inUserInteration);
259}
260
261} // namespace WebCore
262
263#endif // ENABLE(ASYNC_SCROLLING)
264#endif // PLATFORM(IOS)
265