Commit 2a047133 authored by Yoshifumi Inoue's avatar Yoshifumi Inoue Committed by Commit Bot

Implement NGInlineCursor member functions for painitng selection with NGInlineCursor

This patch introduces |NGInlineCursor| member functions for painitng selection:
 - MoveToContainingLine()
 - MoveToFirstChild()
 - MoveToLastChild)(
 - MoveToLastLogicalLeaf()
 - Replacement for NGPaintFragment::InlineFragmentsFor()
   * MoveTo(const LayoutObject&)
   * MoveToNextForSameLayoutObject()
 - MoveToNextSibling()
 - TryToMoveToFirstChild()
 - TryToMoveToLastChild()
 - Property getters for detecting line break
   * Current{Base,Resolved}Direction()
   * CurrentSize()
   * CurrentStyle()
   * CurrentText{Start,End}Offset()
   * IsAtomicInline()
   * IsBeforeSoftLineBreak()
   * IsHiddenForPaint()
   * IsEllipsis()
   * IsLineBreak()

* Rename |NGInlineCursor::IsAtEnd()| to |IsNull()| because
  |MoveTo{First,Last}Child()| set cursor to null if current position has no
  children.
* Rename |NGFragmentItem::ChildrenCount()| to |DescendantsCount()| to avoid
  confusion with immidieate children count and descendants count.

This patch is a preparation of the patch[1].

[1] http://crrev.com/c/1832843 Paint selection with NGFragmentItem

Bug: 982194
Change-Id: Icc09d380371f08477fcc9a45ae0c03c50968a3f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1851885
Commit-Queue: Yoshifumi Inoue <yosin@chromium.org>
Auto-Submit: Yoshifumi Inoue <yosin@chromium.org>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#704547}
parent 3b2e422c
......@@ -12,40 +12,15 @@
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/testing/selection_sample.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/html_collection.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
namespace blink {
namespace {
Element* GetOrCreateElement(ContainerNode* parent,
const HTMLQualifiedName& tag_name) {
HTMLCollection* elements = parent->getElementsByTagNameNS(
tag_name.NamespaceURI(), tag_name.LocalName());
if (!elements->IsEmpty())
return elements->item(0);
return parent->ownerDocument()->CreateRawElement(
tag_name, CreateElementFlags::ByCreateElement());
}
} // namespace
EditingTestBase::EditingTestBase() = default;
EditingTestBase::~EditingTestBase() = default;
void EditingTestBase::InsertStyleElement(const std::string& style_rules) {
Element* const head =
GetOrCreateElement(&GetDocument(), html_names::kHeadTag);
DCHECK_EQ(head, GetOrCreateElement(&GetDocument(), html_names::kHeadTag));
Element* const style = GetDocument().CreateRawElement(
html_names::kStyleTag, CreateElementFlags::ByCreateElement());
style->setTextContent(String(style_rules.data(), style_rules.size()));
head->appendChild(style);
}
Position EditingTestBase::SetCaretTextToBody(
const std::string& selection_text) {
const SelectionInDOMTree selection = SetSelectionTextToBody(selection_text);
......
......@@ -29,10 +29,6 @@ class EditingTestBase : public PageTestBase {
EditingTestBase();
~EditingTestBase() override;
// Insert STYLE element with |style_rules|, no need to have "<style>", into
// HEAD.
void InsertStyleElement(const std::string& style_rules);
// Returns |Position| for specified |caret_text|, which is HTML markup with
// caret marker "|".
Position SetCaretTextToBody(const std::string& caret_text);
......
......@@ -16,7 +16,8 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text)
type_(kText),
style_variant_(static_cast<unsigned>(text.StyleVariant())),
is_flow_control_(text.IsFlowControl()),
is_hidden_for_paint_(false) {
is_hidden_for_paint_(false),
text_direction_(static_cast<unsigned>(text.ResolvedDirection())) {
DCHECK_LE(text_.start_offset, text_.end_offset);
#if DCHECK_IS_ON()
if (text_.shape_result) {
......@@ -34,7 +35,8 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line,
rect_({PhysicalOffset(), line.Size()}),
type_(kLine),
style_variant_(static_cast<unsigned>(line.StyleVariant())),
is_hidden_for_paint_(false) {}
is_hidden_for_paint_(false),
text_direction_(static_cast<unsigned>(line.BaseDirection())) {}
NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box,
wtf_size_t item_count)
......@@ -43,7 +45,10 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box,
rect_({PhysicalOffset(), box.Size()}),
type_(kBox),
style_variant_(static_cast<unsigned>(box.StyleVariant())),
is_hidden_for_paint_(false) {}
is_hidden_for_paint_(false),
// TODO(yosin): We should have |textDirection| parameter from
// |NGLineBoxFragmentBuilder::Child::BidiLevel()|
text_direction_(box.IsAtomicInline() && IsLtr(box.ResolvedDirection())) {}
NGFragmentItem::~NGFragmentItem() {
switch (Type()) {
......@@ -62,6 +67,22 @@ NGFragmentItem::~NGFragmentItem() {
}
}
bool NGFragmentItem::HasSameParent(const NGFragmentItem& other) const {
if (!GetLayoutObject())
return !other.GetLayoutObject();
if (!other.GetLayoutObject())
return false;
return GetLayoutObject()->Parent() == other.GetLayoutObject()->Parent();
}
bool NGFragmentItem::IsAtomicInline() const {
if (Type() != kBox)
return false;
if (const NGPhysicalBoxFragment* box = BoxFragment())
return box->IsAtomicInline();
return false;
}
PhysicalRect NGFragmentItem::SelfInkOverflow() const {
// TODO(kojii): Implement.
return LocalRect();
......@@ -114,6 +135,16 @@ NGTextFragmentPaintInfo NGFragmentItem::TextPaintInfo(
return {};
}
TextDirection NGFragmentItem::BaseDirection() const {
DCHECK_EQ(Type(), kLine);
return static_cast<TextDirection>(text_direction_);
}
TextDirection NGFragmentItem::ResolvedDirection() const {
DCHECK(Type() == kText || Type() == kGeneratedText || IsAtomicInline());
return static_cast<TextDirection>(text_direction_);
}
String NGFragmentItem::DebugName() const {
return "NGFragmentItem";
}
......@@ -180,4 +211,47 @@ NGFragmentItem::ItemsForLayoutObject::Iterator::operator++() {
return *this;
}
std::ostream& operator<<(std::ostream& ostream, const NGFragmentItem& item) {
ostream << "{";
switch (item.Type()) {
case NGFragmentItem::kText:
ostream << "Text " << item.StartOffset() << "-" << item.EndOffset() << " "
<< (IsLtr(item.ResolvedDirection()) ? "LTR" : "RTL");
break;
case NGFragmentItem::kGeneratedText:
ostream << "GeneratedText \"" << item.GeneratedText() << "\"";
break;
case NGFragmentItem::kLine:
ostream << "Line #descendants=" << item.DescendantsCount() << " "
<< (IsLtr(item.BaseDirection()) ? "LTR" : "RTL");
break;
case NGFragmentItem::kBox:
ostream << "Box #descendants=" << item.DescendantsCount();
if (item.IsAtomicInline()) {
ostream << " AtomicInline"
<< (IsLtr(item.ResolvedDirection()) ? "LTR" : "RTL");
}
break;
}
ostream << " ";
switch (item.StyleVariant()) {
case NGStyleVariant::kStandard:
ostream << "Standard";
break;
case NGStyleVariant::kFirstLine:
ostream << "FirstLine";
break;
case NGStyleVariant::kEllipsis:
ostream << "Ellipsis";
break;
}
return ostream << "}";
}
std::ostream& operator<<(std::ostream& ostream, const NGFragmentItem* item) {
if (!item)
return ostream << "<null>";
return ostream << *item;
}
} // namespace blink
......@@ -42,18 +42,20 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
struct LineItem {
NGLineHeightMetrics metrics;
scoped_refptr<const NGInlineBreakToken> inline_break_token;
wtf_size_t children_count;
wtf_size_t descendants_count;
};
// Represents a box fragment appeared in a line. This includes inline boxes
// (e.g., <span>text</span>) and atomic inlines.
struct BoxItem {
// If this item is an inline box, its children are stored as following
// items. |children_count_| has the number of such items.
// items. |descendants_count_| has the number of such items.
//
// If this item is a root of another IFC/BFC, children are stored normally,
// as children of |box_fragment|.
//
// Note:|box_fragment| can be null for <span>.
scoped_refptr<const NGPhysicalBoxFragment> box_fragment;
wtf_size_t children_count;
wtf_size_t descendants_count;
};
enum ItemType { kText, kGeneratedText, kLine, kBox };
......@@ -67,6 +69,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
ItemType Type() const { return static_cast<ItemType>(type_); }
bool IsAtomicInline() const;
bool IsHiddenForPaint() const { return is_hidden_for_paint_; }
NGStyleVariant StyleVariant() const {
......@@ -85,6 +88,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
return layout_object_->EffectiveStyle(StyleVariant());
}
const LayoutObject* GetLayoutObject() const { return layout_object_; }
bool HasSameParent(const NGFragmentItem& other) const;
const PhysicalRect& Rect() const { return rect_; }
const PhysicalOffset& Offset() const { return rect_.offset; }
......@@ -96,12 +100,12 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
// Count of following items that are descendants of this item in the box tree,
// including this item. 1 means this is a box (box or line box) without
// children. 0 if this item type cannot have children.
wtf_size_t ChildrenCount() const {
// descendants. 0 if this item type cannot have children.
wtf_size_t DescendantsCount() const {
if (Type() == kBox)
return box_.children_count;
return box_.descendants_count;
if (Type() == kLine)
return line_.children_count;
return line_.descendants_count;
return 0;
}
......@@ -211,6 +215,10 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
unsigned EndOffset() const;
unsigned TextLength() const { return EndOffset() - StartOffset(); }
StringView Text(const NGFragmentItems& items) const;
String GeneratedText() const {
DCHECK_EQ(Type(), kGeneratedText);
return generated_text_.text;
}
// Compute the inline position from text offset, in logical coordinate
// relative to this fragment.
......@@ -235,6 +243,16 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
unsigned start_offset,
unsigned end_offset) const;
// The base direction of line. Also known as the paragraph direction. This may
// be different from the direction of the container box when first-line style
// is used, or when 'unicode-bidi: plaintext' is used.
// Note: This is valid only for |LineItem|.
TextDirection BaseDirection() const;
// Direction of this item valid for |TextItem| and |IsAtomicInline()|.
// Note: <span> doesn't have text direction.
TextDirection ResolvedDirection() const;
private:
const LayoutObject* layout_object_;
......@@ -269,12 +287,16 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
// Item index delta to the next item for the same |LayoutObject|.
// wtf_size_t delta_to_next_for_same_layout_object_ = 0;
// Note: We should not add |bidi_level_| because it is used only for layout.
unsigned type_ : 2; // ItemType
unsigned style_variant_ : 2; // NGStyleVariant
// TODO(yosin): We will change |is_flow_control_| to call |IsLineBreak()| and
// |TextType() == kFlowControl|.
unsigned is_flow_control_ : 1;
unsigned is_hidden_for_paint_ : 1;
// Note: For |TextItem| and |GeneratedTextItem|, |text_direction_| equals to
// |ShapeResult::Direction()|.
unsigned text_direction_ : 1; // TextDirection.
};
} // namespace blink
......
......@@ -7,10 +7,12 @@
#include "base/containers/span.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/text/text_direction.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
class ComputedStyle;
class LayoutObject;
class LayoutBlockFlow;
class NGFragmentItem;
......@@ -18,6 +20,7 @@ class NGFragmentItems;
class NGPaintFragment;
class NGPhysicalBoxFragment;
struct PhysicalOffset;
struct PhysicalSize;
// This class traverses fragments in an inline formatting context.
//
......@@ -30,52 +33,156 @@ class CORE_EXPORT NGInlineCursor {
STACK_ALLOCATED();
public:
NGInlineCursor(const LayoutBlockFlow& block_flow);
NGInlineCursor(const NGFragmentItems& items);
NGInlineCursor(const NGPaintFragment& root_paint_fragment);
explicit NGInlineCursor(const LayoutBlockFlow& block_flow);
explicit NGInlineCursor(const NGFragmentItems& items);
explicit NGInlineCursor(const NGPaintFragment& root_paint_fragment);
NGInlineCursor(const NGInlineCursor& other);
bool operator==(const NGInlineCursor& other) const;
bool operator!=(const NGInlineCursor& other) const {
return !operator==(other);
}
bool IsItemCursor() const { return fragment_items_; }
bool IsPaintFragmentCursor() const { return root_paint_fragment_; }
const NGFragmentItems& Items() const {
DCHECK(fragment_items_);
return *fragment_items_;
}
// Returns the |LayoutBlockFlow| containing this cursor.
const LayoutBlockFlow* GetLayoutBlockFlow() const;
//
// Functions to query the current position.
//
bool IsAtEnd() const { return !current_item_ && !current_paint_fragment_; }
explicit operator bool() const { return !IsAtEnd(); }
// Returns true if cursor is out of fragment tree, e.g. before first fragment
// or after last fragment in tree.
bool IsNull() const { return !current_item_ && !current_paint_fragment_; }
bool IsNotNull() const { return !IsNull(); }
explicit operator bool() const { return !IsNull(); }
// True if fragment at the current position can have children.
bool CanHaveChildren() const;
// True if the current position is a line box.
// True if fragment at the current position has children.
bool HasChildren() const;
// True if the current position is a atomic inline. It is error to call at
// end.
bool IsAtomicInline() const;
// True if the current position is before soft line break. It is error to call
// at end.
bool IsBeforeSoftLineBreak() const;
// True if the current position is an ellipsis. It is error to call at end.
bool IsEllipsis() const;
// True if the current position is hidden for paint. It is error to call at
// end.
bool IsHiddenForPaint() const;
// True if the current position is a line box. It is error to call at end.
bool IsLineBox() const;
// True if the current position is a line break. It is error to call at end.
bool IsLineBreak() const;
// |Current*| functions return an object for the current position.
const NGFragmentItem* CurrentItem() const { return current_item_; }
const NGPaintFragment* CurrentPaintFragment() const {
return current_paint_fragment_;
}
// Returns text direction of current line. It is error to call at other than
// line.
TextDirection CurrentBaseDirection() const;
const NGPhysicalBoxFragment* CurrentBoxFragment() const;
const LayoutObject* CurrentLayoutObject() const;
const NGFragmentItems& Items() const {
DCHECK(fragment_items_);
return *fragment_items_;
}
// Returns text direction of current text or atomic inline. It is error to
// call at other than text or atomic inline. Note: <span> doesn't have
// reserved direction.
TextDirection CurrentResolvedDirection() const;
const ComputedStyle& CurrentStyle() const;
// The offset relative to the root of the inline formatting context.
const PhysicalOffset CurrentOffset() const;
const PhysicalSize CurrentSize() const;
// Returns start/end of offset in text content of current text fragment.
// It is error when this cursor doesn't point to text fragment.
unsigned CurrentTextStartOffset() const;
unsigned CurrentTextEndOffset() const;
//
// Functions to move the current position.
//
// Move the current position to the next fragment in pre-order DFS. Returns
// |true| if the move was successful.
// Move the current posint at |paint_fragment|.
void MoveTo(const NGPaintFragment& paint_fragment);
// Move to first |NGFragmentItem| or |NGPaintFragment| associated to
// |layout_object|. When |layout_object| has no associated fragments, this
// cursor points nothing.
void MoveTo(const LayoutObject& layout_object);
// Move to containing line box. It is error if the current position is line.
void MoveToContainingLine();
// Move to first child of current container box. If the current position is
// at fragment without children, this cursor points nothing.
// See also |TryToMoveToFirstChild()|.
void MoveToFirstChild();
// Move to last child of current container box. If the current position is
// at fragment without children, this cursor points nothing.
// See also |TryToMoveToFirstChild()|.
void MoveToLastChild();
// Move to last logical leaf of current line box. If current line box has
// no children, curosr becomes null.
void MoveToLastLogicalLeaf();
// Move the current position to the next fragment in pre-order DFS. When
// the current position is at last fragment, this cursor points nothing.
void MoveToNext();
// Move the current position to next fragment on same layout object.
void MoveToNextForSameLayoutObject();
// Move the current position to next sibling fragment.
void MoveToNextSibling();
// Same as |MoveToNext| except that this skips children even if they exist.
void MoveToNextSkippingChildren();
// Returns true if the current position moves to first child.
bool TryToMoveToFirstChild();
// Returns true if the current position moves to last child.
bool TryToMoveToLastChild();
// TODO(kojii): Add more variations as needed, NextSibling,
// NextSkippingChildren, Previous, etc.
private:
using ItemsSpan = base::span<const std::unique_ptr<NGFragmentItem>>;
// |NGInlineCursor| is either paint fragment cursor or fragment item cursor.
// TODO(yosin): When we implement default constructor of |NGInlineCursor|,
// we'll call it "void cursor" instead of "null cursor".
NGInlineCursor() = delete;
// True if the current position is a last line in inline block. It is error
// to call at end or the current position is not line.
bool IsLastLineInInlineBlock() const;
// Make the current position points nothing, e.g. cursor moves over start/end
// fragment, cursor moves to first/last child to parent has no children.
void MakeNull();
void SetRoot(const NGFragmentItems& items);
void SetRoot(ItemsSpan items);
void SetRoot(const NGPaintFragment& root_paint_fragment);
......@@ -83,10 +190,12 @@ class CORE_EXPORT NGInlineCursor {
void MoveToItem(const ItemsSpan::iterator& iter);
void MoveToNextItem();
void MoveToNextItemSkippingChildren();
void MoveToNextSiblingItem();
void MoveToPreviousItem();
void MoveToParentPaintFragment();
void MoveToNextPaintFragment();
void MoveToNextSibilingPaintFragment();
void MoveToNextSiblingPaintFragment();
void MoveToNextPaintFragmentSkippingChildren();
ItemsSpan items_;
......@@ -98,6 +207,9 @@ class CORE_EXPORT NGInlineCursor {
const NGPaintFragment* current_paint_fragment_ = nullptr;
};
CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGInlineCursor&);
CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGInlineCursor*);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CURSOR_H_
......@@ -7,6 +7,7 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
namespace blink {
......@@ -20,18 +21,31 @@ class NGInlineCursorTest : public NGLayoutTest,
NGInlineCursorTest() : ScopedLayoutNGFragmentItemForTest(GetParam()) {}
protected:
Vector<String> ToDebugStringList(NGInlineCursor* cursor) {
NGInlineCursor SetupCursor(const String& html) {
SetBodyInnerHTML(html);
const LayoutBlockFlow& block_flow =
*To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
return NGInlineCursor(block_flow);
}
Vector<String> ToDebugStringList(const NGInlineCursor& start) {
Vector<String> list;
for (; *cursor; cursor->MoveToNext())
list.push_back(ToDebugString(*cursor));
for (NGInlineCursor cursor(start); cursor; cursor.MoveToNext())
list.push_back(ToDebugString(cursor));
return list;
}
String ToDebugString(const NGInlineCursor& cursor) {
const String text_content =
cursor.GetLayoutBlockFlow()->GetNGInlineNodeData()->text_content;
if (const LayoutObject* layout_object = cursor.CurrentLayoutObject()) {
if (const LayoutText* text = ToLayoutTextOrNull(layout_object))
return text->GetText().StripWhiteSpace();
if (layout_object->IsText()) {
return text_content
.Substring(
cursor.CurrentTextStartOffset(),
cursor.CurrentTextEndOffset() - cursor.CurrentTextStartOffset())
.StripWhiteSpace();
}
if (const Element* element =
DynamicTo<Element>(layout_object->GetNode())) {
if (const AtomicString& id = element->GetIdAttribute())
......@@ -51,6 +65,83 @@ INSTANTIATE_TEST_SUITE_P(NGInlineCursorTest,
NGInlineCursorTest,
testing::Bool());
TEST_P(NGInlineCursorTest, ContainingLine) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("a, b { background: gray; }");
NGInlineCursor cursor =
SetupCursor("<div id=root>abc<a id=target>def</a>ghi<br>xyz</div>");
const LayoutBlockFlow& block_flow = *cursor.GetLayoutBlockFlow();
NGInlineCursor line1(cursor);
ASSERT_TRUE(line1.IsLineBox());
NGInlineCursor line2(line1);
line2.MoveToNextSibling();
ASSERT_TRUE(line2.IsLineBox());
cursor.MoveTo(*block_flow.FirstChild());
cursor.MoveToContainingLine();
EXPECT_EQ(line1, cursor);
const LayoutObject& target = *GetLayoutObjectByElementId("target");
cursor.MoveTo(target);
cursor.MoveToContainingLine();
EXPECT_EQ(line1, cursor);
cursor.MoveTo(*target.SlowFirstChild());
cursor.MoveToContainingLine();
EXPECT_EQ(line1, cursor);
cursor.MoveTo(*block_flow.LastChild());
cursor.MoveToContainingLine();
EXPECT_EQ(line2, cursor);
}
TEST_P(NGInlineCursorTest, FirstChild) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("a, b { background: gray; }");
NGInlineCursor cursor =
SetupCursor("<div id=root>abc<a>DEF<b>GHI</b></a>xyz</div>");
cursor.MoveToFirstChild();
EXPECT_EQ("abc", ToDebugString(cursor));
EXPECT_FALSE(cursor.TryToMoveToFirstChild());
}
TEST_P(NGInlineCursorTest, FirstChild2) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("a, b { background: gray; }");
NGInlineCursor cursor = SetupCursor(
"<div id=root><b id=first>abc</b><a>DEF<b>GHI</b></a><a "
"id=last>xyz</a></div>");
cursor.MoveToFirstChild();
EXPECT_EQ("#first", ToDebugString(cursor));
cursor.MoveToFirstChild();
EXPECT_EQ("abc", ToDebugString(cursor));
EXPECT_FALSE(cursor.TryToMoveToFirstChild());
}
TEST_P(NGInlineCursorTest, LastChild) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("a, b { background: gray; }");
NGInlineCursor cursor =
SetupCursor("<div id=root>abc<a>DEF<b>GHI</b></a>xyz</div>");
cursor.MoveToLastChild();
EXPECT_EQ("xyz", ToDebugString(cursor));
EXPECT_FALSE(cursor.TryToMoveToLastChild());
}
TEST_P(NGInlineCursorTest, LastChild2) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("a, b { background: gray; }");
NGInlineCursor cursor = SetupCursor(
"<div id=root><b id=first>abc</b><a>DEF<b>GHI</b></a>"
"<a id=last>xyz</a></div>");
cursor.MoveToLastChild();
EXPECT_EQ("#last", ToDebugString(cursor));
cursor.MoveToLastChild();
EXPECT_EQ("xyz", ToDebugString(cursor));
EXPECT_FALSE(cursor.TryToMoveToLastChild());
}
TEST_P(NGInlineCursorTest, Next) {
SetBodyInnerHTML(R"HTML(
<style>
......@@ -72,16 +163,69 @@ TEST_P(NGInlineCursorTest, Next) {
LayoutBlockFlow* block_flow =
To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
NGInlineCursor cursor(*block_flow);
Vector<String> list = ToDebugStringList(&cursor);
Vector<String> list = ToDebugStringList(cursor);
EXPECT_THAT(list, ElementsAre("#linebox", "text1", "#span1", "text2",
"#span2", "text3", "text4", "text5"));
}
TEST_P(NGInlineCursorTest, NextWithImage) {
NGInlineCursor cursor = SetupCursor("<div id=root>abc<img id=img>xyz</div>");
Vector<String> list = ToDebugStringList(cursor);
EXPECT_THAT(list, ElementsAre("#linebox", "abc", "#img", "xyz"));
}
TEST_P(NGInlineCursorTest, NextWithInlineBox) {
InsertStyleElement("b { display: inline-block; }");
NGInlineCursor cursor =
SetupCursor("<div id=root>abc<b id=ib>def</b>xyz</div>");
Vector<String> list = ToDebugStringList(cursor);
EXPECT_THAT(list, ElementsAre("#linebox", "abc", "#ib", "xyz"));
}
TEST_P(NGInlineCursorTest, NextForSameLayoutObject) {
NGInlineCursor cursor = SetupCursor("<pre id=root>abc\ndef\nghi</pre>");
cursor.MoveTo(*GetLayoutObjectByElementId("root")->SlowFirstChild());
Vector<String> list;
while (cursor) {
list.push_back(ToDebugString(cursor));
cursor.MoveToNextForSameLayoutObject();
}
EXPECT_THAT(list, ElementsAre("abc", "", "def", "", "ghi"));
}
TEST_P(NGInlineCursorTest, NextSibling) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("a, b { background: gray; }");
NGInlineCursor cursor =
SetupCursor("<div id=root>abc<a>DEF<b>GHI</b></a>xyz</div>");
cursor.MoveToFirstChild(); // go to "abc"
Vector<String> list;
while (cursor) {
list.push_back(ToDebugString(cursor));
cursor.MoveToNextSibling();
}
EXPECT_THAT(list, ElementsAre("abc", "LayoutInline A", "xyz"));
}
TEST_P(NGInlineCursorTest, NextSibling2) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("a, b { background: gray; }");
NGInlineCursor cursor =
SetupCursor("<div id=root><a>abc<b>def</b>xyz</a></div>");
cursor.MoveToFirstChild(); // go to <a>abc</a>
cursor.MoveToFirstChild(); // go to "abc"
Vector<String> list;
while (cursor) {
list.push_back(ToDebugString(cursor));
cursor.MoveToNextSibling();
}
EXPECT_THAT(list, ElementsAre("abc", "LayoutInline B", "xyz"));
}
TEST_P(NGInlineCursorTest, NextSkippingChildren) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("span { background: gray; }");
SetBodyInnerHTML(R"HTML(
<style>
span { background: gray; }
</style>
<div id=root>
text1
<span id="span1">
......@@ -121,7 +265,7 @@ TEST_P(NGInlineCursorTest, EmptyOutOfFlow) {
LayoutBlockFlow* block_flow =
To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
NGInlineCursor cursor(*block_flow);
Vector<String> list = ToDebugStringList(&cursor);
Vector<String> list = ToDebugStringList(cursor);
EXPECT_THAT(list, ElementsAre());
}
......
......@@ -407,6 +407,13 @@ bool NGPaintFragment::IsDescendantOfNotSelf(
return false;
}
bool NGPaintFragment::IsEllipsis() const {
if (auto* text_fragment =
DynamicTo<NGPhysicalTextFragment>(PhysicalFragment()))
return text_fragment->IsEllipsis();
return false;
}
bool NGPaintFragment::HasSelfPaintingLayer() const {
return PhysicalFragment().HasSelfPaintingLayer();
}
......
......@@ -130,13 +130,20 @@ class CORE_EXPORT NGPaintFragment : public RefCounted<NGPaintFragment>,
NGPaintFragment* NextSibling() const {
return FirstAlive(next_sibling_.get());
}
NGPaintFragment* NextForSameLayoutObject() const {
return next_for_same_layout_object_;
}
ChildList Children() const { return ChildList(FirstChild()); }
bool IsEllipsis() const;
// Note, as the name implies, |IsDescendantOfNotSelf| returns false for the
// same object. This is different from |LayoutObject::IsDescendant| but is
// same as |Node::IsDescendant|.
bool IsDescendantOfNotSelf(const NGPaintFragment&) const;
// Returns the root box containing this.
const NGPaintFragment* Root() const;
// Returns the first line box for a block-level container.
NGPaintFragment* FirstLineBox() const;
......
......@@ -13,12 +13,27 @@
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/html_collection.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
namespace blink {
namespace {
Element* GetOrCreateElement(ContainerNode* parent,
const HTMLQualifiedName& tag_name) {
HTMLCollection* elements = parent->getElementsByTagNameNS(
tag_name.NamespaceURI(), tag_name.LocalName());
if (!elements->IsEmpty())
return elements->item(0);
return parent->ownerDocument()->CreateRawElement(
tag_name, CreateElementFlags::ByCreateElement());
}
} // namespace
PageTestBase::PageTestBase() = default;
PageTestBase::~PageTestBase() = default;
......@@ -140,6 +155,16 @@ void PageTestBase::SetHtmlInnerHTML(const std::string& html_content) {
UpdateAllLifecyclePhasesForTest();
}
void PageTestBase::InsertStyleElement(const std::string& style_rules) {
Element* const head =
GetOrCreateElement(&GetDocument(), html_names::kHeadTag);
DCHECK_EQ(head, GetOrCreateElement(&GetDocument(), html_names::kHeadTag));
Element* const style = GetDocument().CreateRawElement(
html_names::kStyleTag, CreateElementFlags::ByCreateElement());
style->setTextContent(String(style_rules.data(), style_rules.size()));
head->appendChild(style);
}
void PageTestBase::NavigateTo(const KURL& url,
const String& feature_policy_header,
const String& csp_header) {
......
......@@ -41,6 +41,10 @@ class PageTestBase : public testing::Test {
void SetBodyInnerHTML(const String&);
void SetHtmlInnerHTML(const std::string&);
// Insert STYLE element with |style_rules|, no need to have "<style>", into
// HEAD.
void InsertStyleElement(const std::string& style_rules);
// Navigate to |url| providing an empty response but
// URL and security origin of the Document will be set to |url|.
void NavigateTo(const KURL& url,
......
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