Bug 1814220 - Allow ignoring list item markers in TextLeafPoint::FindBoundary. r=Jamie

Refactor in FindBoundary to allow easier skipping of leaves in the loop.

Differential Revision: https://phabricator.services.mozilla.com/D168445
This commit is contained in:
Eitan Isaacson 2023-02-22 04:17:31 +00:00
Родитель 64bc2ac15b
Коммит 8bf95c7d17
5 изменённых файлов: 310 добавлений и 59 удалений

Просмотреть файл

@ -103,6 +103,9 @@ static uint32_t ContentToRenderedOffset(LocalAccessible* aAcc,
class LeafRule : public PivotRule {
public:
explicit LeafRule(bool aIgnoreListItemMarker)
: mIgnoreListItemMarker(aIgnoreListItemMarker) {}
virtual uint16_t Match(Accessible* aAcc) override {
if (aAcc->IsOuterDoc()) {
// Treat an embedded doc as a single character in this document, but do
@ -110,6 +113,12 @@ class LeafRule : public PivotRule {
return nsIAccessibleTraversalRule::FILTER_MATCH |
nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
}
if (mIgnoreListItemMarker && aAcc->Role() == roles::LISTITEM_MARKER) {
// Ignore list item markers if configured to do so.
return nsIAccessibleTraversalRule::FILTER_IGNORE;
}
// We deliberately include Accessibles such as empty input elements and
// empty containers, as these can be at the start of a line.
if (!aAcc->HasChildren()) {
@ -117,6 +126,9 @@ class LeafRule : public PivotRule {
}
return nsIAccessibleTraversalRule::FILTER_IGNORE;
}
private:
bool mIgnoreListItemMarker;
};
static HyperTextAccessible* HyperTextFor(LocalAccessible* aAcc) {
@ -128,11 +140,12 @@ static HyperTextAccessible* HyperTextFor(LocalAccessible* aAcc) {
return nullptr;
}
static Accessible* NextLeaf(Accessible* aOrigin, bool aIsEditable = false) {
static Accessible* NextLeaf(Accessible* aOrigin, bool aIsEditable = false,
bool aIgnoreListItemMarker = false) {
MOZ_ASSERT(aOrigin);
Accessible* doc = nsAccUtils::DocumentFor(aOrigin);
Pivot pivot(doc);
auto rule = LeafRule();
auto rule = LeafRule(aIgnoreListItemMarker);
Accessible* leaf = pivot.Next(aOrigin, rule);
if (aIsEditable && leaf) {
return leaf->Parent() && (leaf->Parent()->State() & states::EDITABLE)
@ -142,11 +155,12 @@ static Accessible* NextLeaf(Accessible* aOrigin, bool aIsEditable = false) {
return leaf;
}
static Accessible* PrevLeaf(Accessible* aOrigin, bool aIsEditable = false) {
static Accessible* PrevLeaf(Accessible* aOrigin, bool aIsEditable = false,
bool aIgnoreListItemMarker = false) {
MOZ_ASSERT(aOrigin);
Accessible* doc = nsAccUtils::DocumentFor(aOrigin);
Pivot pivot(doc);
auto rule = LeafRule();
auto rule = LeafRule(aIgnoreListItemMarker);
Accessible* leaf = pivot.Prev(aOrigin, rule);
if (aIsEditable && leaf) {
return leaf->Parent() && (leaf->Parent()->State() & states::EDITABLE)
@ -611,6 +625,12 @@ bool TextLeafPoint::IsDocEdge(nsDirection aDirection) const {
!NextLeaf(mAcc);
}
bool TextLeafPoint::IsLeafAfterListItemMarker() const {
Accessible* prev = PrevLeaf(mAcc);
return prev && prev->Role() == roles::LISTITEM_MARKER &&
prev->Parent()->IsAncestorOf(mAcc);
}
bool TextLeafPoint::IsEmptyLastLine() const {
if (mAcc->IsHTMLBr() && mOffset == 1) {
return true;
@ -759,14 +779,40 @@ TextLeafPoint TextLeafPoint::FindLineStartSameRemoteAcc(
return TextLeafPoint(mAcc, lines->ElementAt(index));
}
TextLeafPoint TextLeafPoint::FindLineStartSameAcc(nsDirection aDirection,
bool aIncludeOrigin) const {
if (mAcc->IsLocal()) {
return aDirection == eDirNext
? FindNextLineStartSameLocalAcc(aIncludeOrigin)
: FindPrevLineStartSameLocalAcc(aIncludeOrigin);
TextLeafPoint TextLeafPoint::FindLineStartSameAcc(
nsDirection aDirection, bool aIncludeOrigin,
bool aIgnoreListItemMarker) const {
TextLeafPoint boundary;
if (aIgnoreListItemMarker && aIncludeOrigin && mOffset == 0 &&
IsLeafAfterListItemMarker()) {
// If:
// (1) we are ignoring list markers
// (2) we should include origin
// (3) we are at the start of a leaf that follows a list item marker
// ...then return this point.
return *this;
}
return FindLineStartSameRemoteAcc(aDirection, aIncludeOrigin);
if (mAcc->IsLocal()) {
boundary = aDirection == eDirNext
? FindNextLineStartSameLocalAcc(aIncludeOrigin)
: FindPrevLineStartSameLocalAcc(aIncludeOrigin);
} else {
boundary = FindLineStartSameRemoteAcc(aDirection, aIncludeOrigin);
}
if (aIgnoreListItemMarker && aDirection == eDirPrevious && !boundary &&
mOffset != 0 && IsLeafAfterListItemMarker()) {
// If:
// (1) we are ignoring list markers
// (2) we are searching backwards in accessible
// (3) we did not find a line start before this point
// (4) we are in a leaf that follows a list item marker
// ...then return the first point in this accessible.
boundary = TextLeafPoint(mAcc, 0);
}
return boundary;
}
TextLeafPoint TextLeafPoint::FindPrevWordStartSameAcc(
@ -977,31 +1023,32 @@ TextLeafPoint TextLeafPoint::FindBoundary(AccessibleTextBoundary aBoundaryType,
// is positioned on an empty line at the end of a textarea.
return *this;
}
if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR &&
(aFlags & BoundaryFlags::eIncludeOrigin)) {
return *this;
}
TextLeafPoint searchFrom = *this;
bool includeOrigin = !!(aFlags & BoundaryFlags::eIncludeOrigin);
for (;;) {
bool ignoreListItemMarker = !!(aFlags & BoundaryFlags::eIgnoreListItemMarker);
Accessible* lastAcc = nullptr;
for (TextLeafPoint searchFrom = *this; searchFrom;
searchFrom = searchFrom.NeighborLeafPoint(
aDirection, inEditableAndStopInIt, ignoreListItemMarker)) {
lastAcc = searchFrom.mAcc;
if (ignoreListItemMarker && searchFrom == *this &&
searchFrom.mAcc->Role() == roles::LISTITEM_MARKER) {
continue;
}
TextLeafPoint boundary;
// Search for the boundary within the current Accessible.
switch (aBoundaryType) {
case nsIAccessibleText::BOUNDARY_CHAR:
if (aDirection == eDirPrevious && searchFrom.mOffset > 0) {
if (includeOrigin) {
boundary = searchFrom;
} else if (aDirection == eDirPrevious && searchFrom.mOffset > 0) {
boundary.mAcc = searchFrom.mAcc;
boundary.mOffset = searchFrom.mOffset - 1;
} else if (aDirection == eDirNext) {
if (includeOrigin) {
// We've moved to the next leaf. That means we've set the offset
// to 0, so we're already at the next character.
boundary = searchFrom;
} else if (searchFrom.mOffset + 1 <
static_cast<int32_t>(
nsAccUtils::TextLength(searchFrom.mAcc))) {
boundary.mAcc = searchFrom.mAcc;
boundary.mOffset = searchFrom.mOffset + 1;
}
} else if (aDirection == eDirNext &&
searchFrom.mOffset + 1 <
static_cast<int32_t>(
nsAccUtils::TextLength(searchFrom.mAcc))) {
boundary.mAcc = searchFrom.mAcc;
boundary.mOffset = searchFrom.mOffset + 1;
}
break;
case nsIAccessibleText::BOUNDARY_WORD_START:
@ -1012,10 +1059,12 @@ TextLeafPoint TextLeafPoint::FindBoundary(AccessibleTextBoundary aBoundaryType,
}
break;
case nsIAccessibleText::BOUNDARY_LINE_START:
boundary = searchFrom.FindLineStartSameAcc(aDirection, includeOrigin);
boundary = searchFrom.FindLineStartSameAcc(aDirection, includeOrigin,
ignoreListItemMarker);
break;
case nsIAccessibleText::BOUNDARY_PARAGRAPH:
boundary = searchFrom.FindParagraphSameAcc(aDirection, includeOrigin);
boundary = searchFrom.FindParagraphSameAcc(aDirection, includeOrigin,
ignoreListItemMarker);
break;
default:
MOZ_ASSERT_UNREACHABLE();
@ -1024,30 +1073,18 @@ TextLeafPoint TextLeafPoint::FindBoundary(AccessibleTextBoundary aBoundaryType,
if (boundary) {
return boundary;
}
// We didn't find it in this Accessible, so try the previous/next leaf.
Accessible* acc = aDirection == eDirPrevious
? PrevLeaf(searchFrom.mAcc, inEditableAndStopInIt)
: NextLeaf(searchFrom.mAcc, inEditableAndStopInIt);
if (!acc) {
// No further leaf was found. Use the start/end of the first/last leaf.
return TextLeafPoint(
searchFrom.mAcc,
aDirection == eDirPrevious
? 0
: static_cast<int32_t>(nsAccUtils::TextLength(searchFrom.mAcc)));
}
searchFrom.mAcc = acc;
// When searching backward, search from the end of the text in the
// Accessible. When searching forward, search from the start of the text.
searchFrom.mOffset = aDirection == eDirPrevious
? static_cast<int32_t>(nsAccUtils::TextLength(acc))
: 0;
// The start/end of the Accessible might be a boundary. If so, we must stop
// on it.
includeOrigin = true;
}
MOZ_ASSERT_UNREACHABLE();
return TextLeafPoint();
MOZ_ASSERT(lastAcc);
// No further leaf was found. Use the start/end of the first/last leaf.
return TextLeafPoint(
lastAcc, aDirection == eDirPrevious
? 0
: static_cast<int32_t>(nsAccUtils::TextLength(lastAcc)));
}
TextLeafPoint TextLeafPoint::FindLineEnd(nsDirection aDirection,
@ -1166,13 +1203,21 @@ TextLeafPoint TextLeafPoint::FindWordEnd(nsDirection aDirection,
return boundary;
}
TextLeafPoint TextLeafPoint::FindParagraphSameAcc(nsDirection aDirection,
bool aIncludeOrigin) const {
TextLeafPoint TextLeafPoint::FindParagraphSameAcc(
nsDirection aDirection, bool aIncludeOrigin,
bool aIgnoreListItemMarker) const {
if (aIncludeOrigin && IsDocEdge(eDirPrevious)) {
// The top of the document is a paragraph boundary.
return *this;
}
if (aIgnoreListItemMarker && aIncludeOrigin && mOffset == 0 &&
IsLeafAfterListItemMarker()) {
// If we are in a list item and the previous sibling is
// a bullet, the 0 offset in this leaf is a line start.
return *this;
}
if (mAcc->IsTextLeaf() &&
// We don't want to copy strings unnecessarily. See below for the context
// of these individual conditions.
@ -1198,6 +1243,14 @@ TextLeafPoint TextLeafPoint::FindParagraphSameAcc(nsDirection aDirection,
}
}
if (aIgnoreListItemMarker && mOffset > 0 && aDirection == eDirPrevious &&
IsLeafAfterListItemMarker()) {
// No line breaks were found in the preceding text to this offset.
// If we are in a list item and the previous sibling is
// a bullet, the 0 offset in this leaf is a line start.
return TextLeafPoint(mAcc, 0);
}
// Check whether this Accessible begins a paragraph.
if ((!aIncludeOrigin && mOffset == 0) ||
(aDirection == eDirNext && mOffset > 0)) {
@ -1393,6 +1446,22 @@ TextLeafPoint TextLeafPoint::FindSpellingErrorSameAcc(
return TextLeafPoint(mAcc, (*spellingErrors)[index]);
}
TextLeafPoint TextLeafPoint::NeighborLeafPoint(
nsDirection aDirection, bool aIsEditable,
bool aIgnoreListItemMarker) const {
Accessible* acc = aDirection == eDirPrevious
? PrevLeaf(mAcc, aIsEditable, aIgnoreListItemMarker)
: NextLeaf(mAcc, aIsEditable, aIgnoreListItemMarker);
if (!acc) {
return TextLeafPoint();
}
return TextLeafPoint(
acc, aDirection == eDirPrevious
? static_cast<int32_t>(nsAccUtils::TextLength(acc)) - 1
: 0);
}
/* static */
nsTArray<int32_t> TextLeafPoint::GetSpellingErrorOffsets(
LocalAccessible* aAcc) {

Просмотреть файл

@ -95,6 +95,9 @@ class TextLeafPoint final {
eIncludeOrigin = 1 << 0,
// If current point is in editable, return point within samme editable.
eStopInEditable = 1 << 1,
// Skip over list items in searches and don't consider them line or
// paragraph starts.
eIgnoreListItemMarker = 1 << 2,
};
/**
@ -187,8 +190,9 @@ class TextLeafPoint final {
bool IsSpace() const;
bool IsParagraphStart() const {
return mOffset == 0 && FindParagraphSameAcc(eDirPrevious, true);
bool IsParagraphStart(bool aIgnoreListItemMarker = false) const {
return mOffset == 0 &&
FindParagraphSameAcc(eDirPrevious, true, aIgnoreListItemMarker);
}
private:
@ -196,6 +200,8 @@ class TextLeafPoint final {
bool IsDocEdge(nsDirection aDirection) const;
bool IsLeafAfterListItemMarker() const;
char16_t GetChar() const;
TextLeafPoint FindLineStartSameRemoteAcc(nsDirection aDirection,
@ -206,13 +212,15 @@ class TextLeafPoint final {
*is local or remote.
*/
TextLeafPoint FindLineStartSameAcc(nsDirection aDirection,
bool aIncludeOrigin) const;
bool aIncludeOrigin,
bool aIgnoreListItemMarker = false) const;
TextLeafPoint FindLineEnd(nsDirection aDirection, BoundaryFlags aFlags) const;
TextLeafPoint FindWordEnd(nsDirection aDirection, BoundaryFlags aFlags) const;
TextLeafPoint FindParagraphSameAcc(nsDirection aDirection,
bool aIncludeOrigin) const;
bool aIncludeOrigin,
bool aIgnoreListItemMarker = false) const;
bool IsInSpellingError() const;
@ -223,6 +231,11 @@ class TextLeafPoint final {
*/
TextLeafPoint FindSpellingErrorSameAcc(nsDirection aDirection,
bool aIncludeOrigin) const;
// Return the point immediately succeeding or preceding this leaf depending
// on given direction.
TextLeafPoint NeighborLeafPoint(nsDirection aDirection, bool aIsEditable,
bool aIgnoreListItemMarker) const;
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TextLeafPoint::BoundaryFlags)

Просмотреть файл

@ -38,4 +38,6 @@ interface nsIAccessibleTextLeafPoint : nsISupports
const unsigned long BOUNDARY_FLAG_INCLUDE_ORIGIN = 0x1;
// Do not search past end of editables.
const unsigned long BOUNDARY_FLAG_STOP_IN_EDITABLE = 0x2;
// Skip over list items in searches and don't consider them line or paragraph starts.
const unsigned long BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER = 0x4;
};

Просмотреть файл

@ -294,3 +294,168 @@ on a <span style="display: block;">rug</span></p>
},
{ chrome: true, topLevel: isCacheEnabled, iframe: false, remoteIframe: false }
);
// Test for skipping list item bullets.
addAccessibleTask(
`<ul>
<li>One</li>
<li>Two</li>
<li style="white-space: pre-line;">Three
Four</li>
</ul>`,
function(browser, docAcc) {
const firstPoint = createTextLeafPoint(docAcc, 0);
const lastPoint = createTextLeafPoint(docAcc, kTextEndOffset);
const firstNonMarkerPoint = firstPoint.findBoundary(
BOUNDARY_CHAR,
DIRECTION_NEXT,
BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER | BOUNDARY_FLAG_INCLUDE_ORIGIN
);
Assert.deepEqual(
readablePoint(firstNonMarkerPoint),
["One", 0],
"First non-marker point is correct"
);
const expectedParagraphStart = [
["One", 0],
["Two", 0],
["Three\nFour", 0],
["Three\nFour", 6],
];
testBoundarySequence(
firstPoint,
BOUNDARY_PARAGRAPH,
DIRECTION_NEXT,
[...expectedParagraphStart, readablePoint(lastPoint)],
"Forward BOUNDARY_PARAGRAPH skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
testBoundarySequence(
lastPoint,
BOUNDARY_PARAGRAPH,
DIRECTION_PREVIOUS,
[...expectedParagraphStart].reverse(),
"Backward BOUNDARY_PARAGRAPH skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
const expectedCharSequence = [
["One", 0],
["One", 1],
["One", 2],
["Two", 0],
["Two", 1],
["Two", 2],
["Three\nFour", 0],
["Three\nFour", 1],
["Three\nFour", 2],
["Three\nFour", 3],
["Three\nFour", 4],
["Three\nFour", 5],
["Three\nFour", 6],
["Three\nFour", 7],
["Three\nFour", 8],
["Three\nFour", 9],
["Three\nFour", 10],
];
testBoundarySequence(
firstPoint,
BOUNDARY_CHAR,
DIRECTION_NEXT,
expectedCharSequence,
"Forward BOUNDARY_CHAR skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
testBoundarySequence(
lastPoint,
BOUNDARY_CHAR,
DIRECTION_PREVIOUS,
[...expectedCharSequence].reverse(),
"Backward BOUNDARY_CHAR skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
const expectedWordStartSequence = [
["One", 0],
["Two", 0],
["Three\nFour", 0],
["Three\nFour", 6],
];
testBoundarySequence(
firstPoint,
BOUNDARY_WORD_START,
DIRECTION_NEXT,
[...expectedWordStartSequence, readablePoint(lastPoint)],
"Forward BOUNDARY_WORD_START skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
testBoundarySequence(
lastPoint,
BOUNDARY_WORD_START,
DIRECTION_PREVIOUS,
[...expectedWordStartSequence].reverse(),
"Backward BOUNDARY_WORD_START skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
const expectedWordEndSequence = [
["Two", 0],
["Three\nFour", 0],
["Three\nFour", 5],
["Three\nFour", 10],
];
testBoundarySequence(
firstPoint,
BOUNDARY_WORD_END,
DIRECTION_NEXT,
expectedWordEndSequence,
"Forward BOUNDARY_WORD_END skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
testBoundarySequence(
lastPoint,
BOUNDARY_WORD_END,
DIRECTION_PREVIOUS,
[
readablePoint(firstNonMarkerPoint),
...expectedWordEndSequence,
].reverse(),
"Backward BOUNDARY_WORD_END skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
const expectedLineStartSequence = [
["One", 0],
["Two", 0],
["Three\nFour", 0],
["Three\nFour", 6],
];
testBoundarySequence(
firstPoint,
BOUNDARY_LINE_START,
DIRECTION_NEXT,
// Add last point in doc
[...expectedLineStartSequence, readablePoint(lastPoint)],
"Forward BOUNDARY_LINE_START skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
testBoundarySequence(
lastPoint,
BOUNDARY_LINE_START,
DIRECTION_PREVIOUS,
// Add last point in doc
[...expectedLineStartSequence].reverse(),
"Backward BOUNDARY_LINE_START skipping list item markers sequence is correct",
{ flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
);
},
{ chrome: true, topLevel: isCacheEnabled, iframe: false, remoteIframe: false }
);

Просмотреть файл

@ -6,8 +6,8 @@
/* exported createTextLeafPoint, DIRECTION_NEXT, DIRECTION_PREVIOUS,
BOUNDARY_FLAG_DEFAULT, BOUNDARY_FLAG_INCLUDE_ORIGIN,
BOUNDARY_FLAG_STOP_IN_EDITABLE, readablePoint, testPointEqual,
textBoundaryGenerator, testBoundarySequence */
BOUNDARY_FLAG_STOP_IN_EDITABLE, BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER,
readablePoint, testPointEqual, textBoundaryGenerator, testBoundarySequence */
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
@ -32,6 +32,8 @@ const BOUNDARY_FLAG_INCLUDE_ORIGIN =
Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_INCLUDE_ORIGIN;
const BOUNDARY_FLAG_STOP_IN_EDITABLE =
Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_STOP_IN_EDITABLE;
const BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER =
Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER;
function createTextLeafPoint(acc, offset) {
let accService = Cc["@mozilla.org/accessibilityService;1"].getService(