blob: b813bef59dd6e2a78c566d4c8c236411e7229542 [file] [log] [blame]
/*
* Copyright (C) 2014-2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "RemoteScrollingCoordinatorProxy.h"
#if PLATFORM(IOS_FAMILY)
#if ENABLE(ASYNC_SCROLLING)
#import "RemoteLayerTreeHost.h"
#import "RemoteLayerTreeNode.h"
#import "ScrollingTreeOverflowScrollingNodeIOS.h"
#import "WebPageProxy.h"
#import <UIKit/UIView.h>
#import <WebCore/ScrollingStateFrameScrollingNode.h>
#import <WebCore/ScrollingStateOverflowScrollProxyNode.h>
#import <WebCore/ScrollingStateOverflowScrollingNode.h>
#import <WebCore/ScrollingStatePositionedNode.h>
#import <WebCore/ScrollingStateTree.h>
#if ENABLE(CSS_SCROLL_SNAP)
#import <WebCore/AxisScrollSnapOffsets.h>
#import <WebCore/ScrollSnapOffsetsInfo.h>
#import <WebCore/ScrollTypes.h>
#import <WebCore/ScrollingTreeFrameScrollingNode.h>
#import <WebCore/ScrollingTreeOverflowScrollProxyNode.h>
#import <WebCore/ScrollingTreeOverflowScrollingNode.h>
#import <WebCore/ScrollingTreePositionedNode.h>
#endif
namespace WebKit {
using namespace WebCore;
UIScrollView *RemoteScrollingCoordinatorProxy::scrollViewForScrollingNodeID(WebCore::ScrollingNodeID nodeID) const
{
auto* treeNode = m_scrollingTree->nodeForID(nodeID);
if (!is<ScrollingTreeOverflowScrollingNode>(treeNode))
return nil;
auto* scrollingNode = downcast<ScrollingTreeOverflowScrollingNode>(treeNode);
// All ScrollingTreeOverflowScrollingNodes are ScrollingTreeOverflowScrollingNodeIOS on iOS.
return static_cast<ScrollingTreeOverflowScrollingNodeIOS*>(scrollingNode)->scrollView();
}
void RemoteScrollingCoordinatorProxy::connectStateNodeLayers(ScrollingStateTree& stateTree, const RemoteLayerTreeHost& layerTreeHost)
{
using PlatformLayerID = GraphicsLayer::PlatformLayerID;
for (auto& currNode : stateTree.nodeMap().values()) {
if (currNode->hasChangedProperty(ScrollingStateNode::Layer))
currNode->setLayer(layerTreeHost.layerForID(PlatformLayerID { currNode->layer() }));
switch (currNode->nodeType()) {
case ScrollingNodeType::Overflow: {
ScrollingStateOverflowScrollingNode& scrollingStateNode = downcast<ScrollingStateOverflowScrollingNode>(*currNode);
if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollContainerLayer))
scrollingStateNode.setScrollContainerLayer(layerTreeHost.layerForID(PlatformLayerID { scrollingStateNode.scrollContainerLayer() }));
if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrolledContentsLayer))
scrollingStateNode.setScrolledContentsLayer(layerTreeHost.layerForID(PlatformLayerID { scrollingStateNode.scrolledContentsLayer() }));
break;
};
case ScrollingNodeType::MainFrame:
case ScrollingNodeType::Subframe: {
ScrollingStateFrameScrollingNode& scrollingStateNode = downcast<ScrollingStateFrameScrollingNode>(*currNode);
if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollContainerLayer))
scrollingStateNode.setScrollContainerLayer(layerTreeHost.layerForID(PlatformLayerID { scrollingStateNode.scrollContainerLayer() }));
if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrolledContentsLayer))
scrollingStateNode.setScrolledContentsLayer(layerTreeHost.layerForID(PlatformLayerID { scrollingStateNode.scrolledContentsLayer() }));
if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::CounterScrollingLayer))
scrollingStateNode.setCounterScrollingLayer(layerTreeHost.layerForID(PlatformLayerID { scrollingStateNode.counterScrollingLayer() }));
// FIXME: we should never have header and footer layers coming from the WebProcess.
if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HeaderLayer))
scrollingStateNode.setHeaderLayer(layerTreeHost.layerForID(PlatformLayerID { scrollingStateNode.headerLayer() }));
if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::FooterLayer))
scrollingStateNode.setFooterLayer(layerTreeHost.layerForID(PlatformLayerID { scrollingStateNode.footerLayer() }));
break;
}
case ScrollingNodeType::OverflowProxy:
case ScrollingNodeType::FrameHosting:
case ScrollingNodeType::Fixed:
case ScrollingNodeType::Sticky:
case ScrollingNodeType::Positioned:
break;
}
}
}
FloatRect RemoteScrollingCoordinatorProxy::currentLayoutViewport() const
{
// FIXME: does this give a different value to the last value pushed onto us?
return m_webPageProxy.computeCustomFixedPositionRect(m_webPageProxy.unobscuredContentRect(), m_webPageProxy.unobscuredContentRectRespectingInputViewBounds(), m_webPageProxy.customFixedPositionRect(),
m_webPageProxy.displayedContentScale(), FrameView::LayoutViewportConstraint::Unconstrained);
}
void RemoteScrollingCoordinatorProxy::scrollingTreeNodeWillStartPanGesture()
{
m_webPageProxy.scrollingNodeScrollViewWillStartPanGesture();
}
void RemoteScrollingCoordinatorProxy::scrollingTreeNodeWillStartScroll()
{
m_webPageProxy.scrollingNodeScrollWillStartScroll();
}
void RemoteScrollingCoordinatorProxy::scrollingTreeNodeDidEndScroll()
{
m_webPageProxy.scrollingNodeScrollDidEndScroll();
}
void RemoteScrollingCoordinatorProxy::establishLayerTreeScrollingRelations(const RemoteLayerTreeHost& remoteLayerTreeHost)
{
for (auto layerID : m_layersWithScrollingRelations) {
if (auto* layerNode = remoteLayerTreeHost.nodeForID(layerID)) {
layerNode->setActingScrollContainerID(0);
layerNode->setStationaryScrollContainerIDs({ });
}
}
m_layersWithScrollingRelations.clear();
// Usually a scroll view scrolls its descendant layers. In some positioning cases it also controls non-descendants, or doesn't control a descendant.
// To do overlap hit testing correctly we tell layers about such relations.
for (auto& positionedNode : m_scrollingTree->activePositionedNodes()) {
Vector<GraphicsLayer::PlatformLayerID> stationaryScrollContainerIDs;
for (auto overflowNodeID : positionedNode->relatedOverflowScrollingNodes()) {
auto* overflowNode = downcast<ScrollingTreeOverflowScrollingNode>(m_scrollingTree->nodeForID(overflowNodeID));
if (!overflowNode)
continue;
stationaryScrollContainerIDs.append(RemoteLayerTreeNode::layerID(static_cast<CALayer*>(overflowNode->scrollContainerLayer())));
}
if (auto* layerNode = RemoteLayerTreeNode::forCALayer(positionedNode->layer())) {
layerNode->setStationaryScrollContainerIDs(WTFMove(stationaryScrollContainerIDs));
m_layersWithScrollingRelations.add(layerNode->layerID());
}
}
for (auto& scrollProxyNode : m_scrollingTree->activeOverflowScrollProxyNodes()) {
auto* overflowNode = downcast<ScrollingTreeOverflowScrollingNode>(m_scrollingTree->nodeForID(scrollProxyNode->overflowScrollingNodeID()));
if (!overflowNode)
continue;
if (auto* layerNode = RemoteLayerTreeNode::forCALayer(scrollProxyNode->layer())) {
layerNode->setActingScrollContainerID(RemoteLayerTreeNode::layerID(static_cast<CALayer*>(overflowNode->scrollContainerLayer())));
m_layersWithScrollingRelations.add(layerNode->layerID());
}
}
}
#if ENABLE(CSS_SCROLL_SNAP)
void RemoteScrollingCoordinatorProxy::adjustTargetContentOffsetForSnapping(CGSize maxScrollOffsets, CGPoint velocity, CGFloat topInset, CGPoint* targetContentOffset)
{
// The bounds checking with maxScrollOffsets is to ensure that we won't interfere with rubber-banding when scrolling to the edge of the page.
if (shouldSnapForMainFrameScrolling(WebCore::ScrollEventAxis::Horizontal)) {
float potentialSnapPosition = closestSnapOffsetForMainFrameScrolling(WebCore::ScrollEventAxis::Horizontal, targetContentOffset->x, velocity.x, m_currentHorizontalSnapPointIndex);
if (targetContentOffset->x > 0 && targetContentOffset->x < maxScrollOffsets.width)
targetContentOffset->x = std::min<float>(maxScrollOffsets.width, potentialSnapPosition);
}
if (shouldSnapForMainFrameScrolling(WebCore::ScrollEventAxis::Vertical)) {
float potentialSnapPosition = closestSnapOffsetForMainFrameScrolling(WebCore::ScrollEventAxis::Vertical, targetContentOffset->y, velocity.y, m_currentVerticalSnapPointIndex);
if (m_currentVerticalSnapPointIndex != invalidSnapOffsetIndex)
potentialSnapPosition -= topInset;
if (targetContentOffset->y > 0 && targetContentOffset->y < maxScrollOffsets.height)
targetContentOffset->y = std::min<float>(maxScrollOffsets.height, potentialSnapPosition);
}
}
bool RemoteScrollingCoordinatorProxy::shouldSetScrollViewDecelerationRateFast() const
{
return shouldSnapForMainFrameScrolling(ScrollEventAxis::Horizontal) || shouldSnapForMainFrameScrolling(ScrollEventAxis::Vertical);
}
bool RemoteScrollingCoordinatorProxy::shouldSnapForMainFrameScrolling(ScrollEventAxis axis) const
{
ScrollingTreeNode* root = m_scrollingTree->rootNode();
if (root && root->isFrameScrollingNode()) {
ScrollingTreeFrameScrollingNode* rootFrame = static_cast<ScrollingTreeFrameScrollingNode*>(root);
const Vector<float>& snapOffsets = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsets() : rootFrame->verticalSnapOffsets();
unsigned currentIndex = axis == ScrollEventAxis::Horizontal ? m_currentHorizontalSnapPointIndex : m_currentVerticalSnapPointIndex;
return snapOffsets.size() && (currentIndex < snapOffsets.size() || currentIndex == invalidSnapOffsetIndex);
}
return false;
}
float RemoteScrollingCoordinatorProxy::closestSnapOffsetForMainFrameScrolling(ScrollEventAxis axis, float scrollDestination, float velocity, unsigned& currentIndex) const
{
ScrollingTreeNode* root = m_scrollingTree->rootNode();
ASSERT(root && root->isFrameScrollingNode());
ScrollingTreeFrameScrollingNode* rootFrame = static_cast<ScrollingTreeFrameScrollingNode*>(root);
const Vector<float>& snapOffsets = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsets() : rootFrame->verticalSnapOffsets();
const Vector<ScrollOffsetRange<float>>& snapOffsetRanges = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsetRanges() : rootFrame->verticalSnapOffsetRanges();
float scaledScrollDestination = scrollDestination / m_webPageProxy.displayedContentScale();
float rawClosestSnapOffset = closestSnapOffset(snapOffsets, snapOffsetRanges, scaledScrollDestination, velocity, currentIndex);
return rawClosestSnapOffset * m_webPageProxy.displayedContentScale();
}
bool RemoteScrollingCoordinatorProxy::hasActiveSnapPoint() const
{
ScrollingTreeNode* root = m_scrollingTree->rootNode();
if (!root)
return false;
if (!is<ScrollingTreeFrameScrollingNode>(root))
return false;
ScrollingTreeFrameScrollingNode& rootFrame = downcast<ScrollingTreeFrameScrollingNode>(*root);
const Vector<float>& horizontal = rootFrame.horizontalSnapOffsets();
const Vector<float>& vertical = rootFrame.verticalSnapOffsets();
if (horizontal.isEmpty() && vertical.isEmpty())
return false;
if ((!horizontal.isEmpty() && m_currentHorizontalSnapPointIndex >= horizontal.size())
|| (!vertical.isEmpty() && m_currentVerticalSnapPointIndex >= vertical.size())) {
return false;
}
return true;
}
CGPoint RemoteScrollingCoordinatorProxy::nearestActiveContentInsetAdjustedSnapPoint(CGFloat topInset, const CGPoint& currentPoint) const
{
CGPoint activePoint = currentPoint;
ScrollingTreeNode* root = m_scrollingTree->rootNode();
ASSERT(root && is<ScrollingTreeFrameScrollingNode>(root));
ScrollingTreeFrameScrollingNode& rootFrame = downcast<ScrollingTreeFrameScrollingNode>(*root);
const Vector<float>& horizontal = rootFrame.horizontalSnapOffsets();
const Vector<float>& vertical = rootFrame.verticalSnapOffsets();
// The bounds checking with maxScrollOffsets is to ensure that we won't interfere with rubber-banding when scrolling to the edge of the page.
if (!horizontal.isEmpty() && m_currentHorizontalSnapPointIndex < horizontal.size())
activePoint.x = horizontal[m_currentHorizontalSnapPointIndex] * m_webPageProxy.displayedContentScale();
if (!vertical.isEmpty() && m_currentVerticalSnapPointIndex < vertical.size()) {
float potentialSnapPosition = vertical[m_currentVerticalSnapPointIndex] * m_webPageProxy.displayedContentScale();
potentialSnapPosition -= topInset;
activePoint.y = potentialSnapPosition;
}
return activePoint;
}
#endif
} // namespace WebKit
#endif // ENABLE(ASYNC_SCROLLING)
#endif // PLATFORM(IOS_FAMILY)