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

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

Issue 2636253002: Handle nested position:sticky elements (Closed)
Patch Set: Offset rects correctly and accumulate correctly 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..b099587c37d2ca64e487ac25bc423515b533255e 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
@@ -249,4 +249,559 @@ TEST_F(LayoutBoxModelObjectTest, StickyPositionAnonymousContainer) {
IntRect(15, 165, 100, 100),
enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints)));
}
+
+// Verifies that the correct "to containing block" sticky ancestor is found when
+// computing the sticky constraints.
+//
+// In most cases, this pointer should be null since your parent() is normally
+// your containingBlock. However there are a few cases where this is not true,
+// most notably in tables where the root <table> is the containingBlock for many
+// of the descendant elements.
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionFindsCorrectToContainingBlockStickyAncestor) {
+ setBodyInnerHTML(
+ "<style>#stickyOuterDiv { position: sticky; }"
+ "#stickyInnerDiv { position: sticky; }"
+ "#stickyThead { position: sticky; }"
+ "#stickyTr { position: sticky; }"
+ "#stickyTh { position: sticky; }</style>"
+ "<div id='stickyOuterDiv'><div id='stickyInnerDiv'></div><table>"
+ "<thead id='stickyThead'><tr id='stickyTr'><th id='stickyTh'></th></tr>"
+ "</thead></table></div>");
+
+ // Force the constraints to be calculated.
+ LayoutBoxModelObject* stickyOuterDiv =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuterDiv"));
+ stickyOuterDiv->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyInnerDiv =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyInnerDiv"));
+ stickyInnerDiv->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyThead =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyThead"));
+ stickyThead->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTr =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTr"));
+ stickyTr->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTh =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh"));
+ stickyTh->updateStickyPositionConstraints();
+
+ // There's only one scrollableArea, the main page.
+ PaintLayerScrollableArea* scrollableArea =
+ stickyOuterDiv->layer()->ancestorOverflowLayer()->getScrollableArea();
+ ASSERT_TRUE(scrollableArea);
+ StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap();
+
+ ASSERT_TRUE(constraintsMap.contains(stickyOuterDiv->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyInnerDiv->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyThead->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyTr->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyTh->layer()));
+
+ // The outer <div> trivially has no ancestor.
+ EXPECT_FALSE(constraintsMap.get(stickyOuterDiv->layer())
+ .nearestStickyElementToContainingBlock());
+
+ // Neither does the inner <div>, as the "to containing block" range is
+ // exclusive of the containing block.
+ EXPECT_FALSE(constraintsMap.get(stickyInnerDiv->layer())
+ .nearestStickyElementToContainingBlock());
+
+ // All of the table elements containing block is the <table> itself, so the
+ // <tr> and <th> both have ancestors.
+ EXPECT_FALSE(constraintsMap.get(stickyThead->layer())
+ .nearestStickyElementToContainingBlock());
+ EXPECT_EQ(stickyThead, constraintsMap.get(stickyTr->layer())
+ .nearestStickyElementToContainingBlock());
+ EXPECT_EQ(stickyTr, constraintsMap.get(stickyTh->layer())
+ .nearestStickyElementToContainingBlock());
+}
+
+// Verifies that the correct "to scroll container" sticky ancestor is found when
+// computing the sticky constraints.
+//
+// The "to scroll container" ancestor is the nearest sticky ancestor between our
+// containing block (inclusive) and our scroll container (exclusive).
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionFindsCorrectToScrollContainerStickyAncestor) {
+ // We make the scroller itself sticky in order to check that elements do not
+ // detect it as their sticky 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();
+
+ // There are two scrollable areas, but we only care about the scroller itself.
+ 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 count the scroller as its ancestor.
+ EXPECT_FALSE(constraintsMap.get(stickyParent->layer())
+ .nearestStickyElementFromContainingBlockToScrollContainer());
+
+ // Both inner children should count the parent <div> as their ancestor.
+ EXPECT_EQ(stickyParent,
+ constraintsMap.get(stickyChild->layer())
+ .nearestStickyElementFromContainingBlockToScrollContainer());
+ EXPECT_EQ(stickyParent,
+ constraintsMap.get(stickyNestedChild->layer())
+ .nearestStickyElementFromContainingBlockToScrollContainer());
+}
+
+// Verifies that the correct "to scroll container" sticky ancestor is found when
+// computing the sticky constraints in the case of tables.
+//
+// Tables are unusual because the containingBlock for all table elements is the
+// <table> itself, so we have to skip over elements to find the "to scroll
+// container" stick ancestor.
+TEST_F(LayoutBoxModelObjectTest,
+ StickyPositionFindsCorrectToScrollContainerStickyAncestorTable) {
+ setBodyInnerHTML(
+ "<style>#scroller { overflow-y: scroll; }"
+ "#stickyOuter { position: sticky; }"
+ "#stickyThead { position: sticky; }"
+ "#stickyTr { position: sticky; }"
+ "#stickyTh { position: sticky; }</style>"
+ "<div id='scroller'><div id='stickyOuter'><table><thead id='stickyThead'>"
+ "<tr id='stickyTr'><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* stickyThead =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyThead"));
+ stickyThead->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTr =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTr"));
+ stickyTr->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* stickyTh =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh"));
+ stickyTh->updateStickyPositionConstraints();
+
+ // There are two scrollable areas, but we only care about the scroller itself.
+ 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(stickyThead->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyTr->layer()));
+ ASSERT_TRUE(constraintsMap.contains(stickyTh->layer()));
+
+ // All of the table elements should point to the outer <div> as their
+ // "to scroll container" sticky ancestor.
+ EXPECT_EQ(stickyOuter,
+ constraintsMap.get(stickyThead->layer())
+ .nearestStickyElementFromContainingBlockToScrollContainer());
+ EXPECT_EQ(stickyOuter,
+ constraintsMap.get(stickyTr->layer())
+ .nearestStickyElementFromContainingBlockToScrollContainer());
+ EXPECT_EQ(stickyOuter,
+ constraintsMap.get(stickyTh->layer())
+ .nearestStickyElementFromContainingBlockToScrollContainer());
+}
+
+// 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());
+}
+
+// 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());
+}
+
+// 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());
+}
+
+// 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());
+}
+
+// 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 be corrected for both the containing block and the viewport.
+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 against its containing block and against its
+ // viewport ancestor (the grandparent).
+ 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 all child elements
+// is always the root level <table>.
+TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedStickyInTable) {
+ 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: 0; }"
+ "#stickyTr { position: sticky; top: 0; }"
+ "#stickyTh { position: sticky; top: 0; }"
+ "#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>");
+
+ // 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* 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());
+
+ // TODO(smcgruer): I'm not sure that 12 is correct. My hankerchief math gets
+ // me something like (275 - 50 - 200) = 25? But it renders correctly.
+ 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): Again I have no idea why this is correct. But it does
+ // render correctly.
+ 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());
+}
+
+// Verifies that the calculated position:sticky offsets are correct in the case
+// where a particular position:sticky element is used both as a "to containing
+// block" ancestor as well as a "to scroll container" 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>#scroller { height: 100px; width: 100px; overflow-y: auto; }"
+ "#prePadding { height: 50px; }"
+ "#outerStickyThead { position: sticky; top: 0; }"
+ "#outerStickyTr { position: sticky; top: 0; }"
+ "#outerStickyTh { position: sticky; top: 0; }"
+ "#innerStickyThead { position: sticky; top: 25px; }"
+ "#innerStickyTr { position: sticky; top: 25px; }"
+ "#innerStickyTh { position: sticky; top: 25px; }"
+ "#postPadding { height: 200px; }"
+ "table { border-collapse: collapse; }</style>"
+ "<div id='scroller'><div id='prePadding'></div>"
+ "<table><thead id='outerStickyThead'><tr id='outerStickyTr'>"
+ "<th id='outerStickyTh'><table><thead id='innerStickyThead'>"
+ "<tr id='innerStickyTr'><th id='innerStickyTh'>Nested</th></tr></thead>"
+ "</table></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 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* outerStickyThead =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyThead"));
+ outerStickyThead->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* outerStickyTr =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyTr"));
+ outerStickyTr->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* outerStickyTh =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyTh"));
+ outerStickyTh->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* innerStickyThead =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("innerStickyThead"));
+ innerStickyThead->updateStickyPositionConstraints();
+
+ LayoutBoxModelObject* innerStickyTr =
+ toLayoutBoxModelObject(getLayoutObjectByElementId("innerStickyTr"));
+ innerStickyTr->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());
+
+ // TODO(smcgruer): I have no idea again where these numbers come from, but it
+ // renders correctly!
+ EXPECT_EQ(LayoutSize(0, 12), outerStickyThead->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), outerStickyTr->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), outerStickyTh->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), innerStickyThead->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), innerStickyTr->stickyPositionOffset());
+ EXPECT_EQ(LayoutSize(0, 0), innerStickyTh->stickyPositionOffset());
+
+ // TODO(smcgruer): More scrolling.
+}
+
} // namespace blink

Powered by Google App Engine
This is Rietveld 408576698