1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "HyperTextAccessible-inl.h"
9 #include "Accessible-inl.h"
10 #include "nsAccessibilityService.h"
11 #include "nsIAccessibleTypes.h"
12 #include "DocAccessible.h"
13 #include "HTMLListAccessible.h"
16 #include "TextAttrs.h"
17 #include "TextRange.h"
18 #include "TreeWalker.h"
21 #include "nsContentUtils.h"
23 #include "nsFocusManager.h"
24 #include "nsIEditingSession.h"
25 #include "nsContainerFrame.h"
26 #include "nsFrameSelection.h"
27 #include "nsILineIterator.h"
28 #include "nsIInterfaceRequestorUtils.h"
29 #include "nsPersistentProperties.h"
30 #include "nsIScrollableFrame.h"
31 #include "nsIMathMLFrame.h"
33 #include "nsTextFragment.h"
34 #include "mozilla/Assertions.h"
35 #include "mozilla/BinarySearch.h"
36 #include "mozilla/EventStates.h"
37 #include "mozilla/MathAlgorithms.h"
38 #include "mozilla/PresShell.h"
39 #include "mozilla/TextEditor.h"
40 #include "mozilla/dom/Element.h"
41 #include "mozilla/dom/HTMLBRElement.h"
42 #include "mozilla/dom/HTMLHeadingElement.h"
43 #include "mozilla/dom/Selection.h"
44 #include "gfxSkipChars.h"
47 using namespace mozilla
;
48 using namespace mozilla::a11y
;
50 ////////////////////////////////////////////////////////////////////////////////
51 // HyperTextAccessible
52 ////////////////////////////////////////////////////////////////////////////////
54 HyperTextAccessible::HyperTextAccessible(nsIContent
* aNode
, DocAccessible
* aDoc
)
55 : AccessibleWrap(aNode
, aDoc
) {
56 mType
= eHyperTextType
;
57 mGenericTypes
|= eHyperText
;
60 role
HyperTextAccessible::NativeRole() const {
61 a11y::role r
= GetAccService()->MarkupRole(mContent
);
62 if (r
!= roles::NOTHING
) return r
;
64 nsIFrame
* frame
= GetFrame();
65 if (frame
&& frame
->IsInlineFrame()) return roles::TEXT
;
67 return roles::TEXT_CONTAINER
;
70 uint64_t HyperTextAccessible::NativeState() const {
71 uint64_t states
= AccessibleWrap::NativeState();
73 if (mContent
->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE
)) {
74 states
|= states::EDITABLE
;
76 } else if (mContent
->IsHTMLElement(nsGkAtoms::article
)) {
77 // We want <article> to behave like a document in terms of readonly state.
78 states
|= states::READONLY
;
81 if (HasChildren()) states
|= states::SELECTABLE_TEXT
;
86 nsIntRect
HyperTextAccessible::GetBoundsInFrame(nsIFrame
* aFrame
,
87 uint32_t aStartRenderedOffset
,
88 uint32_t aEndRenderedOffset
) {
89 nsPresContext
* presContext
= mDoc
->PresContext();
90 if (!aFrame
->IsTextFrame()) {
91 return aFrame
->GetScreenRectInAppUnits().ToNearestPixels(
92 presContext
->AppUnitsPerDevPixel());
95 // Substring must be entirely within the same text node.
96 int32_t startContentOffset
, endContentOffset
;
97 nsresult rv
= RenderedToContentOffset(aFrame
, aStartRenderedOffset
,
99 NS_ENSURE_SUCCESS(rv
, nsIntRect());
100 rv
= RenderedToContentOffset(aFrame
, aEndRenderedOffset
, &endContentOffset
);
101 NS_ENSURE_SUCCESS(rv
, nsIntRect());
104 int32_t startContentOffsetInFrame
;
105 // Get the right frame continuation -- not really a child, but a sibling of
106 // the primary frame passed in
107 rv
= aFrame
->GetChildFrameContainingOffset(
108 startContentOffset
, false, &startContentOffsetInFrame
, &frame
);
109 NS_ENSURE_SUCCESS(rv
, nsIntRect());
112 while (frame
&& startContentOffset
< endContentOffset
) {
113 // Start with this frame's screen rect, which we will shrink based on
114 // the substring we care about within it. We will then add that frame to
115 // the total screenRect we are returning.
116 nsRect frameScreenRect
= frame
->GetScreenRectInAppUnits();
118 // Get the length of the substring in this frame that we want the bounds for
119 int32_t startFrameTextOffset
, endFrameTextOffset
;
120 frame
->GetOffsets(startFrameTextOffset
, endFrameTextOffset
);
121 int32_t frameTotalTextLength
= endFrameTextOffset
- startFrameTextOffset
;
122 int32_t seekLength
= endContentOffset
- startContentOffset
;
123 int32_t frameSubStringLength
=
124 std::min(frameTotalTextLength
- startContentOffsetInFrame
, seekLength
);
126 // Add the point where the string starts to the frameScreenRect
127 nsPoint frameTextStartPoint
;
128 rv
= frame
->GetPointFromOffset(startContentOffset
, &frameTextStartPoint
);
129 NS_ENSURE_SUCCESS(rv
, nsIntRect());
131 // Use the point for the end offset to calculate the width
132 nsPoint frameTextEndPoint
;
133 rv
= frame
->GetPointFromOffset(startContentOffset
+ frameSubStringLength
,
135 NS_ENSURE_SUCCESS(rv
, nsIntRect());
137 frameScreenRect
.SetRectX(
138 frameScreenRect
.X() +
139 std::min(frameTextStartPoint
.x
, frameTextEndPoint
.x
),
140 mozilla::Abs(frameTextStartPoint
.x
- frameTextEndPoint
.x
));
142 screenRect
.UnionRect(frameScreenRect
, screenRect
);
144 // Get ready to loop back for next frame continuation
145 startContentOffset
+= frameSubStringLength
;
146 startContentOffsetInFrame
= 0;
147 frame
= frame
->GetNextContinuation();
150 return screenRect
.ToNearestPixels(presContext
->AppUnitsPerDevPixel());
153 void HyperTextAccessible::TextSubstring(int32_t aStartOffset
,
154 int32_t aEndOffset
, nsAString
& aText
) {
157 index_t startOffset
= ConvertMagicOffset(aStartOffset
);
158 index_t endOffset
= ConvertMagicOffset(aEndOffset
);
159 if (!startOffset
.IsValid() || !endOffset
.IsValid() ||
160 startOffset
> endOffset
|| endOffset
> CharacterCount()) {
161 NS_ERROR("Wrong in offset");
165 int32_t startChildIdx
= GetChildIndexAtOffset(startOffset
);
166 if (startChildIdx
== -1) return;
168 int32_t endChildIdx
= GetChildIndexAtOffset(endOffset
);
169 if (endChildIdx
== -1) return;
171 if (startChildIdx
== endChildIdx
) {
172 int32_t childOffset
= GetChildOffset(startChildIdx
);
173 if (childOffset
== -1) return;
175 Accessible
* child
= GetChildAt(startChildIdx
);
176 child
->AppendTextTo(aText
, startOffset
- childOffset
,
177 endOffset
- startOffset
);
181 int32_t startChildOffset
= GetChildOffset(startChildIdx
);
182 if (startChildOffset
== -1) return;
184 Accessible
* startChild
= GetChildAt(startChildIdx
);
185 startChild
->AppendTextTo(aText
, startOffset
- startChildOffset
);
187 for (int32_t childIdx
= startChildIdx
+ 1; childIdx
< endChildIdx
;
189 Accessible
* child
= GetChildAt(childIdx
);
190 child
->AppendTextTo(aText
);
193 int32_t endChildOffset
= GetChildOffset(endChildIdx
);
194 if (endChildOffset
== -1) return;
196 Accessible
* endChild
= GetChildAt(endChildIdx
);
197 endChild
->AppendTextTo(aText
, 0, endOffset
- endChildOffset
);
200 uint32_t HyperTextAccessible::DOMPointToOffset(nsINode
* aNode
,
202 bool aIsEndOffset
) const {
203 if (!aNode
) return 0;
206 nsINode
* findNode
= nullptr;
208 if (aNodeOffset
== -1) {
211 } else if (aNode
->IsText()) {
212 // For text nodes, aNodeOffset comes in as a character offset
213 // Text offset will be added at the end, if we find the offset in this
214 // hypertext We want the "skipped" offset into the text (rendered text
215 // without the extra whitespace)
216 nsIFrame
* frame
= aNode
->AsContent()->GetPrimaryFrame();
217 NS_ENSURE_TRUE(frame
, 0);
219 nsresult rv
= ContentToRenderedOffset(frame
, aNodeOffset
, &offset
);
220 NS_ENSURE_SUCCESS(rv
, 0);
225 // findNode could be null if aNodeOffset == # of child nodes, which means
226 // one of two things:
227 // 1) there are no children, and the passed-in node is not mContent -- use
228 // parentContent for the node to find
229 // 2) there are no children and the passed-in node is mContent, which means
230 // we're an empty nsIAccessibleText
231 // 3) there are children and we're at the end of the children
233 findNode
= aNode
->GetChildAt_Deprecated(aNodeOffset
);
235 if (aNodeOffset
== 0) {
236 if (aNode
== GetNode()) {
237 // Case #1: this accessible has no children and thus has empty text,
238 // we can only be at hypertext offset 0.
242 // Case #2: there are no children, we're at this node.
244 } else if (aNodeOffset
== static_cast<int32_t>(aNode
->GetChildCount())) {
245 // Case #3: we're after the last child, get next node to this one.
246 for (nsINode
* tmpNode
= aNode
;
247 !findNode
&& tmpNode
&& tmpNode
!= mContent
;
248 tmpNode
= tmpNode
->GetParent()) {
249 findNode
= tmpNode
->GetNextSibling();
255 // Get accessible for this findNode, or if that node isn't accessible, use the
256 // accessible for the next DOM node which has one (based on forward depth
258 Accessible
* descendant
= nullptr;
260 dom::HTMLBRElement
* brElement
= dom::HTMLBRElement::FromNode(findNode
);
261 if (brElement
&& brElement
->IsPaddingForEmptyEditor()) {
262 // This <br> is the hacky "padding <br> element" used when there is no
263 // text in the editor.
267 descendant
= mDoc
->GetAccessible(findNode
);
268 if (!descendant
&& findNode
->IsContent()) {
269 Accessible
* container
= mDoc
->GetContainerAccessible(findNode
);
271 TreeWalker
walker(container
, findNode
->AsContent(),
272 TreeWalker::eWalkContextTree
);
273 descendant
= walker
.Next();
274 if (!descendant
) descendant
= container
;
279 return TransformOffset(descendant
, offset
, aIsEndOffset
);
282 uint32_t HyperTextAccessible::TransformOffset(Accessible
* aDescendant
,
284 bool aIsEndOffset
) const {
285 // From the descendant, go up and get the immediate child of this hypertext.
286 uint32_t offset
= aOffset
;
287 Accessible
* descendant
= aDescendant
;
289 Accessible
* parent
= descendant
->Parent();
290 if (parent
== this) return GetChildOffset(descendant
) + offset
;
292 // This offset no longer applies because the passed-in text object is not
293 // a child of the hypertext. This happens when there are nested hypertexts,
294 // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
295 // to make it relative the hypertext.
296 // If the end offset is not supposed to be inclusive and the original point
297 // is not at 0 offset then the returned offset should be after an embedded
298 // character the original point belongs to.
300 // Similar to our special casing in FindOffset, we add handling for
301 // bulleted lists here because PeekOffset returns the inner text node
302 // for a list when it should return the list bullet.
303 // We manually set the offset so the error doesn't propagate up.
304 if (offset
== 0 && parent
&& parent
->IsHTMLListItem() &&
305 descendant
->PrevSibling() && descendant
->PrevSibling()->GetFrame() &&
306 descendant
->PrevSibling()->GetFrame()->IsBulletFrame()) {
309 offset
= (offset
> 0 || descendant
->IndexInParent() > 0) ? 1 : 0;
318 // If the given a11y point cannot be mapped into offset relative this
319 // hypertext offset then return length as fallback value.
320 return CharacterCount();
324 * GetElementAsContentOf() returns a content representing an element which is
327 * XXX This method is enough to retrieve ::before or ::after pseudo element.
328 * So, if you want to use this for other purpose, you might need to check
331 static nsIContent
* GetElementAsContentOf(nsINode
* aNode
) {
332 if (auto* element
= dom::Element::FromNode(aNode
)) {
335 return aNode
->GetParentElement();
338 bool HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset
,
341 DOMPoint startPoint
= OffsetToDOMPoint(aStartOffset
);
342 if (!startPoint
.node
) return false;
344 // HyperTextAccessible manages pseudo elements generated by ::before or
345 // ::after. However, contents of them are not in the DOM tree normally.
346 // Therefore, they are not selectable and editable. So, when this creates
347 // a DOM range, it should not start from nor end in any pseudo contents.
349 nsIContent
* container
= GetElementAsContentOf(startPoint
.node
);
350 DOMPoint startPointForDOMRange
=
351 ClosestNotGeneratedDOMPoint(startPoint
, container
);
352 aRange
->SetStart(startPointForDOMRange
.node
, startPointForDOMRange
.idx
);
354 // If the caller wants collapsed range, let's collapse the range to its start.
355 if (aStartOffset
== aEndOffset
) {
356 aRange
->Collapse(true);
360 DOMPoint endPoint
= OffsetToDOMPoint(aEndOffset
);
361 if (!endPoint
.node
) return false;
363 if (startPoint
.node
!= endPoint
.node
) {
364 container
= GetElementAsContentOf(endPoint
.node
);
367 DOMPoint endPointForDOMRange
=
368 ClosestNotGeneratedDOMPoint(endPoint
, container
);
369 aRange
->SetEnd(endPointForDOMRange
.node
, endPointForDOMRange
.idx
);
373 DOMPoint
HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset
) {
374 // 0 offset is valid even if no children. In this case the associated editor
375 // is empty so return a DOM point for editor root element.
377 RefPtr
<TextEditor
> textEditor
= GetEditor();
379 if (textEditor
->IsEmpty()) {
380 return DOMPoint(textEditor
->GetRoot(), 0);
385 int32_t childIdx
= GetChildIndexAtOffset(aOffset
);
386 if (childIdx
== -1) return DOMPoint();
388 Accessible
* child
= GetChildAt(childIdx
);
389 int32_t innerOffset
= aOffset
- GetChildOffset(childIdx
);
392 if (child
->IsTextLeaf()) {
393 // The point is inside the text node. This is always true for any text leaf
394 // except a last child one. See assertion below.
395 if (aOffset
< GetChildOffset(childIdx
+ 1)) {
396 nsIContent
* content
= child
->GetContent();
398 if (NS_FAILED(RenderedToContentOffset(content
->GetPrimaryFrame(),
402 return DOMPoint(content
, idx
);
405 // Set the DOM point right after the text node.
406 MOZ_ASSERT(static_cast<uint32_t>(aOffset
) == CharacterCount());
410 // Case of embedded object. The point is either before or after the element.
411 NS_ASSERTION(innerOffset
== 0 || innerOffset
== 1, "A wrong inner offset!");
412 nsINode
* node
= child
->GetNode();
413 nsINode
* parentNode
= node
->GetParentNode();
414 return parentNode
? DOMPoint(parentNode
,
415 parentNode
->ComputeIndexOf(node
) + innerOffset
)
419 DOMPoint
HyperTextAccessible::ClosestNotGeneratedDOMPoint(
420 const DOMPoint
& aDOMPoint
, nsIContent
* aElementContent
) {
421 MOZ_ASSERT(aDOMPoint
.node
, "The node must not be null");
423 // ::before pseudo element
424 if (aElementContent
&&
425 aElementContent
->IsGeneratedContentContainerForBefore()) {
426 MOZ_ASSERT(aElementContent
->GetParent(),
427 "::before must have parent element");
428 // The first child of its parent (i.e., immediately after the ::before) is
429 // good point for a DOM range.
430 return DOMPoint(aElementContent
->GetParent(), 0);
433 // ::after pseudo element
434 if (aElementContent
&&
435 aElementContent
->IsGeneratedContentContainerForAfter()) {
436 MOZ_ASSERT(aElementContent
->GetParent(),
437 "::after must have parent element");
438 // The end of its parent (i.e., immediately before the ::after) is good
439 // point for a DOM range.
440 return DOMPoint(aElementContent
->GetParent(),
441 aElementContent
->GetParent()->GetChildCount());
447 uint32_t HyperTextAccessible::FindOffset(uint32_t aOffset
,
448 nsDirection aDirection
,
449 nsSelectionAmount aAmount
,
450 EWordMovementType aWordMovementType
) {
451 NS_ASSERTION(aDirection
== eDirPrevious
|| aAmount
!= eSelectBeginLine
,
452 "eSelectBeginLine should only be used with eDirPrevious");
454 // Find a leaf accessible frame to start with. PeekOffset wants this.
455 HyperTextAccessible
* text
= this;
456 Accessible
* child
= nullptr;
457 int32_t innerOffset
= aOffset
;
460 int32_t childIdx
= text
->GetChildIndexAtOffset(innerOffset
);
462 // We can have an empty text leaf as our only child. Since empty text
463 // leaves are not accessible we then have no children, but 0 is a valid
465 if (childIdx
== -1) {
466 NS_ASSERTION(innerOffset
== 0 && !text
->ChildCount(), "No childIdx?");
467 return DOMPointToOffset(text
->GetNode(), 0, aDirection
== eDirNext
);
470 child
= text
->GetChildAt(childIdx
);
472 // HTML list items may need special processing because PeekOffset doesn't
473 // work with list bullets.
474 if (text
->IsHTMLListItem()) {
475 HTMLLIAccessible
* li
= text
->AsHTMLListItem();
476 if (child
== li
->Bullet()) {
477 // XXX: the logic is broken for multichar bullets in moving by
478 // char/cluster/word cases.
480 return aDirection
== eDirPrevious
? TransformOffset(text
, 0, false)
481 : TransformOffset(text
, 1, true);
483 if (aDirection
== eDirPrevious
) return 0;
485 uint32_t nextOffset
= GetChildOffset(1);
486 if (nextOffset
== 0) return 0;
491 // Ask a text leaf next (if not empty) to the bullet for an offset
492 // since list item may be multiline.
493 return nextOffset
< CharacterCount()
494 ? FindOffset(nextOffset
, aDirection
, aAmount
,
504 innerOffset
-= text
->GetChildOffset(childIdx
);
506 text
= child
->AsHyperText();
509 nsIFrame
* childFrame
= child
->GetFrame();
511 NS_ERROR("No child frame");
515 int32_t innerContentOffset
= innerOffset
;
516 if (child
->IsTextLeaf()) {
517 NS_ASSERTION(childFrame
->IsTextFrame(), "Wrong frame!");
518 RenderedToContentOffset(childFrame
, innerOffset
, &innerContentOffset
);
521 nsIFrame
* frameAtOffset
= childFrame
;
522 int32_t unusedOffsetInFrame
= 0;
523 childFrame
->GetChildFrameContainingOffset(
524 innerContentOffset
, true, &unusedOffsetInFrame
, &frameAtOffset
);
526 const bool kIsJumpLinesOk
= true; // okay to jump lines
527 const bool kIsScrollViewAStop
= false; // do not stop at scroll views
528 const bool kIsKeyboardSelect
= true; // is keyboard selection
529 const bool kIsVisualBidi
= false; // use visual order for bidi text
530 nsPeekOffsetStruct
pos(
531 aAmount
, aDirection
, innerContentOffset
, nsPoint(0, 0), kIsJumpLinesOk
,
532 kIsScrollViewAStop
, kIsKeyboardSelect
, kIsVisualBidi
, false,
533 nsPeekOffsetStruct::ForceEditableRegion::No
, aWordMovementType
, false);
534 nsresult rv
= frameAtOffset
->PeekOffset(&pos
);
536 // PeekOffset fails on last/first lines of the text in certain cases.
537 bool fallBackToSelectEndLine
= false;
538 if (NS_FAILED(rv
) && aAmount
== eSelectLine
) {
539 fallBackToSelectEndLine
= aDirection
== eDirNext
;
540 pos
.mAmount
= fallBackToSelectEndLine
? eSelectEndLine
: eSelectBeginLine
;
541 frameAtOffset
->PeekOffset(&pos
);
543 if (!pos
.mResultContent
) {
544 NS_ERROR("No result content!");
548 // Turn the resulting DOM point into an offset.
549 uint32_t hyperTextOffset
= DOMPointToOffset(
550 pos
.mResultContent
, pos
.mContentOffset
, aDirection
== eDirNext
);
552 if (fallBackToSelectEndLine
&& IsLineEndCharAt(hyperTextOffset
)) {
553 // We used eSelectEndLine, but the caller requested eSelectLine.
554 // If there's a '\n' at the end of the line, eSelectEndLine will stop
555 // on it rather than after it. This is not what we want, since the caller
556 // wants the next line, not the same line.
560 if (aDirection
== eDirPrevious
) {
561 // If we reached the end during search, this means we didn't find the DOM
562 // point and we're actually at the start of the paragraph
563 if (hyperTextOffset
== CharacterCount()) return 0;
565 // PeekOffset stops right before bullet so return 0 to workaround it.
566 if (IsHTMLListItem() && aAmount
== eSelectBeginLine
&&
567 hyperTextOffset
> 0) {
568 Accessible
* prevOffsetChild
= GetChildAtOffset(hyperTextOffset
- 1);
569 if (prevOffsetChild
== AsHTMLListItem()->Bullet()) return 0;
573 return hyperTextOffset
;
576 uint32_t HyperTextAccessible::FindLineBoundary(
577 uint32_t aOffset
, EWhichLineBoundary aWhichLineBoundary
) {
578 // Note: empty last line doesn't have own frame (a previous line contains '\n'
579 // character instead) thus when it makes a difference we need to process this
580 // case separately (otherwise operations are performed on previous line).
581 switch (aWhichLineBoundary
) {
582 case ePrevLineBegin
: {
583 // Fetch a previous line and move to its start (as arrow up and home keys
585 if (IsEmptyLastLineOffset(aOffset
))
586 return FindOffset(aOffset
, eDirPrevious
, eSelectBeginLine
);
588 uint32_t tmpOffset
= FindOffset(aOffset
, eDirPrevious
, eSelectLine
);
589 return FindOffset(tmpOffset
, eDirPrevious
, eSelectBeginLine
);
593 if (IsEmptyLastLineOffset(aOffset
)) return aOffset
- 1;
595 // If offset is at first line then return 0 (first line start).
596 uint32_t tmpOffset
= FindOffset(aOffset
, eDirPrevious
, eSelectBeginLine
);
597 if (tmpOffset
== 0) return 0;
599 // Otherwise move to end of previous line (as arrow up and end keys were
601 tmpOffset
= FindOffset(aOffset
, eDirPrevious
, eSelectLine
);
602 return FindOffset(tmpOffset
, eDirNext
, eSelectEndLine
);
606 if (IsEmptyLastLineOffset(aOffset
)) return aOffset
;
608 // Move to begin of the current line (as home key was pressed).
609 return FindOffset(aOffset
, eDirPrevious
, eSelectBeginLine
);
612 if (IsEmptyLastLineOffset(aOffset
)) return aOffset
;
614 // Move to end of the current line (as end key was pressed).
615 return FindOffset(aOffset
, eDirNext
, eSelectEndLine
);
617 case eNextLineBegin
: {
618 if (IsEmptyLastLineOffset(aOffset
)) return aOffset
;
620 // Move to begin of the next line if any (arrow down and home keys),
621 // otherwise end of the current line (arrow down only).
622 uint32_t tmpOffset
= FindOffset(aOffset
, eDirNext
, eSelectLine
);
623 if (tmpOffset
== CharacterCount()) return tmpOffset
;
625 return FindOffset(tmpOffset
, eDirPrevious
, eSelectBeginLine
);
629 if (IsEmptyLastLineOffset(aOffset
)) return aOffset
;
631 // Move to next line end (as down arrow and end key were pressed).
632 uint32_t tmpOffset
= FindOffset(aOffset
, eDirNext
, eSelectLine
);
633 if (tmpOffset
== CharacterCount()) return tmpOffset
;
635 return FindOffset(tmpOffset
, eDirNext
, eSelectEndLine
);
642 void HyperTextAccessible::TextBeforeOffset(int32_t aOffset
,
643 AccessibleTextBoundary aBoundaryType
,
644 int32_t* aStartOffset
,
647 *aStartOffset
= *aEndOffset
= 0;
650 index_t convertedOffset
= ConvertMagicOffset(aOffset
);
651 if (!convertedOffset
.IsValid() || convertedOffset
> CharacterCount()) {
652 NS_ERROR("Wrong in offset!");
656 uint32_t adjustedOffset
= convertedOffset
;
657 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
)
658 adjustedOffset
= AdjustCaretOffset(adjustedOffset
);
660 switch (aBoundaryType
) {
661 case nsIAccessibleText::BOUNDARY_CHAR
:
662 if (convertedOffset
!= 0)
663 CharAt(convertedOffset
- 1, aText
, aStartOffset
, aEndOffset
);
666 case nsIAccessibleText::BOUNDARY_WORD_START
: {
667 // If the offset is a word start (except text length offset) then move
668 // backward to find a start offset (end offset is the given offset).
669 // Otherwise move backward twice to find both start and end offsets.
670 if (adjustedOffset
== CharacterCount()) {
672 FindWordBoundary(adjustedOffset
, eDirPrevious
, eStartWord
);
673 *aStartOffset
= FindWordBoundary(*aEndOffset
, eDirPrevious
, eStartWord
);
676 FindWordBoundary(adjustedOffset
, eDirPrevious
, eStartWord
);
677 *aEndOffset
= FindWordBoundary(*aStartOffset
, eDirNext
, eStartWord
);
678 if (*aEndOffset
!= static_cast<int32_t>(adjustedOffset
)) {
679 *aEndOffset
= *aStartOffset
;
681 FindWordBoundary(*aEndOffset
, eDirPrevious
, eStartWord
);
684 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
688 case nsIAccessibleText::BOUNDARY_WORD_END
: {
689 // Move word backward twice to find start and end offsets.
690 *aEndOffset
= FindWordBoundary(convertedOffset
, eDirPrevious
, eEndWord
);
691 *aStartOffset
= FindWordBoundary(*aEndOffset
, eDirPrevious
, eEndWord
);
692 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
696 case nsIAccessibleText::BOUNDARY_LINE_START
:
697 *aStartOffset
= FindLineBoundary(adjustedOffset
, ePrevLineBegin
);
698 *aEndOffset
= FindLineBoundary(adjustedOffset
, eThisLineBegin
);
699 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
702 case nsIAccessibleText::BOUNDARY_LINE_END
: {
703 *aEndOffset
= FindLineBoundary(adjustedOffset
, ePrevLineEnd
);
704 int32_t tmpOffset
= *aEndOffset
;
705 // Adjust offset if line is wrapped.
706 if (*aEndOffset
!= 0 && !IsLineEndCharAt(*aEndOffset
)) tmpOffset
--;
708 *aStartOffset
= FindLineBoundary(tmpOffset
, ePrevLineEnd
);
709 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
715 void HyperTextAccessible::TextAtOffset(int32_t aOffset
,
716 AccessibleTextBoundary aBoundaryType
,
717 int32_t* aStartOffset
,
718 int32_t* aEndOffset
, nsAString
& aText
) {
719 *aStartOffset
= *aEndOffset
= 0;
722 uint32_t adjustedOffset
= ConvertMagicOffset(aOffset
);
723 if (adjustedOffset
== std::numeric_limits
<uint32_t>::max()) {
724 NS_ERROR("Wrong given offset!");
728 switch (aBoundaryType
) {
729 case nsIAccessibleText::BOUNDARY_CHAR
:
730 // Return no char if caret is at the end of wrapped line (case of no line
731 // end character). Returning a next line char is confusing for AT.
732 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
&&
733 IsCaretAtEndOfLine())
734 *aStartOffset
= *aEndOffset
= adjustedOffset
;
736 CharAt(adjustedOffset
, aText
, aStartOffset
, aEndOffset
);
739 case nsIAccessibleText::BOUNDARY_WORD_START
:
740 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
)
741 adjustedOffset
= AdjustCaretOffset(adjustedOffset
);
743 *aEndOffset
= FindWordBoundary(adjustedOffset
, eDirNext
, eStartWord
);
744 *aStartOffset
= FindWordBoundary(*aEndOffset
, eDirPrevious
, eStartWord
);
745 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
748 case nsIAccessibleText::BOUNDARY_WORD_END
:
749 // Ignore the spec and follow what WebKitGtk does because Orca expects it,
750 // i.e. return a next word at word end offset of the current word
751 // (WebKitGtk behavior) instead the current word (AKT spec).
752 *aEndOffset
= FindWordBoundary(adjustedOffset
, eDirNext
, eEndWord
);
753 *aStartOffset
= FindWordBoundary(*aEndOffset
, eDirPrevious
, eEndWord
);
754 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
757 case nsIAccessibleText::BOUNDARY_LINE_START
:
758 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
)
759 adjustedOffset
= AdjustCaretOffset(adjustedOffset
);
761 *aStartOffset
= FindLineBoundary(adjustedOffset
, eThisLineBegin
);
762 *aEndOffset
= FindLineBoundary(adjustedOffset
, eNextLineBegin
);
763 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
766 case nsIAccessibleText::BOUNDARY_LINE_END
:
767 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
)
768 adjustedOffset
= AdjustCaretOffset(adjustedOffset
);
770 // In contrast to word end boundary we follow the spec here.
771 *aStartOffset
= FindLineBoundary(adjustedOffset
, ePrevLineEnd
);
772 *aEndOffset
= FindLineBoundary(adjustedOffset
, eThisLineEnd
);
773 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
778 void HyperTextAccessible::TextAfterOffset(int32_t aOffset
,
779 AccessibleTextBoundary aBoundaryType
,
780 int32_t* aStartOffset
,
783 *aStartOffset
= *aEndOffset
= 0;
786 index_t convertedOffset
= ConvertMagicOffset(aOffset
);
787 if (!convertedOffset
.IsValid() || convertedOffset
> CharacterCount()) {
788 NS_ERROR("Wrong in offset!");
792 uint32_t adjustedOffset
= convertedOffset
;
793 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
)
794 adjustedOffset
= AdjustCaretOffset(adjustedOffset
);
796 switch (aBoundaryType
) {
797 case nsIAccessibleText::BOUNDARY_CHAR
:
798 // If caret is at the end of wrapped line (case of no line end character)
799 // then char after the offset is a first char at next line.
800 if (adjustedOffset
>= CharacterCount())
801 *aStartOffset
= *aEndOffset
= CharacterCount();
803 CharAt(adjustedOffset
+ 1, aText
, aStartOffset
, aEndOffset
);
806 case nsIAccessibleText::BOUNDARY_WORD_START
:
807 // Move word forward twice to find start and end offsets.
808 *aStartOffset
= FindWordBoundary(adjustedOffset
, eDirNext
, eStartWord
);
809 *aEndOffset
= FindWordBoundary(*aStartOffset
, eDirNext
, eStartWord
);
810 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
813 case nsIAccessibleText::BOUNDARY_WORD_END
:
814 // If the offset is a word end (except 0 offset) then move forward to find
815 // end offset (start offset is the given offset). Otherwise move forward
816 // twice to find both start and end offsets.
817 if (convertedOffset
== 0) {
818 *aStartOffset
= FindWordBoundary(convertedOffset
, eDirNext
, eEndWord
);
819 *aEndOffset
= FindWordBoundary(*aStartOffset
, eDirNext
, eEndWord
);
821 *aEndOffset
= FindWordBoundary(convertedOffset
, eDirNext
, eEndWord
);
822 *aStartOffset
= FindWordBoundary(*aEndOffset
, eDirPrevious
, eEndWord
);
823 if (*aStartOffset
!= static_cast<int32_t>(convertedOffset
)) {
824 *aStartOffset
= *aEndOffset
;
825 *aEndOffset
= FindWordBoundary(*aStartOffset
, eDirNext
, eEndWord
);
828 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
831 case nsIAccessibleText::BOUNDARY_LINE_START
:
832 *aStartOffset
= FindLineBoundary(adjustedOffset
, eNextLineBegin
);
833 *aEndOffset
= FindLineBoundary(*aStartOffset
, eNextLineBegin
);
834 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
837 case nsIAccessibleText::BOUNDARY_LINE_END
:
838 *aStartOffset
= FindLineBoundary(adjustedOffset
, eThisLineEnd
);
839 *aEndOffset
= FindLineBoundary(adjustedOffset
, eNextLineEnd
);
840 TextSubstring(*aStartOffset
, *aEndOffset
, aText
);
845 already_AddRefed
<nsIPersistentProperties
> HyperTextAccessible::TextAttributes(
846 bool aIncludeDefAttrs
, int32_t aOffset
, int32_t* aStartOffset
,
847 int32_t* aEndOffset
) {
848 // 1. Get each attribute and its ranges one after another.
849 // 2. As we get each new attribute, we pass the current start and end offsets
850 // as in/out parameters. In other words, as attributes are collected,
851 // the attribute range itself can only stay the same or get smaller.
853 *aStartOffset
= *aEndOffset
= 0;
854 index_t offset
= ConvertMagicOffset(aOffset
);
855 if (!offset
.IsValid() || offset
> CharacterCount()) {
856 NS_ERROR("Wrong in offset!");
860 RefPtr
<nsPersistentProperties
> attributes
= new nsPersistentProperties();
862 Accessible
* accAtOffset
= GetChildAtOffset(offset
);
864 // Offset 0 is correct offset when accessible has empty text. Include
865 // default attributes if they were requested, otherwise return empty set.
867 if (aIncludeDefAttrs
) {
868 TextAttrsMgr
textAttrsMgr(this);
869 textAttrsMgr
.GetAttributes(attributes
);
871 return attributes
.forget();
876 int32_t accAtOffsetIdx
= accAtOffset
->IndexInParent();
877 uint32_t startOffset
= GetChildOffset(accAtOffsetIdx
);
878 uint32_t endOffset
= GetChildOffset(accAtOffsetIdx
+ 1);
879 int32_t offsetInAcc
= offset
- startOffset
;
881 TextAttrsMgr
textAttrsMgr(this, aIncludeDefAttrs
, accAtOffset
,
883 textAttrsMgr
.GetAttributes(attributes
, &startOffset
, &endOffset
);
885 // Compute spelling attributes on text accessible only.
886 nsIFrame
* offsetFrame
= accAtOffset
->GetFrame();
887 if (offsetFrame
&& offsetFrame
->IsTextFrame()) {
888 int32_t nodeOffset
= 0;
889 RenderedToContentOffset(offsetFrame
, offsetInAcc
, &nodeOffset
);
891 // Set 'misspelled' text attribute.
892 GetSpellTextAttr(accAtOffset
->GetNode(), nodeOffset
, &startOffset
,
893 &endOffset
, attributes
);
896 *aStartOffset
= startOffset
;
897 *aEndOffset
= endOffset
;
898 return attributes
.forget();
901 already_AddRefed
<nsIPersistentProperties
>
902 HyperTextAccessible::DefaultTextAttributes() {
903 RefPtr
<nsPersistentProperties
> attributes
= new nsPersistentProperties();
905 TextAttrsMgr
textAttrsMgr(this);
906 textAttrsMgr
.GetAttributes(attributes
);
907 return attributes
.forget();
910 int32_t HyperTextAccessible::GetLevelInternal() {
911 if (auto* heading
= dom::HTMLHeadingElement::FromNode(mContent
)) {
912 return heading
->AccessibilityLevel();
914 return AccessibleWrap::GetLevelInternal();
917 void HyperTextAccessible::SetMathMLXMLRoles(
918 nsIPersistentProperties
* aAttributes
) {
919 // Add MathML xmlroles based on the position inside the parent.
920 Accessible
* parent
= Parent();
922 switch (parent
->Role()) {
923 case roles::MATHML_CELL
:
924 case roles::MATHML_ENCLOSED
:
925 case roles::MATHML_ERROR
:
926 case roles::MATHML_MATH
:
927 case roles::MATHML_ROW
:
928 case roles::MATHML_SQUARE_ROOT
:
929 case roles::MATHML_STYLE
:
930 if (Role() == roles::MATHML_OPERATOR
) {
931 // This is an operator inside an <mrow> (or an inferred <mrow>).
932 // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
933 // XXX We should probably do something similar for MATHML_FENCED, but
934 // operators do not appear in the accessible tree. See bug 1175747.
935 nsIMathMLFrame
* mathMLFrame
= do_QueryFrame(GetFrame());
937 nsEmbellishData embellishData
;
938 mathMLFrame
->GetEmbellishData(embellishData
);
939 if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData
.flags
)) {
940 if (!PrevSibling()) {
941 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
942 nsGkAtoms::open_fence
);
943 } else if (!NextSibling()) {
944 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
945 nsGkAtoms::close_fence
);
948 if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData
.flags
)) {
949 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
950 nsGkAtoms::separator_
);
955 case roles::MATHML_FRACTION
:
956 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
957 IndexInParent() == 0 ? nsGkAtoms::numerator
958 : nsGkAtoms::denominator
);
960 case roles::MATHML_ROOT
:
961 nsAccUtils::SetAccAttr(
962 aAttributes
, nsGkAtoms::xmlroles
,
963 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::root_index
);
965 case roles::MATHML_SUB
:
966 nsAccUtils::SetAccAttr(
967 aAttributes
, nsGkAtoms::xmlroles
,
968 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::subscript
);
970 case roles::MATHML_SUP
:
971 nsAccUtils::SetAccAttr(
972 aAttributes
, nsGkAtoms::xmlroles
,
973 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::superscript
);
975 case roles::MATHML_SUB_SUP
: {
976 int32_t index
= IndexInParent();
977 nsAccUtils::SetAccAttr(
978 aAttributes
, nsGkAtoms::xmlroles
,
981 : (index
== 1 ? nsGkAtoms::subscript
: nsGkAtoms::superscript
));
983 case roles::MATHML_UNDER
:
984 nsAccUtils::SetAccAttr(
985 aAttributes
, nsGkAtoms::xmlroles
,
986 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::underscript
);
988 case roles::MATHML_OVER
:
989 nsAccUtils::SetAccAttr(
990 aAttributes
, nsGkAtoms::xmlroles
,
991 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::overscript
);
993 case roles::MATHML_UNDER_OVER
: {
994 int32_t index
= IndexInParent();
995 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
998 : (index
== 1 ? nsGkAtoms::underscript
999 : nsGkAtoms::overscript
));
1001 case roles::MATHML_MULTISCRIPTS
: {
1002 // Get the <multiscripts> base.
1004 bool baseFound
= false;
1005 for (child
= parent
->GetContent()->GetFirstChild(); child
;
1006 child
= child
->GetNextSibling()) {
1007 if (child
->IsMathMLElement()) {
1013 nsIContent
* content
= GetContent();
1014 if (child
== content
) {
1016 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
1019 // Browse the list of scripts to find us and determine our type.
1020 bool postscript
= true;
1021 bool subscript
= true;
1022 for (child
= child
->GetNextSibling(); child
;
1023 child
= child
->GetNextSibling()) {
1024 if (!child
->IsMathMLElement()) continue;
1025 if (child
->IsMathMLElement(nsGkAtoms::mprescripts_
)) {
1030 if (child
== content
) {
1032 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
1033 subscript
? nsGkAtoms::subscript
1034 : nsGkAtoms::superscript
);
1036 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::xmlroles
,
1037 subscript
? nsGkAtoms::presubscript
1038 : nsGkAtoms::presuperscript
);
1042 subscript
= !subscript
;
1053 already_AddRefed
<nsIPersistentProperties
>
1054 HyperTextAccessible::NativeAttributes() {
1055 nsCOMPtr
<nsIPersistentProperties
> attributes
=
1056 AccessibleWrap::NativeAttributes();
1058 // 'formatting' attribute is deprecated, 'display' attribute should be
1060 nsIFrame
* frame
= GetFrame();
1061 if (frame
&& frame
->IsBlockFrame()) {
1062 nsAutoString unused
;
1063 attributes
->SetStringProperty(NS_LITERAL_CSTRING("formatting"),
1064 NS_LITERAL_STRING("block"), unused
);
1067 if (FocusMgr()->IsFocused(this)) {
1068 int32_t lineNumber
= CaretLineNumber();
1069 if (lineNumber
>= 1) {
1070 nsAutoString strLineNumber
;
1071 strLineNumber
.AppendInt(lineNumber
);
1072 nsAccUtils::SetAccAttr(attributes
, nsGkAtoms::lineNumber
, strLineNumber
);
1076 if (HasOwnContent()) {
1077 GetAccService()->MarkupAttributes(mContent
, attributes
);
1078 if (mContent
->IsMathMLElement()) SetMathMLXMLRoles(attributes
);
1081 return attributes
.forget();
1084 nsAtom
* HyperTextAccessible::LandmarkRole() const {
1085 if (!HasOwnContent()) return nullptr;
1087 // For the html landmark elements we expose them like we do ARIA landmarks to
1088 // make AT navigation schemes "just work".
1089 if (mContent
->IsHTMLElement(nsGkAtoms::nav
)) {
1090 return nsGkAtoms::navigation
;
1093 if (mContent
->IsHTMLElement(nsGkAtoms::aside
)) {
1094 return nsGkAtoms::complementary
;
1097 if (mContent
->IsHTMLElement(nsGkAtoms::main
)) {
1098 return nsGkAtoms::main
;
1104 int32_t HyperTextAccessible::OffsetAtPoint(int32_t aX
, int32_t aY
,
1105 uint32_t aCoordType
) {
1106 nsIFrame
* hyperFrame
= GetFrame();
1107 if (!hyperFrame
) return -1;
1110 nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordType
, this);
1112 nsPresContext
* presContext
= mDoc
->PresContext();
1113 nsPoint coordsInAppUnits
=
1114 ToAppUnits(coords
, presContext
->AppUnitsPerDevPixel());
1116 nsRect frameScreenRect
= hyperFrame
->GetScreenRectInAppUnits();
1117 if (!frameScreenRect
.Contains(coordsInAppUnits
.x
, coordsInAppUnits
.y
))
1118 return -1; // Not found
1120 nsPoint
pointInHyperText(coordsInAppUnits
.x
- frameScreenRect
.X(),
1121 coordsInAppUnits
.y
- frameScreenRect
.Y());
1123 // Go through the frames to check if each one has the point.
1124 // When one does, add up the character offsets until we have a match
1126 // We have an point in an accessible child of this, now we need to add up the
1127 // offsets before it to what we already have
1129 uint32_t childCount
= ChildCount();
1130 for (uint32_t childIdx
= 0; childIdx
< childCount
; childIdx
++) {
1131 Accessible
* childAcc
= mChildren
[childIdx
];
1133 nsIFrame
* primaryFrame
= childAcc
->GetFrame();
1134 NS_ENSURE_TRUE(primaryFrame
, -1);
1136 nsIFrame
* frame
= primaryFrame
;
1138 nsIContent
* content
= frame
->GetContent();
1139 NS_ENSURE_TRUE(content
, -1);
1140 nsPoint pointInFrame
= pointInHyperText
- frame
->GetOffsetTo(hyperFrame
);
1141 nsSize frameSize
= frame
->GetSize();
1142 if (pointInFrame
.x
< frameSize
.width
&&
1143 pointInFrame
.y
< frameSize
.height
) {
1145 if (frame
->IsTextFrame()) {
1146 nsIFrame::ContentOffsets contentOffsets
=
1147 frame
->GetContentOffsetsFromPointExternal(
1148 pointInFrame
, nsIFrame::IGNORE_SELECTION_STYLE
);
1149 if (contentOffsets
.IsNull() || contentOffsets
.content
!= content
) {
1150 return -1; // Not found
1152 uint32_t addToOffset
;
1153 nsresult rv
= ContentToRenderedOffset(
1154 primaryFrame
, contentOffsets
.offset
, &addToOffset
);
1155 NS_ENSURE_SUCCESS(rv
, -1);
1156 offset
+= addToOffset
;
1160 frame
= frame
->GetNextContinuation();
1163 offset
+= nsAccUtils::TextLength(childAcc
);
1166 return -1; // Not found
1169 nsIntRect
HyperTextAccessible::TextBounds(int32_t aStartOffset
,
1171 uint32_t aCoordType
) {
1172 index_t startOffset
= ConvertMagicOffset(aStartOffset
);
1173 index_t endOffset
= ConvertMagicOffset(aEndOffset
);
1174 if (!startOffset
.IsValid() || !endOffset
.IsValid() ||
1175 startOffset
> endOffset
|| endOffset
> CharacterCount()) {
1176 NS_ERROR("Wrong in offset");
1180 if (CharacterCount() == 0) {
1181 nsPresContext
* presContext
= mDoc
->PresContext();
1182 // Empty content, use our own bound to at least get x,y coordinates
1183 return GetFrame()->GetScreenRectInAppUnits().ToNearestPixels(
1184 presContext
->AppUnitsPerDevPixel());
1187 int32_t childIdx
= GetChildIndexAtOffset(startOffset
);
1188 if (childIdx
== -1) return nsIntRect();
1191 int32_t prevOffset
= GetChildOffset(childIdx
);
1192 int32_t offset1
= startOffset
- prevOffset
;
1194 while (childIdx
< static_cast<int32_t>(ChildCount())) {
1195 nsIFrame
* frame
= GetChildAt(childIdx
++)->GetFrame();
1197 MOZ_ASSERT_UNREACHABLE("No frame for a child!");
1201 int32_t nextOffset
= GetChildOffset(childIdx
);
1202 if (nextOffset
>= static_cast<int32_t>(endOffset
)) {
1204 bounds
, GetBoundsInFrame(frame
, offset1
, endOffset
- prevOffset
));
1208 bounds
.UnionRect(bounds
,
1209 GetBoundsInFrame(frame
, offset1
, nextOffset
- prevOffset
));
1211 prevOffset
= nextOffset
;
1215 // This document may have a resolution set, we will need to multiply
1216 // the document-relative coordinates by that value and re-apply the doc's
1217 // screen coordinates.
1218 nsPresContext
* presContext
= mDoc
->PresContext();
1219 nsIFrame
* rootFrame
= presContext
->PresShell()->GetRootFrame();
1220 nsIntRect orgRectPixels
=
1221 rootFrame
->GetScreenRectInAppUnits().ToNearestPixels(
1222 presContext
->AppUnitsPerDevPixel());
1223 bounds
.MoveBy(-orgRectPixels
.X(), -orgRectPixels
.Y());
1224 bounds
.ScaleRoundOut(presContext
->PresShell()->GetResolution());
1225 bounds
.MoveBy(orgRectPixels
.X(), orgRectPixels
.Y());
1227 auto boundsX
= bounds
.X();
1228 auto boundsY
= bounds
.Y();
1229 nsAccUtils::ConvertScreenCoordsTo(&boundsX
, &boundsY
, aCoordType
, this);
1230 bounds
.MoveTo(boundsX
, boundsY
);
1234 already_AddRefed
<TextEditor
> HyperTextAccessible::GetEditor() const {
1235 if (!mContent
->HasFlag(NODE_IS_EDITABLE
)) {
1236 // If we're inside an editable container, then return that container's
1238 Accessible
* ancestor
= Parent();
1240 HyperTextAccessible
* hyperText
= ancestor
->AsHyperText();
1242 // Recursion will stop at container doc because it has its own impl
1244 return hyperText
->GetEditor();
1247 ancestor
= ancestor
->Parent();
1253 nsCOMPtr
<nsIDocShell
> docShell
= nsCoreUtils::GetDocShellFor(mContent
);
1254 nsCOMPtr
<nsIEditingSession
> editingSession
;
1255 docShell
->GetEditingSession(getter_AddRefs(editingSession
));
1256 if (!editingSession
) return nullptr; // No editing session interface
1258 dom::Document
* docNode
= mDoc
->DocumentNode();
1259 RefPtr
<HTMLEditor
> htmlEditor
=
1260 editingSession
->GetHTMLEditorForWindow(docNode
->GetWindow());
1261 return htmlEditor
.forget();
1265 * =================== Caret & Selection ======================
1268 nsresult
HyperTextAccessible::SetSelectionRange(int32_t aStartPos
,
1270 // Before setting the selection range, we need to ensure that the editor
1271 // is initialized. (See bug 804927.)
1272 // Otherwise, it's possible that lazy editor initialization will override
1273 // the selection we set here and leave the caret at the end of the text.
1274 // By calling GetEditor here, we ensure that editor initialization is
1275 // completed before we set the selection.
1276 RefPtr
<TextEditor
> textEditor
= GetEditor();
1278 bool isFocusable
= InteractiveState() & states::FOCUSABLE
;
1280 // If accessible is focusable then focus it before setting the selection to
1281 // neglect control's selection changes on focus if any (for example, inputs
1282 // that do select all on focus).
1283 // some input controls
1284 if (isFocusable
) TakeFocus();
1286 dom::Selection
* domSel
= DOMSelection();
1287 NS_ENSURE_STATE(domSel
);
1289 // Set up the selection.
1290 for (int32_t idx
= domSel
->RangeCount() - 1; idx
> 0; idx
--)
1291 domSel
->RemoveRangeAndUnselectFramesAndNotifyListeners(
1292 *domSel
->GetRangeAt(idx
), IgnoreErrors());
1293 SetSelectionBoundsAt(0, aStartPos
, aEndPos
);
1295 // Make sure it is visible
1296 domSel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
1297 ScrollAxis(), ScrollAxis(),
1298 dom::Selection::SCROLL_FOR_CARET_MOVE
|
1299 dom::Selection::SCROLL_OVERFLOW_HIDDEN
);
1301 // When selection is done, move the focus to the selection if accessible is
1302 // not focusable. That happens when selection is set within hypertext
1304 if (isFocusable
) return NS_OK
;
1306 nsFocusManager
* DOMFocusManager
= nsFocusManager::GetFocusManager();
1307 if (DOMFocusManager
) {
1308 NS_ENSURE_TRUE(mDoc
, NS_ERROR_FAILURE
);
1309 dom::Document
* docNode
= mDoc
->DocumentNode();
1310 NS_ENSURE_TRUE(docNode
, NS_ERROR_FAILURE
);
1311 nsCOMPtr
<nsPIDOMWindowOuter
> window
= docNode
->GetWindow();
1312 RefPtr
<dom::Element
> result
;
1313 DOMFocusManager
->MoveFocus(
1314 window
, nullptr, nsIFocusManager::MOVEFOCUS_CARET
,
1315 nsIFocusManager::FLAG_BYMOVEFOCUS
, getter_AddRefs(result
));
1321 int32_t HyperTextAccessible::CaretOffset() const {
1322 // Not focused focusable accessible except document accessible doesn't have
1324 if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
1325 (InteractiveState() & states::FOCUSABLE
)) {
1329 // Check cached value.
1330 int32_t caretOffset
= -1;
1331 HyperTextAccessible
* text
= SelectionMgr()->AccessibleWithCaret(&caretOffset
);
1333 // Use cached value if it corresponds to this accessible.
1334 if (caretOffset
!= -1) {
1335 if (text
== this) return caretOffset
;
1337 nsINode
* textNode
= text
->GetNode();
1338 // Ignore offset if cached accessible isn't a text leaf.
1339 if (nsCoreUtils::IsAncestorOf(GetNode(), textNode
))
1340 return TransformOffset(text
, textNode
->IsText() ? caretOffset
: 0, false);
1343 // No caret if the focused node is not inside this DOM node and this DOM node
1344 // is not inside of focused node.
1345 FocusManager::FocusDisposition focusDisp
=
1346 FocusMgr()->IsInOrContainsFocus(this);
1347 if (focusDisp
== FocusManager::eNone
) return -1;
1349 // Turn the focus node and offset of the selection into caret hypretext
1351 dom::Selection
* domSel
= DOMSelection();
1352 NS_ENSURE_TRUE(domSel
, -1);
1354 nsINode
* focusNode
= domSel
->GetFocusNode();
1355 uint32_t focusOffset
= domSel
->FocusOffset();
1357 // No caret if this DOM node is inside of focused node but the selection's
1358 // focus point is not inside of this DOM node.
1359 if (focusDisp
== FocusManager::eContainedByFocus
) {
1360 nsINode
* resultNode
=
1361 nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode
, focusOffset
);
1363 nsINode
* thisNode
= GetNode();
1364 if (resultNode
!= thisNode
&&
1365 !nsCoreUtils::IsAncestorOf(thisNode
, resultNode
))
1369 return DOMPointToOffset(focusNode
, focusOffset
);
1372 int32_t HyperTextAccessible::CaretLineNumber() {
1373 // Provide the line number for the caret, relative to the
1374 // currently focused node. Use a 1-based index
1375 RefPtr
<nsFrameSelection
> frameSelection
= FrameSelection();
1376 if (!frameSelection
) return -1;
1378 dom::Selection
* domSel
= frameSelection
->GetSelection(SelectionType::eNormal
);
1379 if (!domSel
) return -1;
1381 nsINode
* caretNode
= domSel
->GetFocusNode();
1382 if (!caretNode
|| !caretNode
->IsContent()) return -1;
1384 nsIContent
* caretContent
= caretNode
->AsContent();
1385 if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent
)) return -1;
1387 int32_t returnOffsetUnused
;
1388 uint32_t caretOffset
= domSel
->FocusOffset();
1389 CaretAssociationHint hint
= frameSelection
->GetHint();
1390 nsIFrame
* caretFrame
= frameSelection
->GetFrameForNodeOffset(
1391 caretContent
, caretOffset
, hint
, &returnOffsetUnused
);
1392 NS_ENSURE_TRUE(caretFrame
, -1);
1394 int32_t lineNumber
= 1;
1395 nsAutoLineIterator lineIterForCaret
;
1396 nsIContent
* hyperTextContent
= IsContent() ? mContent
.get() : nullptr;
1397 while (caretFrame
) {
1398 if (hyperTextContent
== caretFrame
->GetContent()) {
1399 return lineNumber
; // Must be in a single line hyper text, there is no
1402 nsContainerFrame
* parentFrame
= caretFrame
->GetParent();
1403 if (!parentFrame
) break;
1405 // Add lines for the sibling frames before the caret
1406 nsIFrame
* sibling
= parentFrame
->PrincipalChildList().FirstChild();
1407 while (sibling
&& sibling
!= caretFrame
) {
1408 nsAutoLineIterator lineIterForSibling
= sibling
->GetLineIterator();
1409 if (lineIterForSibling
) {
1410 // For the frames before that grab all the lines
1411 int32_t addLines
= lineIterForSibling
->GetNumLines();
1412 lineNumber
+= addLines
;
1414 sibling
= sibling
->GetNextSibling();
1417 // Get the line number relative to the container with lines
1418 if (!lineIterForCaret
) { // Add the caret line just once
1419 lineIterForCaret
= parentFrame
->GetLineIterator();
1420 if (lineIterForCaret
) {
1421 // Ancestor of caret
1422 int32_t addLines
= lineIterForCaret
->FindLineContaining(caretFrame
);
1423 lineNumber
+= addLines
;
1427 caretFrame
= parentFrame
;
1430 MOZ_ASSERT_UNREACHABLE(
1431 "DOM ancestry had this hypertext but frame ancestry didn't");
1435 LayoutDeviceIntRect
HyperTextAccessible::GetCaretRect(nsIWidget
** aWidget
) {
1438 RefPtr
<nsCaret
> caret
= mDoc
->PresShellPtr()->GetCaret();
1439 NS_ENSURE_TRUE(caret
, LayoutDeviceIntRect());
1441 bool isVisible
= caret
->IsVisible();
1442 if (!isVisible
) return LayoutDeviceIntRect();
1445 nsIFrame
* frame
= caret
->GetGeometry(&rect
);
1446 if (!frame
|| rect
.IsEmpty()) return LayoutDeviceIntRect();
1449 // Offset from widget origin to the frame origin, which includes chrome
1451 *aWidget
= frame
->GetNearestWidget(offset
);
1452 NS_ENSURE_TRUE(*aWidget
, LayoutDeviceIntRect());
1453 rect
.MoveBy(offset
);
1455 LayoutDeviceIntRect caretRect
= LayoutDeviceIntRect::FromUnknownRect(
1456 rect
.ToOutsidePixels(frame
->PresContext()->AppUnitsPerDevPixel()));
1458 // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
1460 caretRect
.MoveBy((*aWidget
)->WidgetToScreenOffset() -
1461 (*aWidget
)->GetClientOffset());
1463 // Correct for character size, so that caret always matches the size of
1464 // the character. This is important for font size transitions, and is
1465 // necessary because the Gecko caret uses the previous character's size as
1466 // the user moves forward in the text by character.
1467 nsIntRect charRect
= CharBounds(
1468 CaretOffset(), nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
);
1469 if (!charRect
.IsEmpty()) {
1470 caretRect
.SetTopEdge(charRect
.Y());
1475 void HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType
,
1476 nsTArray
<nsRange
*>* aRanges
) {
1477 // Ignore selection if it is not visible.
1478 RefPtr
<nsFrameSelection
> frameSelection
= FrameSelection();
1479 if (!frameSelection
|| frameSelection
->GetDisplaySelection() <=
1480 nsISelectionController::SELECTION_HIDDEN
)
1483 dom::Selection
* domSel
= frameSelection
->GetSelection(aSelectionType
);
1484 if (!domSel
) return;
1486 nsINode
* startNode
= GetNode();
1488 RefPtr
<TextEditor
> textEditor
= GetEditor();
1490 startNode
= textEditor
->GetRoot();
1493 if (!startNode
) return;
1495 uint32_t childCount
= startNode
->GetChildCount();
1496 nsresult rv
= domSel
->GetRangesForIntervalArray(startNode
, 0, startNode
,
1497 childCount
, true, aRanges
);
1498 NS_ENSURE_SUCCESS_VOID(rv
);
1500 // Remove collapsed ranges
1501 uint32_t numRanges
= aRanges
->Length();
1502 for (uint32_t idx
= 0; idx
< numRanges
; idx
++) {
1503 if ((*aRanges
)[idx
]->Collapsed()) {
1504 aRanges
->RemoveElementAt(idx
);
1511 int32_t HyperTextAccessible::SelectionCount() {
1512 nsTArray
<nsRange
*> ranges
;
1513 GetSelectionDOMRanges(SelectionType::eNormal
, &ranges
);
1514 return ranges
.Length();
1517 bool HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum
,
1518 int32_t* aStartOffset
,
1519 int32_t* aEndOffset
) {
1520 *aStartOffset
= *aEndOffset
= 0;
1522 nsTArray
<nsRange
*> ranges
;
1523 GetSelectionDOMRanges(SelectionType::eNormal
, &ranges
);
1525 uint32_t rangeCount
= ranges
.Length();
1526 if (aSelectionNum
< 0 || aSelectionNum
>= static_cast<int32_t>(rangeCount
))
1529 nsRange
* range
= ranges
[aSelectionNum
];
1531 // Get start and end points.
1532 nsINode
* startNode
= range
->GetStartContainer();
1533 nsINode
* endNode
= range
->GetEndContainer();
1534 int32_t startOffset
= range
->StartOffset(), endOffset
= range
->EndOffset();
1536 // Make sure start is before end, by swapping DOM points. This occurs when
1537 // the user selects backwards in the text.
1538 const Maybe
<int32_t> order
=
1539 nsContentUtils::ComparePoints(endNode
, endOffset
, startNode
, startOffset
);
1542 MOZ_ASSERT_UNREACHABLE();
1547 nsINode
* tempNode
= startNode
;
1548 startNode
= endNode
;
1550 int32_t tempOffset
= startOffset
;
1551 startOffset
= endOffset
;
1552 endOffset
= tempOffset
;
1555 if (!startNode
->IsInclusiveDescendantOf(mContent
))
1558 *aStartOffset
= DOMPointToOffset(startNode
, startOffset
);
1560 if (!endNode
->IsInclusiveDescendantOf(mContent
))
1561 *aEndOffset
= CharacterCount();
1563 *aEndOffset
= DOMPointToOffset(endNode
, endOffset
, true);
1567 bool HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum
,
1568 int32_t aStartOffset
,
1569 int32_t aEndOffset
) {
1570 index_t startOffset
= ConvertMagicOffset(aStartOffset
);
1571 index_t endOffset
= ConvertMagicOffset(aEndOffset
);
1572 if (!startOffset
.IsValid() || !endOffset
.IsValid() ||
1573 std::max(startOffset
, endOffset
) > CharacterCount()) {
1574 NS_ERROR("Wrong in offset");
1578 dom::Selection
* domSel
= DOMSelection();
1579 if (!domSel
) return false;
1581 RefPtr
<nsRange
> range
;
1582 uint32_t rangeCount
= domSel
->RangeCount();
1583 if (aSelectionNum
== static_cast<int32_t>(rangeCount
)) {
1584 range
= nsRange::Create(mContent
);
1586 range
= domSel
->GetRangeAt(aSelectionNum
);
1589 if (!range
) return false;
1591 if (!OffsetsToDOMRange(std::min(startOffset
, endOffset
),
1592 std::max(startOffset
, endOffset
), range
))
1595 // If this is not a new range, notify selection listeners that the existing
1596 // selection range has changed. Otherwise, just add the new range.
1597 if (aSelectionNum
!= static_cast<int32_t>(rangeCount
)) {
1598 domSel
->RemoveRangeAndUnselectFramesAndNotifyListeners(*range
,
1602 IgnoredErrorResult err
;
1603 domSel
->AddRangeAndSelectFramesAndNotifyListeners(*range
, err
);
1605 if (!err
.Failed()) {
1606 // Changing the direction of the selection assures that the caret
1607 // will be at the logical end of the selection.
1608 domSel
->SetDirection(startOffset
< endOffset
? eDirNext
: eDirPrevious
);
1615 bool HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum
) {
1616 dom::Selection
* domSel
= DOMSelection();
1617 if (!domSel
) return false;
1619 if (aSelectionNum
< 0 ||
1620 aSelectionNum
>= static_cast<int32_t>(domSel
->RangeCount()))
1623 domSel
->RemoveRangeAndUnselectFramesAndNotifyListeners(
1624 *domSel
->GetRangeAt(aSelectionNum
), IgnoreErrors());
1628 void HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset
,
1630 uint32_t aScrollType
) {
1631 RefPtr
<nsRange
> range
= nsRange::Create(mContent
);
1632 if (OffsetsToDOMRange(aStartOffset
, aEndOffset
, range
))
1633 nsCoreUtils::ScrollSubstringTo(GetFrame(), range
, aScrollType
);
1636 void HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset
,
1638 uint32_t aCoordinateType
,
1639 int32_t aX
, int32_t aY
) {
1640 nsIFrame
* frame
= GetFrame();
1644 nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordinateType
, this);
1646 RefPtr
<nsRange
> range
= nsRange::Create(mContent
);
1647 if (!OffsetsToDOMRange(aStartOffset
, aEndOffset
, range
)) return;
1649 nsPresContext
* presContext
= frame
->PresContext();
1650 nsPoint coordsInAppUnits
=
1651 ToAppUnits(coords
, presContext
->AppUnitsPerDevPixel());
1653 bool initialScrolled
= false;
1654 nsIFrame
* parentFrame
= frame
;
1655 while ((parentFrame
= parentFrame
->GetParent())) {
1656 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(parentFrame
);
1657 if (scrollableFrame
) {
1658 if (!initialScrolled
) {
1659 // Scroll substring to the given point. Turn the point into percents
1660 // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
1661 nsRect frameRect
= parentFrame
->GetScreenRectInAppUnits();
1662 nscoord offsetPointX
= coordsInAppUnits
.x
- frameRect
.X();
1663 nscoord offsetPointY
= coordsInAppUnits
.y
- frameRect
.Y();
1665 nsSize
size(parentFrame
->GetSize());
1667 // avoid divide by zero
1668 size
.width
= size
.width
? size
.width
: 1;
1669 size
.height
= size
.height
? size
.height
: 1;
1671 int16_t hPercent
= offsetPointX
* 100 / size
.width
;
1672 int16_t vPercent
= offsetPointY
* 100 / size
.height
;
1674 nsresult rv
= nsCoreUtils::ScrollSubstringTo(
1675 frame
, range
, ScrollAxis(vPercent
, WhenToScroll::Always
),
1676 ScrollAxis(hPercent
, WhenToScroll::Always
));
1677 if (NS_FAILED(rv
)) return;
1679 initialScrolled
= true;
1681 // Substring was scrolled to the given point already inside its closest
1682 // scrollable area. If there are nested scrollable areas then make
1683 // sure we scroll lower areas to the given point inside currently
1684 // traversed scrollable area.
1685 nsCoreUtils::ScrollFrameToPoint(parentFrame
, frame
, coords
);
1688 frame
= parentFrame
;
1692 void HyperTextAccessible::EnclosingRange(a11y::TextRange
& aRange
) const {
1693 if (IsTextField()) {
1694 aRange
.Set(mDoc
, const_cast<HyperTextAccessible
*>(this), 0,
1695 const_cast<HyperTextAccessible
*>(this), CharacterCount());
1697 aRange
.Set(mDoc
, mDoc
, 0, mDoc
, mDoc
->CharacterCount());
1701 void HyperTextAccessible::SelectionRanges(
1702 nsTArray
<a11y::TextRange
>* aRanges
) const {
1703 MOZ_ASSERT(aRanges
->Length() == 0, "TextRange array supposed to be empty");
1705 dom::Selection
* sel
= DOMSelection();
1708 aRanges
->SetCapacity(sel
->RangeCount());
1710 for (uint32_t idx
= 0; idx
< sel
->RangeCount(); idx
++) {
1711 nsRange
* DOMRange
= sel
->GetRangeAt(idx
);
1712 HyperTextAccessible
* startContainer
=
1713 nsAccUtils::GetTextContainer(DOMRange
->GetStartContainer());
1714 HyperTextAccessible
* endContainer
=
1715 nsAccUtils::GetTextContainer(DOMRange
->GetEndContainer());
1716 if (!startContainer
|| !endContainer
) {
1720 int32_t startOffset
= startContainer
->DOMPointToOffset(
1721 DOMRange
->GetStartContainer(), DOMRange
->StartOffset(), false);
1722 int32_t endOffset
= endContainer
->DOMPointToOffset(
1723 DOMRange
->GetEndContainer(), DOMRange
->EndOffset(), true);
1725 TextRange
tr(IsTextField() ? const_cast<HyperTextAccessible
*>(this) : mDoc
,
1726 startContainer
, startOffset
, endContainer
, endOffset
);
1727 *(aRanges
->AppendElement()) = std::move(tr
);
1731 void HyperTextAccessible::VisibleRanges(
1732 nsTArray
<a11y::TextRange
>* aRanges
) const {}
1734 void HyperTextAccessible::RangeByChild(Accessible
* aChild
,
1735 a11y::TextRange
& aRange
) const {
1736 HyperTextAccessible
* ht
= aChild
->AsHyperText();
1738 aRange
.Set(mDoc
, ht
, 0, ht
, ht
->CharacterCount());
1742 Accessible
* child
= aChild
;
1743 Accessible
* parent
= nullptr;
1744 while ((parent
= child
->Parent()) && !(ht
= parent
->AsHyperText()))
1747 // If no text then return collapsed text range, otherwise return a range
1748 // containing the text enclosed by the given child.
1750 int32_t childIdx
= child
->IndexInParent();
1751 int32_t startOffset
= ht
->GetChildOffset(childIdx
);
1753 child
->IsTextLeaf() ? ht
->GetChildOffset(childIdx
+ 1) : startOffset
;
1754 aRange
.Set(mDoc
, ht
, startOffset
, ht
, endOffset
);
1758 void HyperTextAccessible::RangeAtPoint(int32_t aX
, int32_t aY
,
1759 a11y::TextRange
& aRange
) const {
1760 Accessible
* child
= mDoc
->ChildAtPoint(aX
, aY
, eDeepestChild
);
1763 Accessible
* parent
= nullptr;
1764 while ((parent
= child
->Parent()) && !parent
->IsHyperText()) child
= parent
;
1766 // Return collapsed text range for the point.
1768 HyperTextAccessible
* ht
= parent
->AsHyperText();
1769 int32_t offset
= ht
->GetChildOffset(child
);
1770 aRange
.Set(mDoc
, ht
, offset
, ht
, offset
);
1774 ////////////////////////////////////////////////////////////////////////////////
1775 // Accessible public
1777 // Accessible protected
1778 ENameValueFlag
HyperTextAccessible::NativeName(nsString
& aName
) const {
1779 // Check @alt attribute for invalid img elements.
1780 bool hasImgAlt
= false;
1781 if (mContent
->IsHTMLElement(nsGkAtoms::img
)) {
1782 hasImgAlt
= mContent
->AsElement()->GetAttr(kNameSpaceID_None
,
1783 nsGkAtoms::alt
, aName
);
1784 if (!aName
.IsEmpty()) return eNameOK
;
1787 ENameValueFlag nameFlag
= AccessibleWrap::NativeName(aName
);
1788 if (!aName
.IsEmpty()) return nameFlag
;
1790 // Get name from title attribute for HTML abbr and acronym elements making it
1791 // a valid name from markup. Otherwise their name isn't picked up by recursive
1792 // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
1793 if (IsAbbreviation() && mContent
->AsElement()->GetAttr(
1794 kNameSpaceID_None
, nsGkAtoms::title
, aName
))
1795 aName
.CompressWhitespace();
1797 return hasImgAlt
? eNoNameOnPurpose
: eNameOK
;
1800 void HyperTextAccessible::Shutdown() {
1802 AccessibleWrap::Shutdown();
1805 bool HyperTextAccessible::RemoveChild(Accessible
* aAccessible
) {
1806 int32_t childIndex
= aAccessible
->IndexInParent();
1807 int32_t count
= mOffsets
.Length() - childIndex
;
1808 if (count
> 0) mOffsets
.RemoveElementsAt(childIndex
, count
);
1810 return AccessibleWrap::RemoveChild(aAccessible
);
1813 bool HyperTextAccessible::InsertChildAt(uint32_t aIndex
, Accessible
* aChild
) {
1814 int32_t count
= mOffsets
.Length() - aIndex
;
1816 mOffsets
.RemoveElementsAt(aIndex
, count
);
1818 return AccessibleWrap::InsertChildAt(aIndex
, aChild
);
1821 Relation
HyperTextAccessible::RelationByType(RelationType aType
) const {
1822 Relation rel
= Accessible::RelationByType(aType
);
1825 case RelationType::NODE_CHILD_OF
:
1826 if (HasOwnContent() && mContent
->IsMathMLElement()) {
1827 Accessible
* parent
= Parent();
1829 nsIContent
* parentContent
= parent
->GetContent();
1830 if (parentContent
&&
1831 parentContent
->IsMathMLElement(nsGkAtoms::mroot_
)) {
1832 // Add a relation pointing to the parent <mroot>.
1833 rel
.AppendTarget(parent
);
1838 case RelationType::NODE_PARENT_OF
:
1839 if (HasOwnContent() && mContent
->IsMathMLElement(nsGkAtoms::mroot_
)) {
1840 Accessible
* base
= GetChildAt(0);
1841 Accessible
* index
= GetChildAt(1);
1842 if (base
&& index
) {
1843 // Append the <mroot> children in the order index, base.
1844 rel
.AppendTarget(index
);
1845 rel
.AppendTarget(base
);
1856 ////////////////////////////////////////////////////////////////////////////////
1857 // HyperTextAccessible public static
1859 nsresult
HyperTextAccessible::ContentToRenderedOffset(
1860 nsIFrame
* aFrame
, int32_t aContentOffset
, uint32_t* aRenderedOffset
) const {
1862 // Current frame not rendered -- this can happen if text is set on
1863 // something with display: none
1864 *aRenderedOffset
= 0;
1868 if (IsTextField()) {
1869 *aRenderedOffset
= aContentOffset
;
1873 NS_ASSERTION(aFrame
->IsTextFrame(), "Need text frame for offset conversion");
1874 NS_ASSERTION(aFrame
->GetPrevContinuation() == nullptr,
1875 "Call on primary frame only");
1877 nsIFrame::RenderedText text
=
1878 aFrame
->GetRenderedText(aContentOffset
, aContentOffset
+ 1,
1879 nsIFrame::TextOffsetType::OffsetsInContentText
,
1880 nsIFrame::TrailingWhitespace::DontTrim
);
1881 *aRenderedOffset
= text
.mOffsetWithinNodeRenderedText
;
1886 nsresult
HyperTextAccessible::RenderedToContentOffset(
1887 nsIFrame
* aFrame
, uint32_t aRenderedOffset
, int32_t* aContentOffset
) const {
1888 if (IsTextField()) {
1889 *aContentOffset
= aRenderedOffset
;
1893 *aContentOffset
= 0;
1894 NS_ENSURE_TRUE(aFrame
, NS_ERROR_FAILURE
);
1896 NS_ASSERTION(aFrame
->IsTextFrame(), "Need text frame for offset conversion");
1897 NS_ASSERTION(aFrame
->GetPrevContinuation() == nullptr,
1898 "Call on primary frame only");
1900 nsIFrame::RenderedText text
=
1901 aFrame
->GetRenderedText(aRenderedOffset
, aRenderedOffset
+ 1,
1902 nsIFrame::TextOffsetType::OffsetsInRenderedText
,
1903 nsIFrame::TrailingWhitespace::DontTrim
);
1904 *aContentOffset
= text
.mOffsetWithinNodeText
;
1909 ////////////////////////////////////////////////////////////////////////////////
1910 // HyperTextAccessible public
1912 int32_t HyperTextAccessible::GetChildOffset(uint32_t aChildIndex
,
1913 bool aInvalidateAfter
) const {
1914 if (aChildIndex
== 0) {
1915 if (aInvalidateAfter
) mOffsets
.Clear();
1920 int32_t count
= mOffsets
.Length() - aChildIndex
;
1922 if (aInvalidateAfter
) mOffsets
.RemoveElementsAt(aChildIndex
, count
);
1924 return mOffsets
[aChildIndex
- 1];
1927 uint32_t lastOffset
=
1928 mOffsets
.IsEmpty() ? 0 : mOffsets
[mOffsets
.Length() - 1];
1930 while (mOffsets
.Length() < aChildIndex
) {
1931 Accessible
* child
= mChildren
[mOffsets
.Length()];
1932 lastOffset
+= nsAccUtils::TextLength(child
);
1933 mOffsets
.AppendElement(lastOffset
);
1936 return mOffsets
[aChildIndex
- 1];
1939 int32_t HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset
) const {
1940 uint32_t lastOffset
= 0;
1941 const uint32_t offsetCount
= mOffsets
.Length();
1943 if (offsetCount
> 0) {
1944 lastOffset
= mOffsets
[offsetCount
- 1];
1945 if (aOffset
< lastOffset
) {
1947 if (BinarySearch(mOffsets
, 0, offsetCount
, aOffset
, &index
)) {
1948 return (index
< (offsetCount
- 1)) ? index
+ 1 : index
;
1951 return (index
== offsetCount
) ? -1 : index
;
1955 uint32_t childCount
= ChildCount();
1956 while (mOffsets
.Length() < childCount
) {
1957 Accessible
* child
= GetChildAt(mOffsets
.Length());
1958 lastOffset
+= nsAccUtils::TextLength(child
);
1959 mOffsets
.AppendElement(lastOffset
);
1960 if (aOffset
< lastOffset
) return mOffsets
.Length() - 1;
1963 if (aOffset
== lastOffset
) return mOffsets
.Length() - 1;
1968 ////////////////////////////////////////////////////////////////////////////////
1969 // HyperTextAccessible protected
1971 nsresult
HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame
* aFrame
,
1973 Accessible
* aAccessible
,
1975 NS_ENSURE_ARG(aAccessible
);
1978 // If the given frame is null then set offset after the DOM node of the
1979 // given accessible.
1980 NS_ASSERTION(!aAccessible
->IsDoc(),
1981 "Shouldn't be called on document accessible!");
1983 nsIContent
* content
= aAccessible
->GetContent();
1984 NS_ASSERTION(content
, "Shouldn't operate on defunct accessible!");
1986 nsIContent
* parent
= content
->GetParent();
1988 aPoint
->idx
= parent
->ComputeIndexOf(content
) + 1;
1989 aPoint
->node
= parent
;
1991 } else if (aFrame
->IsTextFrame()) {
1992 nsIContent
* content
= aFrame
->GetContent();
1993 NS_ENSURE_STATE(content
);
1995 nsIFrame
* primaryFrame
= content
->GetPrimaryFrame();
1997 RenderedToContentOffset(primaryFrame
, aOffset
, &(aPoint
->idx
));
1998 NS_ENSURE_SUCCESS(rv
, rv
);
2000 aPoint
->node
= content
;
2003 nsIContent
* content
= aFrame
->GetContent();
2004 NS_ENSURE_STATE(content
);
2006 nsIContent
* parent
= content
->GetParent();
2007 NS_ENSURE_STATE(parent
);
2009 aPoint
->idx
= parent
->ComputeIndexOf(content
);
2010 aPoint
->node
= parent
;
2016 // HyperTextAccessible
2017 void HyperTextAccessible::GetSpellTextAttr(
2018 nsINode
* aNode
, int32_t aNodeOffset
, uint32_t* aStartOffset
,
2019 uint32_t* aEndOffset
, nsIPersistentProperties
* aAttributes
) {
2020 RefPtr
<nsFrameSelection
> fs
= FrameSelection();
2023 dom::Selection
* domSel
= fs
->GetSelection(SelectionType::eSpellCheck
);
2024 if (!domSel
) return;
2026 int32_t rangeCount
= domSel
->RangeCount();
2027 if (rangeCount
<= 0) return;
2029 uint32_t startOffset
= 0, endOffset
= 0;
2030 for (int32_t idx
= 0; idx
< rangeCount
; idx
++) {
2031 nsRange
* range
= domSel
->GetRangeAt(idx
);
2032 if (range
->Collapsed()) continue;
2034 // See if the point comes after the range in which case we must continue in
2035 // case there is another range after this one.
2036 nsINode
* endNode
= range
->GetEndContainer();
2037 int32_t endNodeOffset
= range
->EndOffset();
2038 Maybe
<int32_t> order
= nsContentUtils::ComparePoints(
2039 aNode
, aNodeOffset
, endNode
, endNodeOffset
);
2040 if (NS_WARN_IF(!order
)) {
2048 // At this point our point is either in this range or before it but after
2049 // the previous range. So we check to see if the range starts before the
2050 // point in which case the point is in the missspelled range, otherwise it
2051 // must be before the range and after the previous one if any.
2052 nsINode
* startNode
= range
->GetStartContainer();
2053 int32_t startNodeOffset
= range
->StartOffset();
2054 order
= nsContentUtils::ComparePoints(startNode
, startNodeOffset
, aNode
,
2057 // As (`aNode`, `aNodeOffset`) is comparable to the end of the range, it
2058 // should also be comparable to the range's start. Returning here
2059 // prevents crashes in release builds.
2060 MOZ_ASSERT_UNREACHABLE();
2065 startOffset
= DOMPointToOffset(startNode
, startNodeOffset
);
2067 endOffset
= DOMPointToOffset(endNode
, endNodeOffset
);
2069 if (startOffset
> *aStartOffset
) *aStartOffset
= startOffset
;
2071 if (endOffset
< *aEndOffset
) *aEndOffset
= endOffset
;
2074 nsAccUtils::SetAccAttr(aAttributes
, nsGkAtoms::invalid
,
2075 NS_LITERAL_STRING("spelling"));
2081 // This range came after the point.
2082 endOffset
= DOMPointToOffset(startNode
, startNodeOffset
);
2085 nsRange
* prevRange
= domSel
->GetRangeAt(idx
- 1);
2086 startOffset
= DOMPointToOffset(prevRange
->GetEndContainer(),
2087 prevRange
->EndOffset());
2090 // The previous range might not be within this accessible. In that case,
2091 // DOMPointToOffset returns length as a fallback. We don't want to use
2092 // that offset if so, hence the startOffset < *aEndOffset check.
2093 if (startOffset
> *aStartOffset
&& startOffset
< *aEndOffset
)
2094 *aStartOffset
= startOffset
;
2096 if (endOffset
< *aEndOffset
) *aEndOffset
= endOffset
;
2101 // We never found a range that ended after the point, therefore we know that
2102 // the point is not in a range, that we do not need to compute an end offset,
2103 // and that we should use the end offset of the last range to compute the
2104 // start offset of the text attribute range.
2105 nsRange
* prevRange
= domSel
->GetRangeAt(rangeCount
- 1);
2107 DOMPointToOffset(prevRange
->GetEndContainer(), prevRange
->EndOffset());
2109 // The previous range might not be within this accessible. In that case,
2110 // DOMPointToOffset returns length as a fallback. We don't want to use
2111 // that offset if so, hence the startOffset < *aEndOffset check.
2112 if (startOffset
> *aStartOffset
&& startOffset
< *aEndOffset
)
2113 *aStartOffset
= startOffset
;
2116 bool HyperTextAccessible::IsTextRole() {
2117 const nsRoleMapEntry
* roleMapEntry
= ARIARoleMap();
2118 if (roleMapEntry
&& (roleMapEntry
->role
== roles::GRAPHIC
||
2119 roleMapEntry
->role
== roles::IMAGE_MAP
||
2120 roleMapEntry
->role
== roles::SLIDER
||
2121 roleMapEntry
->role
== roles::PROGRESSBAR
||
2122 roleMapEntry
->role
== roles::SEPARATOR
))