2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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;
22 class HyperTextIterator {
24 HyperTextIterator(HyperTextAccessible* aStartContainer, int32_t aStartOffset,
25 HyperTextAccessible* aEndContainer, int32_t aEndOffset)
26 : mCurrentContainer(aStartContainer),
27 mCurrentStartOffset(0),
29 mEndContainer(aEndContainer),
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()));
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
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;
60 int32_t NextLinkOffset();
62 HyperTextAccessible* mEndContainer;
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
72 if (!mCurrentContainer->IsLink()) {
73 // If we are not a link, it is a root hypertext accessible.
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.
84 uint32_t endOffset = mCurrentContainer->EndOffset();
86 mCurrentContainer = mCurrentContainer->LocalParent()->AsHyperText();
87 mCurrentStartOffset = endOffset;
89 if (mCurrentContainer == mEndContainer &&
90 mCurrentStartOffset >= mEndOffset) {
91 // Reached end boundary.
95 // Call NormalizeForward recursively to get top-most link if at the end of
96 // one, or innermost link if at the beginning.
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");
113 mCurrentContainer = link->AsHyperText();
114 if (link->IsHTMLListItem()) {
115 LocalAccessible* bullet = link->AsHTMLListItem()->Bullet();
116 mCurrentStartOffset = bullet ? nsAccUtils::TextLength(bullet) : 0;
118 mCurrentStartOffset = 0;
121 if (mCurrentContainer == mEndContainer &&
122 mCurrentStartOffset >= mEndOffset) {
123 // Reached end boundary.
127 // Call NormalizeForward recursively to get top-most embedding ancestor
128 // if at the end of one, or innermost link if at the beginning.
137 bool HyperTextIterator::NormalizeBackward() {
138 if (mCurrentStartOffset == 0) {
139 // If this is the start of the current container, mutate to its parent's
141 if (!mCurrentContainer->IsLink()) {
142 // If we are not a link, it is a root hypertext accessible.
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.
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.
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.
178 if (mCurrentContainer->IsHTMLListItem() &&
179 mCurrentContainer->AsHTMLListItem()->Bullet() == link) {
180 mCurrentStartOffset = 0;
189 int32_t HyperTextIterator::SegmentLength() {
190 int32_t endOffset = mCurrentEndOffset < 0
191 ? mCurrentContainer->CharacterCount()
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);
202 int32_t linkStartOffset = link->StartOffset();
203 if (mCurrentStartOffset < linkStartOffset) {
204 return linkStartOffset;
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.
219 if (mCurrentContainer == mEndContainer &&
220 (mCurrentEndOffset == -1 || mEndOffset <= mCurrentEndOffset)) {
223 mCurrentStartOffset = mCurrentEndOffset;
227 int32_t nextLinkOffset = NextLinkOffset();
228 if (mCurrentContainer == mEndContainer &&
229 (nextLinkOffset == -1 || nextLinkOffset > mEndOffset)) {
231 mEndOffset < 0 ? mEndContainer->CharacterCount() : mEndOffset;
233 mCurrentEndOffset = nextLinkOffset < 0 ? mCurrentContainer->CharacterCount()
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);
247 LocalAccessible* bullet = AsHTMLListItem()->Bullet();
248 if (maybeBullet == bullet) {
249 TextSubstring(0, nsAccUtils::TextLength(bullet), aText);
254 HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
255 while (iter.Next()) {
257 iter.mCurrentContainer->TextSubstring(iter.mCurrentStartOffset,
258 iter.mCurrentEndOffset, text);
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);
270 LocalAccessible* bullet = AsHTMLListItem()->Bullet();
271 if (maybeBullet == bullet) {
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);
287 HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
288 while (iter.Next()) {
289 int32_t attrStartOffset = 0;
290 int32_t attrEndOffset = iter.mCurrentStartOffset;
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");
302 iter.mCurrentContainer->TextSubstring(
303 attrStartOffset < iter.mCurrentStartOffset ? iter.mCurrentStartOffset
305 attrEndOffset < iter.mCurrentEndOffset ? attrEndOffset
306 : iter.mCurrentEndOffset,
309 aStrings.AppendElement(text);
310 aProperties.AppendElement(props);
311 aContainers.AppendElement(iter.mCurrentContainer);
312 } while (attrEndOffset < iter.mCurrentEndOffset);
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);
330 int32_t HyperTextAccessibleWrap::LengthForRange(
331 int32_t aStartOffset, HyperTextAccessible* aEndContainer,
332 int32_t aEndOffset) {
334 HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
335 while (iter.Next()) {
336 length += iter.SegmentLength();
342 void HyperTextAccessibleWrap::OffsetAtIndex(int32_t aIndex,
343 HyperTextAccessible** aContainer,
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;
354 index -= segmentLength;
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,
368 case EWhichRange::eRightWord:
369 RightWordAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
372 case EWhichRange::eLine:
373 case EWhichRange::eLeftLine:
374 LineAt(aOffset, false, aStartContainer, aStartOffset, aEndContainer,
377 case EWhichRange::eRightLine:
378 LineAt(aOffset, true, aStartContainer, aStartOffset, aEndContainer,
381 case EWhichRange::eParagraph:
382 ParagraphAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
385 case EWhichRange::eStyle:
386 StyleAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
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);
401 FindTextPoint(aOffset, eDirPrevious, eSelectWord, eStartWord);
402 if (!start.mContainer) {
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);
416 TextPoint end = startContainer->FindTextPoint(start.mOffset, eDirNext,
417 eSelectWord, eEndWord);
419 *aStartContainer = end.mContainer->AsLocal()->AsHyperText();
420 *aEndContainer = here.mContainer->AsLocal()->AsHyperText();
421 *aStartOffset = end.mOffset;
422 *aEndOffset = here.mOffset;
424 *aStartContainer = startContainer;
425 *aEndContainer = end.mContainer->AsLocal()->AsHyperText();
426 *aStartOffset = start.mOffset;
427 *aEndOffset = end.mOffset;
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.
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.
452 TextPoint start = endContainer->FindTextPoint(end.mOffset, eDirPrevious,
453 eSelectWord, eStartWord);
456 *aStartContainer = here.mContainer->AsLocal()->AsHyperText();
457 *aEndContainer = start.mContainer->AsLocal()->AsHyperText();
458 *aStartOffset = here.mOffset;
459 *aEndOffset = start.mOffset;
461 *aStartContainer = start.mContainer->AsLocal()->AsHyperText();
462 *aEndContainer = endContainer;
463 *aStartOffset = start.mOffset;
464 *aEndOffset = end.mOffset;
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);
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.
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,
490 if (!start.mContainer) {
494 auto* startContainer = static_cast<HyperTextAccessibleWrap*>(
495 start.mContainer->AsLocal()->AsHyperText());
496 end = startContainer->FindTextPoint(start.mOffset, eDirNext, eSelectEndLine,
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);
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.
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);
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);
555 MOZ_ASSERT(leaf->LocalParent()->IsHyperText());
556 HyperTextAccessibleWrap* container =
557 static_cast<HyperTextAccessibleWrap*>(leaf->LocalParent()->AsHyperText());
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);
571 FindTextPoint(aOffset, eDirNext, eSelectCluster, eDefaultBehavior);
573 if ((next.mOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
574 next.mContainer == Document()) ||
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;
581 *aNextContainer = next.mContainer->AsLocal()->AsHyperText();
582 *aNextOffset = next.mOffset;
586 void HyperTextAccessibleWrap::PreviousClusterAt(
587 int32_t aOffset, HyperTextAccessible** aPrevContainer,
588 int32_t* aPrevOffset) {
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);
602 *aStartOffset = GetChildOffset(index);
603 // If this is the last child index + 1 will return the total
605 *aEndOffset = GetChildOffset(index + 1);
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;
616 int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
617 if (childIdx == -1) {
621 child = text->LocalChildAt(childIdx);
622 if (!child || nsAccUtils::MustPrune(text)) {
626 innerOffset -= text->GetChildOffset(childIdx);
628 text = child->AsHyperText();
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();
650 iter.NormalizeBackward();
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;
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
664 if (childIdx == -1) {
665 NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?");
666 return TextPoint(text, 0);
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
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);
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
690 innerOffset -= text->GetChildOffset(childIdx);
691 child = text->LocalChildAt(childIdx);
693 innerOffset -= childOffset;
696 text = child->AsHyperText();
699 nsIFrame* childFrame = child->GetFrame();
701 NS_ERROR("No child frame");
702 return TextPoint(this, aOffset);
705 int32_t innerContentOffset = innerOffset;
706 if (child->IsTextLeaf()) {
707 NS_ASSERTION(childFrame->IsTextFrame(), "Wrong frame!");
708 RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
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;
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);
738 if (!pos.mResultContent) {
739 NS_ERROR("No result content!");
740 return TextPoint(this, aOffset);
743 if (aDirection == eDirNext &&
744 nsContentUtils::PositionIsBefore(pos.mResultContent, mContent, nullptr,
746 // Bug 1652833 makes us sometimes return the first element on the doc.
747 return TextPoint(Document(), nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
750 HyperTextAccessible* container =
751 nsAccUtils::GetTextContainer(pos.mResultContent);
752 int32_t offset = container ? container->DOMPointToOffset(
753 pos.mResultContent, pos.mContentOffset,
754 aDirection == eDirNext)
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) {
770 return static_cast<HyperTextAccessibleWrap*>(editable->AsHyperText());