0

Revert "[a11y] Fixes move by format for CSS spellcheck highlights"

This reverts commit c6bfb8a155.

Reason for revert: This change broke Narrator's move-by-headings
feature. Heading navigation is a critical feature of screen readers that
facilites navigating web pages, like the google/bing results page. It
impacts all headings throughout the web, whereas the original CL was
fixing a scoped problem: spelling errors unannounced when exposed using
CSS highlights. We will also revert this change from 134.

Original change's description:
> [a11y] Fixes move by format for CSS spellcheck highlights
>
> Uses Spelling/Grammar error CSS highlight markers in the
> Accessibility Tree to break format boundaries around those
> errors to help navigate better around these spelling/grammar
> errors.
>
> This CL achieves following:
> 1) Fixes Move by format to accommodate CSS spellcheck highlights.
> 2) Narrator calls out the spelling issues correctly.
>
> Bug: 364795299
> Change-Id: I4842a49449c666100e31f859447f951e9f9eb09e
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6178990
> Auto-Submit: Vinay Singh <vinaysingh@microsoft.com>
> Reviewed-by: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
> Commit-Queue: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
> Cr-Commit-Position: refs/heads/main@{#1415710}

Bug: 402800783, 364795299
Change-Id: I1ee7bd394f18fba21e97e5cc8998de1a03501931
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6349562
Reviewed-by: Jacques Newman <janewman@microsoft.com>
Auto-Submit: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
Commit-Queue: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1431891}
This commit is contained in:
Benjamin Beaudry
2025-03-12 19:50:00 -07:00
committed by Chromium LUCI CQ
parent 03badce254
commit fa24efebcd
5 changed files with 72 additions and 292 deletions

@ -1544,43 +1544,42 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L"plain 1\nplain 2\n",
/*expected_text*/ L"plain 1\nplain 2",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/
L"plain 1\nplain 2\nplain heading\n",
L"plain 1\nplain 2\nplain heading",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2\n",
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/ L"plain 1\nplain 2\nplain heading\n",
/*expected_text*/ L"plain 1\nplain 2\nplain heading",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2\n",
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2\nheading\n",
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2\nheading",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic "
L"2\nheading\nheading\n",
L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2\nheading\nheading",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1597,7 +1596,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L"plain 1\nplain 2\n",
/*expected_text*/ L"plain 1\nplain 2",
/*expected_count*/ 1);
}
@ -1716,59 +1715,59 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L"plain 1\nplain 2\n",
/*expected_text*/ L"plain 1\nplain 2",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 1,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\n",
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/ L"plain 1\nplain 2\n",
/*expected_text*/ L"plain 1\nplain 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 2,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\n",
L"1\ncolor 2",
/*expected_count*/ 2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\n",
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 2,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\n",
L"1\ncolor 2\noverline 1\noverline 2",
/*expected_count*/ 2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\n",
L"1\ncolor 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 2,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through 2\n",
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through 2",
/*expected_count*/ 2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\n",
L"1\ncolor 2\noverline 1\noverline 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1776,14 +1775,14 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\n",
L"2\nsup 1\nsup 2",
/*expected_count*/ 2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through 2\n",
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1791,7 +1790,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\nbold 1\nbold 2\n",
L"2\nsup 1\nsup 2\nbold 1\nbold 2",
/*expected_count*/ 2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1799,7 +1798,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\n",
L"2\nsup 1\nsup 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1807,7 +1806,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family 2\n",
L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family 2",
/*expected_count*/ 2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1815,7 +1814,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\nbold 1\nbold 2\n",
L"2\nsup 1\nsup 2\nbold 1\nbold 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1824,7 +1823,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family "
L"2\nspelling 1\nspelling two\n",
L"2\nspelling 1\nspelling two",
/*expected_count*/ 2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1832,7 +1831,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
/*expected_text*/
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family 2\n",
L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family 2",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
@ -1850,7 +1849,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor "
L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through "
L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family "
L"2\nspelling 1\nspelling two\n",
L"2\nspelling 1\nspelling two",
/*expected_count*/ -1);
}
@ -3564,7 +3563,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
"<div><h2>Second Heading</h2><span>\nParagraph Two</span></div>";
const std::vector<const wchar_t*> format_units = {
L"First Heading", L"\nParagraph One\n", L"Second Heading",
L"First Heading", L"\nParagraph One", L"Second Heading",
L"\nParagraph Two"};
AssertMoveByUnitForMarkup(TextUnit_Format, html_markup, format_units);
@ -3584,7 +3583,7 @@ IN_PROC_BROWSER_TEST_F(
</html>)HTML";
const std::vector<const wchar_t*> format_units = {
L"First Heading", L"\nParagraph One\n", L"Second Heading",
L"First Heading", L"\nParagraph One", L"Second Heading",
L"\nParagraph Two"};
AssertMoveByUnitForMarkup(TextUnit_Format, html_markup, format_units);

@ -917,9 +917,8 @@ class AXPosition {
AXBoundaryType GetFormatStartBoundaryType() const {
// Since formats are stored on text anchors, the start of a format boundary
// must be at the start of an anchor.
if (IsNullPosition() || !AtStartOfAnchor()) {
if (IsNullPosition() || !AtStartOfAnchor())
return AXBoundaryType::kNone;
}
// Treat the first iterable node as a format boundary.
if (CreatePreviousLeafTreePosition(
@ -929,9 +928,8 @@ class AXPosition {
}
// Ignored positions cannot be format boundaries.
if (IsIgnored()) {
if (IsIgnored())
return AXBoundaryType::kNone;
}
// Iterate over anchors until a format boundary is found. This will return a
// null position upon crossing a boundary. Make sure the previous position
@ -949,31 +947,14 @@ class AXPosition {
}
bool AtStartOfFormat() const {
AXPositionInstance text_position = AsLeafTextPosition();
switch (text_position->kind_) {
case AXPositionKind::NULL_POSITION:
return false;
case AXPositionKind::TREE_POSITION:
NOTREACHED();
case AXPositionKind::TEXT_POSITION: {
const std::vector<int32_t>& format_starts =
text_position->GetFormatStartOffsets();
if (format_starts.size() <= 1) {
return GetFormatStartBoundaryType() != AXBoundaryType::kNone;
}
return base::Contains(format_starts,
int32_t{text_position->text_offset_});
}
}
return GetFormatStartBoundaryType() != AXBoundaryType::kNone;
}
AXBoundaryType GetFormatEndBoundaryType() const {
// Since formats are stored on text anchors, the end of a format break must
// be at the end of an anchor.
if (IsNullPosition() || !AtEndOfAnchor()) {
if (IsNullPosition() || !AtEndOfAnchor())
return AXBoundaryType::kNone;
}
// Treat the last iterable node as a format boundary
if (CreateNextLeafTreePosition(
@ -982,9 +963,8 @@ class AXPosition {
return AXBoundaryType::kContentEnd;
// Ignored positions cannot be format boundaries.
if (IsIgnored()) {
if (IsIgnored())
return AXBoundaryType::kNone;
}
// Iterate over anchors until a format boundary is found. This will return a
// null position upon crossing a boundary. Make sure the next position is
@ -1002,23 +982,7 @@ class AXPosition {
}
bool AtEndOfFormat() const {
AXPositionInstance text_position = AsLeafTextPosition();
switch (text_position->kind_) {
case AXPositionKind::NULL_POSITION:
return false;
case AXPositionKind::TREE_POSITION:
NOTREACHED();
case AXPositionKind::TEXT_POSITION: {
const std::vector<int32_t>& format_ends =
text_position->GetFormatEndOffsets();
if (format_ends.size() <= 1) {
return GetFormatEndBoundaryType() != AXBoundaryType::kNone;
}
return base::Contains(format_ends,
int32_t{text_position->text_offset_});
}
}
return GetFormatEndBoundaryType() != AXBoundaryType::kNone;
}
bool AtStartOfSentence() const {
@ -3285,8 +3249,7 @@ class AXPosition {
return CreateBoundaryStartPosition(
options, ax::mojom::MoveDirection::kForward,
base::BindRepeating(&AtStartOfFormatPredicate),
base::BindRepeating(&AtEndOfFormatPredicate),
base::BindRepeating(&GetFormatStartOffsetsFunc));
base::BindRepeating(&AtEndOfFormatPredicate));
}
AXPositionInstance CreatePreviousFormatStartPosition(
@ -3294,8 +3257,7 @@ class AXPosition {
return CreateBoundaryStartPosition(
options, ax::mojom::MoveDirection::kBackward,
base::BindRepeating(&AtStartOfFormatPredicate),
base::BindRepeating(&AtEndOfFormatPredicate),
base::BindRepeating(&GetFormatStartOffsetsFunc));
base::BindRepeating(&AtEndOfFormatPredicate));
}
AXPositionInstance CreateNextFormatEndPosition(
@ -3303,8 +3265,7 @@ class AXPosition {
return CreateBoundaryEndPosition(
options, ax::mojom::MoveDirection::kForward,
base::BindRepeating(&AtStartOfFormatPredicate),
base::BindRepeating(&AtEndOfFormatPredicate),
base::BindRepeating(&GetFormatEndOffsetsFunc));
base::BindRepeating(&AtEndOfFormatPredicate));
}
AXPositionInstance CreatePreviousFormatEndPosition(
@ -3312,8 +3273,7 @@ class AXPosition {
return CreateBoundaryEndPosition(
options, ax::mojom::MoveDirection::kBackward,
base::BindRepeating(&AtStartOfFormatPredicate),
base::BindRepeating(&AtEndOfFormatPredicate),
base::BindRepeating(&GetFormatEndOffsetsFunc));
base::BindRepeating(&AtEndOfFormatPredicate));
}
AXPositionInstance CreateNextSentenceStartPosition(
@ -4914,143 +4874,6 @@ class AXPosition {
ax::mojom::IntListAttribute::kWordEnds);
}
const std::vector<int32_t>& GetFormatStartOffsets() const {
if (IsNullPosition()) {
static const base::NoDestructor<std::vector<int32_t>> empty_format_starts;
return *empty_format_starts;
}
DCHECK(GetAnchor());
std::vector<int32_t> format_starts;
format_starts.push_back(0);
// Format is almost always consistent throughout any node -- the only
// exception are inline text boxes with CSS highlights. Therefore, unless
// the node is an inline text box with CSS highlights, we can assume the
// node's format starts only at index 0.
if (GetAnchor()->GetRole() != ax::mojom::Role::kInlineTextBox) {
static const base::NoDestructor<std::vector<int32_t>> format_starts_copy(
std::move(format_starts));
return *format_starts_copy;
}
AXNode* parent = GetAnchor()->GetUnignoredParent();
const std::vector<int32_t>& marker_types =
parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
const std::vector<int32_t>& highlight_types = parent->GetIntListAttribute(
ax::mojom::IntListAttribute::kHighlightTypes);
// Since, there are no highlights, there is no possibility of any spelling
// or grammar highlights.
if (highlight_types.empty()) {
static const base::NoDestructor<std::vector<int32_t>> format_starts_copy(
std::move(format_starts));
return *format_starts_copy;
}
CHECK_EQ(marker_types.size(), highlight_types.size());
const std::vector<int>& marker_starts =
parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
const std::vector<int>& marker_ends =
parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);
CHECK_EQ(marker_types.size(), marker_starts.size());
CHECK_EQ(marker_types.size(), marker_ends.size());
int text_length = GetAnchor()->GetTextContentLengthUTF16();
for (size_t i = 0; i < marker_types.size(); ++i) {
if (HasSpellingOrGrammarErrorHighlight(
static_cast<ax::mojom::MarkerType>(marker_types[i]),
static_cast<ax::mojom::HighlightType>(highlight_types[i]))) {
if (marker_starts[i] != 0) { // 0 is already added
format_starts.push_back(marker_starts[i]);
}
if (marker_ends[i] < text_length - 1) {
format_starts.push_back(marker_ends[i]);
}
}
}
static const base::NoDestructor<std::vector<int32_t>> format_starts_copy(
std::move(format_starts));
return *format_starts_copy;
}
const std::vector<int32_t>& GetFormatEndOffsets() const {
if (IsNullPosition()) {
static const base::NoDestructor<std::vector<int32_t>> empty_format_ends;
return *empty_format_ends;
}
DCHECK(GetAnchor());
int text_length = GetAnchor()->GetTextContentLengthUTF16();
std::vector<int32_t> format_ends;
format_ends.push_back(text_length);
// Format is almost always consistent throughout any node -- the only
// exception are inline text boxes with CSS highlights. Therefore, unless
// the node is an inline text box with CSS highlights, we can assume the
// node's format ends only at the text length.
if (GetAnchor()->GetRole() != ax::mojom::Role::kInlineTextBox) {
static const base::NoDestructor<std::vector<int32_t>> format_ends_copy(
std::move(format_ends));
return *format_ends_copy;
}
AXNode* parent = GetAnchor()->GetUnignoredParent();
const std::vector<int32_t>& marker_types =
parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
const std::vector<int32_t>& highlight_types = parent->GetIntListAttribute(
ax::mojom::IntListAttribute::kHighlightTypes);
// Since, there are no highlights, there is no possibility of any spelling
// or grammar highlights.
if (highlight_types.empty()) {
static const base::NoDestructor<std::vector<int32_t>> format_ends_copy(
std::move(format_ends));
return *format_ends_copy;
}
CHECK_EQ(marker_types.size(), highlight_types.size());
const std::vector<int>& marker_starts =
parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
const std::vector<int>& marker_ends =
parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);
CHECK_EQ(marker_types.size(), marker_starts.size());
CHECK_EQ(marker_types.size(), marker_ends.size());
format_ends.clear();
for (size_t i = 0; i < marker_types.size(); ++i) {
if (HasSpellingOrGrammarErrorHighlight(
static_cast<ax::mojom::MarkerType>(marker_types[i]),
static_cast<ax::mojom::HighlightType>(highlight_types[i]))) {
if (marker_starts[i] > 0) {
format_ends.push_back(marker_starts[i]);
}
format_ends.push_back(marker_ends[i]);
}
}
if (format_ends.empty() || format_ends.back() != text_length) {
format_ends.push_back(text_length);
}
static const base::NoDestructor<std::vector<int32_t>> format_ends_copy(
std::move(format_ends));
return *format_ends_copy;
}
static bool HasSpellingOrGrammarErrorHighlight(
ax::mojom::MarkerType marker_type,
ax::mojom::HighlightType highlight_type) {
return marker_type == ax::mojom::MarkerType::kHighlight &&
(highlight_type == ax::mojom::HighlightType::kSpellingError ||
highlight_type == ax::mojom::HighlightType::kGrammarError);
}
AXNodeID GetNextOnLineID() const {
if (IsNullPosition())
return kInvalidAXNodeID;
@ -5706,16 +5529,6 @@ class AXPosition {
return position->GetWordEndOffsets();
}
static const std::vector<int32_t>& GetFormatStartOffsetsFunc(
const AXPositionInstance& position) {
return position->GetFormatStartOffsets();
}
static const std::vector<int32_t>& GetFormatEndOffsetsFunc(
const AXPositionInstance& position) {
return position->GetFormatEndOffsets();
}
// Creates an ancestor equivalent position at the root node of this position's
// accessibility tree, e.g. at the root of the current iframe (out-of-process
// or not), PDF plugin, Views tree, dialog (native, ARIA or HTML), window, or

@ -884,7 +884,8 @@ HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnitImpl(
MoveEndpointByCharacter(position_to_move, count, units_moved);
break;
case TextUnit_Format:
new_position = MoveEndpointByFormat(position_to_move, count, units_moved);
new_position = MoveEndpointByFormat(position_to_move, is_start_endpoint,
count, units_moved);
break;
case TextUnit_Word:
new_position = MoveEndpointByWord(position_to_move, count, units_moved);
@ -1362,11 +1363,14 @@ AXPlatformNodeTextRangeProviderWin::MoveEndpointByLine(
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
AXPlatformNodeTextRangeProviderWin::MoveEndpointByFormat(
const AXPositionInstance& endpoint,
const bool is_start_endpoint,
const int count,
int* units_moved) {
return MoveEndpointByUnitHelper(std::move(endpoint),
ax::mojom::TextBoundary::kFormatStart, count,
units_moved);
is_start_endpoint
? ax::mojom::TextBoundary::kFormatStart
: ax::mojom::TextBoundary::kFormatEnd,
count, units_moved);
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance

@ -163,6 +163,7 @@ class COMPONENT_EXPORT(AX_PLATFORM) __declspec(uuid(
const int count,
int* units_moved);
AXPositionInstance MoveEndpointByFormat(const AXPositionInstance& endpoint,
const bool is_start_endpoint,
const int count,
int* units_moved);
AXPositionInstance MoveEndpointByDocument(const AXPositionInstance& endpoint,

@ -804,19 +804,8 @@ class AXPlatformNodeTextRangeProviderTest : public AXPlatformNodeWinTest {
AXNodeData paragraph4_text_data;
paragraph4_text_data.id = 17;
paragraph4_text_data.role = ax::mojom::Role::kInlineTextBox;
paragraph4_text_data.SetName("Paraaagraph 4");
// Marking `Paraaagraph` as a misspelled word modeled as a CSS highlight.
paragraph4_data.AddIntListAttribute(
ax::mojom::IntListAttribute::kMarkerTypes,
{(int)ax::mojom::MarkerType::kHighlight});
paragraph4_data.AddIntListAttribute(
ax::mojom::IntListAttribute::kHighlightTypes,
{(int)ax::mojom::HighlightType::kSpellingError});
paragraph4_data.AddIntListAttribute(
ax::mojom::IntListAttribute::kMarkerStarts, {0});
paragraph4_data.AddIntListAttribute(
ax::mojom::IntListAttribute::kMarkerEnds, {11});
paragraph4_text_data.role = ax::mojom::Role::kStaticText;
paragraph4_text_data.SetName("Paragraph 4");
paragraph4_data.child_ids = {paragraph4_text_data.id};
AXNodeData root_data;
@ -1497,7 +1486,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_TEXTRANGE_EQ(
text_range_provider,
L"Text with formatting\nStandalone line with no formatting\nbold "
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParaaagraph 4");
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4");
// https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationtextrange-expandtoenclosingunit
// Consider two consecutive text units A and B.
@ -2089,7 +2078,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveFormat) {
/*count*/ 0,
/*expected_text*/
L"Text with formatting\nStandalone line with no formatting\nbold "
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParaaagraph 4",
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4",
/*expected_count*/ 0);
// Move forward.
@ -2099,32 +2088,28 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveFormat) {
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 2,
/*expected_text*/ L"Paragraph 1\n",
/*expected_text*/ L"Paragraph 1",
/*expected_count*/ 2);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L"Paragraph 2\nParagraph 3\n",
/*expected_text*/ L"Paragraph 2\nParagraph 3",
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L"Paraaagraph",
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L" 4",
/*expected_text*/ L"Paragraph 4",
/*expected_count*/ 1);
// Trying to move past the last format should have no effect.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L" 4",
/*expected_text*/ L"Paragraph 4",
/*expected_count*/ 0);
// Move backward.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ -4,
/*expected_text*/ L"bold text\n",
/*expected_count*/ -4);
/*count*/ -3,
/*expected_text*/ L"bold text",
/*expected_count*/ -3);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ -1,
/*expected_text*/ L"\nStandalone line with no formatting\n",
@ -2157,12 +2142,8 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveFormat) {
// Test degenerate range creation at the end of the document.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 5,
/*expected_text*/ L"Paraaagraph",
/*expected_text*/ L"Paragraph 4",
/*expected_count*/ 5);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 1,
/*expected_text*/ L" 4",
/*expected_count*/ 1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
/*count*/ 1,
@ -2171,7 +2152,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveFormat) {
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
/*count*/ -1,
/*expected_text*/ L" 4",
/*expected_text*/ L"Paragraph 4",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
@ -2181,14 +2162,14 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveFormat) {
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
/*count*/ -1,
/*expected_text*/ L" 4",
/*expected_text*/ L"Paragraph 4",
/*expected_count*/ -1);
// Degenerate range moves.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ -6,
/*count*/ -5,
/*expected_text*/ L"Text with formatting",
/*expected_count*/ -6);
/*expected_count*/ -5);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
@ -2201,7 +2182,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveFormat) {
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
/*count*/ 70,
/*expected_text*/ L"",
/*expected_count*/ 4);
/*expected_count*/ 3);
// Trying to move past the last format should have no effect.
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
@ -3251,38 +3232,20 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_TEXTRANGE_EQ(
text_range_provider,
L"Text with formatting\nStandalone line with no formatting\nbold "
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParaaagraph 4");
// `Paraaagraph 4` should be broken into two separate `format` units based on
// the spelling error (modeled as CSS highlight) in corresponding AXNode.
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4");
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*count*/ -2,
/*expected_text*/
L"Text with formatting\nStandalone line with no formatting\nbold "
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParaaagraph",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/
L"Text with formatting\nStandalone line with no formatting\nbold "
L"text\nParagraph 1\nParagraph 2\nParagraph 3\n",
/*expected_count*/ -1);
L"text\nParagraph 1",
/*expected_count*/ -2);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/
L"Text with formatting\nStandalone line with no formatting\nbold "
L"text\nParagraph 1\n",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -1,
/*expected_text*/
L"Text with formatting\nStandalone line with no formatting\nbold text\n",
L"Text with formatting\nStandalone line with no formatting\nbold text",
/*expected_count*/ -1);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
@ -3306,17 +3269,17 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ 8,
/*count*/ 7,
/*expected_text*/
L"Text with formatting\nStandalone line with no formatting\nbold "
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParaaagraph 4",
/*expected_count*/ 7);
L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4",
/*expected_count*/ 6);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
/*count*/ -8,
/*expected_text*/ L"",
/*expected_count*/ -7);
/*expected_count*/ -6);
}
TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderCompare) {