as its containing-block
// shifting ancestor.
EXPECT_EQ(sticky_outer, constraints_map.at(sticky_th)
.NearestStickyLayerShiftingContainingBlock());
}
// Verifies that the calculated position:sticky offsets are correct when we have
// a simple case of nested sticky elements.
TEST_F(LayoutBoxModelObjectTest, StickyPositionNested) {
SetBodyInnerHTML(
""
"
");
LayoutBoxModelObject* sticky_parent =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyParent"));
LayoutBoxModelObject* sticky_child =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyChild"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 100));
ASSERT_EQ(100.0, scrollable_area->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), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), sticky_child->StickyPositionOffset());
sticky_parent->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), sticky_parent->StickyPositionOffset());
sticky_child->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), sticky_child->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(
""
"
");
LayoutBoxModelObject* sticky_parent =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyParent"));
LayoutBoxModelObject* sticky_child =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyChild"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 100));
ASSERT_EQ(100.0, scrollable_area->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), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), sticky_child->StickyPositionOffset());
sticky_parent->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), sticky_parent->StickyPositionOffset());
sticky_child->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), sticky_child->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(
""
"
");
LayoutBoxModelObject* sticky_parent =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyParent"));
LayoutBoxModelObject* sticky_child =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyChild"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 100));
ASSERT_EQ(100.0, scrollable_area->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), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), sticky_child->StickyPositionOffset());
sticky_parent->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 75), sticky_parent->StickyPositionOffset());
sticky_child->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 75), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), sticky_child->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(
""
"
");
LayoutBoxModelObject* sticky_parent =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyParent"));
LayoutBoxModelObject* sticky_child =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyChild"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 100));
ASSERT_EQ(100.0, scrollable_area->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), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), sticky_child->StickyPositionOffset());
sticky_parent->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), sticky_parent->StickyPositionOffset());
sticky_child->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), sticky_parent->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), sticky_child->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(
""
"
");
LayoutBoxModelObject* outmost_sticky =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("outmostSticky"));
LayoutBoxModelObject* middle_sticky =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("middleSticky"));
LayoutBoxModelObject* inner_sticky =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("innerSticky"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 100));
ASSERT_EQ(100.0, scrollable_area->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), outmost_sticky->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), middle_sticky->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), inner_sticky->StickyPositionOffset());
outmost_sticky->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), outmost_sticky->StickyPositionOffset());
middle_sticky->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), outmost_sticky->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), middle_sticky->StickyPositionOffset());
inner_sticky->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 50), outmost_sticky->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), middle_sticky->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), inner_sticky->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
.
TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedStickyTable) {
SetBodyInnerHTML(
""
"");
LayoutBoxModelObject* sticky_div =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyDiv"));
LayoutBoxModelObject* sticky_th =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("stickyTh"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 150));
ASSERT_EQ(150.0, scrollable_area->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), sticky_div->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 0), sticky_th->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 space to move in.
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 275));
ASSERT_EQ(275.0, scrollable_area->ScrollPosition().Y());
EXPECT_EQ(LayoutSize(0, 200), sticky_div->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), sticky_th->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.
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 350));
ASSERT_EQ(350.0, scrollable_area->ScrollPosition().Y());
EXPECT_EQ(LayoutSize(0, 200), sticky_div->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 100), sticky_th->StickyPositionOffset());
sticky_div->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 200), sticky_div->StickyPositionOffset());
sticky_th->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 200), sticky_div->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 100), sticky_th->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(
""
"");
LayoutBoxModelObject* outer_sticky_th =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("outerStickyTh"));
LayoutBoxModelObject* inner_sticky_th =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("innerStickyTh"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 150));
ASSERT_EQ(150.0, scrollable_area->ScrollPosition().Y());
EXPECT_EQ(LayoutSize(0, 100), outer_sticky_th->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), inner_sticky_th->StickyPositionOffset());
outer_sticky_th->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 100), outer_sticky_th->StickyPositionOffset());
inner_sticky_th->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 100), outer_sticky_th->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), inner_sticky_th->StickyPositionOffset());
}
// Verifies that the calculated position:sticky offsets are correct in the case
// of nested inline elements.
TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedInlineElements) {
SetBodyInnerHTML(
""
"");
LayoutBoxModelObject* outer_inline =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("outerInline"));
LayoutBoxModelObject* inner_inline =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("innerInline"));
// Scroll the page down.
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 50));
ASSERT_EQ(50.0, scrollable_area->ScrollPosition().Y());
EXPECT_EQ(LayoutSize(0, 0), outer_inline->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), inner_inline->StickyPositionOffset());
}
// Verifies that the calculated position:sticky offsets are correct in the case
// of an intermediate position:fixed element.
TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedFixedPos) {
SetBodyInnerHTML(
""
"");
LayoutBoxModelObject* outer_sticky =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("outerSticky"));
LayoutBoxModelObject* inner_sticky =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("innerSticky"));
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
StickyConstraintsMap constraints_map =
scrollable_area->GetStickyConstraintsMap();
ASSERT_TRUE(constraints_map.Contains(outer_sticky->Layer()));
ASSERT_TRUE(constraints_map.Contains(inner_sticky->Layer()));
// The inner sticky should not detect the outer one as any sort of ancestor.
EXPECT_FALSE(constraints_map.at(inner_sticky->Layer())
.NearestStickyLayerShiftingStickyBox());
EXPECT_FALSE(constraints_map.at(inner_sticky->Layer())
.NearestStickyLayerShiftingContainingBlock());
// Scroll the page down.
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 100));
ASSERT_EQ(100.0, scrollable_area->ScrollPosition().Y());
// TODO(smcgruer): Until http://crbug.com/686164 is fixed, we need to update
// the constraints here before calculations will be correct.
inner_sticky->UpdateStickyPositionConstraints();
EXPECT_EQ(LayoutSize(0, 100), outer_sticky->StickyPositionOffset());
EXPECT_EQ(LayoutSize(0, 25), inner_sticky->StickyPositionOffset());
}
TEST_F(LayoutBoxModelObjectTest, NoCrashStackingContextChangeNonRooted) {
SetBodyInnerHTML("");
auto& object = *GetLayoutObjectByElementId("target");
auto* parent = object.Parent();
object.SetDangerousOneWayParent(nullptr);
EXPECT_FALSE(object.IsRooted());
auto style = ComputedStyle::Create();
style->SetIsStackingContext(true);
object.SetStyle(style); // This should not crash.
object.SetDangerousOneWayParent(parent);
}
TEST_F(LayoutBoxModelObjectTest, InvalidatePaintLayerOnStackedChange) {
SetBodyInnerHTML(
""
"");
auto* target_element = GetDocument().getElementById("target");
auto* target = target_element->GetLayoutBoxModelObject();
auto* parent = target->Parent();
auto* original_compositing_container =
target->Layer()->CompositingContainer();
EXPECT_FALSE(target->StyleRef().IsStackingContext());
EXPECT_TRUE(target->StyleRef().IsStacked());
EXPECT_FALSE(parent->StyleRef().IsStacked());
EXPECT_NE(parent, original_compositing_container->GetLayoutObject());
target_element->setAttribute(HTMLNames::classAttr, "non-stacked");
GetDocument().View()->UpdateLifecycleToLayoutClean();
EXPECT_FALSE(target->StyleRef().IsStacked());
EXPECT_TRUE(target->Layer()->NeedsRepaint());
EXPECT_TRUE(original_compositing_container->NeedsRepaint());
auto* new_compositing_container = target->Layer()->CompositingContainer();
EXPECT_EQ(parent, new_compositing_container->GetLayoutObject());
GetDocument().View()->UpdateAllLifecyclePhases();
target_element->setAttribute(HTMLNames::classAttr, "stacked");
GetDocument().View()->UpdateLifecycleToLayoutClean();
EXPECT_TRUE(target->StyleRef().IsStacked());
EXPECT_TRUE(target->Layer()->NeedsRepaint());
EXPECT_TRUE(new_compositing_container->NeedsRepaint());
EXPECT_EQ(original_compositing_container,
target->Layer()->CompositingContainer());
}
// Tests that when a sticky object is removed from the root scroller it
// correctly clears its viewport constrained position: https://crbug.com/755307.
TEST_F(LayoutBoxModelObjectTest, StickyRemovedFromRootScrollableArea) {
SetBodyInnerHTML(
""
"");
LayoutBoxModelObject* sticky =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("sticky"));
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
// The 'scroller' starts as non-overflow, so the sticky element's ancestor
// overflow layer should be the outer scroller.
EXPECT_TRUE(sticky->Layer()->AncestorOverflowLayer()->IsRootLayer());
// We need the sticky element to not be a PaintLayer child of the scroller,
// so that it is later reparented under the scroller's PaintLayer
EXPECT_FALSE(scroller->Layer());
// Now make the scroller into an actual scroller. This will reparent the
// sticky element to be a child of the scroller, and will set its previous
// overflow layer to nullptr.
ToElement(scroller->GetNode())
->SetInlineStyleProperty(CSSPropertyOverflow, "scroll");
GetDocument().View()->UpdateAllLifecyclePhases();
// The sticky element should no longer be viewport constrained.
EXPECT_FALSE(GetDocument().View()->HasViewportConstrainedObjects());
// Making the scroller have visible overflow but still have a PaintLayer
// (in this case by making it position: relative) will cause us to need to
// recompute the sticky element's ancestor overflow layer.
ToElement(scroller->GetNode())
->SetInlineStyleProperty(CSSPropertyPosition, "relative");
ToElement(scroller->GetNode())
->SetInlineStyleProperty(CSSPropertyOverflow, "visible");
// Now try to scroll to the sticky element, this used to crash.
GetDocument().GetFrame()->DomWindow()->scrollTo(0, 500);
}
TEST_F(LayoutBoxModelObjectTest, BackfaceVisibilityChange) {
// TODO(wangxianzhu): Change this to V175.
ScopedSlimmingPaintV2ForTest spv2(true);
AtomicString base_style =
"width: 100px; height: 100px; background: blue; position: absolute";
SetBodyInnerHTML("");
auto* target = GetDocument().getElementById("target");
auto* target_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
ASSERT_NE(nullptr, target_layer);
EXPECT_FALSE(target_layer->NeedsRepaint());
target->setAttribute(HTMLNames::styleAttr,
base_style + "; backface-visibility: hidden");
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint();
EXPECT_TRUE(target_layer->NeedsRepaint());
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(target_layer->NeedsRepaint());
target->setAttribute(HTMLNames::styleAttr, base_style);
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint();
EXPECT_TRUE(target_layer->NeedsRepaint());
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(target_layer->NeedsRepaint());
}
} // namespace blink