commit ea44e6826a9435364e3f0322fabfe189d466cbfc
parent 0615fb365e73e9435be3bc88cefad6d990d298a2
Author: James Teh <jteh@mozilla.com>
Date: Fri, 19 Dec 2025 07:38:08 +0000
Bug 2006816 part 3: Fix TextLeafPoint::GetCaret for LocalAccessible when the caret is deeper than a direct child of the origin container. r=eeejay
Previously, it used HyperTextAccessible::CaretOffset on the origin container, then ToTextLeafPoint on that offset.
However, offsets can only refer to a direct child.
This meant that ToTextLeafPoint was descending into the direct child using offset 0; i.e. returning the start of the child's content.
This was completely incorrect when the caret wasn't at the start of the child's content.
To fix this, first try using the caret cached by SelectionManager, as that avoids redundant transformation of the caret for each descendant.
If that fails, traverse using CaretOffset on each descendant until we can't go any further.
Differential Revision: https://phabricator.services.mozilla.com/D276963
Diffstat:
2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/accessible/base/TextLeafRange.cpp b/accessible/base/TextLeafRange.cpp
@@ -1084,18 +1084,49 @@ TextLeafPoint TextLeafPoint::FindNextWordStartSameAcc(
/* static */
TextLeafPoint TextLeafPoint::GetCaret(Accessible* aAcc) {
if (LocalAccessible* localAcc = aAcc->AsLocal()) {
- // Use HyperTextAccessible::CaretOffset. Eventually, we'll want to move
- // that code into TextLeafPoint, but existing code depends on it living in
- // HyperTextAccessible (including caret events).
- HyperTextAccessible* ht = HyperTextFor(localAcc);
- if (!ht) {
- return TextLeafPoint();
- }
- int32_t htOffset = ht->CaretOffset();
- if (htOffset == -1) {
- return TextLeafPoint();
+ // Use the HyperTextAccessible caret offset. Eventually, we'll want to move
+ // that code into TextLeafPoint, but existing code depends on it being based
+ // on HyperTextAccessible (including caret events).
+ int32_t htOffset = -1;
+ // Try the cached caret.
+ HyperTextAccessible* ht = SelectionMgr()->AccessibleWithCaret(&htOffset);
+ if (ht) {
+ MOZ_ASSERT(htOffset != -1);
+ } else {
+ // There is no cached caret, but there might still be a caret; see bug
+ // 1425112.
+ ht = HyperTextFor(localAcc);
+ if (!ht) {
+ return TextLeafPoint();
+ }
+ // An offset can only refer to a child, but the caret might be in a deeper
+ // descendant. Walk to the deepest HyperTextAccessible using CaretOffset.
+ bool gotCaret = false;
+ for (;;) {
+ htOffset = ht->CaretOffset();
+ if (htOffset == -1) {
+ break;
+ }
+ // A descendant might return -1 in some cases, but it's okay as long as
+ // the call on the outermost HyperTextAccessible succeeds.
+ gotCaret = true;
+ LocalAccessible* child = ht->GetChildAtOffset(htOffset);
+ if (!child) {
+ break;
+ }
+ if (HyperTextAccessible* childHt = child->AsHyperText()) {
+ ht = childHt;
+ } else {
+ break;
+ }
+ }
+ if (!gotCaret) {
+ return TextLeafPoint();
+ }
}
- TextLeafPoint point = ht->ToTextLeafPoint(htOffset);
+ // As noted above, CaretOffset on a descendant might return -1. Use 0 in
+ // that case.
+ TextLeafPoint point = ht->ToTextLeafPoint(htOffset == -1 ? 0 : htOffset);
if (!point) {
// Bug 1905021: This happens in the wild, but we don't understand why.
// ToTextLeafPoint should only fail if the HyperText offset is invalid,
diff --git a/accessible/tests/browser/text/browser_text_caret.js b/accessible/tests/browser/text/browser_text_caret.js
@@ -734,23 +734,23 @@ ij
moved = waitForEvent(EVENT_TEXT_CARET_MOVED, blockquote);
blockquote.caretOffset = 3;
await moved;
- testAttrs(docAcc, { "line-number": "3" }, true, true);
+ testAttrs(docAcc, { "line-number": "3" }, true);
info("Moving caret to f");
moved = waitForEvent(EVENT_TEXT_CARET_MOVED, blockquote);
blockquote.caretOffset = 4;
await moved;
- testAttrs(docAcc, { "line-number": "3" }, true, true);
+ testAttrs(docAcc, { "line-number": "3" }, true);
info("moving caret to g");
const p = findAccessibleChildByID(docAcc, "p", [nsIAccessibleText]);
moved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
p.caretOffset = 0;
await moved;
- testAttrs(docAcc, { "line-number": "4" }, true, true);
+ testAttrs(docAcc, { "line-number": "4" }, true);
info("moving caret to h");
moved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
p.caretOffset = 1;
await moved;
- testAttrs(docAcc, { "line-number": "4" }, true, true);
+ testAttrs(docAcc, { "line-number": "4" }, true);
info("moving caret to i");
moved = waitForEvent(EVENT_TEXT_CARET_MOVED, docAcc);
docAcc.caretOffset = 4;