Commit 957cd8a0 authored by Aleks Totic's avatar Aleks Totic Committed by Commit Bot

[TablesNG] Sticky position

Legacy used Table as a sticky container for TD.

Bring TablesNG in sync with legacy, and make
table rows also use table as a sticky container.

Standard not settled yet, discussion at:
https://github.com/w3c/csswg-drafts/issues/5020

Makes 2 layout tests pass:

external/wpt/css/css-position/sticky/position-sticky-table-th-bottom.html
external/wpt/css/css-position/sticky/position-sticky-table-th-top.html

Makes 3 unit tests pass
LayoutBoxModelObjectTest.StickyPositionComplexTableNesting (layout_box_model_object_test.cc:886)
LayoutBoxModelObjectTest.StickyPositionNestedStickyTable (layout_box_model_object_test.cc:818)
LayoutBoxModelObjectTest.StickyPositionTableContainers (layout_box_model_object_test.cc:349)

Bug: 958381
Change-Id: If099efe32a6a23154b53bf96981f7176030cd52c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2542737Reviewed-by: default avatarMorten Stenshorne <mstensho@chromium.org>
Commit-Queue: Aleks Totic <atotic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#828994}
parent 56dea612
......@@ -70,7 +70,7 @@ PaintLayer* FindFirstStickyBetween(LayoutObject* from, LayoutObject* to) {
maybe_sticky_ancestor =
maybe_sticky_ancestor->IsLayoutInline()
? maybe_sticky_ancestor->Container()
: To<LayoutBox>(maybe_sticky_ancestor)->LocationContainer();
: To<LayoutBox>(maybe_sticky_ancestor)->StickyContainer();
}
return nullptr;
}
......@@ -942,6 +942,10 @@ PhysicalOffset LayoutBoxModelObject::RelativePositionOffset() const {
return offset;
}
LayoutBlock* LayoutBoxModelObject::StickyContainer() const {
return ContainingBlock();
}
void LayoutBoxModelObject::UpdateStickyPositionConstraints() const {
NOT_DESTROYED();
DCHECK(StyleRef().HasStickyConstrainedPosition());
......@@ -950,13 +954,13 @@ void LayoutBoxModelObject::UpdateStickyPositionConstraints() const {
StickyPositionScrollingConstraints constraints;
PhysicalOffset skipped_containers_offset;
LayoutBlock* containing_block = ContainingBlock();
LayoutBlock* sticky_container = StickyContainer();
// The location container for boxes is not always the containing block.
LayoutObject* location_container =
IsLayoutInline() ? Container() : To<LayoutBox>(this)->LocationContainer();
// Skip anonymous containing blocks.
while (containing_block->IsAnonymous()) {
containing_block = containing_block->ContainingBlock();
while (sticky_container->IsAnonymous()) {
sticky_container = sticky_container->ContainingBlock();
}
// The sticky position constraint rects should be independent of the current
......@@ -966,29 +970,29 @@ void LayoutBoxModelObject::UpdateStickyPositionConstraints() const {
MapCoordinatesFlags flags =
kIgnoreTransforms | kIgnoreScrollOffset | kIgnoreStickyOffset;
skipped_containers_offset = location_container->LocalToAncestorPoint(
PhysicalOffset(), containing_block, flags);
PhysicalOffset(), sticky_container, flags);
auto& scroll_ancestor =
To<LayoutBox>(Layer()->AncestorScrollContainerLayer()->GetLayoutObject());
LayoutUnit max_container_width =
IsA<LayoutView>(containing_block)
? containing_block->LogicalWidth()
: containing_block->ContainingBlockLogicalWidthForContent();
IsA<LayoutView>(sticky_container)
? sticky_container->LogicalWidth()
: sticky_container->ContainingBlockLogicalWidthForContent();
// Sticky positioned element ignore any override logical width on the
// containing block, as they don't call containingBlockLogicalWidthForContent.
// It's unclear whether this is totally fine.
// Compute the container-relative area within which the sticky element is
// allowed to move.
LayoutUnit max_width = containing_block->AvailableLogicalWidth();
LayoutUnit max_width = sticky_container->AvailableLogicalWidth();
// Map the containing block to the inner corner of the scroll ancestor without
// transforms.
PhysicalRect scroll_container_relative_padding_box_rect(
containing_block->LayoutOverflowRect());
if (containing_block != &scroll_ancestor) {
PhysicalRect local_rect = containing_block->PhysicalPaddingBoxRect();
sticky_container->LayoutOverflowRect());
if (sticky_container != &scroll_ancestor) {
PhysicalRect local_rect = sticky_container->PhysicalPaddingBoxRect();
scroll_container_relative_padding_box_rect =
containing_block->LocalToAncestorRect(local_rect, &scroll_ancestor,
sticky_container->LocalToAncestorRect(local_rect, &scroll_ancestor,
flags);
}
......@@ -1007,16 +1011,16 @@ void LayoutBoxModelObject::UpdateStickyPositionConstraints() const {
// It is an open issue whether the margin should collapse.
// See https://www.w3.org/TR/css-position-3/#sticky-pos
scroll_container_relative_containing_block_rect.ContractEdges(
MinimumValueForLength(containing_block->StyleRef().PaddingTop(),
MinimumValueForLength(sticky_container->StyleRef().PaddingTop(),
max_container_width) +
MinimumValueForLength(StyleRef().MarginTop(), max_width),
MinimumValueForLength(containing_block->StyleRef().PaddingRight(),
MinimumValueForLength(sticky_container->StyleRef().PaddingRight(),
max_container_width) +
MinimumValueForLength(StyleRef().MarginRight(), max_width),
MinimumValueForLength(containing_block->StyleRef().PaddingBottom(),
MinimumValueForLength(sticky_container->StyleRef().PaddingBottom(),
max_container_width) +
MinimumValueForLength(StyleRef().MarginBottom(), max_width),
MinimumValueForLength(containing_block->StyleRef().PaddingLeft(),
MinimumValueForLength(sticky_container->StyleRef().PaddingLeft(),
max_container_width) +
MinimumValueForLength(StyleRef().MarginLeft(), max_width));
......@@ -1028,7 +1032,7 @@ void LayoutBoxModelObject::UpdateStickyPositionConstraints() const {
sticky_box_rect = To<LayoutInline>(this)->PhysicalLinesBoundingBox();
} else {
sticky_box_rect =
containing_block->FlipForWritingMode(To<LayoutBox>(this)->FrameRect());
sticky_container->FlipForWritingMode(To<LayoutBox>(this)->FrameRect());
}
PhysicalOffset sticky_location =
sticky_box_rect.offset + skipped_containers_offset;
......@@ -1038,8 +1042,8 @@ void LayoutBoxModelObject::UpdateStickyPositionConstraints() const {
// within the scroll ancestor if the container is not our scroll ancestor. If
// the container is our scroll ancestor, we also need to remove the border
// box because we want the position from within the scroller border.
PhysicalOffset container_border_offset(containing_block->BorderLeft(),
containing_block->BorderTop());
PhysicalOffset container_border_offset(sticky_container->BorderLeft(),
sticky_container->BorderTop());
sticky_location -= container_border_offset;
constraints.scroll_container_relative_sticky_box_rect = PhysicalRect(
scroll_container_relative_padding_box_rect.offset + sticky_location,
......@@ -1052,12 +1056,12 @@ void LayoutBoxModelObject::UpdateStickyPositionConstraints() const {
// The respective search ranges are [container, containingBlock) and
// [containingBlock, scrollAncestor).
constraints.nearest_sticky_layer_shifting_sticky_box =
FindFirstStickyBetween(location_container, containing_block);
FindFirstStickyBetween(location_container, sticky_container);
// We cannot use |scrollAncestor| here as it disregards the root
// ancestorOverflowLayer(), which we should include.
constraints.nearest_sticky_layer_shifting_containing_block =
FindFirstStickyBetween(
containing_block,
sticky_container,
&Layer()->AncestorScrollContainerLayer()->GetLayoutObject());
// We skip the right or top sticky offset if there is not enough space to
......
......@@ -150,6 +150,7 @@ class CORE_EXPORT LayoutBoxModelObject : public LayoutObject {
void UpdateStickyPositionConstraints() const;
PhysicalOffset StickyPositionOffset() const;
bool IsSlowRepaintConstrainedObject() const;
virtual LayoutBlock* StickyContainer() const;
PhysicalOffset OffsetForInFlowPosition() const;
......
......@@ -129,6 +129,11 @@ LayoutBox* LayoutNGTableCell::CreateAnonymousBoxWithSameTypeAs(
return LayoutObjectFactory::CreateAnonymousTableCellWithParent(*parent);
}
LayoutBlock* LayoutNGTableCell::StickyContainer() const {
NOT_DESTROYED();
return Table();
}
bool LayoutNGTableCell::BackgroundIsKnownToBeOpaqueInRect(
const PhysicalRect& local_rect) const {
NOT_DESTROYED();
......
......@@ -87,6 +87,8 @@ class CORE_EXPORT LayoutNGTableCell
LayoutBox* CreateAnonymousBoxWithSameTypeAs(
const LayoutObject* parent) const override;
LayoutBlock* StickyContainer() const override;
// LayoutBlockFlow methods end.
// LayoutNGTableCellInterface methods start.
......
......@@ -105,6 +105,11 @@ LayoutBox* LayoutNGTableRow::CreateAnonymousBoxWithSameTypeAs(
return LayoutObjectFactory::CreateAnonymousTableRowWithParent(*parent);
}
LayoutBlock* LayoutNGTableRow::StickyContainer() const {
NOT_DESTROYED();
return Table();
}
// This is necessary because TableRow paints beyond border box if it contains
// rowspanned cells.
void LayoutNGTableRow::AddVisualOverflowFromBlockChildren() {
......
......@@ -46,6 +46,8 @@ class CORE_EXPORT LayoutNGTableRow : public LayoutNGMixin<LayoutBlock>,
LayoutBox* CreateAnonymousBoxWithSameTypeAs(
const LayoutObject* parent) const override;
LayoutBlock* StickyContainer() const override;
// Whether a row has opaque background depends on many factors, e.g. border
// spacing, border collapsing, missing cells, etc.
// For simplicity, just conservatively assume all table rows are not opaque.
......
......@@ -663,6 +663,7 @@ crbug.com/764031 external/wpt/css/css-tables/toggle-row-display-property-001.htm
crbug.com/613753 external/wpt/css/css-tables/border-spacing-included-in-sizes-001.html [ Failure ]
crbug.com/353580 external/wpt/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children-005.html [ Failure ]
crbug.com/1108224 external/wpt/css/css-tables/min-max-size-table-content-box.html [ Failure ]
crbug.com/958381 external/wpt/css/css-tables/tentative/position-sticky-container.html [ Failure ]
# [css-contain]
......
<!doctype html>
<title>Table parts sticky containers</title>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<link rel="author" title="Aleks Totic" href="mailto:atotic@chromium.org" />
<link rel="help" href="https://www.w3.org/TR/css-tables-3/" />
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5020"/>
<style>
body {
margin: 0;
}
main * {
box-sizing: border-box;
}
main .scroller {
width: 350px;
height: 302px;
overflow-y: scroll;
outline-offset: -1px;
outline: gray solid 1px;
}
main .padblock {
width: 300px;
height: 400px;
outline-offset: -2px;
outline: black dotted 2px;
}
main table {
border-spacing: 0;
}
main td {
width: 100px;
height: 25px;
}
.sticky {
position:sticky;
top: 0;
background: rgba(0,255,0, 0.3);
}
</style>
<main>
<div class="scroller">
<div class="padblock">top</div>
<table>
<thead>
<tr>
<td>h:0,0</td>
<td>h:0,1</td>
<td>h:0,2</td>
</tr>
<tr >
<td>h:1,0</td>
<td >h:1,1</td>
<td>h:1,2</td>
</tr>
<tr>
<td>h:2,0</td>
<td>h:2,1</td>
<td>h:2,2</td>
</tr>
</thead>
<tbody>
<tr>
<td>b:0,0</td>
<td>b:0,1</td>
<td>b:0,2</td>
</tr>
<tr>
<td>b:1,0</td>
<td>b:1,1</td>
<td>b:1,2</td>
</tr>
<tr>
<td>b:2,0</td>
<td>b:2,1</td>
<td>b:2,2</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>f:0,0</td>
<td>f:0,1</td>
<td>f:0,2</td>
</tr>
<tr>
<td>f:1,0</td>
<td>f:1,1</td>
<td>f:1,2</td>
</tr>
<tr>
<td>f:2,0</td>
<td>f:2,1</td>
<td>f:2,2</td>
</tr>
</tfoot>
</table>
<div class="padblock">bottom</div>
</div>
</main>
<script>
function scrollTo(y) {
let scroller = document.querySelector("main .scroller");
scroller.scrollTop = y;
}
test( () => {
// Setup
let target = document.querySelector("main tbody tr:nth-child(2) td:nth-child(2)");
let scroller = document.querySelector("main .scroller");
target.classList.toggle("sticky");
// Tests
scrollTo(0);
assert_equals(target.getBoundingClientRect().top, 500, "intrinsic position");
scrollTo(600);
assert_equals(target.getBoundingClientRect().top, 0, "sticking to the table");
scrollTo(640);
assert_equals(target.getBoundingClientRect().top, -40, "sticking to the table bottom");
// Teardown
target.classList.toggle("sticky");
}, "TD sticky container is table");
test( () => {
// Setup
let target = document.querySelector("main tbody tr:nth-child(2)");
let scroller = document.querySelector("main .scroller");
target.classList.toggle("sticky");
// Tests
scrollTo(0);
assert_equals(target.getBoundingClientRect().top, 500, "intrinsic position");
scrollTo(600);
assert_equals(target.getBoundingClientRect().top, 0, "sticking to the table");
scrollTo(640);
assert_equals(target.getBoundingClientRect().top, -40, "sticking to the table bottom");
// Teardown
target.classList.toggle("sticky");
}, "TR sticky container is table");
test( () => {
// Setup
let target = document.querySelector("main tbody");
let scroller = document.querySelector("main .scroller");
target.classList.toggle("sticky");
// Tests
scrollTo(0);
assert_equals(target.getBoundingClientRect().top, 475, "intrinsic position");
scrollTo(550);
assert_equals(target.getBoundingClientRect().top, 0, "sticking to the table");
scrollTo(600);
assert_equals(target.getBoundingClientRect().top, -50, "sticking to the table bottom");
// Teardown
target.classList.toggle("sticky");
}, "TBODY sticky container is table");
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment