Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(838)

Unified Diff: third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp

Issue 2636253002: Handle nested position:sticky elements (Closed)
Patch Set: Rebase Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp b/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
index ca16f0c6be9a2f1ec16ff0bf5dd4f3dcc545b033..25e0af324937adb08cf05b93ab6b6b297364006d 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
@@ -328,4 +328,672 @@ TEST_F(LayoutBoxModelObjectTest, StickyPositionConstraintInvalidation) {
.x());
}
+// Verifies that the correct sticky-box shifting ancestor is found when
+// computing the sticky constraints. Any such ancestor is the first sticky
+// element between you and your containing block (exclusive).
+//
+// In most cases, this pointer should be null since your parent is normally your
+// containing block. However there are cases where this is not true, including
+// inline blocks and tables. The latter is currently irrelevant since only table
+// cells can be sticky in CSS2.1, but we can test the former.
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionFindsCorrectStickyBoxShiftingAncestor) {
+ setBodyInnerHTML(
+ "<style>#stickyOuterDiv { position: sticky; }"
+ "#stickyOuterInline { position: sticky; display: inline; }"
+ "#stickyInnerInline { position: sticky; display: inline; }</style>"
+ "<div id='stickyOuterDiv'><div id='stickyOuterInline'>"
+ "<div id='stickyInnerInline'></div></div></div>");
+
+ // Force the constraints to be calculated.
+ LayoutBoxModelObject* stickyOuterDiv =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuterDiv"));
+ stickyOuterDiv->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyOuterInline =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuterInline"));
+ stickyOuterInline->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyInnerInline =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyInnerInline"));
+ stickyInnerInline->updateStickyPositionConstraints();
+
+ PaintLayerScrollableArea* scrollableArea =
+ stickyOuterDiv->layer()->ancestorOverflowLayer()->getScrollableArea();
+ ASSERT_TRUE(scrollableArea);
+ StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap();
+
+ ASSERT_TRUE(constraintsMap.contains(stickyOuterDiv->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyOuterInline->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyInnerInline->layer()));
+
+ // The outer block element trivially has no sticky-box shifting ancestor.
+ EXPECT_FALSE(constraintsMap.get(stickyOuterDiv->layer())
+ .nearestStickyElementShiftingStickyBox());
+
+ // Neither does the outer inline element, as its parent element is also its
+ // containing block.
+ EXPECT_FALSE(constraintsMap.get(stickyOuterInline->layer())
+ .nearestStickyElementShiftingStickyBox());
+
+ // However the inner inline element does have a sticky-box shifting ancestor,
+ // as its containing block is the ancestor block element, not its parent.
+ EXPECT_EQ(stickyOuterInline, constraintsMap.get(stickyInnerInline->layer())
+ .nearestStickyElementShiftingStickyBox());
+}
+
+// Verifies that the correct containing-block shifting ancestor is found when
+// computing the sticky constraints. Any such ancestor is the first sticky
+// element between your containing block (inclusive) and your ancestor overflow
+// layer (exclusive).
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionFindsCorrectContainingBlockShiftingAncestor) {
+ // We make the scroller itself sticky in order to check that elements do not
+ // detect it as their containing-block shifting ancestor.
+ setBodyInnerHTML(
+ "<style>#scroller { overflow-y: scroll; position: sticky; }"
+ "#stickyParent { position: sticky; }"
+ "#stickyChild { position: sticky; }"
+ "#stickyNestedChild { position: sticky; }</style>"
+ "<div id='scroller'><div id='stickyParent'><div id='stickyChild'></div>"
+ "<div><div id='stickyNestedChild'></div></div></div></div>");
+
+ // Force the constraints to be calculated.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ scroller->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyParent =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent"));
+ stickyParent->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyChild =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild"));
+ stickyChild->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyNestedChild =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyNestedChild"));
+ stickyNestedChild->updateStickyPositionConstraints();
+
+ PaintLayerScrollableArea* scrollableArea =
+ scroller->layer()->getScrollableArea();
+ ASSERT_TRUE(scrollableArea);
+ StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap();
+
+ ASSERT_FALSE(constraintsMap.contains(scroller->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyParent->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyChild->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyNestedChild->layer()));
+
+ // The outer <div> should not detect the scroller as its containing-block
+ // shifting ancestor.
+ EXPECT_FALSE(constraintsMap.get(stickyParent->layer())
+ .nearestStickyElementShiftingContainingBlock());
+
+ // Both inner children should detect the parent <div> as their
+ // containing-block shifting ancestor.
+ EXPECT_EQ(stickyParent, constraintsMap.get(stickyChild->layer())
+ .nearestStickyElementShiftingContainingBlock());
+ EXPECT_EQ(stickyParent, constraintsMap.get(stickyNestedChild->layer())
+ .nearestStickyElementShiftingContainingBlock());
+}
+
+// Verifies that the correct containing-block shifting ancestor is found when
+// computing the sticky constraints, in the case where the overflow ancestor is
+// the page itself. This is a special-case version of the test above, as we
+// often treat the root page as special when it comes to scroll logic. It should
+// not make a difference for containing-block shifting ancestor calculations.
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionFindsCorrectContainingBlockShiftingAncestorRoot) {
+ setBodyInnerHTML(
+ "<style>#stickyParent { position: sticky; }"
+ "#stickyGrandchild { position: sticky; }</style>"
+ "<div id='stickyParent'><div><div id='stickyGrandchild'></div></div>"
+ "</div>");
+
+ // Force the constraints to be calculated.
+
+ LayoutBoxModelObject* stickyParent =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent"));
+ stickyParent->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyGrandchild =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyGrandchild"));
+ stickyGrandchild->updateStickyPositionConstraints();
+
+ PaintLayerScrollableArea* scrollableArea =
+ stickyParent->layer()->ancestorOverflowLayer()->getScrollableArea();
+ ASSERT_TRUE(scrollableArea);
+ StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap();
+
+ ASSERT_TRUE(constraintsMap.contains(stickyParent->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyGrandchild->layer()));
+
+ // The grandchild sticky should detect the parent as its containing-block
+ // shifting ancestor.
+ EXPECT_EQ(stickyParent, constraintsMap.get(stickyGrandchild->layer())
+ .nearestStickyElementShiftingContainingBlock());
+}
+
+// Verifies that the correct containing-block shifting ancestor is found when
+// computing the sticky constraints, in the case of tables. Tables are unusual
+// because the containing block for all table elements is the <table> itself, so
+// we have to skip over elements to find the correct ancestor.
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionFindsCorrectContainingBlockShiftingAncestorTable) {
+ setBodyInnerHTML(
+ "<style>#scroller { overflow-y: scroll; }"
+ "#stickyOuter { position: sticky; }"
+ "#stickyTh { position: sticky; }</style>"
+ "<div id='scroller'><div id='stickyOuter'><table><thead><tr>"
+ "<th id='stickyTh'></th></tr></thead></table></div></div>");
+
+ // Force the constraints to be calculated.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ scroller->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyOuter =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuter"));
+ stickyOuter->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTh =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh"));
+ stickyTh->updateStickyPositionConstraints();
+
+ PaintLayerScrollableArea* scrollableArea =
+ scroller->layer()->getScrollableArea();
+ ASSERT_TRUE(scrollableArea);
+ StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap();
+
+ ASSERT_FALSE(constraintsMap.contains(scroller->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyOuter->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyTh->layer()));
+
+ // The table cell should detect the outer <div> as its containing-block
+ // shifting ancestor.
+ EXPECT_EQ(stickyOuter, constraintsMap.get(stickyTh->layer())
+ .nearestStickyElementShiftingContainingBlock());
+}
+
+// Verifies that the calculated position:sticky offsets are correct when we have
+// a simple case of nested sticky elements.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNested) {
+ setBodyInnerHTML(
+ "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px }"
+ "#stickyParent { position: sticky; top: 0; height: 50px; }"
+ "#stickyChild { position: sticky; top: 0; height: 25px; }"
+ "#postPadding { height: 200px }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>"
+ "<div id='stickyChild'></div></div><div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* stickyParent =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent"));
+ stickyParent->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyChild =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild"));
+ stickyChild->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 100));
+ ASSERT_EQ(100.0, scrollableArea->scrollPosition().y());
+
+ // Both the parent and child sticky divs are attempting to place themselves at
+ // the top of the scrollable area. To achieve this the parent must offset on
+ // the y-axis against its starting position. The child is offset relative to
+ // its parent so should not move at all.
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset());
+
+ stickyParent->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+
+ stickyChild->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct when the
+// child has a larger edge constraint value than the parent.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionChildHasLargerTop) {
+ setBodyInnerHTML(
+ "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px }"
+ "#stickyParent { position: sticky; top: 0; height: 50px; }"
+ "#stickyChild { position: sticky; top: 25px; height: 25px; }"
+ "#postPadding { height: 200px }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>"
+ "<div id='stickyChild'></div></div><div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* stickyParent =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent"));
+ stickyParent->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyChild =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild"));
+ stickyChild->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 100));
+ ASSERT_EQ(100.0, scrollableArea->scrollPosition().y());
+
+ // The parent is attempting to place itself at the top of the scrollable area,
+ // whilst the child is attempting to be 25 pixels from the top. To achieve
+ // this both must offset on the y-axis against their starting positions, but
+ // note the child is offset relative to the parent.
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset());
+
+ stickyParent->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+
+ stickyChild->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct when the
+// child has a smaller edge constraint value than the parent.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionParentHasLargerTop) {
+ setBodyInnerHTML(
+ "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px }"
+ "#stickyParent { position: sticky; top: 25px; height: 50px; }"
+ "#stickyChild { position: sticky; top: 0; height: 25px; }"
+ "#postPadding { height: 200px }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>"
+ "<div id='stickyChild'></div></div><div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* stickyParent =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent"));
+ stickyParent->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyChild =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild"));
+ stickyChild->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 100));
+ ASSERT_EQ(100.0, scrollableArea->scrollPosition().y());
+
+ // The parent is attempting to place itself 25 pixels from the top of the
+ // scrollable area, whilst the child is attempting to be at the top. However,
+ // the child must stay contained within the parent, so it should be pushed
+ // down to the same height. As always, the child offset is relative.
+ EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset());
+
+ stickyParent->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset());
+
+ stickyChild->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct when the
+// child has a large enough edge constraint value to push outside of its parent.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionChildPushingOutsideParent) {
+ setBodyInnerHTML(
+ "<style> #scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px; }"
+ "#stickyParent { position: sticky; top: 0; height: 50px; }"
+ "#stickyChild { position: sticky; top: 50px; height: 25px; }"
+ "#postPadding { height: 200px }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>"
+ "<div id='stickyChild'></div></div><div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* stickyParent =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent"));
+ stickyParent->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyChild =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild"));
+ stickyChild->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 100));
+ ASSERT_EQ(100.0, scrollableArea->scrollPosition().y());
+
+ // The parent is attempting to place itself at the top of the scrollable area,
+ // whilst the child is attempting to be 50 pixels from the top. However, there
+ // is only 25 pixels of space for the child to move into, so it should be
+ // capped by that offset. As always, the child offset is relative.
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset());
+
+ stickyParent->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+
+ stickyChild->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct in the case
+// of triple nesting. Triple (or more) nesting must be tested as the grandchild
+// sticky must correct both its sticky box constraint rect and its containing
+// block constaint rect.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionTripleNestedDiv) {
+ setBodyInnerHTML(
+ "<style>#scroller { height: 200px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px; }"
+ "#outmostSticky { position: sticky; top: 0; height: 100px; }"
+ "#middleSticky { position: sticky; top: 0; height: 75px; }"
+ "#innerSticky { position: sticky; top: 25px; height: 25px; }"
+ "#postPadding { height: 400px }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='outmostSticky'>"
+ "<div id='middleSticky'><div id='innerSticky'></div></div></div>"
+ "<div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* outmostSticky =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("outmostSticky"));
+ outmostSticky->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* middleSticky =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("middleSticky"));
+ middleSticky->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* innerSticky =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("innerSticky"));
+ innerSticky->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 100));
+ ASSERT_EQ(100.0, scrollableArea->scrollPosition().y());
+
+ // The grandparent and parent divs are attempting to place themselves at the
+ // top of the scrollable area. The child div is attempting to place itself at
+ // an offset of 25 pixels to the top of the scrollable area. The result of
+ // this sticky offset calculation is quite simple, but internally the child
+ // offset has to offset both its sticky box constraint rect and its containing
+ // block constraint rect.
+ EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), middleSticky->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), innerSticky->stickyPositionOffset());
+
+ outmostSticky->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset());
+
+ middleSticky->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), middleSticky->stickyPositionOffset());
+
+ innerSticky->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), middleSticky->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), innerSticky->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct in the case
+// of tables. Tables are special as the containing block for table elements is
+// always the root level <table>.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedStickyTable) {
+ setBodyInnerHTML(
+ "<style>table { border-collapse: collapse; }"
+ "td, th { height: 25px; width: 25px; padding: 0; }"
+ "#scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px; }"
+ "#stickyDiv { position: sticky; top: 0; height: 200px; }"
+ "#stickyTh { position: sticky; top: 0; }"
+ "#postPadding { height: 200px; }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='stickyDiv'>"
+ "<table><thead><tr><th id='stickyTh'></th></tr></thead><tbody><tr><td>"
+ "</td></tr><tr><td></td></tr><tr><td></td></tr><tr><td></td></tr></tbody>"
+ "</table></div><div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* stickyDiv =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyDiv"));
+ stickyDiv->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTh =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh"));
+ stickyTh->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 150));
+ ASSERT_EQ(150.0, scrollableArea->scrollPosition().y());
+
+ // All sticky elements are attempting to stick to the top of the scrollable
+ // area. For the root sticky div, this requires an offset. All the other
+ // descendant sticky elements are positioned relatively so don't need offset.
+ EXPECT_EQ(LayoutSize(0, 100), stickyDiv->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset());
+
+ // If we now scroll to the point where the overall sticky div starts to move,
+ // the table headers should continue to stick to the top of the scrollable
+ // area until they run out of <table> space to move in.
+
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 275));
+ ASSERT_EQ(275.0, scrollableArea->scrollPosition().y());
+
+ EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), stickyTh->stickyPositionOffset());
+
+ // Finally, if we scroll so that the table is off the top of the page, the
+ // sticky header should travel as far as it can (i.e. the table height) then
+ // move off the top with it.
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 350));
+ ASSERT_EQ(350.0, scrollableArea->scrollPosition().y());
+
+ EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 100), stickyTh->stickyPositionOffset());
+
+ stickyDiv->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset());
+
+ stickyTh->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 100), stickyTh->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct in the case
+// where a particular position:sticky element is used both as a sticky-box
+// shifting ancestor as well as a containing-block shifting ancestor.
+//
+// This is a rare case that can be replicated by nesting tables so that a sticky
+// cell contains another table that has sticky elements. See the HTML below.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionComplexTableNesting) {
+ setBodyInnerHTML(
+ "<style>table { border-collapse: collapse; }"
+ "td, th { height: 25px; width: 25px; padding: 0; }"
+ "#scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px; }"
+ "#outerStickyTh { height: 50px; position: sticky; top: 0; }"
+ "#innerStickyTh { position: sticky; top: 25px; }"
+ "#postPadding { height: 200px; }</style>"
+ "<div id='scroller'><div id='prePadding'></div>"
+ "<table><thead><tr><th id='outerStickyTh'><table><thead><tr>"
+ "<th id='innerStickyTh'></th></tr></thead><tbody><tr><td></td></tr>"
+ "</tbody></table></th></tr></thead><tbody><tr><td></td></tr><tr><td></td>"
+ "</tr><tr><td></td></tr><tr><td></td></tr></tbody></table>"
+ "<div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* outerStickyTh =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyTh"));
+ outerStickyTh->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* innerStickyTh =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("innerStickyTh"));
+ innerStickyTh->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 150));
+ ASSERT_EQ(150.0, scrollableArea->scrollPosition().y());
+
+ EXPECT_EQ(LayoutSize(0, 100), outerStickyTh->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), innerStickyTh->stickyPositionOffset());
+
+ outerStickyTh->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 100), outerStickyTh->stickyPositionOffset());
+
+ innerStickyTh->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 100), outerStickyTh->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), innerStickyTh->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct in the case
+// of nested inline elements.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedInlineElements) {
+ setBodyInnerHTML(
+ "<style>#scroller { width: 100px; height: 100px; overflow-y: scroll; }"
+ "#paddingBefore { height: 50px; }"
+ "#outerInline { display: inline; position: sticky; top: 0; }"
+ "#innerInline { display: inline; position: sticky; top: 25px; }"
+ "#paddingAfter { height: 200px; }</style>"
+ "<div id='scroller'><div id='paddingBefore'></div><div id='outerInline'>"
+ "<div id='innerInline'></div></div><div id='paddingAfter'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* outerInline =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("outerInline"));
+ outerInline->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* innerInline =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("innerInline"));
+ innerInline->updateStickyPositionConstraints();
+
+ // Scroll the page down.
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 50));
+ ASSERT_EQ(50.0, scrollableArea->scrollPosition().y());
+
+ EXPECT_EQ(LayoutSize(0, 0), outerInline->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), innerInline->stickyPositionOffset());
+
+ outerInline->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 0), outerInline->stickyPositionOffset());
+
+ innerInline->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 0), outerInline->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), innerInline->stickyPositionOffset());
+}
+
+// Verifies that the calculated position:sticky offsets are correct in the case
+// of an intermediate position:fixed element.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedFixedPos) {
+ setBodyInnerHTML(
+ "<style>body { margin: 0; }"
+ "#scroller { height: 200px; width: 100px; overflow-y: auto; }"
+ "#outerSticky { position: sticky; top: 0; height: 50px; }"
+ "#fixedDiv { position: fixed; top: 0; left: 300px; height: 100px; "
+ "width: 100px; }"
+ "#innerSticky { position: sticky; top: 25px; height: 25px; }"
+ "#padding { height: 400px }</style>"
+ "<div id='scroller'><div id='outerSticky'><div id='fixedDiv'>"
+ "<div id='innerSticky'></div></div></div><div id='padding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling or they may be
+ // calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* outerSticky =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("outerSticky"));
+ outerSticky->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* innerSticky =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("innerSticky"));
+ innerSticky->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* scroller =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+
+ StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap();
+ ASSERT_TRUE(constraintsMap.contains(outerSticky->layer()));
+ ASSERT_TRUE(constraintsMap.contains(innerSticky->layer()));
+
+ // The inner sticky should not detect the outer one as any sort of ancestor.
+ EXPECT_FALSE(constraintsMap.get(innerSticky->layer())
+ .nearestStickyElementShiftingStickyBox());
+ EXPECT_FALSE(constraintsMap.get(innerSticky->layer())
+ .nearestStickyElementShiftingContainingBlock());
+
+ // Scroll the page down.
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 100));
+ ASSERT_EQ(100.0, scrollableArea->scrollPosition().y());
+
+ // TODO(smcgruer): Until http://crbug.com/686164 is fixed, we need to update
+ // the constraints here before calculations will be correct.
+ innerSticky->updateStickyPositionConstraints();
+
+ EXPECT_EQ(LayoutSize(0, 100), outerSticky->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), innerSticky->stickyPositionOffset());
+}
+
} // namespace blink
« no previous file with comments | « third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp ('k') | third_party/WebKit/Source/core/layout/LayoutObject.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698