Commit 827abd88 authored by Koji Ishii's avatar Koji Ishii Committed by Commit Bot

[LayoutNG] Fix NGInlineNode::MarkLineBoxesDirty

This patch fixes MarkLineBoxesDirty to mark correctly when:
1. The changed node has a parent inline box, and
2. is the first child of the parent, and
3. the parent has box fragment (not culled,) and
4. the parent wraps to multiple lines.

e.g.,
<span id="parent" style="background: yellow">
  <span id="changed"></span>
  <br>
  text
</span>

In this case, the old code marks the line box that contains
the last fragment of #parent because #parent is "previous"
of #changed in the pre-order DFS. The marked line box is the
one after #changed, and thus we try to reuse changed line.

This issue was found by a WIP to apply reusing line boxes in
more cases. From the stack, this looks the same as
crbug.com/900898.

Bug: 636993, 900898
Change-Id: I43d1d8150bfff7e1d9a6572be5a235cbe937caac
Reviewed-on: https://chromium-review.googlesource.com/c/1314086
Commit-Queue: Koji Ishii <kojii@chromium.org>
Reviewed-by: default avatarEmil A Eklund <eae@chromium.org>
Cr-Commit-Position: refs/heads/master@{#605422}
parent b653a893
...@@ -729,15 +729,26 @@ bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) { ...@@ -729,15 +729,26 @@ bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) {
for (LayoutObject* layout_object = block_flow->NextInPreOrder(block_flow); for (LayoutObject* layout_object = block_flow->NextInPreOrder(block_flow);
layout_object;) { layout_object;) {
bool should_dirty_lines = false; bool should_dirty_lines = false;
NGPaintFragment* fragment = nullptr;
LayoutObject* next = nullptr; LayoutObject* next = nullptr;
if (layout_object->IsText()) { if (LayoutText* layout_text = ToLayoutTextOrNull(layout_object)) {
should_dirty_lines = if (!has_dirtied_lines) {
!has_dirtied_lines && layout_object->SelfNeedsLayout(); should_dirty_lines = layout_object->SelfNeedsLayout();
if (!should_dirty_lines)
fragment = layout_text->FirstInlineFragment();
}
next = layout_object->NextInPreOrderAfterChildren(block_flow); next = layout_object->NextInPreOrderAfterChildren(block_flow);
layout_object->ClearNeedsLayout(); layout_object->ClearNeedsLayout();
} else if (layout_object->IsLayoutInline()) { } else if (LayoutInline* layout_inline =
should_dirty_lines = ToLayoutInlineOrNull(layout_object)) {
!has_dirtied_lines && layout_object->SelfNeedsLayout(); if (!has_dirtied_lines) {
should_dirty_lines = layout_object->SelfNeedsLayout();
// Do not keep fragments of LayoutInline unless it's a leaf, because
// the last fragment of LayoutInline is not the previous fragment of its
// descendants.
if (!should_dirty_lines && !layout_inline->FirstChild())
fragment = layout_inline->FirstInlineFragment();
}
next = layout_object->NextInPreOrder(block_flow); next = layout_object->NextInPreOrder(block_flow);
layout_object->ClearNeedsLayout(); layout_object->ClearNeedsLayout();
} else if (UNLIKELY(layout_object->IsFloatingOrOutOfFlowPositioned())) { } else if (UNLIKELY(layout_object->IsFloatingOrOutOfFlowPositioned())) {
...@@ -751,7 +762,11 @@ bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) { ...@@ -751,7 +762,11 @@ bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) {
// solution if any. // solution if any.
return false; return false;
} else if (layout_object->IsAtomicInlineLevel()) { } else if (layout_object->IsAtomicInlineLevel()) {
should_dirty_lines = !has_dirtied_lines && layout_object->NeedsLayout(); if (!has_dirtied_lines) {
should_dirty_lines = layout_object->NeedsLayout();
if (!should_dirty_lines)
fragment = layout_object->FirstInlineFragment();
}
next = layout_object->NextInPreOrderAfterChildren(block_flow); next = layout_object->NextInPreOrderAfterChildren(block_flow);
} else { } else {
NOTREACHED(); NOTREACHED();
...@@ -778,9 +793,8 @@ bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) { ...@@ -778,9 +793,8 @@ bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) {
first_line->MarkLineBoxDirty(); first_line->MarkLineBoxDirty();
} }
has_dirtied_lines = true; has_dirtied_lines = true;
} else { } else if (fragment) {
if (NGPaintFragment* fragment = layout_object->FirstInlineFragment()) last_fragment = fragment;
last_fragment = fragment;
} }
} }
......
...@@ -830,4 +830,25 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutWithBox) { ...@@ -830,4 +830,25 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutWithBox) {
EXPECT_TRUE(lines[1]->IsDirty()); EXPECT_TRUE(lines[1]->IsDirty());
} }
// Test marking line boxes when a span inside a span has NeedsLayout.
// The parent span has a box fragment, and wraps, so that its fragment
// is seen earlier in pre-order DFS.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnChildOfWrappedBox) {
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px">
<span style="background: yellow">
<span id=t>target</span>
<br>
12345678
</span>
</div>
)HTML");
LayoutObject* span = GetLayoutObjectByElementId("t");
span->SetNeedsLayout("");
auto lines = MarkLineBoxesDirty();
EXPECT_TRUE(lines[0]->IsDirty());
}
} // namespace blink } // namespace blink
...@@ -601,9 +601,6 @@ NGPaintFragment* NGPaintFragment::FirstLineBox() const { ...@@ -601,9 +601,6 @@ NGPaintFragment* NGPaintFragment::FirstLineBox() const {
void NGPaintFragment::MarkLineBoxesDirtyFor(const LayoutObject& layout_object) { void NGPaintFragment::MarkLineBoxesDirtyFor(const LayoutObject& layout_object) {
DCHECK(layout_object.IsInline()) << layout_object; DCHECK(layout_object.IsInline()) << layout_object;
if (TryMarkFirstLineBoxDirtyFor(layout_object))
return;
// Since |layout_object| isn't in fragment tree, check preceding siblings. // Since |layout_object| isn't in fragment tree, check preceding siblings.
// Note: Once we reuse lines below dirty lines, we should check next siblings. // Note: Once we reuse lines below dirty lines, we should check next siblings.
for (LayoutObject* previous = layout_object.PreviousSibling(); previous; for (LayoutObject* previous = layout_object.PreviousSibling(); previous;
......
...@@ -584,9 +584,10 @@ TEST_F(NGPaintFragmentTest, MarkLineBoxesDirtyByTextSetData) { ...@@ -584,9 +584,10 @@ TEST_F(NGPaintFragmentTest, MarkLineBoxesDirtyByTextSetData) {
Element& target = *GetDocument().getElementById("target"); Element& target = *GetDocument().getElementById("target");
ToText(*target.firstChild()).setData("abc"); ToText(*target.firstChild()).setData("abc");
const NGPaintFragment& container = *GetPaintFragmentByElementId("container"); const NGPaintFragment& container = *GetPaintFragmentByElementId("container");
EXPECT_FALSE(container.FirstChild()->IsDirty()); auto lines = ToList(container.Children());
EXPECT_TRUE(ToList(container.Children())[1]->IsDirty()); // TODO(kojii): Currently we don't optimzie for <br>. We can do this, then
EXPECT_FALSE(ToList(container.Children())[2]->IsDirty()); // lines[0] should not be dirty.
EXPECT_TRUE(lines[0]->IsDirty());
} }
TEST_F(NGPaintFragmentTest, MarkLineBoxesDirtyWrappedLine) { TEST_F(NGPaintFragmentTest, MarkLineBoxesDirtyWrappedLine) {
......
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