Commit f1e3dff6 authored by Ian Kilpatrick's avatar Ian Kilpatrick Committed by Commit Bot

[TableNG] Add specialized cache logic for table-cell baseline alignment.

This patch allows a NGBlockLayoutAlgorithm to "shift" its content, in
order to perform vertical-alignment within table-cells.

Previously this was done by intrinsic-padding.

And the end of layout, we "finalize" for the table cell, shifting
the content to the desired alignment location.

Within the caching logic for the common case where we don't have an
alignment baseline (for the "measure" pass), then we do (for the
"layout" pass), we hit the cache if the alignment-baseline, and the
actual baseline are the same.

Bug: 958381
Change-Id: I4a9edd3dd8ecc6e8ec90f6b7042f7af644bd0be0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2500981
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: default avatarAleks Totic <atotic@chromium.org>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821784}
parent 4a7cabda
...@@ -367,6 +367,19 @@ base::Optional<LogicalOffset> NGFragmentItemsBuilder::LogicalOffsetFor( ...@@ -367,6 +367,19 @@ base::Optional<LogicalOffset> NGFragmentItemsBuilder::LogicalOffsetFor(
return base::nullopt; return base::nullopt;
} }
void NGFragmentItemsBuilder::MoveChildrenInBlockDirection(LayoutUnit delta) {
DCHECK(!is_converted_to_physical_);
for (ItemWithOffset* iter = items_.begin(); iter != items_.end(); ++iter) {
if (iter->item->Type() == NGFragmentItem::kLine) {
iter->offset.block_offset += delta;
std::advance(iter, iter->item->DescendantsCount() - 1);
DCHECK_LE(iter, items_.end());
continue;
}
iter->offset.block_offset += delta;
}
}
void NGFragmentItemsBuilder::ToFragmentItems(const PhysicalSize& outer_size, void NGFragmentItemsBuilder::ToFragmentItems(const PhysicalSize& outer_size,
void* data) { void* data) {
DCHECK(text_content_); DCHECK(text_content_);
......
...@@ -126,6 +126,9 @@ class CORE_EXPORT NGFragmentItemsBuilder { ...@@ -126,6 +126,9 @@ class CORE_EXPORT NGFragmentItemsBuilder {
// Find |LogicalOffset| of the first |NGFragmentItem| for |LayoutObject|. // Find |LogicalOffset| of the first |NGFragmentItem| for |LayoutObject|.
base::Optional<LogicalOffset> LogicalOffsetFor(const LayoutObject&) const; base::Optional<LogicalOffset> LogicalOffsetFor(const LayoutObject&) const;
// Moves all the |NGFragmentItem|s by |offset| in the block-direction.
void MoveChildrenInBlockDirection(LayoutUnit offset);
// Converts the |NGFragmentItem| vector to the physical coordinate space and // Converts the |NGFragmentItem| vector to the physical coordinate space and
// returns the result. This should only be used for determining the inline // returns the result. This should only be used for determining the inline
// containing block geometry for OOF-positioned nodes. // containing block geometry for OOF-positioned nodes.
......
...@@ -489,7 +489,8 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( ...@@ -489,7 +489,8 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout(
ConstraintSpace(), Node(), ChildAvailableSize(), BorderScrollbarPadding(), ConstraintSpace(), Node(), ChildAvailableSize(), BorderScrollbarPadding(),
BorderPadding()); BorderPadding());
container_builder_.AdjustBorderScrollbarPaddingForTableCell(); if (ConstraintSpace().IsLegacyTableCell())
container_builder_.AdjustBorderScrollbarPaddingForTableCell();
DCHECK_EQ(!!inline_child_layout_context, DCHECK_EQ(!!inline_child_layout_context,
Node().IsInlineFormattingContextRoot()); Node().IsInlineFormattingContextRoot());
...@@ -862,6 +863,7 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( ...@@ -862,6 +863,7 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout(
} }
// Save the unconstrained intrinsic size on the builder before clamping it. // Save the unconstrained intrinsic size on the builder before clamping it.
LayoutUnit unconstrained_intrinsic_block_size = intrinsic_block_size_;
container_builder_.SetOverflowBlockSize(intrinsic_block_size_); container_builder_.SetOverflowBlockSize(intrinsic_block_size_);
intrinsic_block_size_ = ClampIntrinsicBlockSize( intrinsic_block_size_ = ClampIntrinsicBlockSize(
...@@ -880,13 +882,6 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( ...@@ -880,13 +882,6 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout(
border_box_size.inline_size); border_box_size.inline_size);
container_builder_.SetFragmentsTotalBlockSize(border_box_size.block_size); container_builder_.SetFragmentsTotalBlockSize(border_box_size.block_size);
// At this point, we need to perform any final table-cell adjustments
// needed.
if (ConstraintSpace().IsTableCell()) {
container_builder_.SetHasCollapsedBorders(
ConstraintSpace().IsTableCellWithCollapsedBorders());
}
// If our BFC block-offset is still unknown, we check: // If our BFC block-offset is still unknown, we check:
// - If we have a non-zero block-size (margins don't collapse through us). // - If we have a non-zero block-size (margins don't collapse through us).
// - If we have a break token. (Even if we are self-collapsing we position // - If we have a break token. (Even if we are self-collapsing we position
...@@ -941,6 +936,10 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( ...@@ -941,6 +936,10 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout(
} }
} }
// At this point, perform any final table-cell adjustments needed.
if (ConstraintSpace().IsTableCell())
FinalizeForTableCell(unconstrained_intrinsic_block_size);
// We only finalize for fragmentation if the fragment has a BFC block offset. // We only finalize for fragmentation if the fragment has a BFC block offset.
// This may occur with a zero block size fragment. We need to know the BFC // This may occur with a zero block size fragment. We need to know the BFC
// block offset to determine where the fragmentation line is relative to us. // block offset to determine where the fragmentation line is relative to us.
...@@ -971,13 +970,6 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( ...@@ -971,13 +970,6 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout(
if (ConstraintSpace().UseFirstLineStyle()) if (ConstraintSpace().UseFirstLineStyle())
container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine); container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine);
// Table-cells with separate borders, "empty-cells: hide", and no children
// don't paint.
if (ConstraintSpace().HideTableCellIfEmpty()) {
container_builder_.SetIsHiddenForPaint(
container_builder_.Children().IsEmpty());
}
return container_builder_.ToBoxFragment(); return container_builder_.ToBoxFragment();
} }
...@@ -2151,6 +2143,68 @@ LayoutUnit NGBlockLayoutAlgorithm::PositionSelfCollapsingChildWithParentBfc( ...@@ -2151,6 +2143,68 @@ LayoutUnit NGBlockLayoutAlgorithm::PositionSelfCollapsingChildWithParentBfc(
return child_bfc_block_offset; return child_bfc_block_offset;
} }
void NGBlockLayoutAlgorithm::FinalizeForTableCell(
LayoutUnit unconstrained_intrinsic_block_size) {
// Hide table-cells if:
// - They are within a collapsed column(s).
// - They have "empty-cells: hide", non-collapsed borders, and no children.
container_builder_.SetIsHiddenForPaint(
ConstraintSpace().IsTableCellHiddenForPaint() ||
(ConstraintSpace().HideTableCellIfEmpty() &&
container_builder_.Children().IsEmpty()));
container_builder_.SetHasCollapsedBorders(
ConstraintSpace().IsTableCellWithCollapsedBorders());
// Everything else within this function only applies to new table-cells.
if (ConstraintSpace().IsLegacyTableCell())
return;
container_builder_.SetTableCellColumnIndex(
ConstraintSpace().TableCellColumnIndex());
switch (Style().VerticalAlign()) {
case EVerticalAlign::kTop:
// Do nothing for 'top' vertical alignment.
break;
case EVerticalAlign::kBaselineMiddle:
case EVerticalAlign::kSub:
case EVerticalAlign::kSuper:
case EVerticalAlign::kTextTop:
case EVerticalAlign::kTextBottom:
case EVerticalAlign::kLength:
// All of the above are treated as 'baseline' for the purposes of
// table-cell vertical alignment.
case EVerticalAlign::kBaseline:
// Table-cells (with baseline vertical alignment) always produce a
// baseline of their end-content edge (even if the content doesn't have
// any baselines).
if (!container_builder_.Baseline() ||
Node().ShouldApplyLayoutContainment()) {
container_builder_.SetBaseline(unconstrained_intrinsic_block_size -
BorderScrollbarPadding().block_end);
}
if (auto alignment_baseline =
ConstraintSpace().TableCellAlignmentBaseline()) {
container_builder_.MoveChildrenInBlockDirection(
*alignment_baseline - *container_builder_.Baseline());
}
break;
case EVerticalAlign::kMiddle:
container_builder_.MoveChildrenInBlockDirection(
(container_builder_.FragmentBlockSize() -
unconstrained_intrinsic_block_size) /
2);
break;
case EVerticalAlign::kBottom:
container_builder_.MoveChildrenInBlockDirection(
container_builder_.FragmentBlockSize() -
unconstrained_intrinsic_block_size);
break;
};
}
LayoutUnit NGBlockLayoutAlgorithm::FragmentainerSpaceAvailable() const { LayoutUnit NGBlockLayoutAlgorithm::FragmentainerSpaceAvailable() const {
DCHECK(container_builder_.BfcBlockOffset()); DCHECK(container_builder_.BfcBlockOffset());
return FragmentainerSpaceAtBfcStart(ConstraintSpace()) - return FragmentainerSpaceAtBfcStart(ConstraintSpace()) -
......
...@@ -215,6 +215,9 @@ class CORE_EXPORT NGBlockLayoutAlgorithm ...@@ -215,6 +215,9 @@ class CORE_EXPORT NGBlockLayoutAlgorithm
NGInlineChildLayoutContext*, NGInlineChildLayoutContext*,
scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token); scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token);
// Performs any final adjustments for table-cells.
void FinalizeForTableCell(LayoutUnit unconstrained_intrinsic_block_size);
// Return the amount of block space available in the current fragmentainer // Return the amount of block space available in the current fragmentainer
// for the node being laid out by this algorithm. // for the node being laid out by this algorithm.
LayoutUnit FragmentainerSpaceAvailable() const; LayoutUnit FragmentainerSpaceAvailable() const;
......
...@@ -339,6 +339,35 @@ EBreakBetween NGBoxFragmentBuilder::JoinedBreakBetweenValue( ...@@ -339,6 +339,35 @@ EBreakBetween NGBoxFragmentBuilder::JoinedBreakBetweenValue(
return JoinFragmentainerBreakValues(previous_break_after_, break_before); return JoinFragmentainerBreakValues(previous_break_after_, break_before);
} }
void NGBoxFragmentBuilder::MoveChildrenInBlockDirection(LayoutUnit delta) {
DCHECK(is_new_fc_);
DCHECK(!has_oof_candidate_that_needs_block_offset_adjustment_);
DCHECK_NE(FragmentBlockSize(), kIndefiniteSize);
DCHECK(oof_positioned_descendants_.IsEmpty());
if (delta == LayoutUnit())
return;
if (baseline_)
*baseline_ += delta;
if (last_baseline_)
*last_baseline_ += delta;
if (inflow_bounds_)
inflow_bounds_->offset.block_offset += delta;
for (auto& child : children_)
child.offset.block_offset += delta;
for (auto& candidate : oof_positioned_candidates_)
candidate.static_position.offset.block_offset += delta;
for (auto& descendant : oof_positioned_fragmentainer_descendants_)
descendant.static_position.offset.block_offset += delta;
if (NGFragmentItemsBuilder* items_builder = ItemsBuilder())
items_builder->MoveChildrenInBlockDirection(delta);
}
void NGBoxFragmentBuilder::PropagateBreak( void NGBoxFragmentBuilder::PropagateBreak(
const NGLayoutResult& child_layout_result) { const NGLayoutResult& child_layout_result) {
if (LIKELY(!has_block_fragmentation_)) if (LIKELY(!has_block_fragmentation_))
......
...@@ -546,6 +546,10 @@ class CORE_EXPORT NGBoxFragmentBuilder final ...@@ -546,6 +546,10 @@ class CORE_EXPORT NGBoxFragmentBuilder final
void CheckNoBlockFragmentation() const; void CheckNoBlockFragmentation() const;
#endif #endif
// Moves all the children by |offset| in the block-direction. (Ensure that
// any baselines, OOFs, etc, are also moved by the appropriate amount).
void MoveChildrenInBlockDirection(LayoutUnit offset);
private: private:
// Update whether we have fragmented in this flow. // Update whether we have fragmented in this flow.
void PropagateBreak(const NGLayoutResult&); void PropagateBreak(const NGLayoutResult&);
......
...@@ -1733,5 +1733,108 @@ TEST_F(NGLayoutResultCachingTest, SimpleTable) { ...@@ -1733,5 +1733,108 @@ TEST_F(NGLayoutResultCachingTest, SimpleTable) {
EXPECT_EQ(result2.get(), measure2.get()); EXPECT_EQ(result2.get(), measure2.get());
} }
TEST_F(NGLayoutResultCachingTest, MissTableCellMiddleAlignment) {
ScopedLayoutNGTableForTest table_ng(true);
SetBodyInnerHTML(R"HTML(
<table>
<td id="target" style="vertical-align: middle;">abc</td>
<td>abc<br>abc</td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should be stretched, and miss the measure cache.
scoped_refptr<const NGLayoutResult> result = target->GetCachedLayoutResult();
scoped_refptr<const NGLayoutResult> measure =
target->GetCachedMeasureResult();
EXPECT_NE(measure.get(), nullptr);
EXPECT_NE(result.get(), nullptr);
EXPECT_EQ(measure->GetConstraintSpaceForCaching().CacheSlot(),
NGCacheSlot::kMeasure);
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
NGCacheSlot::kLayout);
EXPECT_NE(result.get(), measure.get());
}
TEST_F(NGLayoutResultCachingTest, MissTableCellBottomAlignment) {
ScopedLayoutNGTableForTest table_ng(true);
SetBodyInnerHTML(R"HTML(
<table>
<td id="target" style="vertical-align: bottom;">abc</td>
<td>abc<br>abc</td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should be stretched, and miss the measure cache.
scoped_refptr<const NGLayoutResult> result = target->GetCachedLayoutResult();
scoped_refptr<const NGLayoutResult> measure =
target->GetCachedMeasureResult();
EXPECT_NE(measure.get(), nullptr);
EXPECT_NE(result.get(), nullptr);
EXPECT_EQ(measure->GetConstraintSpaceForCaching().CacheSlot(),
NGCacheSlot::kMeasure);
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
NGCacheSlot::kLayout);
EXPECT_NE(result.get(), measure.get());
}
TEST_F(NGLayoutResultCachingTest, HitTableCellBaselineAlignment) {
ScopedLayoutNGTableForTest table_ng(true);
SetBodyInnerHTML(R"HTML(
<style>
td { vertical-align: baseline; }
</style>
<table>
<td id="target">abc</td>
<td>def</td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should align to the baseline, but hit the cache.
scoped_refptr<const NGLayoutResult> result = target->GetCachedLayoutResult();
scoped_refptr<const NGLayoutResult> measure =
target->GetCachedMeasureResult();
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
NGCacheSlot::kMeasure);
EXPECT_NE(result.get(), nullptr);
EXPECT_EQ(result.get(), measure.get());
}
TEST_F(NGLayoutResultCachingTest, MissTableCellBaselineAlignment) {
ScopedLayoutNGTableForTest table_ng(true);
SetBodyInnerHTML(R"HTML(
<style>
td { vertical-align: baseline; }
</style>
<table>
<td id="target">abc</td>
<td><span style="font-size: 32px">def</span></td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should align to the baseline, but miss the cache.
scoped_refptr<const NGLayoutResult> result = target->GetCachedLayoutResult();
scoped_refptr<const NGLayoutResult> measure =
target->GetCachedMeasureResult();
EXPECT_NE(measure.get(), nullptr);
EXPECT_NE(result.get(), nullptr);
EXPECT_EQ(measure->GetConstraintSpaceForCaching().CacheSlot(),
NGCacheSlot::kMeasure);
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
NGCacheSlot::kLayout);
EXPECT_NE(result.get(), measure.get());
}
} // namespace } // namespace
} // namespace blink } // namespace blink
...@@ -221,7 +221,7 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry( ...@@ -221,7 +221,7 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry(
// value, as the following |block_size| calculation would be incorrect. // value, as the following |block_size| calculation would be incorrect.
// TODO(dgrogan): We can hit the cache here for row flexboxes when they // TODO(dgrogan): We can hit the cache here for row flexboxes when they
// don't have stretchy children. // don't have stretchy children.
if (layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { if (physical_fragment.DependsOnPercentageBlockSize()) {
if (new_space.PercentageResolutionBlockSize() != if (new_space.PercentageResolutionBlockSize() !=
old_space.PercentageResolutionBlockSize()) old_space.PercentageResolutionBlockSize())
return NGLayoutCacheStatus::kNeedsLayout; return NGLayoutCacheStatus::kNeedsLayout;
...@@ -299,7 +299,7 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry( ...@@ -299,7 +299,7 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry(
// this false-positive by checking if we have an initial indefinite // this false-positive by checking if we have an initial indefinite
// block-size. // block-size.
if (is_new_initial_block_size_indefinite && if (is_new_initial_block_size_indefinite &&
layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { physical_fragment.DependsOnPercentageBlockSize()) {
DCHECK(is_old_initial_block_size_indefinite); DCHECK(is_old_initial_block_size_indefinite);
if (new_space.PercentageResolutionBlockSize() != if (new_space.PercentageResolutionBlockSize() !=
old_space.PercentageResolutionBlockSize()) old_space.PercentageResolutionBlockSize())
...@@ -313,12 +313,57 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry( ...@@ -313,12 +313,57 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry(
if (style.MayHavePadding() && fragment_geometry.padding != fragment.Padding()) if (style.MayHavePadding() && fragment_geometry.padding != fragment.Padding())
return NGLayoutCacheStatus::kNeedsLayout; return NGLayoutCacheStatus::kNeedsLayout;
// Table-cells with vertical alignment might shift their contents if their // Table-cells with vertical alignment might shift their contents if the
// block-size changes. // block-size changes.
if (new_space.IsTableCell() && !is_block_size_equal && if (new_space.IsTableCell()) {
style.VerticalAlign() != DCHECK(old_space.IsTableCell());
ComputedStyleInitialValues::InitialVerticalAlign())
return NGLayoutCacheStatus::kNeedsLayout; switch (style.VerticalAlign()) {
case EVerticalAlign::kTop:
// Do nothing special for 'top' vertical alignment.
break;
case EVerticalAlign::kBaselineMiddle:
case EVerticalAlign::kSub:
case EVerticalAlign::kSuper:
case EVerticalAlign::kTextTop:
case EVerticalAlign::kTextBottom:
case EVerticalAlign::kLength:
// All of the above are treated as 'baseline' for the purposes of
// table-cell vertical alignment.
case EVerticalAlign::kBaseline: {
auto new_alignment_baseline = new_space.TableCellAlignmentBaseline();
auto old_alignment_baseline = old_space.TableCellAlignmentBaseline();
// Do nothing if neither alignment baseline is set.
if (!new_alignment_baseline && !old_alignment_baseline)
break;
// If we only have an old alignment baseline set, we need layout, as we
// can't determine where the un-adjusted baseline is.
if (!new_alignment_baseline && old_alignment_baseline)
return NGLayoutCacheStatus::kNeedsLayout;
// We've been provided a new alignment baseline, just check that it
// matches the previously generated baseline.
if (!old_alignment_baseline) {
if (*new_alignment_baseline != physical_fragment.Baseline())
return NGLayoutCacheStatus::kNeedsLayout;
break;
}
// If the alignment baselines differ at this stage, we need layout.
if (*new_alignment_baseline != *old_alignment_baseline)
return NGLayoutCacheStatus::kNeedsLayout;
break;
}
case EVerticalAlign::kMiddle:
case EVerticalAlign::kBottom:
// 'middle', and 'bottom' vertical alignment depend on the block-size.
if (!is_block_size_equal)
return NGLayoutCacheStatus::kNeedsLayout;
break;
}
}
// If we've reached here we know that we can potentially "stretch"/"shrink" // If we've reached here we know that we can potentially "stretch"/"shrink"
// ourselves without affecting any of our children. // ourselves without affecting any of our children.
......
...@@ -237,6 +237,8 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment( ...@@ -237,6 +237,8 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment(
is_math_fraction_ = builder->is_math_fraction_; is_math_fraction_ = builder->is_math_fraction_;
is_math_operator_ = builder->is_math_operator_; is_math_operator_ = builder->is_math_operator_;
// TODO(ikilpatrick): Investigate if new table-cells should always produce a
// baseline.
bool has_layout_containment = layout_object_->ShouldApplyLayoutContainment(); bool has_layout_containment = layout_object_->ShouldApplyLayoutContainment();
if (builder->baseline_.has_value() && !has_layout_containment) { if (builder->baseline_.has_value() && !has_layout_containment) {
has_baseline_ = true; has_baseline_ = true;
......
...@@ -69,6 +69,9 @@ NGSimplifiedLayoutAlgorithm::NGSimplifiedLayoutAlgorithm( ...@@ -69,6 +69,9 @@ NGSimplifiedLayoutAlgorithm::NGSimplifiedLayoutAlgorithm(
if (physical_fragment.LastBaseline()) if (physical_fragment.LastBaseline())
container_builder_.SetLastBaseline(*physical_fragment.LastBaseline()); container_builder_.SetLastBaseline(*physical_fragment.LastBaseline());
// TODO(ikilpatrick): Set any fields for table-cells which are also set
// within the NGBlockLayoutAlgorithm.
} else { } else {
// Only block-flow layout sets the following fields. // Only block-flow layout sets the following fields.
DCHECK(physical_fragment.IsFormattingContextRoot()); DCHECK(physical_fragment.IsFormattingContextRoot());
......
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