Bug 1804798 - Explicitly set auto page name (and corresponding debug flag) when inser...
[gecko.git] / accessible / mac / HyperTextAccessibleWrap.mm
blob361eb9ca225ed7e643f94bd91b50c205dc0c7b8c
1 /* clang-format off */
2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* clang-format on */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "HyperTextAccessibleWrap.h"
10 #include "LocalAccessible-inl.h"
11 #include "HTMLListAccessible.h"
12 #include "nsAccUtils.h"
13 #include "nsFrameSelection.h"
14 #include "TextRange.h"
15 #include "TreeWalker.h"
17 using namespace mozilla;
18 using namespace mozilla::a11y;
20 // HyperTextIterator
22 class HyperTextIterator {
23  public:
24   HyperTextIterator(HyperTextAccessible* aStartContainer, int32_t aStartOffset,
25                     HyperTextAccessible* aEndContainer, int32_t aEndOffset)
26       : mCurrentContainer(aStartContainer),
27         mCurrentStartOffset(0),
28         mCurrentEndOffset(0),
29         mEndContainer(aEndContainer),
30         mEndOffset(0) {
31     mCurrentStartOffset =
32         std::min(aStartOffset,
33                  static_cast<int32_t>(mCurrentContainer->CharacterCount()));
34     mCurrentEndOffset = mCurrentStartOffset;
35     mEndOffset = std::min(
36         aEndOffset, static_cast<int32_t>(mEndContainer->CharacterCount()));
37   }
39   bool Next();
41   int32_t SegmentLength();
43   // If offset is set to a child hyperlink, adjust it so it set on the first
44   // offset in the deepest link. Or, if the offset to the last character, set it
45   // to the outermost end offset in an ancestor. Returns true if iterator was
46   // mutated.
47   bool NormalizeForward();
49   // If offset is set right after child hyperlink, adjust it so it set on the
50   // last offset in the deepest link. Or, if the offset is on the first
51   // character of a link, set it to the outermost start offset in an ancestor.
52   // Returns true if iterator was mutated.
53   bool NormalizeBackward();
55   HyperTextAccessible* mCurrentContainer;
56   int32_t mCurrentStartOffset;
57   int32_t mCurrentEndOffset;
59  private:
60   int32_t NextLinkOffset();
62   HyperTextAccessible* mEndContainer;
63   int32_t mEndOffset;
66 bool HyperTextIterator::NormalizeForward() {
67   if (mCurrentStartOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT ||
68       mCurrentStartOffset >=
69           static_cast<int32_t>(mCurrentContainer->CharacterCount())) {
70     // If this is the end of the current container, mutate to its parent's
71     // end offset.
72     if (!mCurrentContainer->IsLink()) {
73       // If we are not a link, it is a root hypertext accessible.
74       return false;
75     }
76     if (!mCurrentContainer->LocalParent() ||
77         !mCurrentContainer->LocalParent()->IsHyperText()) {
78       // If we are a link, but our parent is not a hypertext accessible
79       // treat the current container as the root hypertext accessible.
80       // This can be the case with some XUL containers that are not
81       // hypertext accessibles.
82       return false;
83     }
84     uint32_t endOffset = mCurrentContainer->EndOffset();
85     if (endOffset != 0) {
86       mCurrentContainer = mCurrentContainer->LocalParent()->AsHyperText();
87       mCurrentStartOffset = endOffset;
89       if (mCurrentContainer == mEndContainer &&
90           mCurrentStartOffset >= mEndOffset) {
91         // Reached end boundary.
92         return false;
93       }
95       // Call NormalizeForward recursively to get top-most link if at the end of
96       // one, or innermost link if at the beginning.
97       NormalizeForward();
98       return true;
99     }
100   } else {
101     LocalAccessible* link = mCurrentContainer->LinkAt(
102         mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset));
104     // If there is a link at this offset, mutate into it.
105     if (link && link->IsHyperText()) {
106       if (mCurrentStartOffset > 0 &&
107           mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset) ==
108               mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset - 1)) {
109         MOZ_ASSERT_UNREACHABLE("Same link for previous offset");
110         return false;
111       }
113       mCurrentContainer = link->AsHyperText();
114       if (link->IsHTMLListItem()) {
115         LocalAccessible* bullet = link->AsHTMLListItem()->Bullet();
116         mCurrentStartOffset = bullet ? nsAccUtils::TextLength(bullet) : 0;
117       } else {
118         mCurrentStartOffset = 0;
119       }
121       if (mCurrentContainer == mEndContainer &&
122           mCurrentStartOffset >= mEndOffset) {
123         // Reached end boundary.
124         return false;
125       }
127       // Call NormalizeForward recursively to get top-most embedding ancestor
128       // if at the end of one, or innermost link if at the beginning.
129       NormalizeForward();
130       return true;
131     }
132   }
134   return false;
137 bool HyperTextIterator::NormalizeBackward() {
138   if (mCurrentStartOffset == 0) {
139     // If this is the start of the current container, mutate to its parent's
140     // start offset.
141     if (!mCurrentContainer->IsLink()) {
142       // If we are not a link, it is a root hypertext accessible.
143       return false;
144     }
145     if (!mCurrentContainer->LocalParent() ||
146         !mCurrentContainer->LocalParent()->IsHyperText()) {
147       // If we are a link, but our parent is not a hypertext accessible
148       // treat the current container as the root hypertext accessible.
149       // This can be the case with some XUL containers that are not
150       // hypertext accessibles.
151       return false;
152     }
154     uint32_t startOffset = mCurrentContainer->StartOffset();
155     mCurrentContainer = mCurrentContainer->LocalParent()->AsHyperText();
156     mCurrentStartOffset = startOffset;
158     // Call NormalizeBackward recursively to get top-most link if at the
159     // beginning of one, or innermost link if at the end.
160     NormalizeBackward();
161     return true;
162   } else {
163     LocalAccessible* link =
164         mCurrentContainer->GetChildAtOffset(mCurrentStartOffset - 1);
166     // If there is a link before this offset, mutate into it,
167     // and set the offset to its last character.
168     if (link && link->IsHyperText()) {
169       mCurrentContainer = link->AsHyperText();
170       mCurrentStartOffset = mCurrentContainer->CharacterCount();
172       // Call NormalizeBackward recursively to get top-most top-most embedding
173       // ancestor if at the beginning of one, or innermost link if at the end.
174       NormalizeBackward();
175       return true;
176     }
178     if (mCurrentContainer->IsHTMLListItem() &&
179         mCurrentContainer->AsHTMLListItem()->Bullet() == link) {
180       mCurrentStartOffset = 0;
181       NormalizeBackward();
182       return true;
183     }
184   }
186   return false;
189 int32_t HyperTextIterator::SegmentLength() {
190   int32_t endOffset = mCurrentEndOffset < 0
191                           ? mCurrentContainer->CharacterCount()
192                           : mCurrentEndOffset;
194   return endOffset - mCurrentStartOffset;
197 int32_t HyperTextIterator::NextLinkOffset() {
198   int32_t linkCount = mCurrentContainer->LinkCount();
199   for (int32_t i = 0; i < linkCount; i++) {
200     LocalAccessible* link = mCurrentContainer->LinkAt(i);
201     MOZ_ASSERT(link);
202     int32_t linkStartOffset = link->StartOffset();
203     if (mCurrentStartOffset < linkStartOffset) {
204       return linkStartOffset;
205     }
206   }
208   return -1;
211 bool HyperTextIterator::Next() {
212   if (!mCurrentContainer->Document()->HasLoadState(
213           DocAccessible::eTreeConstructed)) {
214     // If the accessible tree is still being constructed the text tree
215     // is not in a traversable state yet.
216     return false;
217   }
219   if (mCurrentContainer == mEndContainer &&
220       (mCurrentEndOffset == -1 || mEndOffset <= mCurrentEndOffset)) {
221     return false;
222   } else {
223     mCurrentStartOffset = mCurrentEndOffset;
224     NormalizeForward();
225   }
227   int32_t nextLinkOffset = NextLinkOffset();
228   if (mCurrentContainer == mEndContainer &&
229       (nextLinkOffset == -1 || nextLinkOffset > mEndOffset)) {
230     mCurrentEndOffset =
231         mEndOffset < 0 ? mEndContainer->CharacterCount() : mEndOffset;
232   } else {
233     mCurrentEndOffset = nextLinkOffset < 0 ? mCurrentContainer->CharacterCount()
234                                            : nextLinkOffset;
235   }
237   return mCurrentStartOffset != mCurrentEndOffset;
240 void HyperTextAccessibleWrap::TextForRange(nsAString& aText,
241                                            int32_t aStartOffset,
242                                            HyperTextAccessible* aEndContainer,
243                                            int32_t aEndOffset) {
244   if (IsHTMLListItem()) {
245     LocalAccessible* maybeBullet = GetChildAtOffset(aStartOffset - 1);
246     if (maybeBullet) {
247       LocalAccessible* bullet = AsHTMLListItem()->Bullet();
248       if (maybeBullet == bullet) {
249         TextSubstring(0, nsAccUtils::TextLength(bullet), aText);
250       }
251     }
252   }
254   HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
255   while (iter.Next()) {
256     nsAutoString text;
257     iter.mCurrentContainer->TextSubstring(iter.mCurrentStartOffset,
258                                           iter.mCurrentEndOffset, text);
259     aText.Append(text);
260   }
263 void HyperTextAccessibleWrap::AttributedTextForRange(
264     nsTArray<nsString>& aStrings, nsTArray<RefPtr<AccAttributes>>& aProperties,
265     nsTArray<LocalAccessible*>& aContainers, int32_t aStartOffset,
266     HyperTextAccessible* aEndContainer, int32_t aEndOffset) {
267   if (IsHTMLListItem()) {
268     LocalAccessible* maybeBullet = GetChildAtOffset(aStartOffset - 1);
269     if (maybeBullet) {
270       LocalAccessible* bullet = AsHTMLListItem()->Bullet();
271       if (maybeBullet == bullet) {
272         nsAutoString text;
273         TextSubstring(0, nsAccUtils::TextLength(bullet), text);
275         int32_t unusedAttrStartOffset, unusedAttrEndOffset;
276         RefPtr<AccAttributes> props =
277             TextAttributes(true, aStartOffset - 1, &unusedAttrStartOffset,
278                            &unusedAttrEndOffset);
280         aStrings.AppendElement(text);
281         aProperties.AppendElement(props);
282         aContainers.AppendElement(this);
283       }
284     }
285   }
287   HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
288   while (iter.Next()) {
289     int32_t attrStartOffset = 0;
290     int32_t attrEndOffset = iter.mCurrentStartOffset;
291     do {
292       int32_t oldEndOffset = attrEndOffset;
293       RefPtr<AccAttributes> props = iter.mCurrentContainer->TextAttributes(
294           true, attrEndOffset, &attrStartOffset, &attrEndOffset);
296       if (oldEndOffset == attrEndOffset) {
297         MOZ_ASSERT_UNREACHABLE("new attribute end offset should be different");
298         break;
299       }
301       nsAutoString text;
302       iter.mCurrentContainer->TextSubstring(
303           attrStartOffset < iter.mCurrentStartOffset ? iter.mCurrentStartOffset
304                                                      : attrStartOffset,
305           attrEndOffset < iter.mCurrentEndOffset ? attrEndOffset
306                                                  : iter.mCurrentEndOffset,
307           text);
309       aStrings.AppendElement(text);
310       aProperties.AppendElement(props);
311       aContainers.AppendElement(iter.mCurrentContainer);
312     } while (attrEndOffset < iter.mCurrentEndOffset);
313   }
316 LayoutDeviceIntRect HyperTextAccessibleWrap::BoundsForRange(
317     int32_t aStartOffset, HyperTextAccessible* aEndContainer,
318     int32_t aEndOffset) {
319   LayoutDeviceIntRect rect;
320   HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
321   while (iter.Next()) {
322     LayoutDeviceIntRect stringRect = iter.mCurrentContainer->TextBounds(
323         iter.mCurrentStartOffset, iter.mCurrentEndOffset);
324     rect.UnionRect(rect, stringRect);
325   }
327   return rect;
330 int32_t HyperTextAccessibleWrap::LengthForRange(
331     int32_t aStartOffset, HyperTextAccessible* aEndContainer,
332     int32_t aEndOffset) {
333   int32_t length = 0;
334   HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
335   while (iter.Next()) {
336     length += iter.SegmentLength();
337   }
339   return length;
342 void HyperTextAccessibleWrap::OffsetAtIndex(int32_t aIndex,
343                                             HyperTextAccessible** aContainer,
344                                             int32_t* aOffset) {
345   int32_t index = aIndex;
346   HyperTextIterator iter(this, 0, this, CharacterCount());
347   while (iter.Next()) {
348     int32_t segmentLength = iter.SegmentLength();
349     if (index <= segmentLength) {
350       *aContainer = iter.mCurrentContainer;
351       *aOffset = iter.mCurrentStartOffset + index;
352       break;
353     }
354     index -= segmentLength;
355   }
358 void HyperTextAccessibleWrap::RangeAt(int32_t aOffset, EWhichRange aRangeType,
359                                       HyperTextAccessible** aStartContainer,
360                                       int32_t* aStartOffset,
361                                       HyperTextAccessible** aEndContainer,
362                                       int32_t* aEndOffset) {
363   switch (aRangeType) {
364     case EWhichRange::eLeftWord:
365       LeftWordAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
366                  aEndOffset);
367       break;
368     case EWhichRange::eRightWord:
369       RightWordAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
370                   aEndOffset);
371       break;
372     case EWhichRange::eLine:
373     case EWhichRange::eLeftLine:
374       LineAt(aOffset, false, aStartContainer, aStartOffset, aEndContainer,
375              aEndOffset);
376       break;
377     case EWhichRange::eRightLine:
378       LineAt(aOffset, true, aStartContainer, aStartOffset, aEndContainer,
379              aEndOffset);
380       break;
381     case EWhichRange::eParagraph:
382       ParagraphAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
383                   aEndOffset);
384       break;
385     case EWhichRange::eStyle:
386       StyleAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
387               aEndOffset);
388       break;
389     default:
390       break;
391   }
394 void HyperTextAccessibleWrap::LeftWordAt(int32_t aOffset,
395                                          HyperTextAccessible** aStartContainer,
396                                          int32_t* aStartOffset,
397                                          HyperTextAccessible** aEndContainer,
398                                          int32_t* aEndOffset) {
399   TextPoint here(this, aOffset);
400   TextPoint start =
401       FindTextPoint(aOffset, eDirPrevious, eSelectWord, eStartWord);
402   if (!start.mContainer) {
403     return;
404   }
406   auto* startContainer = static_cast<HyperTextAccessibleWrap*>(
407       start.mContainer->AsLocal()->AsHyperText());
408   if ((NativeState() & states::EDITABLE) &&
409       !(startContainer->NativeState() & states::EDITABLE)) {
410     // The word search crossed an editable boundary. Return the first word of
411     // the editable root.
412     return EditableRoot()->RightWordAt(0, aStartContainer, aStartOffset,
413                                        aEndContainer, aEndOffset);
414   }
416   TextPoint end = startContainer->FindTextPoint(start.mOffset, eDirNext,
417                                                 eSelectWord, eEndWord);
418   if (end < here) {
419     *aStartContainer = end.mContainer->AsLocal()->AsHyperText();
420     *aEndContainer = here.mContainer->AsLocal()->AsHyperText();
421     *aStartOffset = end.mOffset;
422     *aEndOffset = here.mOffset;
423   } else {
424     *aStartContainer = startContainer;
425     *aEndContainer = end.mContainer->AsLocal()->AsHyperText();
426     *aStartOffset = start.mOffset;
427     *aEndOffset = end.mOffset;
428   }
431 void HyperTextAccessibleWrap::RightWordAt(int32_t aOffset,
432                                           HyperTextAccessible** aStartContainer,
433                                           int32_t* aStartOffset,
434                                           HyperTextAccessible** aEndContainer,
435                                           int32_t* aEndOffset) {
436   TextPoint here(this, aOffset);
437   TextPoint end = FindTextPoint(aOffset, eDirNext, eSelectWord, eEndWord);
438   if (!end.mContainer || end < here || here == end) {
439     // If we didn't find a word end, or if we wrapped around (bug 1652833),
440     // return with no result.
441     return;
442   }
444   auto* endContainer = static_cast<HyperTextAccessibleWrap*>(
445       end.mContainer->AsLocal()->AsHyperText());
446   if ((NativeState() & states::EDITABLE) &&
447       !(endContainer->NativeState() & states::EDITABLE)) {
448     // The word search crossed an editable boundary. Return with no result.
449     return;
450   }
452   TextPoint start = endContainer->FindTextPoint(end.mOffset, eDirPrevious,
453                                                 eSelectWord, eStartWord);
455   if (here < start) {
456     *aStartContainer = here.mContainer->AsLocal()->AsHyperText();
457     *aEndContainer = start.mContainer->AsLocal()->AsHyperText();
458     *aStartOffset = here.mOffset;
459     *aEndOffset = start.mOffset;
460   } else {
461     *aStartContainer = start.mContainer->AsLocal()->AsHyperText();
462     *aEndContainer = endContainer;
463     *aStartOffset = start.mOffset;
464     *aEndOffset = end.mOffset;
465   }
468 void HyperTextAccessibleWrap::LineAt(int32_t aOffset, bool aNextLine,
469                                      HyperTextAccessible** aStartContainer,
470                                      int32_t* aStartOffset,
471                                      HyperTextAccessible** aEndContainer,
472                                      int32_t* aEndOffset) {
473   TextPoint here(this, aOffset);
474   TextPoint end =
475       FindTextPoint(aOffset, eDirNext, eSelectEndLine, eDefaultBehavior);
476   if (!end.mContainer || end < here) {
477     // If we didn't find a word end, or if we wrapped around (bug 1652833),
478     // return with no result.
479     return;
480   }
482   auto* endContainer = static_cast<HyperTextAccessibleWrap*>(
483       end.mContainer->AsLocal()->AsHyperText());
484   TextPoint start = endContainer->FindTextPoint(
485       end.mOffset, eDirPrevious, eSelectBeginLine, eDefaultBehavior);
487   if (!aNextLine && here < start) {
488     start = FindTextPoint(aOffset, eDirPrevious, eSelectBeginLine,
489                           eDefaultBehavior);
490     if (!start.mContainer) {
491       return;
492     }
494     auto* startContainer = static_cast<HyperTextAccessibleWrap*>(
495         start.mContainer->AsLocal()->AsHyperText());
496     end = startContainer->FindTextPoint(start.mOffset, eDirNext, eSelectEndLine,
497                                         eDefaultBehavior);
498   }
500   *aStartContainer = start.mContainer->AsLocal()->AsHyperText();
501   *aEndContainer = end.mContainer->AsLocal()->AsHyperText();
502   *aStartOffset = start.mOffset;
503   *aEndOffset = end.mOffset;
506 void HyperTextAccessibleWrap::ParagraphAt(int32_t aOffset,
507                                           HyperTextAccessible** aStartContainer,
508                                           int32_t* aStartOffset,
509                                           HyperTextAccessible** aEndContainer,
510                                           int32_t* aEndOffset) {
511   TextPoint here(this, aOffset);
512   TextPoint end =
513       FindTextPoint(aOffset, eDirNext, eSelectParagraph, eDefaultBehavior);
515   if (!end.mContainer || end < here) {
516     // If we didn't find a word end, or if we wrapped around (bug 1652833),
517     // return with no result.
518     return;
519   }
521   if (end.mOffset == -1 && LocalParent() && LocalParent()->IsHyperText()) {
522     // If end offset is -1 we didn't find a paragraph boundary.
523     // This must be an inline container, go to its parent to
524     // retrieve paragraph boundaries.
525     static_cast<HyperTextAccessibleWrap*>(LocalParent()->AsHyperText())
526         ->ParagraphAt(StartOffset(), aStartContainer, aStartOffset,
527                       aEndContainer, aEndOffset);
528     return;
529   }
531   auto* endContainer = static_cast<HyperTextAccessibleWrap*>(
532       end.mContainer->AsLocal()->AsHyperText());
533   TextPoint start = static_cast<HyperTextAccessibleWrap*>(endContainer)
534                         ->FindTextPoint(end.mOffset, eDirPrevious,
535                                         eSelectParagraph, eDefaultBehavior);
537   *aStartContainer = start.mContainer->AsLocal()->AsHyperText();
538   *aEndContainer = endContainer;
539   *aStartOffset = start.mOffset;
540   *aEndOffset = end.mOffset;
543 void HyperTextAccessibleWrap::StyleAt(int32_t aOffset,
544                                       HyperTextAccessible** aStartContainer,
545                                       int32_t* aStartOffset,
546                                       HyperTextAccessible** aEndContainer,
547                                       int32_t* aEndOffset) {
548   // Get the range of the text leaf at this offset.
549   // A text leaf represents a stretch of like-styled text.
550   auto leaf = LeafAtOffset(aOffset);
551   if (!leaf) {
552     return;
553   }
555   MOZ_ASSERT(leaf->LocalParent()->IsHyperText());
556   HyperTextAccessibleWrap* container =
557       static_cast<HyperTextAccessibleWrap*>(leaf->LocalParent()->AsHyperText());
558   if (!container) {
559     return;
560   }
562   *aStartContainer = *aEndContainer = container;
563   container->RangeOfChild(leaf, aStartOffset, aEndOffset);
566 void HyperTextAccessibleWrap::NextClusterAt(
567     int32_t aOffset, HyperTextAccessible** aNextContainer,
568     int32_t* aNextOffset) {
569   TextPoint here(this, aOffset);
570   TextPoint next =
571       FindTextPoint(aOffset, eDirNext, eSelectCluster, eDefaultBehavior);
573   if ((next.mOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
574        next.mContainer == Document()) ||
575       (next < here)) {
576     // If we reached the end of the doc, or if we wrapped to the start of the
577     // doc return given offset as-is.
578     *aNextContainer = this;
579     *aNextOffset = aOffset;
580   } else {
581     *aNextContainer = next.mContainer->AsLocal()->AsHyperText();
582     *aNextOffset = next.mOffset;
583   }
586 void HyperTextAccessibleWrap::PreviousClusterAt(
587     int32_t aOffset, HyperTextAccessible** aPrevContainer,
588     int32_t* aPrevOffset) {
589   TextPoint prev =
590       FindTextPoint(aOffset, eDirPrevious, eSelectCluster, eDefaultBehavior);
591   *aPrevContainer = prev.mContainer->AsLocal()->AsHyperText();
592   *aPrevOffset = prev.mOffset;
595 void HyperTextAccessibleWrap::RangeOfChild(LocalAccessible* aChild,
596                                            int32_t* aStartOffset,
597                                            int32_t* aEndOffset) {
598   MOZ_ASSERT(aChild->LocalParent() == this);
599   *aStartOffset = *aEndOffset = -1;
600   int32_t index = GetIndexOf(aChild);
601   if (index != -1) {
602     *aStartOffset = GetChildOffset(index);
603     // If this is the last child index + 1 will return the total
604     // chracter count.
605     *aEndOffset = GetChildOffset(index + 1);
606   }
609 LocalAccessible* HyperTextAccessibleWrap::LeafAtOffset(int32_t aOffset) {
610   HyperTextAccessible* text = this;
611   LocalAccessible* child = nullptr;
612   // The offset needed should "attach" the previous accessible if
613   // in between two accessibles.
614   int32_t innerOffset = aOffset > 0 ? aOffset - 1 : aOffset;
615   do {
616     int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
617     if (childIdx == -1) {
618       return text;
619     }
621     child = text->LocalChildAt(childIdx);
622     if (!child || nsAccUtils::MustPrune(text)) {
623       return text;
624     }
626     innerOffset -= text->GetChildOffset(childIdx);
628     text = child->AsHyperText();
629   } while (text);
631   return child;
634 void HyperTextAccessibleWrap::SelectRange(int32_t aStartOffset,
635                                           HyperTextAccessible* aEndContainer,
636                                           int32_t aEndOffset) {
637   TextRange range(this, this, aStartOffset, aEndContainer, aEndOffset);
638   range.SetSelectionAt(0);
641 TextPoint HyperTextAccessibleWrap::FindTextPoint(
642     int32_t aOffset, nsDirection aDirection, nsSelectionAmount aAmount,
643     EWordMovementType aWordMovementType) {
644   // Layout can remain trapped in an editable. We normalize out of
645   // it if we are in its last offset.
646   HyperTextIterator iter(this, aOffset, this, CharacterCount());
647   if (aDirection == eDirNext) {
648     iter.NormalizeForward();
649   } else {
650     iter.NormalizeBackward();
651   }
653   // Find a leaf accessible frame to start with. PeekOffset wants this.
654   HyperTextAccessible* text = iter.mCurrentContainer;
655   LocalAccessible* child = nullptr;
656   int32_t innerOffset = iter.mCurrentStartOffset;
658   do {
659     int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
661     // We can have an empty text leaf as our only child. Since empty text
662     // leaves are not accessible we then have no children, but 0 is a valid
663     // innerOffset.
664     if (childIdx == -1) {
665       NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?");
666       return TextPoint(text, 0);
667     }
669     child = text->LocalChildAt(childIdx);
670     if (child->IsHyperText() && !child->ChildCount()) {
671       // If this is a childless hypertext, jump to its
672       // previous or next sibling, depending on
673       // direction.
674       if (aDirection == eDirPrevious && childIdx > 0) {
675         child = text->LocalChildAt(--childIdx);
676       } else if (aDirection == eDirNext &&
677                  childIdx + 1 < static_cast<int32_t>(text->ChildCount())) {
678         child = text->LocalChildAt(++childIdx);
679       }
680     }
682     int32_t childOffset = text->GetChildOffset(childIdx);
684     if (child->IsHyperText() && aDirection == eDirPrevious && childIdx > 0 &&
685         innerOffset - childOffset == 0) {
686       // If we are searching backwards, and this is the begining of a
687       // segment, get the previous sibling so that layout will start
688       // its search there.
689       childIdx--;
690       innerOffset -= text->GetChildOffset(childIdx);
691       child = text->LocalChildAt(childIdx);
692     } else {
693       innerOffset -= childOffset;
694     }
696     text = child->AsHyperText();
697   } while (text);
699   nsIFrame* childFrame = child->GetFrame();
700   if (!childFrame) {
701     NS_ERROR("No child frame");
702     return TextPoint(this, aOffset);
703   }
705   int32_t innerContentOffset = innerOffset;
706   if (child->IsTextLeaf()) {
707     NS_ASSERTION(childFrame->IsTextFrame(), "Wrong frame!");
708     RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
709   }
711   nsIFrame* frameAtOffset = childFrame;
712   int32_t offsetInFrame = 0;
713   childFrame->GetChildFrameContainingOffset(innerContentOffset, true,
714                                             &offsetInFrame, &frameAtOffset);
715   if (aDirection == eDirPrevious && offsetInFrame == 0) {
716     // If we are searching backwards, and we are at the start of a frame,
717     // get the previous continuation frame.
718     if (nsIFrame* prevInContinuation = frameAtOffset->GetPrevContinuation()) {
719       frameAtOffset = prevInContinuation;
720     }
721   }
723   const bool kIsJumpLinesOk = true;       // okay to jump lines
724   const bool kIsScrollViewAStop = false;  // do not stop at scroll views
725   const bool kIsKeyboardSelect = true;    // is keyboard selection
726   const bool kIsVisualBidi = false;       // use visual order for bidi text
727   nsPeekOffsetStruct pos(
728       aAmount, aDirection, innerContentOffset, nsPoint(0, 0), kIsJumpLinesOk,
729       kIsScrollViewAStop, kIsKeyboardSelect, kIsVisualBidi, false,
730       nsPeekOffsetStruct::ForceEditableRegion::No, aWordMovementType, false);
731   nsresult rv = frameAtOffset->PeekOffset(&pos);
733   // PeekOffset fails on last/first lines of the text in certain cases.
734   if (NS_FAILED(rv) && aAmount == eSelectLine) {
735     pos.mAmount = aDirection == eDirNext ? eSelectEndLine : eSelectBeginLine;
736     frameAtOffset->PeekOffset(&pos);
737   }
738   if (!pos.mResultContent) {
739     NS_ERROR("No result content!");
740     return TextPoint(this, aOffset);
741   }
743   if (aDirection == eDirNext &&
744       nsContentUtils::PositionIsBefore(pos.mResultContent, mContent, nullptr,
745                                        nullptr)) {
746     // Bug 1652833 makes us sometimes return the first element on the doc.
747     return TextPoint(Document(), nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
748   }
750   HyperTextAccessible* container =
751       nsAccUtils::GetTextContainer(pos.mResultContent);
752   int32_t offset = container ? container->DOMPointToOffset(
753                                    pos.mResultContent, pos.mContentOffset,
754                                    aDirection == eDirNext)
755                              : 0;
756   return TextPoint(container, offset);
759 HyperTextAccessibleWrap* HyperTextAccessibleWrap::EditableRoot() {
760   LocalAccessible* editable = nullptr;
761   for (LocalAccessible* acc = this; acc && acc != Document();
762        acc = acc->LocalParent()) {
763     if (acc->NativeState() & states::EDITABLE) {
764       editable = acc;
765     } else {
766       break;
767     }
768   }
770   return static_cast<HyperTextAccessibleWrap*>(editable->AsHyperText());