1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "HyperTextAccessibleBase.h"
8 #include "mozilla/a11y/Accessible.h"
9 #include "nsAccUtils.h"
10 #include "TextLeafRange.h"
11 #include "TextRange.h"
13 namespace mozilla::a11y
{
15 int32_t HyperTextAccessibleBase::GetChildIndexAtOffset(uint32_t aOffset
) const {
17 const_cast<HyperTextAccessibleBase
*>(this)->GetCachedHyperTextOffsets();
18 int32_t lastOffset
= 0;
19 const uint32_t offsetCount
= offsets
.Length();
21 if (offsetCount
> 0) {
22 lastOffset
= offsets
[offsetCount
- 1];
23 if (static_cast<int32_t>(aOffset
) < lastOffset
) {
24 // We've cached up to aOffset.
26 if (BinarySearch(offsets
, 0, offsetCount
, static_cast<int32_t>(aOffset
),
28 // aOffset is the exclusive end of a child, so return the child before
30 return static_cast<int32_t>((index
< offsetCount
- 1) ? index
+ 1
33 if (index
== offsetCount
) {
34 // aOffset is past the end of the text.
37 // index points at the exclusive end after aOffset.
38 return static_cast<int32_t>(index
);
42 // We haven't yet cached up to aOffset. Find it, caching as we go.
43 const Accessible
* thisAcc
= Acc();
44 uint32_t childCount
= thisAcc
->ChildCount();
45 // Even though we're only caching up to aOffset, it's likely that we'll
46 // eventually cache offsets for all children. Pre-allocate thus to minimize
48 offsets
.SetCapacity(childCount
);
49 while (offsets
.Length() < childCount
) {
50 Accessible
* child
= thisAcc
->ChildAt(offsets
.Length());
51 lastOffset
+= static_cast<int32_t>(nsAccUtils::TextLength(child
));
52 offsets
.AppendElement(lastOffset
);
53 if (static_cast<int32_t>(aOffset
) < lastOffset
) {
54 return static_cast<int32_t>(offsets
.Length() - 1);
58 if (static_cast<int32_t>(aOffset
) == lastOffset
) {
59 return static_cast<int32_t>(offsets
.Length() - 1);
65 Accessible
* HyperTextAccessibleBase::GetChildAtOffset(uint32_t aOffset
) const {
66 const Accessible
* thisAcc
= Acc();
67 return thisAcc
->ChildAt(GetChildIndexAtOffset(aOffset
));
70 int32_t HyperTextAccessibleBase::GetChildOffset(const Accessible
* aChild
,
71 bool aInvalidateAfter
) const {
72 const Accessible
* thisAcc
= Acc();
73 if (aChild
->Parent() != thisAcc
) {
76 int32_t index
= aChild
->IndexInParent();
80 return GetChildOffset(index
, aInvalidateAfter
);
83 int32_t HyperTextAccessibleBase::GetChildOffset(uint32_t aChildIndex
,
84 bool aInvalidateAfter
) const {
86 const_cast<HyperTextAccessibleBase
*>(this)->GetCachedHyperTextOffsets();
87 if (aChildIndex
== 0) {
88 if (aInvalidateAfter
) {
94 int32_t countCachedAfterChild
= static_cast<int32_t>(offsets
.Length()) -
95 static_cast<int32_t>(aChildIndex
);
96 if (countCachedAfterChild
> 0) {
97 // We've cached up to aChildIndex.
98 if (aInvalidateAfter
) {
99 offsets
.RemoveElementsAt(aChildIndex
, countCachedAfterChild
);
101 return offsets
[aChildIndex
- 1];
104 // We haven't yet cached up to aChildIndex. Find it, caching as we go.
105 const Accessible
* thisAcc
= Acc();
106 // Even though we're only caching up to aChildIndex, it's likely that we'll
107 // eventually cache offsets for all children. Pre-allocate thus to minimize
109 offsets
.SetCapacity(thisAcc
->ChildCount());
110 uint32_t lastOffset
= offsets
.IsEmpty() ? 0 : offsets
[offsets
.Length() - 1];
111 while (offsets
.Length() < aChildIndex
) {
112 Accessible
* child
= thisAcc
->ChildAt(offsets
.Length());
113 lastOffset
+= nsAccUtils::TextLength(child
);
114 offsets
.AppendElement(lastOffset
);
117 return offsets
[aChildIndex
- 1];
120 uint32_t HyperTextAccessibleBase::CharacterCount() const {
121 return GetChildOffset(Acc()->ChildCount());
124 index_t
HyperTextAccessibleBase::ConvertMagicOffset(int32_t aOffset
) const {
125 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT
) {
126 return CharacterCount();
129 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
130 return CaretOffset();
136 void HyperTextAccessibleBase::TextSubstring(int32_t aStartOffset
,
138 nsAString
& aText
) const {
141 index_t startOffset
= ConvertMagicOffset(aStartOffset
);
142 index_t endOffset
= ConvertMagicOffset(aEndOffset
);
143 if (!startOffset
.IsValid() || !endOffset
.IsValid() ||
144 startOffset
> endOffset
|| endOffset
> CharacterCount()) {
145 NS_ERROR("Wrong in offset");
149 int32_t startChildIdx
= GetChildIndexAtOffset(startOffset
);
150 if (startChildIdx
== -1) {
154 int32_t endChildIdx
= GetChildIndexAtOffset(endOffset
);
155 if (endChildIdx
== -1) {
159 const Accessible
* thisAcc
= Acc();
160 if (startChildIdx
== endChildIdx
) {
161 int32_t childOffset
= GetChildOffset(startChildIdx
);
162 if (childOffset
== -1) {
166 Accessible
* child
= thisAcc
->ChildAt(startChildIdx
);
167 child
->AppendTextTo(aText
, startOffset
- childOffset
,
168 endOffset
- startOffset
);
172 int32_t startChildOffset
= GetChildOffset(startChildIdx
);
173 if (startChildOffset
== -1) {
177 Accessible
* startChild
= thisAcc
->ChildAt(startChildIdx
);
178 startChild
->AppendTextTo(aText
, startOffset
- startChildOffset
);
180 for (int32_t childIdx
= startChildIdx
+ 1; childIdx
< endChildIdx
;
182 Accessible
* child
= thisAcc
->ChildAt(childIdx
);
183 child
->AppendTextTo(aText
);
186 int32_t endChildOffset
= GetChildOffset(endChildIdx
);
187 if (endChildOffset
== -1) {
191 Accessible
* endChild
= thisAcc
->ChildAt(endChildIdx
);
192 endChild
->AppendTextTo(aText
, 0, endOffset
- endChildOffset
);
195 bool HyperTextAccessibleBase::CharAt(int32_t aOffset
, nsAString
& aChar
,
196 int32_t* aStartOffset
,
197 int32_t* aEndOffset
) {
198 MOZ_ASSERT(!aStartOffset
== !aEndOffset
,
199 "Offsets should be both defined or both undefined!");
201 int32_t childIdx
= GetChildIndexAtOffset(aOffset
);
202 if (childIdx
== -1) {
206 Accessible
* child
= Acc()->ChildAt(childIdx
);
207 child
->AppendTextTo(aChar
, aOffset
- GetChildOffset(childIdx
), 1);
209 if (aStartOffset
&& aEndOffset
) {
210 *aStartOffset
= aOffset
;
211 *aEndOffset
= aOffset
+ aChar
.Length();
216 LayoutDeviceIntRect
HyperTextAccessibleBase::CharBounds(int32_t aOffset
,
217 uint32_t aCoordType
) {
218 index_t offset
= ConvertMagicOffset(aOffset
);
219 if (!offset
.IsValid() || offset
> CharacterCount()) {
220 return LayoutDeviceIntRect();
222 TextLeafPoint point
= ToTextLeafPoint(static_cast<int32_t>(offset
), false);
224 return LayoutDeviceIntRect();
227 LayoutDeviceIntRect bounds
= point
.CharBounds();
228 if (!bounds
.x
&& !bounds
.y
&& bounds
.IsZeroArea()) {
231 nsAccUtils::ConvertScreenCoordsTo(&bounds
.x
, &bounds
.y
, aCoordType
, Acc());
235 LayoutDeviceIntRect
HyperTextAccessibleBase::TextBounds(int32_t aStartOffset
,
237 uint32_t aCoordType
) {
238 LayoutDeviceIntRect result
;
239 if (CharacterCount() == 0) {
240 result
= Acc()->Bounds();
241 nsAccUtils::ConvertScreenCoordsTo(&result
.x
, &result
.y
, aCoordType
, Acc());
245 index_t startOffset
= ConvertMagicOffset(aStartOffset
);
246 index_t endOffset
= ConvertMagicOffset(aEndOffset
);
247 if (!startOffset
.IsValid() || startOffset
>= endOffset
) {
248 return LayoutDeviceIntRect();
251 // Here's where things get complicated. We can't simply query the first
252 // and last character, and union their bounds. They might reside on different
253 // lines, and a simple union may yield an incorrect width. We
254 // should use the length of the longest spanned line for our width.
256 TextLeafPoint startPoint
=
257 ToTextLeafPoint(static_cast<int32_t>(startOffset
), false);
258 TextLeafPoint endPoint
=
259 ToTextLeafPoint(static_cast<int32_t>(endOffset
), true);
261 // The caller provided an invalid offset.
262 return LayoutDeviceIntRect();
265 // Step backwards from the point returned by ToTextLeafPoint above.
266 // For our purposes, `endPoint` should be inclusive.
268 endPoint
.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR
, eDirPrevious
);
269 if (endPoint
< startPoint
) {
273 if (endPoint
== startPoint
) {
274 result
= startPoint
.CharBounds();
276 TextLeafRange
range(startPoint
, endPoint
);
277 result
= range
.Bounds();
280 // Calls to TextLeafRange::Bounds() will construct screen coordinates.
281 // Perform any additional conversions here.
282 nsAccUtils::ConvertScreenCoordsTo(&result
.x
, &result
.y
, aCoordType
, Acc());
286 int32_t HyperTextAccessibleBase::OffsetAtPoint(int32_t aX
, int32_t aY
,
287 uint32_t aCoordType
) {
288 Accessible
* thisAcc
= Acc();
289 LayoutDeviceIntPoint coords
=
290 nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordType
, thisAcc
);
291 if (!thisAcc
->Bounds().Contains(coords
.x
, coords
.y
)) {
292 // The requested point does not exist in this accessible.
293 // Check if we used fuzzy hittesting to get here and, if
294 // so, return 0 to indicate this text leaf is a valid match.
295 LayoutDeviceIntPoint
p(aX
, aY
);
296 if (aCoordType
!= nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
) {
297 p
= nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordType
, thisAcc
);
299 if (Accessible
* doc
= nsAccUtils::DocumentFor(thisAcc
)) {
300 Accessible
* hittestMatch
= doc
->ChildAtPoint(
301 p
.x
, p
.y
, Accessible::EWhichChildAtPoint::DeepestChild
);
302 if (hittestMatch
&& thisAcc
== hittestMatch
->Parent()) {
309 TextLeafPoint startPoint
= ToTextLeafPoint(0, false);
310 // As with TextBounds, we walk to the very end of the text contained in this
311 // hypertext and then step backwards to make our endPoint inclusive.
312 TextLeafPoint endPoint
=
313 ToTextLeafPoint(static_cast<int32_t>(CharacterCount()), true);
315 endPoint
.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR
, eDirPrevious
);
316 TextLeafPoint point
= startPoint
;
317 // XXX: We should create a TextLeafRange object for this hypertext and move
318 // this search inside the TextLeafRange class.
319 // If there are no characters in this container, we might have moved endPoint
320 // before startPoint. In that case, we shouldn't try to move further forward,
321 // as that might result in an infinite loop.
322 if (startPoint
<= endPoint
) {
323 for (; !point
.ContainsPoint(coords
.x
, coords
.y
) && point
!= endPoint
;
325 point
.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR
, eDirNext
)) {
328 if (!point
.ContainsPoint(coords
.x
, coords
.y
)) {
329 LayoutDeviceIntRect startRect
= startPoint
.CharBounds();
330 if (coords
.x
< startRect
.x
|| coords
.y
< startRect
.y
) {
331 // Bug 1816601: The point is within the container but above or to the left
332 // of the rectangle at offset 0. We should really return -1, but we've
333 // returned 0 for many years due to a bug. Some users have unfortunately
334 // come to rely on this, so perpetuate this here.
339 DebugOnly
<bool> ok
= false;
341 std::tie(ok
, htOffset
) =
342 TransformOffset(point
.mAcc
, point
.mOffset
, /* aIsEndOffset */ false);
343 MOZ_ASSERT(ok
, "point should be a descendant of this");
347 TextLeafPoint
HyperTextAccessibleBase::ToTextLeafPoint(int32_t aOffset
,
348 bool aDescendToEnd
) {
349 Accessible
* thisAcc
= Acc();
350 if (!thisAcc
->HasChildren()) {
351 return TextLeafPoint(thisAcc
, 0);
353 Accessible
* child
= GetChildAtOffset(aOffset
);
355 return TextLeafPoint();
357 if (HyperTextAccessibleBase
* childHt
= child
->AsHyperTextBase()) {
358 return childHt
->ToTextLeafPoint(
359 aDescendToEnd
? static_cast<int32_t>(childHt
->CharacterCount()) : 0,
362 int32_t offset
= aOffset
- GetChildOffset(child
);
363 return TextLeafPoint(child
, offset
);
366 std::pair
<bool, int32_t> HyperTextAccessibleBase::TransformOffset(
367 Accessible
* aDescendant
, int32_t aOffset
, bool aIsEndOffset
) const {
368 const Accessible
* thisAcc
= Acc();
369 // From the descendant, go up and get the immediate child of this hypertext.
370 int32_t offset
= aOffset
;
371 Accessible
* descendant
= aDescendant
;
373 Accessible
* parent
= descendant
->Parent();
374 if (parent
== thisAcc
) {
375 return {true, GetChildOffset(descendant
) + offset
};
378 // This offset no longer applies because the passed-in text object is not
379 // a child of the hypertext. This happens when there are nested hypertexts,
380 // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
381 // to make it relative the hypertext.
382 // If the end offset is not supposed to be inclusive and the original point
383 // is not at 0 offset then the returned offset should be after an embedded
384 // character the original point belongs to.
386 offset
= (offset
> 0 || descendant
->IndexInParent() > 0) ? 1 : 0;
394 // The given a11y point cannot be mapped to an offset relative to this
395 // hypertext accessible. Return the start or the end depending on whether this
396 // is a start ofset or an end offset, thus clipping to the relevant endpoint.
397 return {false, aIsEndOffset
? static_cast<int32_t>(CharacterCount()) : 0};
400 void HyperTextAccessibleBase::AdjustOriginIfEndBoundary(
401 TextLeafPoint
& aOrigin
, AccessibleTextBoundary aBoundaryType
,
402 bool aAtOffset
) const {
403 if (aBoundaryType
!= nsIAccessibleText::BOUNDARY_LINE_END
&&
404 aBoundaryType
!= nsIAccessibleText::BOUNDARY_WORD_END
) {
407 TextLeafPoint actualOrig
=
408 aOrigin
.IsCaret() ? aOrigin
.ActualizeCaret(/* aAdjustAtEndOfLine */ false)
410 if (aBoundaryType
== nsIAccessibleText::BOUNDARY_LINE_END
) {
411 if (!actualOrig
.IsLineFeedChar()) {
415 actualOrig
.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR
, eDirPrevious
);
416 } else { // BOUNDARY_WORD_END
418 // For TextAtOffset with BOUNDARY_WORD_END, we follow WebKitGtk here and
419 // return the word which ends after the origin if the origin is a word end
420 // boundary. Also, if the caret is at the end of a line, our tests expect
421 // the word after the caret, not the word before. The reason for that
422 // is a mystery lost to history. We can do that by explicitly using the
423 // actualized caret without adjusting for end of line.
424 aOrigin
= actualOrig
;
427 if (!actualOrig
.IsSpace()) {
430 TextLeafPoint prevChar
=
431 actualOrig
.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR
, eDirPrevious
);
432 if (prevChar
!= actualOrig
&& !prevChar
.IsSpace()) {
433 // aOrigin is a word end boundary.
439 void HyperTextAccessibleBase::TextBeforeOffset(
440 int32_t aOffset
, AccessibleTextBoundary aBoundaryType
,
441 int32_t* aStartOffset
, int32_t* aEndOffset
, nsAString
& aText
) {
442 *aStartOffset
= *aEndOffset
= 0;
445 if (aBoundaryType
== nsIAccessibleText::BOUNDARY_SENTENCE_START
||
446 aBoundaryType
== nsIAccessibleText::BOUNDARY_SENTENCE_END
) {
447 return; // Not implemented.
450 uint32_t adjustedOffset
= ConvertMagicOffset(aOffset
);
451 if (adjustedOffset
== std::numeric_limits
<uint32_t>::max()) {
452 NS_ERROR("Wrong given offset!");
456 if (aBoundaryType
== nsIAccessibleText::BOUNDARY_CHAR
) {
457 if (adjustedOffset
> 0) {
458 CharAt(static_cast<int32_t>(adjustedOffset
) - 1, aText
, aStartOffset
,
465 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
466 orig
= TextLeafPoint::GetCaret(Acc());
468 orig
= ToTextLeafPoint(static_cast<int32_t>(adjustedOffset
));
471 // This can happen if aOffset is invalid.
474 AdjustOriginIfEndBoundary(orig
, aBoundaryType
);
476 orig
.FindBoundary(aBoundaryType
, eDirPrevious
,
477 TextLeafPoint::BoundaryFlags::eIncludeOrigin
);
479 std::tie(ok
, *aEndOffset
) = TransformOffset(end
.mAcc
, end
.mOffset
,
480 /* aIsEndOffset */ true);
482 // There is no previous boundary inside this HyperText.
483 *aStartOffset
= *aEndOffset
= 0;
486 TextLeafPoint start
= end
.FindBoundary(aBoundaryType
, eDirPrevious
);
487 // If TransformOffset fails because start is outside this HyperText,
488 // *aStartOffset will be 0, which is what we want.
489 std::tie(ok
, *aStartOffset
) = TransformOffset(start
.mAcc
, start
.mOffset
,
490 /* aIsEndOffset */ false);
491 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
494 void HyperTextAccessibleBase::TextAtOffset(int32_t aOffset
,
495 AccessibleTextBoundary aBoundaryType
,
496 int32_t* aStartOffset
,
499 *aStartOffset
= *aEndOffset
= 0;
502 if (aBoundaryType
== nsIAccessibleText::BOUNDARY_SENTENCE_START
||
503 aBoundaryType
== nsIAccessibleText::BOUNDARY_SENTENCE_END
) {
504 return; // Not implemented.
507 uint32_t adjustedOffset
= ConvertMagicOffset(aOffset
);
508 if (adjustedOffset
== std::numeric_limits
<uint32_t>::max()) {
509 NS_ERROR("Wrong given offset!");
513 if (aBoundaryType
== nsIAccessibleText::BOUNDARY_CHAR
) {
514 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
515 TextLeafPoint caret
= TextLeafPoint::GetCaret(Acc());
516 if (caret
.IsCaretAtEndOfLine()) {
517 // The caret is at the end of the line. Return no character.
518 *aStartOffset
= *aEndOffset
= static_cast<int32_t>(adjustedOffset
);
522 CharAt(adjustedOffset
, aText
, aStartOffset
, aEndOffset
);
526 TextLeafPoint start
, end
;
527 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
528 start
= TextLeafPoint::GetCaret(Acc());
529 AdjustOriginIfEndBoundary(start
, aBoundaryType
, /* aAtOffset */ true);
532 start
= ToTextLeafPoint(static_cast<int32_t>(adjustedOffset
));
533 Accessible
* childAcc
= GetChildAtOffset(adjustedOffset
);
534 if (childAcc
&& childAcc
->IsHyperText()) {
535 // We're searching for boundaries enclosing an embedded object.
536 // An embedded object might contain several boundaries itself.
537 // Thus, we must ensure we search for the end boundary from the last
538 // text in the subtree, not just the first.
539 // For example, if the embedded object is a link and it contains two
540 // words, but the second word expands beyond the link, we want to
541 // include the part of the second word which is outside of the link.
542 end
= ToTextLeafPoint(static_cast<int32_t>(adjustedOffset
),
543 /* aDescendToEnd */ true);
545 AdjustOriginIfEndBoundary(start
, aBoundaryType
,
546 /* aAtOffset */ true);
551 // This can happen if aOffset is invalid.
554 start
= start
.FindBoundary(aBoundaryType
, eDirPrevious
,
555 TextLeafPoint::BoundaryFlags::eIncludeOrigin
);
557 std::tie(ok
, *aStartOffset
) = TransformOffset(start
.mAcc
, start
.mOffset
,
558 /* aIsEndOffset */ false);
559 end
= end
.FindBoundary(aBoundaryType
, eDirNext
);
560 std::tie(ok
, *aEndOffset
) = TransformOffset(end
.mAcc
, end
.mOffset
,
561 /* aIsEndOffset */ true);
562 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
565 void HyperTextAccessibleBase::TextAfterOffset(
566 int32_t aOffset
, AccessibleTextBoundary aBoundaryType
,
567 int32_t* aStartOffset
, int32_t* aEndOffset
, nsAString
& aText
) {
568 *aStartOffset
= *aEndOffset
= 0;
571 if (aBoundaryType
== nsIAccessibleText::BOUNDARY_SENTENCE_START
||
572 aBoundaryType
== nsIAccessibleText::BOUNDARY_SENTENCE_END
) {
573 return; // Not implemented.
576 uint32_t adjustedOffset
= ConvertMagicOffset(aOffset
);
577 if (adjustedOffset
== std::numeric_limits
<uint32_t>::max()) {
578 NS_ERROR("Wrong given offset!");
582 if (aBoundaryType
== nsIAccessibleText::BOUNDARY_CHAR
) {
583 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
&& adjustedOffset
> 0 &&
584 TextLeafPoint::GetCaret(Acc()).IsCaretAtEndOfLine()) {
587 uint32_t count
= CharacterCount();
588 if (adjustedOffset
>= count
) {
589 *aStartOffset
= *aEndOffset
= static_cast<int32_t>(count
);
591 CharAt(static_cast<int32_t>(adjustedOffset
) + 1, aText
, aStartOffset
,
598 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
599 orig
= TextLeafPoint::GetCaret(Acc());
601 orig
= ToTextLeafPoint(static_cast<int32_t>(adjustedOffset
),
602 /* aDescendToEnd */ true);
605 // This can happen if aOffset is invalid.
608 AdjustOriginIfEndBoundary(orig
, aBoundaryType
);
609 TextLeafPoint start
= orig
.FindBoundary(aBoundaryType
, eDirNext
);
611 std::tie(ok
, *aStartOffset
) = TransformOffset(start
.mAcc
, start
.mOffset
,
612 /* aIsEndOffset */ false);
614 // There is no next boundary inside this HyperText.
615 *aStartOffset
= *aEndOffset
= static_cast<int32_t>(CharacterCount());
618 TextLeafPoint end
= start
.FindBoundary(aBoundaryType
, eDirNext
);
619 // If TransformOffset fails because end is outside this HyperText,
620 // *aEndOffset will be CharacterCount(), which is what we want.
621 std::tie(ok
, *aEndOffset
) = TransformOffset(end
.mAcc
, end
.mOffset
,
622 /* aIsEndOffset */ true);
623 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
626 int32_t HyperTextAccessibleBase::CaretOffset() const {
627 TextLeafPoint point
= TextLeafPoint::GetCaret(const_cast<Accessible
*>(Acc()))
628 .ActualizeCaret(/* aAdjustAtEndOfLine */ false);
629 if (point
.mOffset
== 0 && point
.mAcc
== Acc()) {
630 // If a text box is empty, there will be no children, so point.mAcc will be
634 auto [ok
, htOffset
] =
635 TransformOffset(point
.mAcc
, point
.mOffset
, /* aIsEndOffset */ false);
637 // The caret is not within this HyperText.
643 int32_t HyperTextAccessibleBase::CaretLineNumber() {
644 TextLeafPoint point
= TextLeafPoint::GetCaret(const_cast<Accessible
*>(Acc()))
645 .ActualizeCaret(/* aAdjustAtEndOfLine */ false);
646 if (point
.mOffset
== 0 && point
.mAcc
== Acc()) {
647 MOZ_ASSERT(CharacterCount() == 0);
648 // If a text box is empty, there will be no children, so point.mAcc will be
654 (point
.mAcc
!= Acc() && !Acc()->IsAncestorOf(point
.mAcc
))) {
655 // The caret is not within this HyperText.
659 TextLeafPoint firstPointInThis
= TextLeafPoint(Acc(), 0);
660 int32_t lineNumber
= 1;
661 for (TextLeafPoint line
= point
; line
&& firstPointInThis
< line
;
662 line
= line
.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START
,
670 bool HyperTextAccessibleBase::IsValidOffset(int32_t aOffset
) {
671 index_t offset
= ConvertMagicOffset(aOffset
);
672 return offset
.IsValid() && offset
<= CharacterCount();
675 bool HyperTextAccessibleBase::IsValidRange(int32_t aStartOffset
,
676 int32_t aEndOffset
) {
677 index_t startOffset
= ConvertMagicOffset(aStartOffset
);
678 index_t endOffset
= ConvertMagicOffset(aEndOffset
);
679 return startOffset
.IsValid() && endOffset
.IsValid() &&
680 startOffset
<= endOffset
&& endOffset
<= CharacterCount();
683 uint32_t HyperTextAccessibleBase::LinkCount() {
684 return Acc()->EmbeddedChildCount();
687 Accessible
* HyperTextAccessibleBase::LinkAt(uint32_t aIndex
) {
688 return Acc()->EmbeddedChildAt(aIndex
);
691 int32_t HyperTextAccessibleBase::LinkIndexOf(Accessible
* aLink
) {
692 return Acc()->IndexOfEmbeddedChild(aLink
);
695 already_AddRefed
<AccAttributes
> HyperTextAccessibleBase::TextAttributes(
696 bool aIncludeDefAttrs
, int32_t aOffset
, int32_t* aStartOffset
,
697 int32_t* aEndOffset
) {
698 *aStartOffset
= *aEndOffset
= 0;
699 index_t offset
= ConvertMagicOffset(aOffset
);
700 if (!offset
.IsValid() || offset
> CharacterCount()) {
701 NS_ERROR("Wrong in offset!");
702 return RefPtr
{new AccAttributes()}.forget();
705 Accessible
* originAcc
= GetChildAtOffset(offset
);
707 // Offset 0 is correct offset when accessible has empty text. Include
708 // default attributes if they were requested, otherwise return empty set.
710 if (aIncludeDefAttrs
) {
711 return DefaultTextAttributes();
714 return RefPtr
{new AccAttributes()}.forget();
717 if (!originAcc
->IsText()) {
718 // This is an embedded object. One or more consecutive embedded objects
719 // form a single attrs run with no attributes.
720 *aStartOffset
= aOffset
;
721 *aEndOffset
= aOffset
+ 1;
722 Accessible
* parent
= originAcc
->Parent();
724 return RefPtr
{new AccAttributes()}.forget();
726 int32_t originIdx
= originAcc
->IndexInParent();
728 // Check for embedded objects before the origin.
729 for (uint32_t idx
= originIdx
- 1;; --idx
) {
730 Accessible
* sibling
= parent
->ChildAt(idx
);
731 if (sibling
->IsText()) {
740 // Check for embedded objects after the origin.
741 for (uint32_t idx
= originIdx
+ 1;; ++idx
) {
742 Accessible
* sibling
= parent
->ChildAt(idx
);
743 if (!sibling
|| sibling
->IsText()) {
748 return RefPtr
{new AccAttributes()}.forget();
751 TextLeafPoint origin
= ToTextLeafPoint(static_cast<int32_t>(offset
));
752 TextLeafPoint start
=
753 origin
.FindTextAttrsStart(eDirPrevious
, /* aIncludeOrigin */ true);
755 std::tie(ok
, *aStartOffset
) = TransformOffset(start
.mAcc
, start
.mOffset
,
756 /* aIsEndOffset */ false);
758 origin
.FindTextAttrsStart(eDirNext
, /* aIncludeOrigin */ false);
759 std::tie(ok
, *aEndOffset
) = TransformOffset(end
.mAcc
, end
.mOffset
,
760 /* aIsEndOffset */ true);
761 return origin
.GetTextAttributes(aIncludeDefAttrs
);
764 void HyperTextAccessibleBase::CroppedSelectionRanges(
765 nsTArray
<TextRange
>& aRanges
) const {
766 SelectionRanges(&aRanges
);
767 const Accessible
* acc
= Acc();
768 aRanges
.RemoveElementsBy([acc
](auto& range
) {
769 if (range
.StartPoint() == range
.EndPoint()) {
770 return true; // Collapsed, so remove this range.
772 // If this is the document, it contains all ranges, so there's no need to
775 // If we fail to crop, the range is outside acc, so remove it.
776 return !range
.Crop(const_cast<Accessible
*>(acc
));
782 int32_t HyperTextAccessibleBase::SelectionCount() {
783 nsTArray
<TextRange
> ranges
;
784 CroppedSelectionRanges(ranges
);
785 return static_cast<int32_t>(ranges
.Length());
788 bool HyperTextAccessibleBase::SelectionBoundsAt(int32_t aSelectionNum
,
789 int32_t* aStartOffset
,
790 int32_t* aEndOffset
) {
791 nsTArray
<TextRange
> ranges
;
792 CroppedSelectionRanges(ranges
);
793 if (aSelectionNum
>= static_cast<int32_t>(ranges
.Length())) {
796 TextRange
& range
= ranges
[aSelectionNum
];
797 Accessible
* thisAcc
= Acc();
798 if (range
.StartContainer() == thisAcc
) {
799 *aStartOffset
= range
.StartOffset();
802 // range.StartContainer() isn't a text leaf, so don't use its offset.
803 std::tie(ok
, *aStartOffset
) =
804 TransformOffset(range
.StartContainer(), 0, /* aDescendToEnd */ false);
806 if (range
.EndContainer() == thisAcc
) {
807 *aEndOffset
= range
.EndOffset();
810 // range.EndContainer() isn't a text leaf, so don't use its offset. If
811 // range.EndOffset() is > 0, we want to include this container, so pas
813 std::tie(ok
, *aEndOffset
) =
814 TransformOffset(range
.EndContainer(), range
.EndOffset() == 0 ? 0 : 1,
815 /* aDescendToEnd */ true);
820 bool HyperTextAccessibleBase::SetSelectionBoundsAt(int32_t aSelectionNum
,
821 int32_t aStartOffset
,
822 int32_t aEndOffset
) {
823 TextLeafRange
range(ToTextLeafPoint(aStartOffset
),
824 ToTextLeafPoint(aEndOffset
, true));
826 NS_ERROR("Wrong in offset");
830 return range
.SetSelection(aSelectionNum
);
833 void HyperTextAccessibleBase::ScrollSubstringTo(int32_t aStartOffset
,
835 uint32_t aScrollType
) {
836 TextLeafRange
range(ToTextLeafPoint(aStartOffset
),
837 ToTextLeafPoint(aEndOffset
, true));
838 range
.ScrollIntoView(aScrollType
);
841 } // namespace mozilla::a11y