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

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

Issue 2636253002: Handle nested position:sticky elements (Closed)
Patch Set: Created 3 years, 11 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 9fbdbbdc0334a6846b9c92221a2d739b6697cc32..f29810a3c90ae406eb4c6d4ab6d22dd2cbfe4616 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
@@ -249,4 +249,296 @@ TEST_F(LayoutBoxModelObjectTest, StickyPositionAnonymousContainer) {
IntRect(15, 165, 100, 100),
enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints)));
}
+
+// Verifies that the sticky constraints are correct when we have a simple case
+// of nested position:sticky.
+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 will
+ // simply 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 the child should not move at all.
+
+ EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset());
+}
+
+// Verifies that the sticky constraints are correct when the child has a
+// larger edge constraint value than the parent.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedChildLargerTop) {
+ 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 will
+ // simply 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());
+}
+
+// Verifies that the sticky constraints are correct when the child has a
+// smaller edge constraint value than the parent.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedParentLargerTop) {
+ 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 will
+ // simply 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());
+}
+
+// Verifies that the sticky constraints are correct when the child has a large
+// enough edge constraint value to try and push outside of its parent.
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionNestedChildPushingOutsideParent) {
+ 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 will
+ // simply 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());
+}
+
+// Verifies that the sticky constraints are correct in the case of triple
+// nesting. Triple (or more) nesting must be tested as the grandchild sticky
+// offset must be corrected for both the containing block and the viewport.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedTripleNestedDiv) {
+ setBodyInnerHTML(
+ "<style>#scroller { height: 200px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px; }"
+ "#sticky1 { position: sticky; top: 0; height: 100px; }"
+ "#sticky2 { position: sticky; top: 0; height: 75px; }"
+ "#sticky3 { position: sticky; top: 25px; height: 25px; }"
+ "#postPadding { height: 400px }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='sticky1'>"
+ "<div id='sticky2'><div id='sticky3'></div></div></div>"
+ "<div id='postPadding'></div></div>");
+
+ // Make sure that the constraints are cached before scrolling, or they will
+ // simply be calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* stickyOne =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("sticky1"));
+ stickyOne->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTwo =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("sticky2"));
+ stickyTwo->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyThree =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("sticky3"));
+ stickyThree->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 against its containing block and against its
+ // viewport ancestor (the grandparent).
+ EXPECT_EQ(LayoutSize(0, 50), stickyOne->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyTwo->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 25), stickyThree->stickyPositionOffset());
+}
+
+// Verifies that the sticky constraints are correct in the case of tables.
+// Tables are special as the containing block for all child elements is always
+// the root level <table>.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedTableExample) {
+ setBodyInnerHTML(
+ "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px; }"
+ "#stickyDiv { position: sticky; top: 0; height: 200px; }"
+ "#table { border-collapse: collapse; }"
+ "#stickyThead { position: sticky; top: 0px; }"
+ "#stickyTr { position: sticky; top: 0px; }"
+ "#stickyTh { position: sticky; top: 0px; }"
+ "#postPadding { height: 200px; }</style>"
+ "<div id='scroller'><div id='prePadding'></div><div id='stickyDiv'>"
+ "<table id='table'><thead id='stickyThead'><tr id='stickyTr'>"
+ "<th id='stickyTh'>Header</th></tr></thread><tbody><tr><td>0</td></tr>"
+ "<tr><td>1</td></tr><tr><td>2</td></tr><tr><td>3</td></tr></tbody>"
+ "</table></div><div id='postPadding'></div></div>");
+
+ LOG(INFO) << document().body()->innerHTML();
+
+ // Make sure that the constraints are cached before scrolling, or they will
+ // simply be calculated post-scroll and thus not require offset correction.
+
+ LayoutBoxModelObject* stickyDiv =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyDiv"));
+ stickyDiv->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyThead =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyThead"));
+ stickyThead->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTr =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTr"));
+ stickyTr->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), stickyThead->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyTr->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());
+
+ // NOTE(smcgruer): Currently fails - the <th> also moves 12
+ // NOTE(smcgruer): I'm not sure that 12 is correct. My hankerchief math gets
+ // me something like (275 - 50 - 200) = 25? But it renders correct aisde from
+ // the <th>...
+ EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 12), stickyThead->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyTr->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset());
+
+ // Finally, if we scroll so that the table is off the top of the page, the
+ // sticky header should go off the top with it.
+
+ scrollableArea->scrollToAbsolutePosition(
+ FloatPoint(scrollableArea->scrollPosition().x(), 350));
+ ASSERT_EQ(350.0, scrollableArea->scrollPosition().y());
+
+ // TODO(smcgruer): Do EXPECT_EQ for this scenario. It's currently broken
+ // anyway.
+}
+
} // namespace blink

Powered by Google App Engine
This is Rietveld 408576698