[iOS] position:fixed inside touch-scrollable overflow is mispositioned
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / RemoteScrollingCoordinatorProxyIOS.mm
1 /*
2  * Copyright (C) 2014-2015 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 "RemoteScrollingCoordinatorProxy.h"
28
29 #if PLATFORM(IOS)
30 #if ENABLE(ASYNC_SCROLLING)
31
32 #import "LayerRepresentation.h"
33 #import "RemoteLayerTreeHost.h"
34 #import "WebPageProxy.h"
35 #import <UIKit/UIView.h>
36 #import <WebCore/ScrollingStateFrameScrollingNode.h>
37 #import <WebCore/ScrollingStateOverflowScrollingNode.h>
38 #import <WebCore/ScrollingStateTree.h>
39
40 #if ENABLE(CSS_SCROLL_SNAP)
41 #import <WebCore/AxisScrollSnapOffsets.h>
42 #import <WebCore/ScrollSnapOffsetsInfo.h>
43 #import <WebCore/ScrollTypes.h>
44 #import <WebCore/ScrollingTreeFrameScrollingNode.h>
45 #endif
46
47 using namespace WebCore;
48
49 namespace WebKit {
50
51 static LayerRepresentation layerRepresentationFromLayerOrView(LayerOrView *layerOrView)
52 {
53     return LayerRepresentation(layerOrView.layer);
54 }
55
56 void RemoteScrollingCoordinatorProxy::connectStateNodeLayers(ScrollingStateTree& stateTree, const RemoteLayerTreeHost& layerTreeHost)
57 {
58     for (auto& currNode : stateTree.nodeMap().values()) {
59         switch (currNode->nodeType()) {
60         case OverflowScrollingNode: {
61             ScrollingStateOverflowScrollingNode& scrollingStateNode = downcast<ScrollingStateOverflowScrollingNode>(*currNode);
62             
63             if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer))
64                 scrollingStateNode.setLayer(layerRepresentationFromLayerOrView(layerTreeHost.getLayer(scrollingStateNode.layer())));
65             
66             if (scrollingStateNode.hasChangedProperty(ScrollingStateOverflowScrollingNode::ScrolledContentsLayer))
67                 scrollingStateNode.setScrolledContentsLayer(layerRepresentationFromLayerOrView(layerTreeHost.getLayer(scrollingStateNode.scrolledContentsLayer())));
68             break;
69         };
70         case FrameScrollingNode: {
71             ScrollingStateFrameScrollingNode& scrollingStateNode = downcast<ScrollingStateFrameScrollingNode>(*currNode);
72             
73             if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer))
74                 scrollingStateNode.setLayer(layerRepresentationFromLayerOrView(layerTreeHost.getLayer(scrollingStateNode.layer())));
75
76             if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::CounterScrollingLayer))
77                 scrollingStateNode.setCounterScrollingLayer(layerRepresentationFromLayerOrView(layerTreeHost.getLayer(scrollingStateNode.counterScrollingLayer())));
78
79             // FIXME: we should never have header and footer layers coming from the WebProcess.
80             if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HeaderLayer))
81                 scrollingStateNode.setHeaderLayer(layerRepresentationFromLayerOrView(layerTreeHost.getLayer(scrollingStateNode.headerLayer())));
82
83             if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::FooterLayer))
84                 scrollingStateNode.setFooterLayer(layerRepresentationFromLayerOrView(layerTreeHost.getLayer(scrollingStateNode.footerLayer())));
85             break;
86         }
87         case FixedNode:
88         case StickyNode:
89             if (currNode->hasChangedProperty(ScrollingStateNode::ScrollLayer))
90                 currNode->setLayer(layerRepresentationFromLayerOrView(layerTreeHost.getLayer(currNode->layer())));
91             break;
92         }
93     }
94 }
95
96 FloatRect RemoteScrollingCoordinatorProxy::customFixedPositionRect() const
97 {
98     return m_webPageProxy.computeCustomFixedPositionRect(m_webPageProxy.unobscuredContentRect(), m_webPageProxy.unobscuredContentRectRespectingInputViewBounds(), m_webPageProxy.customFixedPositionRect(),
99         m_webPageProxy.displayedContentScale(), WebPageProxy::UnobscuredRectConstraint::Unconstrained, visualViewportEnabled());
100 }
101
102 void RemoteScrollingCoordinatorProxy::scrollingTreeNodeWillStartPanGesture()
103 {
104     m_webPageProxy.overflowScrollViewWillStartPanGesture();
105 }
106
107 void RemoteScrollingCoordinatorProxy::scrollingTreeNodeWillStartScroll()
108 {
109     m_webPageProxy.overflowScrollWillStartScroll();
110 }
111     
112 void RemoteScrollingCoordinatorProxy::scrollingTreeNodeDidEndScroll()
113 {
114     m_webPageProxy.overflowScrollDidEndScroll();
115 }
116
117 #if ENABLE(CSS_SCROLL_SNAP)
118 void RemoteScrollingCoordinatorProxy::adjustTargetContentOffsetForSnapping(CGSize maxScrollOffsets, CGPoint velocity, CGFloat topInset, CGPoint* targetContentOffset)
119 {
120     // The bounds checking with maxScrollOffsets is to ensure that we won't interfere with rubber-banding when scrolling to the edge of the page.
121     if (shouldSnapForMainFrameScrolling(WebCore::ScrollEventAxis::Horizontal)) {
122         float potentialSnapPosition = closestSnapOffsetForMainFrameScrolling(WebCore::ScrollEventAxis::Horizontal, targetContentOffset->x, velocity.x, m_currentHorizontalSnapPointIndex);
123         if (targetContentOffset->x > 0 && targetContentOffset->x < maxScrollOffsets.width)
124             targetContentOffset->x = std::min<float>(maxScrollOffsets.width, potentialSnapPosition);
125     }
126
127     if (shouldSnapForMainFrameScrolling(WebCore::ScrollEventAxis::Vertical)) {
128         float potentialSnapPosition = closestSnapOffsetForMainFrameScrolling(WebCore::ScrollEventAxis::Vertical, targetContentOffset->y, velocity.y, m_currentVerticalSnapPointIndex);
129         if (m_currentVerticalSnapPointIndex != invalidSnapOffsetIndex)
130             potentialSnapPosition -= topInset;
131
132         if (targetContentOffset->y > 0 && targetContentOffset->y < maxScrollOffsets.height)
133             targetContentOffset->y = std::min<float>(maxScrollOffsets.height, potentialSnapPosition);
134     }
135 }
136
137 bool RemoteScrollingCoordinatorProxy::shouldSetScrollViewDecelerationRateFast() const
138 {
139     return shouldSnapForMainFrameScrolling(ScrollEventAxis::Horizontal) || shouldSnapForMainFrameScrolling(ScrollEventAxis::Vertical);
140 }
141
142 bool RemoteScrollingCoordinatorProxy::shouldSnapForMainFrameScrolling(ScrollEventAxis axis) const
143 {
144     ScrollingTreeNode* root = m_scrollingTree->rootNode();
145     if (root && root->isFrameScrollingNode()) {
146         ScrollingTreeFrameScrollingNode* rootFrame = static_cast<ScrollingTreeFrameScrollingNode*>(root);
147         const Vector<float>& snapOffsets = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsets() : rootFrame->verticalSnapOffsets();
148         unsigned currentIndex = axis == ScrollEventAxis::Horizontal ? m_currentHorizontalSnapPointIndex : m_currentVerticalSnapPointIndex;
149         return snapOffsets.size() && (currentIndex < snapOffsets.size() || currentIndex == invalidSnapOffsetIndex);
150     }
151     return false;
152 }
153
154 float RemoteScrollingCoordinatorProxy::closestSnapOffsetForMainFrameScrolling(ScrollEventAxis axis, float scrollDestination, float velocity, unsigned& currentIndex) const
155 {
156     ScrollingTreeNode* root = m_scrollingTree->rootNode();
157     ASSERT(root && root->isFrameScrollingNode());
158     ScrollingTreeFrameScrollingNode* rootFrame = static_cast<ScrollingTreeFrameScrollingNode*>(root);
159     const Vector<float>& snapOffsets = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsets() : rootFrame->verticalSnapOffsets();
160     const Vector<ScrollOffsetRange<float>>& snapOffsetRanges = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsetRanges() : rootFrame->verticalSnapOffsetRanges();
161
162     float scaledScrollDestination = scrollDestination / m_webPageProxy.displayedContentScale();
163     float rawClosestSnapOffset = closestSnapOffset(snapOffsets, snapOffsetRanges, scaledScrollDestination, velocity, currentIndex);
164     return rawClosestSnapOffset * m_webPageProxy.displayedContentScale();
165 }
166
167 bool RemoteScrollingCoordinatorProxy::hasActiveSnapPoint() const
168 {
169     ScrollingTreeNode* root = m_scrollingTree->rootNode();
170     if (!root)
171         return false;
172
173     if (!is<ScrollingTreeFrameScrollingNode>(root))
174         return false;
175
176     ScrollingTreeFrameScrollingNode& rootFrame = downcast<ScrollingTreeFrameScrollingNode>(*root);
177     const Vector<float>& horizontal = rootFrame.horizontalSnapOffsets();
178     const Vector<float>& vertical = rootFrame.verticalSnapOffsets();
179
180     if (horizontal.isEmpty() && vertical.isEmpty())
181         return false;
182
183     if ((!horizontal.isEmpty() && m_currentHorizontalSnapPointIndex >= horizontal.size())
184         || (!vertical.isEmpty() && m_currentVerticalSnapPointIndex >= vertical.size())) {
185         return false;
186     }
187     
188     return true;
189 }
190     
191 CGPoint RemoteScrollingCoordinatorProxy::nearestActiveContentInsetAdjustedSnapPoint(CGFloat topInset, const CGPoint& currentPoint) const
192 {
193     CGPoint activePoint = currentPoint;
194
195     ScrollingTreeNode* root = m_scrollingTree->rootNode();
196     ASSERT(root && is<ScrollingTreeFrameScrollingNode>(root));
197     ScrollingTreeFrameScrollingNode& rootFrame = downcast<ScrollingTreeFrameScrollingNode>(*root);
198     const Vector<float>& horizontal = rootFrame.horizontalSnapOffsets();
199     const Vector<float>& vertical = rootFrame.verticalSnapOffsets();
200
201     // The bounds checking with maxScrollOffsets is to ensure that we won't interfere with rubber-banding when scrolling to the edge of the page.
202     if (!horizontal.isEmpty() && m_currentHorizontalSnapPointIndex < horizontal.size())
203         activePoint.x = horizontal[m_currentHorizontalSnapPointIndex] * m_webPageProxy.displayedContentScale();
204
205     if (!vertical.isEmpty() && m_currentVerticalSnapPointIndex < vertical.size()) {
206         float potentialSnapPosition = vertical[m_currentVerticalSnapPointIndex] * m_webPageProxy.displayedContentScale();
207         potentialSnapPosition -= topInset;
208         activePoint.y = potentialSnapPosition;
209     }
210
211     return activePoint;
212 }
213
214 #endif
215
216 } // namespace WebKit
217
218
219 #endif // ENABLE(ASYNC_SCROLLING)
220 #endif // PLATFORM(IOS)