Commit 680153ab authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

Fixes CharacterWidths() and text Len(), and simplifies OffsetInContainer() in AbstractInlineTextBox

The text length of an abstract inline text box was sometimes reported as one character short because
the trailing white space was not included, whilst it was included in the actual text.
Similarly, the character widths did not include the width of any trailing white space.
Also simplified the calculation of OffsetInContainer by using some existing helper methods in Layout NG
and improved tests.

AX-Relnotes: n/a.

R=dmazzoni@chromium.org, aleventhal@chromium.org

Change-Id: Ia5af8a475c028ecc1980b7cbe12695807164824d
Bug: 1008031
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2346289Reviewed-by: default avatarYoshifumi Inoue <yosin@chromium.org>
Reviewed-by: default avatarIan Kilpatrick <ikilpatrick@chromium.org>
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797185}
parent eb76985f
...@@ -146,31 +146,38 @@ LayoutRect LegacyAbstractInlineTextBox::LocalBounds() const { ...@@ -146,31 +146,38 @@ LayoutRect LegacyAbstractInlineTextBox::LocalBounds() const {
unsigned LegacyAbstractInlineTextBox::Len() const { unsigned LegacyAbstractInlineTextBox::Len() const {
if (!inline_text_box_) if (!inline_text_box_)
return 0; return 0u;
return inline_text_box_->Len(); return NeedsTrailingSpace() ? inline_text_box_->Len() + 1
: inline_text_box_->Len();
} }
unsigned LegacyAbstractInlineTextBox::TextOffsetInContainer( unsigned LegacyAbstractInlineTextBox::TextOffsetInContainer(
unsigned offset) const { unsigned offset) const {
if (!inline_text_box_) if (!inline_text_box_)
return 0; return 0U;
unsigned offset_in_container = inline_text_box_->Start() + offset;
const NGOffsetMapping* offset_mapping = GetOffsetMapping();
if (!offset_mapping)
return offset_in_container;
// The start offset of the inline text box returned by // The start offset of the inline text box returned by
// inline_text_box_->Start() includes the collapsed white-spaces. Here, we // inline_text_box_->Start() includes the collapsed white-spaces. Here, we
// want the position in the parent node after white-space collapsing. // want the position in the parent node after white-space collapsing.
// NGOffsetMapping can map an offset before whites-spaces are collapsed to the // NGOffsetMapping can map an offset before whites-spaces are collapsed to the
// offset after white-spaces are collapsed. // offset after white-spaces are collapsed.
Position position(GetNode(), offset_in_container); unsigned int offset_in_container = inline_text_box_->Start() + offset;
const NGOffsetMappingUnit* unit = const Position position(GetNode(), offset_in_container);
offset_mapping->GetMappingUnitForPosition(position); LayoutBlockFlow* formatting_context =
return offset_in_container - unit->DOMStart() + unit->TextContentStart(); NGOffsetMapping::GetInlineFormattingContextOf(position);
if (!formatting_context)
return offset_in_container;
// If "formatting_context" is not a Layout NG object, the offset mappings will
// be computed on demand and cached.
const NGOffsetMapping* offset_mapping =
NGInlineNode::GetOffsetMapping(formatting_context);
if (!offset_mapping)
return offset_in_container;
return offset_mapping->GetTextContentOffset(position).value_or(
offset_in_container);
} }
AbstractInlineTextBox::Direction LegacyAbstractInlineTextBox::GetDirection() AbstractInlineTextBox::Direction LegacyAbstractInlineTextBox::GetDirection()
...@@ -198,6 +205,8 @@ void LegacyAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const { ...@@ -198,6 +205,8 @@ void LegacyAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const {
return; return;
inline_text_box_->CharacterWidths(widths); inline_text_box_->CharacterWidths(widths);
if (NeedsTrailingSpace())
widths.push_back(inline_text_box_->NewlineSpaceWidth());
} }
void AbstractInlineTextBox::GetWordBoundaries( void AbstractInlineTextBox::GetWordBoundaries(
...@@ -289,13 +298,8 @@ String LegacyAbstractInlineTextBox::GetText() const { ...@@ -289,13 +298,8 @@ String LegacyAbstractInlineTextBox::GetText() const {
} }
// Insert a space at the end of this if necessary. // Insert a space at the end of this if necessary.
if (InlineTextBox* next = inline_text_box_->NextForSameLayoutObject()) { if (NeedsTrailingSpace())
if (next->Start() > inline_text_box_->Start() + inline_text_box_->Len() && return result + " ";
result.length() && !result.Right(1).ContainsOnlyWhitespaceOrEmpty() &&
next->GetText().length() &&
!next->GetText().Left(1).ContainsOnlyWhitespaceOrEmpty())
return result + " ";
}
return result; return result;
} }
...@@ -351,25 +355,18 @@ bool LegacyAbstractInlineTextBox::IsLineBreak() const { ...@@ -351,25 +355,18 @@ bool LegacyAbstractInlineTextBox::IsLineBreak() const {
return inline_text_box_->IsLineBreak(); return inline_text_box_->IsLineBreak();
} }
const NGOffsetMapping* LegacyAbstractInlineTextBox::GetOffsetMapping() const { bool LegacyAbstractInlineTextBox::NeedsTrailingSpace() const {
const auto* text_node = DynamicTo<Text>(GetNode()); if (const InlineTextBox* next = inline_text_box_->NextForSameLayoutObject()) {
if (!text_node) return next->Start() >
return nullptr; inline_text_box_->Start() + inline_text_box_->Len() &&
inline_text_box_->GetText().length() &&
LayoutBlockFlow& block_flow = *NGOffsetMapping::GetInlineFormattingContextOf( !inline_text_box_->GetText()
*text_node->GetLayoutObject()); .Right(1)
const NGOffsetMapping* offset_mapping = .ContainsOnlyWhitespaceOrEmpty() &&
NGInlineNode::GetOffsetMapping(&block_flow); next->GetText().length() &&
!next->GetText().Left(1).ContainsOnlyWhitespaceOrEmpty();
if (UNLIKELY(!offset_mapping)) {
// TODO(crbug.com/955678): There are certain cases where we fail to
// compute // |NGOffsetMapping| due to failures in layout. As the root
// cause is hard to fix at the moment, we work around it here so that the
// production build doesn't crash.
NOTREACHED();
return nullptr;
} }
return offset_mapping; return false;
} }
} // namespace blink } // namespace blink
...@@ -42,7 +42,6 @@ ...@@ -42,7 +42,6 @@
namespace blink { namespace blink {
class InlineTextBox; class InlineTextBox;
class NGOffsetMapping;
// High-level abstraction of InlineTextBox to allow the accessibility module to // High-level abstraction of InlineTextBox to allow the accessibility module to
// get information about InlineTextBoxes without tight coupling. // get information about InlineTextBoxes without tight coupling.
...@@ -78,6 +77,7 @@ class CORE_EXPORT AbstractInlineTextBox ...@@ -78,6 +77,7 @@ class CORE_EXPORT AbstractInlineTextBox
virtual scoped_refptr<AbstractInlineTextBox> NextOnLine() const = 0; virtual scoped_refptr<AbstractInlineTextBox> NextOnLine() const = 0;
virtual scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const = 0; virtual scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const = 0;
virtual bool IsLineBreak() const = 0; virtual bool IsLineBreak() const = 0;
virtual bool NeedsTrailingSpace() const = 0;
protected: protected:
explicit AbstractInlineTextBox(LineLayoutText line_layout_item); explicit AbstractInlineTextBox(LineLayoutText line_layout_item);
...@@ -123,7 +123,7 @@ class CORE_EXPORT LegacyAbstractInlineTextBox final ...@@ -123,7 +123,7 @@ class CORE_EXPORT LegacyAbstractInlineTextBox final
scoped_refptr<AbstractInlineTextBox> NextOnLine() const final; scoped_refptr<AbstractInlineTextBox> NextOnLine() const final;
scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final; scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final;
bool IsLineBreak() const final; bool IsLineBreak() const final;
const NGOffsetMapping* GetOffsetMapping() const; bool NeedsTrailingSpace() const final;
InlineTextBox* inline_text_box_; InlineTextBox* inline_text_box_;
......
...@@ -39,6 +39,8 @@ TEST_P(AbstractInlineTextBoxTest, GetTextWithCollapsedWhiteSpace) { ...@@ -39,6 +39,8 @@ TEST_P(AbstractInlineTextBoxTest, GetTextWithCollapsedWhiteSpace) {
layout_text.FirstAbstractInlineTextBox(); layout_text.FirstAbstractInlineTextBox();
EXPECT_EQ("abc", inline_text_box->GetText()); EXPECT_EQ("abc", inline_text_box->GetText());
EXPECT_EQ(3u, inline_text_box->Len());
EXPECT_FALSE(inline_text_box->NeedsTrailingSpace());
} }
// For DumpAccessibilityTreeTest.AccessibilityInputTextValue/blink // For DumpAccessibilityTreeTest.AccessibilityInputTextValue/blink
...@@ -55,12 +57,14 @@ TEST_P(AbstractInlineTextBoxTest, GetTextWithLineBreakAtCollapsedWhiteSpace) { ...@@ -55,12 +57,14 @@ TEST_P(AbstractInlineTextBoxTest, GetTextWithLineBreakAtCollapsedWhiteSpace) {
layout_text.FirstAbstractInlineTextBox(); layout_text.FirstAbstractInlineTextBox();
EXPECT_EQ("abc:", inline_text_box->GetText()); EXPECT_EQ("abc:", inline_text_box->GetText());
EXPECT_EQ(4u, inline_text_box->Len());
EXPECT_FALSE(inline_text_box->NeedsTrailingSpace());
} }
// For "web_tests/accessibility/inline-text-change-style.html" // For "web_tests/accessibility/inline-text-change-style.html"
TEST_P(AbstractInlineTextBoxTest, TEST_P(AbstractInlineTextBoxTest,
GetTextWithLineBreakAtMiddleCollapsedWhiteSpace) { GetTextWithLineBreakAtMiddleCollapsedWhiteSpace) {
// Line break at a space after "012". // There should be a line break at the space after "012".
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
<style>* { font-size: 10px; }</style> <style>* { font-size: 10px; }</style>
<div id="target" style="width: 0ch">012 345</div>)HTML"); <div id="target" style="width: 0ch">012 345</div>)HTML");
...@@ -72,12 +76,14 @@ TEST_P(AbstractInlineTextBoxTest, ...@@ -72,12 +76,14 @@ TEST_P(AbstractInlineTextBoxTest,
layout_text.FirstAbstractInlineTextBox(); layout_text.FirstAbstractInlineTextBox();
EXPECT_EQ("012 ", inline_text_box->GetText()); EXPECT_EQ("012 ", inline_text_box->GetText());
EXPECT_EQ(4u, inline_text_box->Len());
EXPECT_TRUE(inline_text_box->NeedsTrailingSpace());
} }
// DumpAccessibilityTreeTest.AccessibilitySpanLineBreak/blink // DumpAccessibilityTreeTest.AccessibilitySpanLineBreak/blink
TEST_P(AbstractInlineTextBoxTest, TEST_P(AbstractInlineTextBoxTest,
GetTextWithLineBreakAtSpanCollapsedWhiteSpace) { GetTextWithLineBreakAtSpanCollapsedWhiteSpace) {
// Line break at a space in <span>. // There should be a line break at the space in <span>.
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
<style>* { font-size: 10px; }</style> <style>* { font-size: 10px; }</style>
<p id="t1" style="width: 0ch">012<span id="t2"> </span>345</p>)HTML"); <p id="t1" style="width: 0ch">012<span id="t2"> </span>345</p>)HTML");
...@@ -89,6 +95,8 @@ TEST_P(AbstractInlineTextBoxTest, ...@@ -89,6 +95,8 @@ TEST_P(AbstractInlineTextBoxTest,
layout_text1.FirstAbstractInlineTextBox(); layout_text1.FirstAbstractInlineTextBox();
EXPECT_EQ("012", inline_text_box1->GetText()); EXPECT_EQ("012", inline_text_box1->GetText());
EXPECT_EQ(3u, inline_text_box1->Len());
EXPECT_FALSE(inline_text_box1->NeedsTrailingSpace());
const Element& target2 = *GetDocument().getElementById("t2"); const Element& target2 = *GetDocument().getElementById("t2");
LayoutText& layout_text2 = LayoutText& layout_text2 =
...@@ -96,13 +104,14 @@ TEST_P(AbstractInlineTextBoxTest, ...@@ -96,13 +104,14 @@ TEST_P(AbstractInlineTextBoxTest,
scoped_refptr<AbstractInlineTextBox> inline_text_box2 = scoped_refptr<AbstractInlineTextBox> inline_text_box2 =
layout_text2.FirstAbstractInlineTextBox(); layout_text2.FirstAbstractInlineTextBox();
EXPECT_FALSE(inline_text_box2) << "We don't have inline box when <span> " EXPECT_EQ(nullptr, inline_text_box2)
"contains only collapsed white spaces."; << "We don't have inline box when <span> "
"contains only collapsed white spaces.";
} }
// For DumpAccessibilityTreeTest.AccessibilityInputTypes/blink // For DumpAccessibilityTreeTest.AccessibilityInputTypes/blink
TEST_P(AbstractInlineTextBoxTest, GetTextWithLineBreakAtTrailingWhiteSpace) { TEST_P(AbstractInlineTextBoxTest, GetTextWithLineBreakAtTrailingWhiteSpace) {
// Line break at a space of "abc: ". // There should be a line break at the space of "abc: ".
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
<style>* { font-size: 10px; }</style> <style>* { font-size: 10px; }</style>
<div style="width: 10ch"><label id=label>abc: <input></label></div>)HTML"); <div style="width: 10ch"><label id=label>abc: <input></label></div>)HTML");
...@@ -114,40 +123,56 @@ TEST_P(AbstractInlineTextBoxTest, GetTextWithLineBreakAtTrailingWhiteSpace) { ...@@ -114,40 +123,56 @@ TEST_P(AbstractInlineTextBoxTest, GetTextWithLineBreakAtTrailingWhiteSpace) {
layout_text.FirstAbstractInlineTextBox(); layout_text.FirstAbstractInlineTextBox();
EXPECT_EQ("abc: ", inline_text_box->GetText()); EXPECT_EQ("abc: ", inline_text_box->GetText());
EXPECT_EQ(5u, inline_text_box->Len());
if (LayoutNGEnabled()) {
EXPECT_TRUE(inline_text_box->NeedsTrailingSpace());
} else {
EXPECT_FALSE(inline_text_box->NeedsTrailingSpace());
}
} }
TEST_P(AbstractInlineTextBoxTest, GetTextOffsetInContainer) { TEST_P(AbstractInlineTextBoxTest, GetTextOffsetInContainer) {
// "&#10" is a Line Feed ("\n"). // The span should not affect the offset in container of the following inline
// text boxes in the paragraph.
//
// Note that "&#10" is a Line Feed, ("\n").
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
<style>p { white-space: pre-line; }</style> <style>p { white-space: pre-line; }</style>
<p id="paragraph">First sentence of the &#10; paragraph. Second sentence of &#10; the paragraph. </p> <p id="paragraph"><span>Offset</span>First sentence &#10; of the paragraph. Second sentence of &#10; the paragraph.</p>
<br id='br'>)HTML"); <br id="br">)HTML");
const Element& paragraph = *GetDocument().getElementById("paragraph"); const Element& paragraph = *GetDocument().getElementById("paragraph");
LayoutText& layout_text = const Node& text_node = *paragraph.firstChild()->nextSibling();
*ToLayoutText(paragraph.firstChild()->GetLayoutObject()); LayoutText& layout_text = *ToLayoutText(text_node.GetLayoutObject());
// This test has 5 AbstractInlineTextBox. 1.text 2.\n 3.text 4.\n 5.text. // The above "layout_text" should create five AbstractInlineTextBoxes:
// The AbstractInlineTextBoxes are all child of the same text node and an // 1. "First sentence "
// 2. "\n"
// 3. "of the paragraph. Second sentence of "
// 4." \n"
// 5. "the paragraph."
//
// The AbstractInlineTextBoxes are all children of the same text node and an
// an offset calculated in the container node should always be the same for // an offset calculated in the container node should always be the same for
// both LayoutNG and Legacy, even though Legacy doesn't collapse the // both LayoutNG and Legacy, even though Legacy doesn't collapse the
// white-spaces at the end of an AbstractInlineTextBox. // white spaces at the end of an AbstractInlineTextBox. White spaces at the
// beginning of the third and fifth inline text box should be collapsed.
scoped_refptr<AbstractInlineTextBox> inline_text_box = scoped_refptr<AbstractInlineTextBox> inline_text_box =
layout_text.FirstAbstractInlineTextBox(); layout_text.FirstAbstractInlineTextBox();
String text = "First sentence of the"; String text = "First sentence";
EXPECT_EQ(LayoutNGEnabled() ? text : text + " ", inline_text_box->GetText()); EXPECT_EQ(LayoutNGEnabled() ? text : text + " ", inline_text_box->GetText());
EXPECT_EQ(0u, inline_text_box->TextOffsetInContainer(0)); EXPECT_EQ(6u, inline_text_box->TextOffsetInContainer(0));
// Need to jump over the line break AbstractInlineTextBox. // We need to jump over the AbstractInlineTextBox with the line break.
inline_text_box = inline_text_box->NextInlineTextBox()->NextInlineTextBox(); inline_text_box = inline_text_box->NextInlineTextBox()->NextInlineTextBox();
text = "paragraph. Second sentence of"; text = "of the paragraph. Second sentence of";
EXPECT_EQ(LayoutNGEnabled() ? text : text + " ", inline_text_box->GetText()); EXPECT_EQ(LayoutNGEnabled() ? text : text + " ", inline_text_box->GetText());
EXPECT_EQ(22u, inline_text_box->TextOffsetInContainer(0)); EXPECT_EQ(21u, inline_text_box->TextOffsetInContainer(0u));
// See comment above. // See comment above.
inline_text_box = inline_text_box->NextInlineTextBox()->NextInlineTextBox(); inline_text_box = inline_text_box->NextInlineTextBox()->NextInlineTextBox();
EXPECT_EQ("the paragraph.", inline_text_box->GetText()); EXPECT_EQ("the paragraph.", inline_text_box->GetText());
EXPECT_EQ(52u, inline_text_box->TextOffsetInContainer(0)); EXPECT_EQ(58u, inline_text_box->TextOffsetInContainer(0u));
// Ensure that calling TextOffsetInContainer on a br gives the correct result. // Ensure that calling TextOffsetInContainer on a br gives the correct result.
const Element& br_element = *GetDocument().getElementById("br"); const Element& br_element = *GetDocument().getElementById("br");
...@@ -157,4 +182,23 @@ TEST_P(AbstractInlineTextBoxTest, GetTextOffsetInContainer) { ...@@ -157,4 +182,23 @@ TEST_P(AbstractInlineTextBoxTest, GetTextOffsetInContainer) {
EXPECT_EQ(0u, inline_text_box->TextOffsetInContainer(0)); EXPECT_EQ(0u, inline_text_box->TextOffsetInContainer(0));
} }
TEST_P(AbstractInlineTextBoxTest, CharacterWidths) {
// There should be a line break at the space after "012".
SetBodyInnerHTML(R"HTML(
<style>* { font-size: 10px; }</style>
<div id="div" style="width: 0ch">012 345</div>)HTML");
const Element& div = *GetDocument().getElementById("div");
LayoutText& layout_text = *ToLayoutText(div.firstChild()->GetLayoutObject());
scoped_refptr<AbstractInlineTextBox> inline_text_box =
layout_text.FirstAbstractInlineTextBox();
Vector<float> widths;
inline_text_box->CharacterWidths(widths);
// There should be four elements in the "widths" vector, not three, because
// the width of the trailing space should be included.
EXPECT_EQ(4u, widths.size());
EXPECT_TRUE(inline_text_box->NeedsTrailingSpace());
}
} // namespace blink } // namespace blink
...@@ -38,7 +38,6 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox { ...@@ -38,7 +38,6 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox {
NGInlineCursor GetCursor() const; NGInlineCursor GetCursor() const;
NGInlineCursor GetCursorOnLine() const; NGInlineCursor GetCursorOnLine() const;
String GetTextContent() const; String GetTextContent() const;
bool NeedsTrailingSpace() const;
// Implementations of AbstractInlineTextBox member functions. // Implementations of AbstractInlineTextBox member functions.
void Detach() final; void Detach() final;
...@@ -54,6 +53,7 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox { ...@@ -54,6 +53,7 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox {
scoped_refptr<AbstractInlineTextBox> NextOnLine() const final; scoped_refptr<AbstractInlineTextBox> NextOnLine() const final;
scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final; scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final;
bool IsLineBreak() const final; bool IsLineBreak() const final;
bool NeedsTrailingSpace() const final;
union { union {
const NGPaintFragment* fragment_; const NGPaintFragment* fragment_;
......
...@@ -99,7 +99,7 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { ...@@ -99,7 +99,7 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
// Returns the DOM to text content offset mapping of this block. If it is not // Returns the DOM to text content offset mapping of this block. If it is not
// computed before, compute and store it in NGInlineNodeData. // computed before, compute and store it in NGInlineNodeData.
// This funciton must be called with clean layout. // This function must be called with clean layout.
const NGOffsetMapping* ComputeOffsetMappingIfNeeded() const; const NGOffsetMapping* ComputeOffsetMappingIfNeeded() const;
// Get |NGOffsetMapping| for the |layout_block_flow|. |layout_block_flow| // Get |NGOffsetMapping| for the |layout_block_flow|. |layout_block_flow|
......
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