diff --git a/cc/blink/web_layer_impl.cc b/cc/blink/web_layer_impl.cc index a88fd3068c090f..ba40a266e7526a 100644 --- a/cc/blink/web_layer_impl.cc +++ b/cc/blink/web_layer_impl.cc @@ -372,6 +372,8 @@ ToWebLayerStickyPositionConstraint( web_constraint.rightOffset = constraint.right_offset; web_constraint.topOffset = constraint.top_offset; web_constraint.bottomOffset = constraint.bottom_offset; + web_constraint.parentRelativeStickyBoxOffset = + constraint.parent_relative_sticky_box_offset; web_constraint.scrollContainerRelativeStickyBoxRect = constraint.scroll_container_relative_sticky_box_rect; web_constraint.scrollContainerRelativeContainingBlockRect = @@ -390,6 +392,8 @@ static cc::LayerStickyPositionConstraint ToStickyPositionConstraint( constraint.right_offset = web_constraint.rightOffset; constraint.top_offset = web_constraint.topOffset; constraint.bottom_offset = web_constraint.bottomOffset; + constraint.parent_relative_sticky_box_offset = + web_constraint.parentRelativeStickyBoxOffset; constraint.scroll_container_relative_sticky_box_rect = web_constraint.scrollContainerRelativeStickyBoxRect; constraint.scroll_container_relative_containing_block_rect = diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc index 9143d556f76e42..43ad88a526794d 100644 --- a/cc/layers/layer.cc +++ b/cc/layers/layer.cc @@ -586,7 +586,7 @@ void Layer::SetPosition(const gfx::PointF& position) { transform_tree_index()); sticky_data->main_thread_offset = position.OffsetFromOrigin() - - sticky_data->constraints.scroll_container_relative_sticky_box_rect + sticky_data->constraints.parent_relative_sticky_box_offset .OffsetFromOrigin(); } transform_node->needs_local_transform_update = true; diff --git a/cc/layers/layer_sticky_position_constraint.cc b/cc/layers/layer_sticky_position_constraint.cc index 0fc0af84a8e890..2b6b33295db698 100644 --- a/cc/layers/layer_sticky_position_constraint.cc +++ b/cc/layers/layer_sticky_position_constraint.cc @@ -30,6 +30,8 @@ LayerStickyPositionConstraint::LayerStickyPositionConstraint( right_offset(other.right_offset), top_offset(other.top_offset), bottom_offset(other.bottom_offset), + parent_relative_sticky_box_offset( + other.parent_relative_sticky_box_offset), scroll_container_relative_sticky_box_rect( other.scroll_container_relative_sticky_box_rect), scroll_container_relative_containing_block_rect( @@ -46,6 +48,8 @@ void LayerStickyPositionConstraint::ToProtobuf( proto->set_right_offset(right_offset); proto->set_top_offset(top_offset); proto->set_bottom_offset(bottom_offset); + PointToProto(parent_relative_sticky_box_offset, + proto->mutable_parent_relative_sticky_box_offset()); RectToProto(scroll_container_relative_sticky_box_rect, proto->mutable_scroll_container_relative_sticky_box_rect()); RectToProto(scroll_container_relative_containing_block_rect, @@ -63,6 +67,8 @@ void LayerStickyPositionConstraint::FromProtobuf( right_offset = proto.right_offset(); top_offset = proto.top_offset(); bottom_offset = proto.bottom_offset(); + parent_relative_sticky_box_offset = + ProtoToPoint(proto.parent_relative_sticky_box_offset()); scroll_container_relative_sticky_box_rect = ProtoToRect(proto.scroll_container_relative_sticky_box_rect()); scroll_container_relative_containing_block_rect = @@ -81,6 +87,8 @@ bool LayerStickyPositionConstraint::operator==( left_offset == other.left_offset && right_offset == other.right_offset && top_offset == other.top_offset && bottom_offset == other.bottom_offset && + parent_relative_sticky_box_offset == + other.parent_relative_sticky_box_offset && scroll_container_relative_sticky_box_rect == other.scroll_container_relative_sticky_box_rect && scroll_container_relative_containing_block_rect == diff --git a/cc/layers/layer_sticky_position_constraint.h b/cc/layers/layer_sticky_position_constraint.h index b575a23aa26bf1..51a4da0c0b2853 100644 --- a/cc/layers/layer_sticky_position_constraint.h +++ b/cc/layers/layer_sticky_position_constraint.h @@ -32,6 +32,11 @@ struct CC_EXPORT LayerStickyPositionConstraint { float top_offset; float bottom_offset; + // The layout offset of the sticky box relative to its containing layer. + // This is used to detect the sticky offset the main thread has applied + // to the layer. + gfx::Point parent_relative_sticky_box_offset; + // The rectangle corresponding to original layout position of the sticky box // relative to the scroll ancestor. The sticky box is only offset once the // scroll has passed its initial position (e.g. top_offset will only push diff --git a/cc/proto/layer_sticky_position_constraint.proto b/cc/proto/layer_sticky_position_constraint.proto index 97d8fea1cc9250..fe9f962aff2e27 100644 --- a/cc/proto/layer_sticky_position_constraint.proto +++ b/cc/proto/layer_sticky_position_constraint.proto @@ -4,6 +4,7 @@ syntax = "proto2"; +import "point.proto"; import "rect.proto"; import "transform.proto"; @@ -23,6 +24,7 @@ message LayerStickyPositionConstraint { optional float top_offset = 8; optional float bottom_offset = 9; + optional Point parent_relative_sticky_box_offset = 12; optional Rect scroll_container_relative_sticky_box_rect = 10; optional Rect scroll_container_relative_containing_block_rect = 11; }; diff --git a/cc/trees/layer_tree_host_common_unittest.cc b/cc/trees/layer_tree_host_common_unittest.cc index 3ede7ca293014a..6eea68cb4b5046 100644 --- a/cc/trees/layer_tree_host_common_unittest.cc +++ b/cc/trees/layer_tree_host_common_unittest.cc @@ -6793,6 +6793,7 @@ TEST_F(LayerTreeHostCommonTest, StickyPositionTop) { sticky_position.is_sticky = true; sticky_position.is_anchored_top = true; sticky_position.top_offset = 10.0f; + sticky_position.parent_relative_sticky_box_offset = gfx::Point(10, 20); sticky_position.scroll_container_relative_sticky_box_rect = gfx::Rect(10, 20, 10, 10); sticky_position.scroll_container_relative_containing_block_rect = @@ -6864,6 +6865,7 @@ TEST_F(LayerTreeHostCommonTest, StickyPositionBottom) { sticky_position.is_sticky = true; sticky_position.is_anchored_bottom = true; sticky_position.bottom_offset = 10.0f; + sticky_position.parent_relative_sticky_box_offset = gfx::Point(0, 150); sticky_position.scroll_container_relative_sticky_box_rect = gfx::Rect(0, 150, 10, 10); sticky_position.scroll_container_relative_containing_block_rect = @@ -6935,6 +6937,7 @@ TEST_F(LayerTreeHostCommonTest, StickyPositionLeftRight) { sticky_position.is_anchored_right = true; sticky_position.left_offset = 10.f; sticky_position.right_offset = 10.f; + sticky_position.parent_relative_sticky_box_offset = gfx::Point(145, 0); sticky_position.scroll_container_relative_sticky_box_rect = gfx::Rect(145, 0, 10, 10); sticky_position.scroll_container_relative_containing_block_rect = @@ -7037,6 +7040,7 @@ TEST_F(LayerTreeHostCommonTest, StickyPositionMainThreadUpdates) { sticky_position.is_sticky = true; sticky_position.is_anchored_top = true; sticky_position.top_offset = 10.0f; + sticky_position.parent_relative_sticky_box_offset = gfx::Point(10, 20); sticky_position.scroll_container_relative_sticky_box_rect = gfx::Rect(10, 20, 10, 10); sticky_position.scroll_container_relative_containing_block_rect = @@ -7108,6 +7112,110 @@ TEST_F(LayerTreeHostCommonTest, StickyPositionMainThreadUpdates) { sticky_pos_impl->ScreenSpaceTransform().To2dTranslation()); } +// This tests the main thread updates with a composited sticky container. In +// this case the position received from main is relative to the container but +// the constraint rects are relative to the ancestor scroller. +TEST_F(LayerTreeHostCommonTest, StickyPositionCompositedContainer) { + scoped_refptr root = Layer::Create(); + scoped_refptr container = Layer::Create(); + scoped_refptr scroller = Layer::Create(); + scoped_refptr sticky_container = Layer::Create(); + scoped_refptr sticky_pos = Layer::Create(); + root->AddChild(container); + container->AddChild(scroller); + scroller->AddChild(sticky_container); + sticky_container->AddChild(sticky_pos); + host()->SetRootLayer(root); + scroller->SetScrollClipLayerId(container->id()); + + LayerStickyPositionConstraint sticky_position; + sticky_position.is_sticky = true; + sticky_position.is_anchored_top = true; + sticky_position.top_offset = 10.0f; + // The sticky position layer is only offset by (0, 10) from its parent + // layer, this position is used to determine the offset applied by the main + // thread. + sticky_position.parent_relative_sticky_box_offset = gfx::Point(0, 10); + sticky_position.scroll_container_relative_sticky_box_rect = + gfx::Rect(20, 30, 10, 10); + sticky_position.scroll_container_relative_containing_block_rect = + gfx::Rect(20, 20, 30, 30); + sticky_pos->SetStickyPositionConstraint(sticky_position); + + root->SetBounds(gfx::Size(100, 100)); + container->SetBounds(gfx::Size(100, 100)); + scroller->SetBounds(gfx::Size(1000, 1000)); + sticky_container->SetPosition(gfx::PointF(20, 20)); + sticky_container->SetBounds(gfx::Size(30, 30)); + sticky_pos->SetBounds(gfx::Size(10, 10)); + sticky_pos->SetPosition(gfx::PointF(0, 10)); + + ExecuteCalculateDrawProperties(root.get()); + host()->host_impl()->CreatePendingTree(); + host()->CommitAndCreatePendingTree(); + host()->host_impl()->ActivateSyncTree(); + LayerTreeImpl* layer_tree_impl = host()->host_impl()->active_tree(); + + LayerImpl* root_impl = layer_tree_impl->LayerById(root->id()); + LayerImpl* scroller_impl = layer_tree_impl->LayerById(scroller->id()); + LayerImpl* sticky_pos_impl = layer_tree_impl->LayerById(sticky_pos->id()); + + ExecuteCalculateDrawProperties(root_impl); + EXPECT_VECTOR2DF_EQ( + gfx::Vector2dF(20.f, 30.f), + sticky_pos_impl->ScreenSpaceTransform().To2dTranslation()); + + // Scroll less than sticking point, sticky element should move with scroll as + // we haven't gotten to the initial sticky item location yet. + SetScrollOffsetDelta(scroller_impl, gfx::Vector2dF(0.f, 5.f)); + ExecuteCalculateDrawProperties(root_impl); + EXPECT_VECTOR2DF_EQ( + gfx::Vector2dF(20.f, 25.f), + sticky_pos_impl->ScreenSpaceTransform().To2dTranslation()); + + // Scroll past the sticking point, the Y coordinate should now be clamped. + SetScrollOffsetDelta(scroller_impl, gfx::Vector2dF(0.f, 25.f)); + ExecuteCalculateDrawProperties(root_impl); + EXPECT_VECTOR2DF_EQ( + gfx::Vector2dF(20.f, 10.f), + sticky_pos_impl->ScreenSpaceTransform().To2dTranslation()); + + // Now the main thread commits the new position of the sticky element. + scroller->SetScrollOffset(gfx::ScrollOffset(0, 25)); + sticky_pos->SetPosition(gfx::PointF(0, 15)); + ExecuteCalculateDrawProperties(root.get()); + host()->host_impl()->CreatePendingTree(); + host()->CommitAndCreatePendingTree(); + host()->host_impl()->ActivateSyncTree(); + layer_tree_impl = host()->host_impl()->active_tree(); + root_impl = layer_tree_impl->LayerById(root->id()); + scroller_impl = layer_tree_impl->LayerById(scroller->id()); + sticky_pos_impl = layer_tree_impl->LayerById(sticky_pos->id()); + + // The element should still be where it was before. We reset the delta to + // (0, 0) because we have synced a scroll offset of (0, 25) from the main + // thread. + SetScrollOffsetDelta(scroller_impl, gfx::Vector2dF(0.f, 0.f)); + ExecuteCalculateDrawProperties(root_impl); + EXPECT_VECTOR2DF_EQ( + gfx::Vector2dF(20.f, 10.f), + sticky_pos_impl->ScreenSpaceTransform().To2dTranslation()); + + // And if we scroll a little further it remains there. + SetScrollOffsetDelta(scroller_impl, gfx::Vector2dF(0.f, 5.f)); + ExecuteCalculateDrawProperties(root_impl); + EXPECT_VECTOR2DF_EQ( + gfx::Vector2dF(20.f, 10.f), + sticky_pos_impl->ScreenSpaceTransform().To2dTranslation()); + + // And hits the bottom of the container. + SetScrollOffsetDelta(scroller_impl, gfx::Vector2dF(0.f, 10.f)); + ExecuteCalculateDrawProperties(root_impl); + EXPECT_VECTOR2DF_EQ( + gfx::Vector2dF(20.f, 5.f), + sticky_pos_impl->ScreenSpaceTransform().To2dTranslation()); +} + // A transform on a sticky element should not affect its sticky position. TEST_F(LayerTreeHostCommonTest, StickyPositionScaledStickyBox) { scoped_refptr root = Layer::Create(); @@ -7127,6 +7235,7 @@ TEST_F(LayerTreeHostCommonTest, StickyPositionScaledStickyBox) { sticky_position.is_sticky = true; sticky_position.is_anchored_top = true; sticky_position.top_offset = 0.0f; + sticky_position.parent_relative_sticky_box_offset = gfx::Point(0, 20); sticky_position.scroll_container_relative_sticky_box_rect = gfx::Rect(0, 20, 10, 10); sticky_position.scroll_container_relative_containing_block_rect = @@ -7205,6 +7314,7 @@ TEST_F(LayerTreeHostCommonTest, StickyPositionScaledContainer) { sticky_position.is_sticky = true; sticky_position.is_anchored_top = true; sticky_position.top_offset = 0.0f; + sticky_position.parent_relative_sticky_box_offset = gfx::Point(0, 20); sticky_position.scroll_container_relative_sticky_box_rect = gfx::Rect(0, 20, 10, 10); sticky_position.scroll_container_relative_containing_block_rect = diff --git a/cc/trees/property_tree_builder.cc b/cc/trees/property_tree_builder.cc index edc102623e0f4d..a8398bc55ecd84 100644 --- a/cc/trees/property_tree_builder.cc +++ b/cc/trees/property_tree_builder.cc @@ -699,7 +699,7 @@ bool AddTransformNodeIfNeeded( sticky_data->scroll_ancestor = GetScrollParentId(data_from_ancestor, layer); sticky_data->main_thread_offset = layer->position().OffsetFromOrigin() - - sticky_data->constraints.scroll_container_relative_sticky_box_rect + sticky_data->constraints.parent_relative_sticky_box_offset .OffsetFromOrigin(); } diff --git a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp index ff4963bf0446c3..a088bbb7cde042 100644 --- a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp +++ b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp @@ -304,6 +304,17 @@ void CompositedLayerMapping::updateStickyConstraints( const StickyPositionScrollingConstraints& constraints = ancestorOverflowLayer->getScrollableArea()->stickyConstraintsMap().get( &m_owningLayer); + + // Find the layout offset of the unshifted sticky box within its enclosing + // layer. + LayoutPoint enclosingLayerOffset; + m_owningLayer.enclosingLayerWithCompositedLayerMapping(ExcludeSelf) + ->convertToLayerCoords(m_owningLayer.ancestorOverflowLayer(), + enclosingLayerOffset); + FloatPoint stickyBoxOffset = + constraints.scrollContainerRelativeStickyBoxRect().location(); + stickyBoxOffset.moveBy(FloatPoint(-enclosingLayerOffset)); + webConstraint.isSticky = true; webConstraint.isAnchoredLeft = constraints.anchorEdges() & @@ -321,6 +332,8 @@ void CompositedLayerMapping::updateStickyConstraints( webConstraint.rightOffset = constraints.rightOffset(); webConstraint.topOffset = constraints.topOffset(); webConstraint.bottomOffset = constraints.bottomOffset(); + webConstraint.parentRelativeStickyBoxOffset = + roundedIntPoint(stickyBoxOffset); webConstraint.scrollContainerRelativeStickyBoxRect = enclosingIntRect(constraints.scrollContainerRelativeStickyBoxRect()); webConstraint.scrollContainerRelativeContainingBlockRect = enclosingIntRect( diff --git a/third_party/WebKit/Source/web/tests/ScrollingCoordinatorTest.cpp b/third_party/WebKit/Source/web/tests/ScrollingCoordinatorTest.cpp index f3d36dc1736034..a2194782061ed5 100644 --- a/third_party/WebKit/Source/web/tests/ScrollingCoordinatorTest.cpp +++ b/third_party/WebKit/Source/web/tests/ScrollingCoordinatorTest.cpp @@ -34,6 +34,8 @@ #include "core/layout/compositing/CompositedLayerMapping.h" #include "core/layout/compositing/PaintLayerCompositor.h" #include "core/page/Page.h" +#include "platform/geometry/IntPoint.h" +#include "platform/geometry/IntRect.h" #include "platform/graphics/GraphicsLayer.h" #include "platform/testing/URLTestHelpers.h" #include "public/platform/Platform.h" @@ -328,6 +330,112 @@ TEST_F(ScrollingCoordinatorTest, fastScrollingForFixedPosition) { } } +TEST_F(ScrollingCoordinatorTest, fastScrollingForStickyPosition) { + registerMockedHttpURLLoad("sticky-position.html"); + navigateTo(m_baseURL + "sticky-position.html"); + forceFullCompositingUpdate(); + + // Sticky position should not fall back to main thread scrolling. + WebLayer* rootScrollLayer = getRootScrollLayer(); + EXPECT_FALSE(rootScrollLayer->shouldScrollOnMainThread()); + + Document* document = frame()->document(); + { + Element* element = document->getElementById("div-tl"); + ASSERT_TRUE(element); + WebLayer* layer = webLayerFromElement(element); + ASSERT_TRUE(layer); + WebLayerStickyPositionConstraint constraint = + layer->stickyPositionConstraint(); + ASSERT_TRUE(constraint.isSticky); + EXPECT_TRUE(constraint.isAnchoredTop && constraint.isAnchoredLeft && + !constraint.isAnchoredRight && !constraint.isAnchoredBottom); + EXPECT_EQ(1.f, constraint.topOffset); + EXPECT_EQ(1.f, constraint.leftOffset); + EXPECT_EQ(IntRect(100, 100, 10, 10), + IntRect(constraint.scrollContainerRelativeStickyBoxRect)); + EXPECT_EQ(IntRect(100, 100, 200, 200), + IntRect(constraint.scrollContainerRelativeContainingBlockRect)); + EXPECT_EQ(IntPoint(100, 100), + IntPoint(constraint.parentRelativeStickyBoxOffset)); + } + { + Element* element = document->getElementById("div-tr"); + ASSERT_TRUE(element); + WebLayer* layer = webLayerFromElement(element); + ASSERT_TRUE(layer); + WebLayerStickyPositionConstraint constraint = + layer->stickyPositionConstraint(); + ASSERT_TRUE(constraint.isSticky); + EXPECT_TRUE(constraint.isAnchoredTop && !constraint.isAnchoredLeft && + constraint.isAnchoredRight && !constraint.isAnchoredBottom); + } + { + Element* element = document->getElementById("div-bl"); + ASSERT_TRUE(element); + WebLayer* layer = webLayerFromElement(element); + ASSERT_TRUE(layer); + WebLayerStickyPositionConstraint constraint = + layer->stickyPositionConstraint(); + ASSERT_TRUE(constraint.isSticky); + EXPECT_TRUE(!constraint.isAnchoredTop && constraint.isAnchoredLeft && + !constraint.isAnchoredRight && constraint.isAnchoredBottom); + } + { + Element* element = document->getElementById("div-br"); + ASSERT_TRUE(element); + WebLayer* layer = webLayerFromElement(element); + ASSERT_TRUE(layer); + WebLayerStickyPositionConstraint constraint = + layer->stickyPositionConstraint(); + ASSERT_TRUE(constraint.isSticky); + EXPECT_TRUE(!constraint.isAnchoredTop && !constraint.isAnchoredLeft && + constraint.isAnchoredRight && constraint.isAnchoredBottom); + } + { + Element* element = document->getElementById("span-tl"); + ASSERT_TRUE(element); + WebLayer* layer = webLayerFromElement(element); + ASSERT_TRUE(layer); + WebLayerStickyPositionConstraint constraint = + layer->stickyPositionConstraint(); + ASSERT_TRUE(constraint.isSticky); + EXPECT_TRUE(constraint.isAnchoredTop && constraint.isAnchoredLeft && + !constraint.isAnchoredRight && !constraint.isAnchoredBottom); + } + { + Element* element = document->getElementById("span-tlbr"); + ASSERT_TRUE(element); + WebLayer* layer = webLayerFromElement(element); + ASSERT_TRUE(layer); + WebLayerStickyPositionConstraint constraint = + layer->stickyPositionConstraint(); + ASSERT_TRUE(constraint.isSticky); + EXPECT_TRUE(constraint.isAnchoredTop && constraint.isAnchoredLeft && + constraint.isAnchoredRight && constraint.isAnchoredBottom); + EXPECT_EQ(1.f, constraint.topOffset); + EXPECT_EQ(1.f, constraint.leftOffset); + EXPECT_EQ(1.f, constraint.rightOffset); + EXPECT_EQ(1.f, constraint.bottomOffset); + } + { + Element* element = document->getElementById("composited-top"); + ASSERT_TRUE(element); + WebLayer* layer = webLayerFromElement(element); + ASSERT_TRUE(layer); + WebLayerStickyPositionConstraint constraint = + layer->stickyPositionConstraint(); + ASSERT_TRUE(constraint.isSticky); + EXPECT_TRUE(constraint.isAnchoredTop); + EXPECT_EQ(IntRect(100, 110, 10, 10), + IntRect(constraint.scrollContainerRelativeStickyBoxRect)); + EXPECT_EQ(IntRect(100, 100, 200, 200), + IntRect(constraint.scrollContainerRelativeContainingBlockRect)); + EXPECT_EQ(IntPoint(0, 10), + IntPoint(constraint.parentRelativeStickyBoxOffset)); + } +} + TEST_F(ScrollingCoordinatorTest, touchEventHandler) { registerMockedHttpURLLoad("touch-event-handler.html"); navigateTo(m_baseURL + "touch-event-handler.html"); diff --git a/third_party/WebKit/Source/web/tests/data/sticky-position.html b/third_party/WebKit/Source/web/tests/data/sticky-position.html new file mode 100644 index 00000000000000..613a7bd8008ed5 --- /dev/null +++ b/third_party/WebKit/Source/web/tests/data/sticky-position.html @@ -0,0 +1,58 @@ + + + + + + + + +
+
X
+
X
+
X
+
X
+ X + X +
+
+
+
X
+
+ + diff --git a/third_party/WebKit/public/platform/WebLayerStickyPositionConstraint.h b/third_party/WebKit/public/platform/WebLayerStickyPositionConstraint.h index 83d04c64abf48b..296496d76f24a2 100644 --- a/third_party/WebKit/public/platform/WebLayerStickyPositionConstraint.h +++ b/third_party/WebKit/public/platform/WebLayerStickyPositionConstraint.h @@ -26,6 +26,9 @@ #ifndef WebLayerStickyPositionConstraint_h #define WebLayerStickyPositionConstraint_h +#include "public/platform/WebPoint.h" +#include "public/platform/WebRect.h" + namespace blink { // TODO(flackr): Combine with WebLayerPositionConstraint. @@ -47,6 +50,10 @@ struct WebLayerStickyPositionConstraint { float topOffset; float bottomOffset; + // This is the layout position of the sticky element before it has been + // shifted relative to the enclosing composited layer. + WebPoint parentRelativeStickyBoxOffset; + // The layout rectangle of the sticky element before it has been shifted // to stick. WebRect scrollContainerRelativeStickyBoxRect;