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 @@ ...@@ -12,40 +12,15 @@
#include "third_party/blink/renderer/core/editing/selection_template.h" #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/editing/testing/selection_sample.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.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/html/html_element.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
namespace blink { 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;
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( Position EditingTestBase::SetCaretTextToBody(
const std::string& selection_text) { const std::string& selection_text) {
const SelectionInDOMTree selection = SetSelectionTextToBody(selection_text); const SelectionInDOMTree selection = SetSelectionTextToBody(selection_text);
......
...@@ -29,10 +29,6 @@ class EditingTestBase : public PageTestBase { ...@@ -29,10 +29,6 @@ class EditingTestBase : public PageTestBase {
EditingTestBase(); EditingTestBase();
~EditingTestBase() override; ~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 // Returns |Position| for specified |caret_text|, which is HTML markup with
// caret marker "|". // caret marker "|".
Position SetCaretTextToBody(const std::string& caret_text); Position SetCaretTextToBody(const std::string& caret_text);
......
...@@ -16,7 +16,8 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) ...@@ -16,7 +16,8 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text)
type_(kText), type_(kText),
style_variant_(static_cast<unsigned>(text.StyleVariant())), style_variant_(static_cast<unsigned>(text.StyleVariant())),
is_flow_control_(text.IsFlowControl()), 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); DCHECK_LE(text_.start_offset, text_.end_offset);
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
if (text_.shape_result) { if (text_.shape_result) {
...@@ -34,7 +35,8 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line, ...@@ -34,7 +35,8 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line,
rect_({PhysicalOffset(), line.Size()}), rect_({PhysicalOffset(), line.Size()}),
type_(kLine), type_(kLine),
style_variant_(static_cast<unsigned>(line.StyleVariant())), 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, NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box,
wtf_size_t item_count) wtf_size_t item_count)
...@@ -43,7 +45,10 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box, ...@@ -43,7 +45,10 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box,
rect_({PhysicalOffset(), box.Size()}), rect_({PhysicalOffset(), box.Size()}),
type_(kBox), type_(kBox),
style_variant_(static_cast<unsigned>(box.StyleVariant())), 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() { NGFragmentItem::~NGFragmentItem() {
switch (Type()) { switch (Type()) {
...@@ -62,6 +67,22 @@ NGFragmentItem::~NGFragmentItem() { ...@@ -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 { PhysicalRect NGFragmentItem::SelfInkOverflow() const {
// TODO(kojii): Implement. // TODO(kojii): Implement.
return LocalRect(); return LocalRect();
...@@ -114,6 +135,16 @@ NGTextFragmentPaintInfo NGFragmentItem::TextPaintInfo( ...@@ -114,6 +135,16 @@ NGTextFragmentPaintInfo NGFragmentItem::TextPaintInfo(
return {}; 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 { String NGFragmentItem::DebugName() const {
return "NGFragmentItem"; return "NGFragmentItem";
} }
...@@ -180,4 +211,47 @@ NGFragmentItem::ItemsForLayoutObject::Iterator::operator++() { ...@@ -180,4 +211,47 @@ NGFragmentItem::ItemsForLayoutObject::Iterator::operator++() {
return *this; 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 } // namespace blink
...@@ -42,18 +42,20 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { ...@@ -42,18 +42,20 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
struct LineItem { struct LineItem {
NGLineHeightMetrics metrics; NGLineHeightMetrics metrics;
scoped_refptr<const NGInlineBreakToken> inline_break_token; 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 // Represents a box fragment appeared in a line. This includes inline boxes
// (e.g., <span>text</span>) and atomic inlines. // (e.g., <span>text</span>) and atomic inlines.
struct BoxItem { struct BoxItem {
// If this item is an inline box, its children are stored as following // 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, // If this item is a root of another IFC/BFC, children are stored normally,
// as children of |box_fragment|. // as children of |box_fragment|.
//
// Note:|box_fragment| can be null for <span>.
scoped_refptr<const NGPhysicalBoxFragment> box_fragment; scoped_refptr<const NGPhysicalBoxFragment> box_fragment;
wtf_size_t children_count; wtf_size_t descendants_count;
}; };
enum ItemType { kText, kGeneratedText, kLine, kBox }; enum ItemType { kText, kGeneratedText, kLine, kBox };
...@@ -67,6 +69,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { ...@@ -67,6 +69,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
ItemType Type() const { return static_cast<ItemType>(type_); } ItemType Type() const { return static_cast<ItemType>(type_); }
bool IsAtomicInline() const;
bool IsHiddenForPaint() const { return is_hidden_for_paint_; } bool IsHiddenForPaint() const { return is_hidden_for_paint_; }
NGStyleVariant StyleVariant() const { NGStyleVariant StyleVariant() const {
...@@ -85,6 +88,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { ...@@ -85,6 +88,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
return layout_object_->EffectiveStyle(StyleVariant()); return layout_object_->EffectiveStyle(StyleVariant());
} }
const LayoutObject* GetLayoutObject() const { return layout_object_; } const LayoutObject* GetLayoutObject() const { return layout_object_; }
bool HasSameParent(const NGFragmentItem& other) const;
const PhysicalRect& Rect() const { return rect_; } const PhysicalRect& Rect() const { return rect_; }
const PhysicalOffset& Offset() const { return rect_.offset; } const PhysicalOffset& Offset() const { return rect_.offset; }
...@@ -96,12 +100,12 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { ...@@ -96,12 +100,12 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
// Count of following items that are descendants of this item in the box tree, // 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 // including this item. 1 means this is a box (box or line box) without
// children. 0 if this item type cannot have children. // descendants. 0 if this item type cannot have children.
wtf_size_t ChildrenCount() const { wtf_size_t DescendantsCount() const {
if (Type() == kBox) if (Type() == kBox)
return box_.children_count; return box_.descendants_count;
if (Type() == kLine) if (Type() == kLine)
return line_.children_count; return line_.descendants_count;
return 0; return 0;
} }
...@@ -211,6 +215,10 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { ...@@ -211,6 +215,10 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
unsigned EndOffset() const; unsigned EndOffset() const;
unsigned TextLength() const { return EndOffset() - StartOffset(); } unsigned TextLength() const { return EndOffset() - StartOffset(); }
StringView Text(const NGFragmentItems& items) const; 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 // Compute the inline position from text offset, in logical coordinate
// relative to this fragment. // relative to this fragment.
...@@ -235,6 +243,16 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { ...@@ -235,6 +243,16 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
unsigned start_offset, unsigned start_offset,
unsigned end_offset) const; 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: private:
const LayoutObject* layout_object_; const LayoutObject* layout_object_;
...@@ -269,12 +287,16 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { ...@@ -269,12 +287,16 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
// Item index delta to the next item for the same |LayoutObject|. // Item index delta to the next item for the same |LayoutObject|.
// wtf_size_t delta_to_next_for_same_layout_object_ = 0; // 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 type_ : 2; // ItemType
unsigned style_variant_ : 2; // NGStyleVariant unsigned style_variant_ : 2; // NGStyleVariant
// TODO(yosin): We will change |is_flow_control_| to call |IsLineBreak()| and // TODO(yosin): We will change |is_flow_control_| to call |IsLineBreak()| and
// |TextType() == kFlowControl|. // |TextType() == kFlowControl|.
unsigned is_flow_control_ : 1; unsigned is_flow_control_ : 1;
unsigned is_hidden_for_paint_ : 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 } // namespace blink
......
...@@ -7,10 +7,12 @@ ...@@ -7,10 +7,12 @@
#include "base/containers/span.h" #include "base/containers/span.h"
#include "third_party/blink/renderer/core/core_export.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" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink { namespace blink {
class ComputedStyle;
class LayoutObject; class LayoutObject;
class LayoutBlockFlow; class LayoutBlockFlow;
class NGFragmentItem; class NGFragmentItem;
...@@ -18,6 +20,7 @@ class NGFragmentItems; ...@@ -18,6 +20,7 @@ class NGFragmentItems;
class NGPaintFragment; class NGPaintFragment;
class NGPhysicalBoxFragment; class NGPhysicalBoxFragment;
struct PhysicalOffset; struct PhysicalOffset;
struct PhysicalSize;
// This class traverses fragments in an inline formatting context. // This class traverses fragments in an inline formatting context.
// //
...@@ -30,52 +33,156 @@ class CORE_EXPORT NGInlineCursor { ...@@ -30,52 +33,156 @@ class CORE_EXPORT NGInlineCursor {
STACK_ALLOCATED(); STACK_ALLOCATED();
public: public:
NGInlineCursor(const LayoutBlockFlow& block_flow); explicit NGInlineCursor(const LayoutBlockFlow& block_flow);
NGInlineCursor(const NGFragmentItems& items); explicit NGInlineCursor(const NGFragmentItems& items);
NGInlineCursor(const NGPaintFragment& root_paint_fragment); 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. // Functions to query the current position.
// //
bool IsAtEnd() const { return !current_item_ && !current_paint_fragment_; } // Returns true if cursor is out of fragment tree, e.g. before first fragment
explicit operator bool() const { return !IsAtEnd(); } // 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; 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. // |Current*| functions return an object for the current position.
const NGFragmentItem* CurrentItem() const { return current_item_; } const NGFragmentItem* CurrentItem() const { return current_item_; }
const NGPaintFragment* CurrentPaintFragment() const { const NGPaintFragment* CurrentPaintFragment() const {
return current_paint_fragment_; 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 NGPhysicalBoxFragment* CurrentBoxFragment() const;
const LayoutObject* CurrentLayoutObject() const; const LayoutObject* CurrentLayoutObject() const;
const NGFragmentItems& Items() const { // Returns text direction of current text or atomic inline. It is error to
DCHECK(fragment_items_); // call at other than text or atomic inline. Note: <span> doesn't have
return *fragment_items_; // reserved direction.
} TextDirection CurrentResolvedDirection() const;
const ComputedStyle& CurrentStyle() const;
// The offset relative to the root of the inline formatting context. // The offset relative to the root of the inline formatting context.
const PhysicalOffset CurrentOffset() const; 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. // Functions to move the current position.
// //
// Move the current position to the next fragment in pre-order DFS. Returns // Move the current posint at |paint_fragment|.
// |true| if the move was successful. 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(); 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. // Same as |MoveToNext| except that this skips children even if they exist.
void MoveToNextSkippingChildren(); 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, // TODO(kojii): Add more variations as needed, NextSibling,
// NextSkippingChildren, Previous, etc. // NextSkippingChildren, Previous, etc.
private: private:
using ItemsSpan = base::span<const std::unique_ptr<NGFragmentItem>>; 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(const NGFragmentItems& items);
void SetRoot(ItemsSpan items); void SetRoot(ItemsSpan items);
void SetRoot(const NGPaintFragment& root_paint_fragment); void SetRoot(const NGPaintFragment& root_paint_fragment);
...@@ -83,10 +190,12 @@ class CORE_EXPORT NGInlineCursor { ...@@ -83,10 +190,12 @@ class CORE_EXPORT NGInlineCursor {
void MoveToItem(const ItemsSpan::iterator& iter); void MoveToItem(const ItemsSpan::iterator& iter);
void MoveToNextItem(); void MoveToNextItem();
void MoveToNextItemSkippingChildren(); void MoveToNextItemSkippingChildren();
void MoveToNextSiblingItem();
void MoveToPreviousItem();
void MoveToParentPaintFragment(); void MoveToParentPaintFragment();
void MoveToNextPaintFragment(); void MoveToNextPaintFragment();
void MoveToNextSibilingPaintFragment(); void MoveToNextSiblingPaintFragment();
void MoveToNextPaintFragmentSkippingChildren(); void MoveToNextPaintFragmentSkippingChildren();
ItemsSpan items_; ItemsSpan items_;
...@@ -98,6 +207,9 @@ class CORE_EXPORT NGInlineCursor { ...@@ -98,6 +207,9 @@ class CORE_EXPORT NGInlineCursor {
const NGPaintFragment* current_paint_fragment_ = nullptr; 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 } // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CURSOR_H_ #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CURSOR_H_
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.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/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" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
namespace blink { namespace blink {
...@@ -20,18 +21,31 @@ class NGInlineCursorTest : public NGLayoutTest, ...@@ -20,18 +21,31 @@ class NGInlineCursorTest : public NGLayoutTest,
NGInlineCursorTest() : ScopedLayoutNGFragmentItemForTest(GetParam()) {} NGInlineCursorTest() : ScopedLayoutNGFragmentItemForTest(GetParam()) {}
protected: 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; Vector<String> list;
for (; *cursor; cursor->MoveToNext()) for (NGInlineCursor cursor(start); cursor; cursor.MoveToNext())
list.push_back(ToDebugString(*cursor)); list.push_back(ToDebugString(cursor));
return list; return list;
} }
String ToDebugString(const NGInlineCursor& cursor) { String ToDebugString(const NGInlineCursor& cursor) {
const String text_content =
cursor.GetLayoutBlockFlow()->GetNGInlineNodeData()->text_content;
if (const LayoutObject* layout_object = cursor.CurrentLayoutObject()) { if (const LayoutObject* layout_object = cursor.CurrentLayoutObject()) {
if (const LayoutText* text = ToLayoutTextOrNull(layout_object)) if (layout_object->IsText()) {
return text->GetText().StripWhiteSpace(); return text_content
.Substring(
cursor.CurrentTextStartOffset(),
cursor.CurrentTextEndOffset() - cursor.CurrentTextStartOffset())
.StripWhiteSpace();
}
if (const Element* element = if (const Element* element =
DynamicTo<Element>(layout_object->GetNode())) { DynamicTo<Element>(layout_object->GetNode())) {
if (const AtomicString& id = element->GetIdAttribute()) if (const AtomicString& id = element->GetIdAttribute())
...@@ -51,6 +65,83 @@ INSTANTIATE_TEST_SUITE_P(NGInlineCursorTest, ...@@ -51,6 +65,83 @@ INSTANTIATE_TEST_SUITE_P(NGInlineCursorTest,
NGInlineCursorTest, NGInlineCursorTest,
testing::Bool()); 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) { TEST_P(NGInlineCursorTest, Next) {
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
<style> <style>
...@@ -72,16 +163,69 @@ TEST_P(NGInlineCursorTest, Next) { ...@@ -72,16 +163,69 @@ TEST_P(NGInlineCursorTest, Next) {
LayoutBlockFlow* block_flow = LayoutBlockFlow* block_flow =
To<LayoutBlockFlow>(GetLayoutObjectByElementId("root")); To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
NGInlineCursor cursor(*block_flow); NGInlineCursor cursor(*block_flow);
Vector<String> list = ToDebugStringList(&cursor); Vector<String> list = ToDebugStringList(cursor);
EXPECT_THAT(list, ElementsAre("#linebox", "text1", "#span1", "text2", EXPECT_THAT(list, ElementsAre("#linebox", "text1", "#span1", "text2",
"#span2", "text3", "text4", "text5")); "#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) { TEST_P(NGInlineCursorTest, NextSkippingChildren) {
// TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline.
InsertStyleElement("span { background: gray; }");
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
<style>
span { background: gray; }
</style>
<div id=root> <div id=root>
text1 text1
<span id="span1"> <span id="span1">
...@@ -121,7 +265,7 @@ TEST_P(NGInlineCursorTest, EmptyOutOfFlow) { ...@@ -121,7 +265,7 @@ TEST_P(NGInlineCursorTest, EmptyOutOfFlow) {
LayoutBlockFlow* block_flow = LayoutBlockFlow* block_flow =
To<LayoutBlockFlow>(GetLayoutObjectByElementId("root")); To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
NGInlineCursor cursor(*block_flow); NGInlineCursor cursor(*block_flow);
Vector<String> list = ToDebugStringList(&cursor); Vector<String> list = ToDebugStringList(cursor);
EXPECT_THAT(list, ElementsAre()); EXPECT_THAT(list, ElementsAre());
} }
......
...@@ -407,6 +407,13 @@ bool NGPaintFragment::IsDescendantOfNotSelf( ...@@ -407,6 +407,13 @@ bool NGPaintFragment::IsDescendantOfNotSelf(
return false; return false;
} }
bool NGPaintFragment::IsEllipsis() const {
if (auto* text_fragment =
DynamicTo<NGPhysicalTextFragment>(PhysicalFragment()))
return text_fragment->IsEllipsis();
return false;
}
bool NGPaintFragment::HasSelfPaintingLayer() const { bool NGPaintFragment::HasSelfPaintingLayer() const {
return PhysicalFragment().HasSelfPaintingLayer(); return PhysicalFragment().HasSelfPaintingLayer();
} }
......
...@@ -130,13 +130,20 @@ class CORE_EXPORT NGPaintFragment : public RefCounted<NGPaintFragment>, ...@@ -130,13 +130,20 @@ class CORE_EXPORT NGPaintFragment : public RefCounted<NGPaintFragment>,
NGPaintFragment* NextSibling() const { NGPaintFragment* NextSibling() const {
return FirstAlive(next_sibling_.get()); return FirstAlive(next_sibling_.get());
} }
NGPaintFragment* NextForSameLayoutObject() const {
return next_for_same_layout_object_;
}
ChildList Children() const { return ChildList(FirstChild()); } ChildList Children() const { return ChildList(FirstChild()); }
bool IsEllipsis() const;
// Note, as the name implies, |IsDescendantOfNotSelf| returns false for the // Note, as the name implies, |IsDescendantOfNotSelf| returns false for the
// same object. This is different from |LayoutObject::IsDescendant| but is // same object. This is different from |LayoutObject::IsDescendant| but is
// same as |Node::IsDescendant|. // same as |Node::IsDescendant|.
bool IsDescendantOfNotSelf(const NGPaintFragment&) const; 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. // Returns the first line box for a block-level container.
NGPaintFragment* FirstLineBox() const; NGPaintFragment* FirstLineBox() const;
......
...@@ -13,12 +13,27 @@ ...@@ -13,12 +13,27 @@
#include "third_party/blink/renderer/core/frame/local_frame.h" #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/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.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/core/html/html_element.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h" #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
namespace blink { 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;
PageTestBase::~PageTestBase() = default; PageTestBase::~PageTestBase() = default;
...@@ -140,6 +155,16 @@ void PageTestBase::SetHtmlInnerHTML(const std::string& html_content) { ...@@ -140,6 +155,16 @@ void PageTestBase::SetHtmlInnerHTML(const std::string& html_content) {
UpdateAllLifecyclePhasesForTest(); 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, void PageTestBase::NavigateTo(const KURL& url,
const String& feature_policy_header, const String& feature_policy_header,
const String& csp_header) { const String& csp_header) {
......
...@@ -41,6 +41,10 @@ class PageTestBase : public testing::Test { ...@@ -41,6 +41,10 @@ class PageTestBase : public testing::Test {
void SetBodyInnerHTML(const String&); void SetBodyInnerHTML(const String&);
void SetHtmlInnerHTML(const std::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 // Navigate to |url| providing an empty response but
// URL and security origin of the Document will be set to |url|. // URL and security origin of the Document will be set to |url|.
void NavigateTo(const KURL& 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