Commit c90f1359 authored by Adam Ettenberger's avatar Adam Ettenberger Committed by Commit Bot

Paragraph movement for CSS preserved white-space

1. Implement AXInlineTextBox::IsLineBreakingObject to detect
   preserved newlines.

2. Adjust AXPosition<>::AbortMoveAtParagraphBoundary to be able to
   detect preserved newlines from ancestor anchors using the leaf
   text position.

Bug: 847971
Change-Id: I96e9eacd55cfdb4aa99ceb6460c530ee6d17a1ec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1677553
Commit-Queue: Adam Ettenberger <adettenb@microsoft.com>
Reviewed-by: default avatarChristian Biesinger <cbiesinger@chromium.org>
Reviewed-by: default avatarKurt Catti-Schmidt <kschmi@microsoft.com>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#685366}
parent 6fb8f3c6
......@@ -270,4 +270,13 @@ LegacyAbstractInlineTextBox::PreviousOnLine() const {
return nullptr;
}
bool LegacyAbstractInlineTextBox::IsLineBreak() const {
DCHECK(!inline_text_box_ ||
!inline_text_box_->GetLineLayoutItem().NeedsLayout());
if (!inline_text_box_)
return false;
return inline_text_box_->IsLineBreak();
}
} // namespace blink
......@@ -75,6 +75,7 @@ class CORE_EXPORT AbstractInlineTextBox
virtual bool IsLast() const = 0;
virtual scoped_refptr<AbstractInlineTextBox> NextOnLine() const = 0;
virtual scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const = 0;
virtual bool IsLineBreak() const = 0;
protected:
explicit AbstractInlineTextBox(LineLayoutText line_layout_item);
......@@ -118,6 +119,7 @@ class CORE_EXPORT LegacyAbstractInlineTextBox final
bool IsLast() const final;
scoped_refptr<AbstractInlineTextBox> NextOnLine() const final;
scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final;
bool IsLineBreak() const final;
InlineTextBox* inline_text_box_;
......
......@@ -256,4 +256,11 @@ scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::PreviousOnLine()
return nullptr;
}
bool NGAbstractInlineTextBox::IsLineBreak() const {
if (!fragment_)
return false;
DCHECK(!NeedsLayout());
return PhysicalTextFragment().IsLineBreak();
}
} // namespace blink
......@@ -50,6 +50,7 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox {
bool IsLast() const final;
scoped_refptr<AbstractInlineTextBox> NextOnLine() const final;
scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final;
bool IsLineBreak() const final;
const NGPaintFragment* fragment_;
......
......@@ -50,6 +50,17 @@ void AXInlineTextBox::Detach() {
inline_text_box_ = nullptr;
}
bool AXInlineTextBox::IsLineBreakingObject() const {
if (IsDetached())
return AXObject::IsLineBreakingObject();
// If this object is a forced line break, or the parent is a <br>
// element, then this object is line breaking.
const AXObject* parent = ParentObject();
return inline_text_box_->IsLineBreak() ||
(parent && parent->RoleValue() == ax::mojom::Role::kLineBreak);
}
void AXInlineTextBox::GetRelativeBounds(AXObject** out_container,
FloatRect& out_bounds_in_container,
SkMatrix44& out_container_transform,
......
......@@ -48,6 +48,8 @@ class AXInlineTextBox final : public AXObject {
bool IsDetached() const override { return !inline_text_box_; }
bool IsAXInlineTextBox() const override { return true; }
bool IsLineBreakingObject() const override;
public:
ax::mojom::Role RoleValue() const override {
return ax::mojom::Role::kInlineTextBox;
......
......@@ -332,5 +332,56 @@ TEST_P(AccessibilityLayoutTest, NextOnLine) {
EXPECT_EQ("b", next->GetNode()->textContent());
}
TEST_F(AccessibilityTest, AxObjectPreservedWhitespaceIsLineBreakingObjects) {
SetBodyInnerHTML(R"HTML(
<span style='white-space: pre-line' id="preserved">
First Paragraph
Second Paragraph
Third Paragraph
</span>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* preserved_span = GetAXObjectByElementId("preserved");
ASSERT_NE(nullptr, preserved_span);
ASSERT_EQ(ax::mojom::Role::kGenericContainer, preserved_span->RoleValue());
ASSERT_EQ(1, preserved_span->ChildCount());
EXPECT_FALSE(preserved_span->IsLineBreakingObject());
AXObject* preserved_text = preserved_span->FirstChild();
ASSERT_NE(nullptr, preserved_text);
ASSERT_EQ(ax::mojom::Role::kStaticText, preserved_text->RoleValue());
EXPECT_FALSE(preserved_text->IsLineBreakingObject());
// Expect 7 kInlineTextBox children
// 3 lines of text, and 4 newlines
preserved_text->LoadInlineTextBoxes();
ASSERT_EQ(7, preserved_text->ChildCount());
bool all_children_are_inline_text_boxes = true;
for (const AXObject* child : preserved_text->Children()) {
if (child->RoleValue() != ax::mojom::Role::kInlineTextBox) {
all_children_are_inline_text_boxes = false;
break;
}
}
ASSERT_TRUE(all_children_are_inline_text_boxes);
ASSERT_EQ(preserved_text->Children()[0]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[0]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[1]->ComputedName(), "First Paragraph");
EXPECT_FALSE(preserved_text->Children()[1]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[2]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[2]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[3]->ComputedName(), "Second Paragraph");
EXPECT_FALSE(preserved_text->Children()[3]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[4]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[4]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[5]->ComputedName(), "Third Paragraph");
EXPECT_FALSE(preserved_text->Children()[5]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[6]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[6]->IsLineBreakingObject());
}
} // namespace test
} // namespace blink
......@@ -153,8 +153,9 @@ bool AXNode::IsText() const {
bool AXNode::IsLineBreak() const {
return data().role == ax::mojom::Role::kLineBreak ||
(IsText() && parent() &&
parent()->data().role == ax::mojom::Role::kLineBreak);
(data().role == ax::mojom::Role::kInlineTextBox &&
data().GetBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject));
}
void AXNode::SetData(const AXNodeData& src) {
......
This diff is collapsed.
......@@ -226,6 +226,7 @@ class AXRange {
int max_count = -1) const {
base::string16 range_text;
bool should_append_newline = false;
bool found_trailing_newline = false;
for (const AXRange& leaf_text_range : *this) {
AXPositionType* start = leaf_text_range.anchor();
AXPositionType* end = leaf_text_range.focus();
......@@ -236,7 +237,6 @@ class AXRange {
if (should_append_newline)
range_text += base::ASCIIToUTF16("\n");
bool current_leaf_is_line_break = false;
base::string16 current_anchor_text = start->GetText();
int current_leaf_text_length = end->text_offset() - start->text_offset();
......@@ -246,7 +246,11 @@ class AXRange {
current_leaf_text_length)
: current_leaf_text_length;
current_leaf_is_line_break = start->IsInLineBreak();
// Collapse all whitespace following any line break.
found_trailing_newline =
start->IsInLineBreak() ||
(found_trailing_newline && start->IsInWhiteSpace());
range_text += current_anchor_text.substr(start->text_offset(),
characters_to_append);
}
......@@ -260,7 +264,7 @@ class AXRange {
// its respective anchor is invisible to the text representation.
if (concatenation_behavior == AXTextConcatenationBehavior::kAsInnerText)
should_append_newline = current_anchor_text.length() > 0 &&
!current_leaf_is_line_break &&
!found_trailing_newline &&
end->AtEndOfParagraph();
}
return range_text;
......
......@@ -397,7 +397,7 @@ class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
}
const base::string16 tree_for_move_full_text =
L"First line of text\n\nStandalone line\n"
L"First line of text\nStandalone line\n"
L"bold text\nParagraph 1\nParagraph 2";
ui::AXTreeUpdate BuildAXTreeForMove() {
......@@ -1138,7 +1138,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
int count;
ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
TextPatternRangeEndpoint_End, TextUnit_Paragraph, /*count*/ -6, &count));
EXPECT_EQ(-6, count);
EXPECT_EQ(-5, count);
EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
ASSERT_HRESULT_SUCCEEDED(
......@@ -1720,7 +1720,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveWord) {
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ 2,
/*expected_text*/ L"text\n\n",
/*expected_text*/ L"text\n",
/*expected_count*/ 2);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ 2,
......@@ -1752,7 +1752,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveWord) {
/*expected_count*/ -3);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ -2,
/*expected_text*/ L"text\n\n",
/*expected_text*/ L"text\n",
/*expected_count*/ -2);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ -6,
......@@ -1889,18 +1889,18 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
/*count*/ -4,
/*expected_text*/ L"First line of text\n\n",
/*expected_text*/ L"First line of text\n",
/*expected_count*/ -4);
// Move forward.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ 1,
/*expected_text*/ L"\n",
/*expected_text*/ L"Standalone line\n",
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ 2,
/*count*/ 1,
/*expected_text*/ L"bold text",
/*expected_count*/ 2);
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ 1,
/*expected_text*/ L"Paragraph 1",
......@@ -1923,7 +1923,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
/*expected_count*/ -3);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ -2,
/*expected_text*/ L"First line of text",
/*expected_text*/ L"First line of text\n",
/*expected_count*/ -2);
// Moving backward by any number of paragraphs at the start of document
......@@ -1931,7 +1931,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ -1,
/*expected_text*/
L"First line of text",
L"First line of text\n",
/*expected_count*/ 0);
// Test degenerate range creation at the beginning of the document.
......@@ -1943,14 +1943,14 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
/*count*/ 1,
/*expected_text*/ L"First line of text",
/*expected_text*/ L"First line of text\n",
/*expected_count*/ 1);
// Test degenerate range creation at the end of the document.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ 6,
/*expected_text*/ L"Paragraph 2",
/*expected_count*/ 5);
/*expected_count*/ 4);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
/*count*/ 1,
......@@ -1975,8 +1975,8 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
// Degenerate range moves.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ -7,
/*expected_text*/ L"First line of text",
/*expected_count*/ -6);
/*expected_text*/ L"First line of text\n",
/*expected_count*/ -5);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
/*count*/ -1,
......@@ -1989,7 +1989,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ 70,
/*expected_text*/ L"",
/*expected_count*/ 3);
/*expected_count*/ 2);
// Trying to move past the last paragraph should have no effect.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
......
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