Commit d966311c authored by Yoichi Osato's avatar Yoichi Osato Committed by Commit Bot

[LayoutNG] Implement NG offset conversion and LayoutSelectionStartEndForNG().

This patch is a part of crrev.com/c/813336
([LayoutNG] Paint text selection on load.)

LayoutSelection.cpp:
- This converts legacy offset to NG offset if LayoutObject has
enclosing NGBlockFlowlayout before paint phase.
- LayoutSelectionStartEndForNG() returns
start/end offsets that the NGPhysicalTextFragment is selected.

LayoutSelectionTest.cpp:
- This patch adds NGLayoutSelectionTest to confirm/demonstrate
how we use the function. Also you can refer my following
painting patch(crrev.com/c/818884).

Bug: 708452
Change-Id: Ic9fc6822f83e0821c5f2f4917d4ac316543702e2
Reviewed-on: https://chromium-review.googlesource.com/818864Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Reviewed-by: default avatarXiaocheng Hu <xiaochengh@chromium.org>
Commit-Queue: Yoichi Osato <yoichio@chromium.org>
Cr-Commit-Position: refs/heads/master@{#523263}
parent 2588cac2
...@@ -1201,6 +1201,11 @@ void FrameSelection::ClearLayoutSelection() { ...@@ -1201,6 +1201,11 @@ void FrameSelection::ClearLayoutSelection() {
layout_selection_->ClearSelection(); layout_selection_->ClearSelection();
} }
std::pair<unsigned, unsigned> FrameSelection::LayoutSelectionStartEndForNG(
const NGPhysicalTextFragment& text_fragment) {
return layout_selection_->SelectionStartEndForNG(text_fragment);
}
bool FrameSelection::IsDirectional() const { bool FrameSelection::IsDirectional() const {
return GetSelectionInDOMTree().IsDirectional(); return GetSelectionInDOMTree().IsDirectional();
} }
......
...@@ -49,6 +49,7 @@ class LocalFrame; ...@@ -49,6 +49,7 @@ class LocalFrame;
class FrameCaret; class FrameCaret;
class GranularityStrategy; class GranularityStrategy;
class GraphicsContext; class GraphicsContext;
class NGPhysicalTextFragment;
class Range; class Range;
class SelectionEditor; class SelectionEditor;
class LayoutSelection; class LayoutSelection;
...@@ -226,6 +227,8 @@ class CORE_EXPORT FrameSelection final ...@@ -226,6 +227,8 @@ class CORE_EXPORT FrameSelection final
WTF::Optional<unsigned> LayoutSelectionStart() const; WTF::Optional<unsigned> LayoutSelectionStart() const;
WTF::Optional<unsigned> LayoutSelectionEnd() const; WTF::Optional<unsigned> LayoutSelectionEnd() const;
void ClearLayoutSelection(); void ClearLayoutSelection();
std::pair<unsigned, unsigned> LayoutSelectionStartEndForNG(
const NGPhysicalTextFragment&);
void Trace(blink::Visitor*); void Trace(blink::Visitor*);
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
#include "core/layout/LayoutText.h" #include "core/layout/LayoutText.h"
#include "core/layout/LayoutTextFragment.h" #include "core/layout/LayoutTextFragment.h"
#include "core/layout/LayoutView.h" #include "core/layout/LayoutView.h"
#include "core/layout/ng/inline/ng_offset_mapping.h"
#include "core/layout/ng/inline/ng_physical_text_fragment.h"
#include "core/paint/PaintLayer.h" #include "core/paint/PaintLayer.h"
namespace blink { namespace blink {
...@@ -604,6 +606,99 @@ static NewPaintRangeAndSelectedLayoutObjects MarkStartAndEndInTwoNodes( ...@@ -604,6 +606,99 @@ static NewPaintRangeAndSelectedLayoutObjects MarkStartAndEndInTwoNodes(
std::move(selected_objects)}; std::move(selected_objects)};
} }
static WTF::Optional<unsigned> GetTextContentOffset(
LayoutObject* layout_object,
WTF::Optional<unsigned> node_offset) {
DCHECK(layout_object->EnclosingNGBlockFlow());
// |layout_object| is start or end of selection and offset is only valid
// if it is LayoutText.
if (!layout_object->IsText())
return WTF::nullopt;
// There are LayoutText that selection can't be inside it(BR, WBR,
// LayoutCounter).
if (!node_offset.has_value())
return WTF::nullopt;
const Position position_in_dom(*layout_object->GetNode(),
node_offset.value());
const NGOffsetMapping* const offset_mapping =
NGOffsetMapping::GetFor(position_in_dom);
DCHECK(offset_mapping);
const WTF::Optional<unsigned>& ng_offset =
offset_mapping->GetTextContentOffset(position_in_dom);
return ng_offset;
}
static NewPaintRangeAndSelectedLayoutObjects ComputeNewPaintRange(
const NewPaintRangeAndSelectedLayoutObjects& new_range,
LayoutObject* start_layout_object,
WTF::Optional<unsigned> start_node_offset,
LayoutObject* end_layout_object,
WTF::Optional<unsigned> end_node_offset) {
if (new_range.PaintRange().IsNull())
return {};
LayoutObject* const start = new_range.PaintRange().StartLayoutObject();
// If LayoutObject is not in NG, use legacy offset.
const WTF::Optional<unsigned> start_offset =
start->EnclosingNGBlockFlow()
? GetTextContentOffset(start_layout_object, start_node_offset)
: new_range.PaintRange().StartOffset();
LayoutObject* const end = new_range.PaintRange().EndLayoutObject();
const WTF::Optional<unsigned> end_offset =
end->EnclosingNGBlockFlow()
? GetTextContentOffset(end_layout_object, end_node_offset)
: new_range.PaintRange().EndOffset();
return {{start, start_offset, end, end_offset},
std::move(new_range.LayoutObjects())};
}
// ClampOffset modifies |offset| fixed in a range of |text_fragment| start/end
// offsets.
static unsigned ClampOffset(unsigned offset,
const NGPhysicalTextFragment& text_fragment) {
return std::min(std::max(offset, text_fragment.StartOffset()),
text_fragment.EndOffset());
}
std::pair<unsigned, unsigned> LayoutSelection::SelectionStartEndForNG(
const NGPhysicalTextFragment& text_fragment) {
// FrameSelection holds selection offsets in layout block flow at
// LayoutSelection::Commit() if selection starts/ends within Text that
// each LayoutObject::SelectionState indicates.
// These offset can out of |text_fragment| because SelectionState is of each
// LayoutText and not |text_fragment|.
switch (text_fragment.GetLayoutObject()->GetSelectionState()) {
case SelectionState::kStart: {
DCHECK(SelectionStart().has_value());
unsigned start_in_block = SelectionStart().value_or(0);
return {ClampOffset(start_in_block, text_fragment),
text_fragment.EndOffset()};
}
case SelectionState::kEnd: {
DCHECK(SelectionEnd().has_value());
unsigned end_in_block =
SelectionEnd().value_or(text_fragment.EndOffset());
return {text_fragment.StartOffset(),
ClampOffset(end_in_block, text_fragment)};
}
case SelectionState::kStartAndEnd: {
DCHECK(SelectionStart().has_value());
DCHECK(SelectionEnd().has_value());
unsigned start_in_block = SelectionStart().value_or(0);
unsigned end_in_block =
SelectionEnd().value_or(text_fragment.EndOffset());
return {ClampOffset(start_in_block, text_fragment),
ClampOffset(end_in_block, text_fragment)};
}
case SelectionState::kInside:
return {text_fragment.StartOffset(), text_fragment.EndOffset()};
default:
// This block is not included in selection.
return {0, 0};
}
}
static NewPaintRangeAndSelectedLayoutObjects static NewPaintRangeAndSelectedLayoutObjects
CalcSelectionRangeAndSetSelectionState(const FrameSelection& frame_selection) { CalcSelectionRangeAndSetSelectionState(const FrameSelection& frame_selection) {
const SelectionInDOMTree& selection_in_dom = const SelectionInDOMTree& selection_in_dom =
...@@ -652,14 +747,19 @@ CalcSelectionRangeAndSetSelectionState(const FrameSelection& frame_selection) { ...@@ -652,14 +747,19 @@ CalcSelectionRangeAndSetSelectionState(const FrameSelection& frame_selection) {
const WTF::Optional<unsigned> end_offset = ComputeEndOffset( const WTF::Optional<unsigned> end_offset = ComputeEndOffset(
*end_layout_object, selection.EndPosition().ToOffsetInAnchor()); *end_layout_object, selection.EndPosition().ToOffsetInAnchor());
if (start_layout_object == end_layout_object) { NewPaintRangeAndSelectedLayoutObjects new_range =
return MarkStartAndEndInOneNode(std::move(selected_objects), start_layout_object == end_layout_object
start_layout_object, start_offset, ? MarkStartAndEndInOneNode(std::move(selected_objects),
end_offset); start_layout_object, start_offset,
} end_offset)
return MarkStartAndEndInTwoNodes(std::move(selected_objects), : MarkStartAndEndInTwoNodes(std::move(selected_objects),
start_layout_object, start_offset, start_layout_object, start_offset,
end_layout_object, end_offset); end_layout_object, end_offset);
if (!RuntimeEnabledFeatures::LayoutNGPaintFragmentsEnabled())
return new_range;
return ComputeNewPaintRange(new_range, start_layout_object, start_offset,
end_layout_object, end_offset);
} }
void LayoutSelection::SetHasPendingSelection() { void LayoutSelection::SetHasPendingSelection() {
......
...@@ -31,6 +31,7 @@ namespace blink { ...@@ -31,6 +31,7 @@ namespace blink {
class IntRect; class IntRect;
class LayoutObject; class LayoutObject;
class NGPhysicalTextFragment;
class FrameSelection; class FrameSelection;
// This class represents a selection range in layout tree for painting and // This class represents a selection range in layout tree for painting and
...@@ -104,6 +105,13 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> { ...@@ -104,6 +105,13 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> {
void ClearSelection(); void ClearSelection();
WTF::Optional<unsigned> SelectionStart() const; WTF::Optional<unsigned> SelectionStart() const;
WTF::Optional<unsigned> SelectionEnd() const; WTF::Optional<unsigned> SelectionEnd() const;
// This function returns selected part of |text_fragment|.
// Returned pair is a partial range of
// (text_fragment.StartOffset(), text_fragment.EndOffset()).
// If first equals second, it indicates "no selection in fragment".
std::pair<unsigned, unsigned> SelectionStartEndForNG(
const NGPhysicalTextFragment&);
void OnDocumentShutdown(); void OnDocumentShutdown();
void Trace(blink::Visitor*); void Trace(blink::Visitor*);
......
...@@ -11,6 +11,11 @@ ...@@ -11,6 +11,11 @@
#include "core/editing/testing/EditingTestBase.h" #include "core/editing/testing/EditingTestBase.h"
#include "core/layout/LayoutObject.h" #include "core/layout/LayoutObject.h"
#include "core/layout/LayoutText.h" #include "core/layout/LayoutText.h"
#include "core/layout/LayoutTextFragment.h"
#include "core/layout/ng/inline/ng_physical_text_fragment.h"
#include "core/layout/ng/layout_ng_block_flow.h"
#include "core/paint/ng/ng_paint_fragment.h"
#include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h"
#include "platform/wtf/Assertions.h" #include "platform/wtf/Assertions.h"
#include "platform/wtf/Functional.h" #include "platform/wtf/Functional.h"
...@@ -73,6 +78,7 @@ using IsTypeOfSimple = bool(const LayoutObject& layout_object); ...@@ -73,6 +78,7 @@ using IsTypeOfSimple = bool(const LayoutObject& layout_object);
USING_LAYOUTOBJECT_FUNC(IsLayoutBlock); USING_LAYOUTOBJECT_FUNC(IsLayoutBlock);
USING_LAYOUTOBJECT_FUNC(IsLayoutBlockFlow); USING_LAYOUTOBJECT_FUNC(IsLayoutBlockFlow);
USING_LAYOUTOBJECT_FUNC(IsLayoutNGBlockFlow);
USING_LAYOUTOBJECT_FUNC(IsLayoutInline); USING_LAYOUTOBJECT_FUNC(IsLayoutInline);
USING_LAYOUTOBJECT_FUNC(IsBR); USING_LAYOUTOBJECT_FUNC(IsBR);
USING_LAYOUTOBJECT_FUNC(IsListItem); USING_LAYOUTOBJECT_FUNC(IsListItem);
...@@ -99,6 +105,10 @@ static bool IsSVGTSpan(const LayoutObject& layout_object) { ...@@ -99,6 +105,10 @@ static bool IsSVGTSpan(const LayoutObject& layout_object) {
return layout_object.GetName() == String("LayoutSVGTSpan"); return layout_object.GetName() == String("LayoutSVGTSpan");
} }
static bool IsLegacyBlockFlow(const LayoutObject& layout_object) {
return layout_object.IsLayoutBlockFlow() && !layout_object.IsLayoutNGMixin();
}
static bool TestLayoutObject(LayoutObject* object, static bool TestLayoutObject(LayoutObject* object,
IsTypeOfSimple& predicate, IsTypeOfSimple& predicate,
SelectionState state, SelectionState state,
...@@ -666,4 +676,182 @@ TEST_F(LayoutSelectionTest, Embed) { ...@@ -666,4 +676,182 @@ TEST_F(LayoutSelectionTest, Embed) {
TEST_NO_NEXT_LAYOUT_OBJECT(); TEST_NO_NEXT_LAYOUT_OBJECT();
} }
class NGLayoutSelectionTest
: public LayoutSelectionTest,
private ScopedLayoutNGForTest,
private ScopedLayoutNGPaintFragmentsForTest,
private ScopedPaintUnderInvalidationCheckingForTest {
public:
NGLayoutSelectionTest()
: ScopedLayoutNGForTest(true),
ScopedLayoutNGPaintFragmentsForTest(true),
ScopedPaintUnderInvalidationCheckingForTest(true) {}
};
static const NGPaintFragment* FindNGPaintFragmentInternal(
const NGPaintFragment* paint,
const LayoutObject* layout_object) {
if (paint->GetLayoutObject() == layout_object)
return paint;
for (const auto& child : paint->Children()) {
if (const NGPaintFragment* child_fragment =
FindNGPaintFragmentInternal(child.get(), layout_object))
return child_fragment;
}
return nullptr;
}
static const NGPhysicalTextFragment& GetNGPhysicalTextFragment(
const LayoutObject* layout_object) {
DCHECK(layout_object->IsText());
LayoutBlockFlow* block_flow = layout_object->EnclosingNGBlockFlow();
DCHECK(block_flow);
DCHECK(block_flow->IsLayoutNGMixin());
LayoutNGBlockFlow* layout_ng = ToLayoutNGBlockFlow(block_flow);
const NGPaintFragment* paint_fragment =
FindNGPaintFragmentInternal(layout_ng->PaintFragment(), layout_object);
const NGPhysicalFragment& physical_fragment =
paint_fragment->PhysicalFragment();
return ToNGPhysicalTextFragment(physical_fragment);
}
TEST_F(NGLayoutSelectionTest, SelectOnOneText) {
#ifndef NDEBUG
// This line prohibits compiler optimization removing the debug function.
PrintLayoutTreeForDebug();
#endif
const SelectionInDOMTree& selection =
SetSelectionTextToBody("foo<span>b^a|r</span>");
Selection().SetSelection(selection);
Selection().CommitAppearanceIfNeeded();
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT("foo", kNone, NotInvalidate);
TEST_NEXT(IsLayoutInline, kNone, NotInvalidate);
TEST_NEXT("bar", kStartAndEnd, ShouldInvalidate);
TEST_NO_NEXT_LAYOUT_OBJECT();
LayoutObject* const foo =
GetDocument().body()->firstChild()->GetLayoutObject();
EXPECT_EQ(std::make_pair(0u, 0u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(foo)));
LayoutObject* const bar = GetDocument()
.body()
->firstChild()
->nextSibling()
->firstChild()
->GetLayoutObject();
EXPECT_EQ(std::make_pair(4u, 5u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(bar)));
}
TEST_F(NGLayoutSelectionTest, FirstLetterInAnotherBlockFlow) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(
"<style>:first-letter { float: right}</style>^fo|o");
Selection().SetSelection(selection);
Selection().CommitAppearanceIfNeeded();
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLayoutTextFragmentOf("f"), kStart, ShouldInvalidate);
TEST_NEXT(IsLayoutTextFragmentOf("oo"), kEnd, ShouldInvalidate);
TEST_NO_NEXT_LAYOUT_OBJECT();
Node* const foo = GetDocument().body()->firstChild()->nextSibling();
const LayoutTextFragment* const foo_f =
ToLayoutTextFragment(AssociatedLayoutObjectOf(*foo, 0));
EXPECT_EQ(std::make_pair(0u, 1u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(foo_f)));
const LayoutTextFragment* const foo_oo =
ToLayoutTextFragment(AssociatedLayoutObjectOf(*foo, 1));
EXPECT_EQ(std::make_pair(1u, 2u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(foo_oo)));
}
TEST_F(NGLayoutSelectionTest, TwoNGBlockFlows) {
const SelectionInDOMTree& selection =
SetSelectionTextToBody("<div>f^oo</div><div>ba|r</div>");
Selection().SetSelection(selection);
Selection().CommitAppearanceIfNeeded();
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT("foo", kStart, ShouldInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT("bar", kEnd, ShouldInvalidate);
TEST_NO_NEXT_LAYOUT_OBJECT();
LayoutObject* const foo =
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject();
EXPECT_EQ(std::make_pair(1u, 3u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(foo)));
LayoutObject* const bar = GetDocument()
.body()
->firstChild()
->nextSibling()
->firstChild()
->GetLayoutObject();
EXPECT_EQ(std::make_pair(0u, 2u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(bar)));
}
TEST_F(NGLayoutSelectionTest, MixedBlockFlowsAsSibling) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(
"<div>f^oo</div>"
"<div contenteditable>ba|r</div>");
Selection().SetSelection(selection);
Selection().CommitAppearanceIfNeeded();
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT("foo", kStart, ShouldInvalidate);
TEST_NEXT(IsLegacyBlockFlow, kContain, NotInvalidate);
TEST_NEXT("bar", kEnd, ShouldInvalidate);
TEST_NO_NEXT_LAYOUT_OBJECT();
LayoutObject* const foo =
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject();
EXPECT_EQ(std::make_pair(1u, 3u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(foo)));
EXPECT_EQ(2u, Selection().LayoutSelectionEnd().value());
}
TEST_F(NGLayoutSelectionTest, MixedBlockFlowsAnscestor) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(
"<div contenteditable>f^oo"
"<div contenteditable=false>ba|r</div></div>");
Selection().SetSelection(selection);
Selection().CommitAppearanceIfNeeded();
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLegacyBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT("foo", kStart, ShouldInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT("bar", kEnd, ShouldInvalidate);
TEST_NO_NEXT_LAYOUT_OBJECT();
EXPECT_EQ(1u, Selection().LayoutSelectionStart().value());
LayoutObject* const bar = GetDocument()
.body()
->firstChild()
->firstChild()
->nextSibling()
->firstChild()
->GetLayoutObject();
EXPECT_EQ(std::make_pair(0u, 2u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(bar)));
}
TEST_F(NGLayoutSelectionTest, MixedBlockFlowsDecendant) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(
"<div contenteditable=false>f^oo"
"<div contenteditable>ba|r</div></div>");
Selection().SetSelection(selection);
Selection().CommitAppearanceIfNeeded();
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT(IsLayoutNGBlockFlow, kContain, NotInvalidate);
TEST_NEXT("foo", kStart, ShouldInvalidate);
TEST_NEXT(IsLegacyBlockFlow, kContain, NotInvalidate);
TEST_NEXT("bar", kEnd, ShouldInvalidate);
TEST_NO_NEXT_LAYOUT_OBJECT();
LayoutObject* const foo =
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject();
EXPECT_EQ(std::make_pair(1u, 3u), Selection().LayoutSelectionStartEndForNG(
GetNGPhysicalTextFragment(foo)));
EXPECT_EQ(2u, Selection().LayoutSelectionEnd().value());
}
} // namespace blink } // namespace blink
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