Bug 1588114 - [remote] Implement Network.setCookie and Network.setCookies. r=remote...
[gecko.git] / accessible / generic / HyperTextAccessible.cpp
blobd642f55964f58779dd88b14dab4d7a687484c6d0
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"
14 #include "Role.h"
15 #include "States.h"
16 #include "TextAttrs.h"
17 #include "TextRange.h"
18 #include "TreeWalker.h"
20 #include "nsCaret.h"
21 #include "nsContentUtils.h"
22 #include "nsDebug.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"
32 #include "nsRange.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"
45 #include <algorithm>
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;
83 return states;
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,
98 &startContentOffset);
99 NS_ENSURE_SUCCESS(rv, nsIntRect());
100 rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset);
101 NS_ENSURE_SUCCESS(rv, nsIntRect());
103 nsIFrame* frame;
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());
111 nsRect screenRect;
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,
134 &frameTextEndPoint);
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) {
155 aText.Truncate();
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");
162 return;
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);
178 return;
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;
188 childIdx++) {
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,
201 int32_t aNodeOffset,
202 bool aIsEndOffset) const {
203 if (!aNode) return 0;
205 uint32_t offset = 0;
206 nsINode* findNode = nullptr;
208 if (aNodeOffset == -1) {
209 findNode = aNode;
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);
222 findNode = aNode;
224 } else {
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);
234 if (!findNode) {
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.
239 return 0;
242 // Case #2: there are no children, we're at this node.
243 findNode = aNode;
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
257 // first search)
258 Accessible* descendant = nullptr;
259 if (findNode) {
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.
264 return 0;
267 descendant = mDoc->GetAccessible(findNode);
268 if (!descendant && findNode->IsContent()) {
269 Accessible* container = mDoc->GetContainerAccessible(findNode);
270 if (container) {
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,
283 uint32_t aOffset,
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;
288 while (descendant) {
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.
299 if (aIsEndOffset) {
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()) {
307 offset = 0;
308 } else {
309 offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
311 } else {
312 offset = 0;
315 descendant = parent;
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
325 * or includes aNode.
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
329 * ancestors too.
331 static nsIContent* GetElementAsContentOf(nsINode* aNode) {
332 if (auto* element = dom::Element::FromNode(aNode)) {
333 return element;
335 return aNode->GetParentElement();
338 bool HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset,
339 int32_t aEndOffset,
340 nsRange* aRange) {
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);
357 return 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);
370 return true;
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.
376 if (aOffset == 0) {
377 RefPtr<TextEditor> textEditor = GetEditor();
378 if (textEditor) {
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);
391 // A text leaf case.
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();
397 int32_t idx = 0;
398 if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
399 innerOffset, &idx)))
400 return DOMPoint();
402 return DOMPoint(content, idx);
405 // Set the DOM point right after the text node.
406 MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
407 innerOffset = 1;
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)
416 : DOMPoint();
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());
444 return aDOMPoint;
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;
459 do {
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
464 // innerOffset.
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.
479 if (text != this) {
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;
488 switch (aAmount) {
489 case eSelectLine:
490 case eSelectEndLine:
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,
495 aWordMovementType)
496 : nextOffset;
498 default:
499 return nextOffset;
504 innerOffset -= text->GetChildOffset(childIdx);
506 text = child->AsHyperText();
507 } while (text);
509 nsIFrame* childFrame = child->GetFrame();
510 if (!childFrame) {
511 NS_ERROR("No child frame");
512 return 0;
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!");
545 return 0;
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.
557 ++hyperTextOffset;
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
584 // were pressed).
585 if (IsEmptyLastLineOffset(aOffset))
586 return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
588 uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
589 return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
592 case ePrevLineEnd: {
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
600 // pressed).
601 tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
602 return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
605 case eThisLineBegin:
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);
611 case eThisLineEnd:
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);
628 case eNextLineEnd: {
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);
639 return 0;
642 void HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
643 AccessibleTextBoundary aBoundaryType,
644 int32_t* aStartOffset,
645 int32_t* aEndOffset,
646 nsAString& aText) {
647 *aStartOffset = *aEndOffset = 0;
648 aText.Truncate();
650 index_t convertedOffset = ConvertMagicOffset(aOffset);
651 if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
652 NS_ERROR("Wrong in offset!");
653 return;
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);
664 break;
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()) {
671 *aEndOffset =
672 FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
673 *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
674 } else {
675 *aStartOffset =
676 FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
677 *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
678 if (*aEndOffset != static_cast<int32_t>(adjustedOffset)) {
679 *aEndOffset = *aStartOffset;
680 *aStartOffset =
681 FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
684 TextSubstring(*aStartOffset, *aEndOffset, aText);
685 break;
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);
693 break;
696 case nsIAccessibleText::BOUNDARY_LINE_START:
697 *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin);
698 *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
699 TextSubstring(*aStartOffset, *aEndOffset, aText);
700 break;
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);
710 break;
715 void HyperTextAccessible::TextAtOffset(int32_t aOffset,
716 AccessibleTextBoundary aBoundaryType,
717 int32_t* aStartOffset,
718 int32_t* aEndOffset, nsAString& aText) {
719 *aStartOffset = *aEndOffset = 0;
720 aText.Truncate();
722 uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
723 if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
724 NS_ERROR("Wrong given offset!");
725 return;
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;
735 else
736 CharAt(adjustedOffset, aText, aStartOffset, aEndOffset);
737 break;
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);
746 break;
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);
755 break;
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);
764 break;
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);
774 break;
778 void HyperTextAccessible::TextAfterOffset(int32_t aOffset,
779 AccessibleTextBoundary aBoundaryType,
780 int32_t* aStartOffset,
781 int32_t* aEndOffset,
782 nsAString& aText) {
783 *aStartOffset = *aEndOffset = 0;
784 aText.Truncate();
786 index_t convertedOffset = ConvertMagicOffset(aOffset);
787 if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
788 NS_ERROR("Wrong in offset!");
789 return;
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();
802 else
803 CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset);
804 break;
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);
811 break;
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);
820 } else {
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);
829 break;
831 case nsIAccessibleText::BOUNDARY_LINE_START:
832 *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
833 *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin);
834 TextSubstring(*aStartOffset, *aEndOffset, aText);
835 break;
837 case nsIAccessibleText::BOUNDARY_LINE_END:
838 *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
839 *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd);
840 TextSubstring(*aStartOffset, *aEndOffset, aText);
841 break;
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!");
857 return nullptr;
860 RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties();
862 Accessible* accAtOffset = GetChildAtOffset(offset);
863 if (!accAtOffset) {
864 // Offset 0 is correct offset when accessible has empty text. Include
865 // default attributes if they were requested, otherwise return empty set.
866 if (offset == 0) {
867 if (aIncludeDefAttrs) {
868 TextAttrsMgr textAttrsMgr(this);
869 textAttrsMgr.GetAttributes(attributes);
871 return attributes.forget();
873 return nullptr;
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,
882 accAtOffsetIdx);
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();
921 if (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());
936 if (mathMLFrame) {
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_);
954 break;
955 case roles::MATHML_FRACTION:
956 nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
957 IndexInParent() == 0 ? nsGkAtoms::numerator
958 : nsGkAtoms::denominator);
959 break;
960 case roles::MATHML_ROOT:
961 nsAccUtils::SetAccAttr(
962 aAttributes, nsGkAtoms::xmlroles,
963 IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::root_index);
964 break;
965 case roles::MATHML_SUB:
966 nsAccUtils::SetAccAttr(
967 aAttributes, nsGkAtoms::xmlroles,
968 IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::subscript);
969 break;
970 case roles::MATHML_SUP:
971 nsAccUtils::SetAccAttr(
972 aAttributes, nsGkAtoms::xmlroles,
973 IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::superscript);
974 break;
975 case roles::MATHML_SUB_SUP: {
976 int32_t index = IndexInParent();
977 nsAccUtils::SetAccAttr(
978 aAttributes, nsGkAtoms::xmlroles,
979 index == 0
980 ? nsGkAtoms::base
981 : (index == 1 ? nsGkAtoms::subscript : nsGkAtoms::superscript));
982 } break;
983 case roles::MATHML_UNDER:
984 nsAccUtils::SetAccAttr(
985 aAttributes, nsGkAtoms::xmlroles,
986 IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::underscript);
987 break;
988 case roles::MATHML_OVER:
989 nsAccUtils::SetAccAttr(
990 aAttributes, nsGkAtoms::xmlroles,
991 IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::overscript);
992 break;
993 case roles::MATHML_UNDER_OVER: {
994 int32_t index = IndexInParent();
995 nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
996 index == 0
997 ? nsGkAtoms::base
998 : (index == 1 ? nsGkAtoms::underscript
999 : nsGkAtoms::overscript));
1000 } break;
1001 case roles::MATHML_MULTISCRIPTS: {
1002 // Get the <multiscripts> base.
1003 nsIContent* child;
1004 bool baseFound = false;
1005 for (child = parent->GetContent()->GetFirstChild(); child;
1006 child = child->GetNextSibling()) {
1007 if (child->IsMathMLElement()) {
1008 baseFound = true;
1009 break;
1012 if (baseFound) {
1013 nsIContent* content = GetContent();
1014 if (child == content) {
1015 // We are the base.
1016 nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
1017 nsGkAtoms::base);
1018 } else {
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_)) {
1026 postscript = false;
1027 subscript = true;
1028 continue;
1030 if (child == content) {
1031 if (postscript) {
1032 nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
1033 subscript ? nsGkAtoms::subscript
1034 : nsGkAtoms::superscript);
1035 } else {
1036 nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
1037 subscript ? nsGkAtoms::presubscript
1038 : nsGkAtoms::presuperscript);
1040 break;
1042 subscript = !subscript;
1046 } break;
1047 default:
1048 break;
1053 already_AddRefed<nsIPersistentProperties>
1054 HyperTextAccessible::NativeAttributes() {
1055 nsCOMPtr<nsIPersistentProperties> attributes =
1056 AccessibleWrap::NativeAttributes();
1058 // 'formatting' attribute is deprecated, 'display' attribute should be
1059 // instead.
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;
1101 return nullptr;
1104 int32_t HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY,
1105 uint32_t aCoordType) {
1106 nsIFrame* hyperFrame = GetFrame();
1107 if (!hyperFrame) return -1;
1109 nsIntPoint coords =
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
1128 int32_t offset = 0;
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;
1137 while (frame) {
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) {
1144 // Finished
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;
1158 return offset;
1160 frame = frame->GetNextContinuation();
1163 offset += nsAccUtils::TextLength(childAcc);
1166 return -1; // Not found
1169 nsIntRect HyperTextAccessible::TextBounds(int32_t aStartOffset,
1170 int32_t aEndOffset,
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");
1177 return nsIntRect();
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();
1190 nsIntRect bounds;
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();
1196 if (!frame) {
1197 MOZ_ASSERT_UNREACHABLE("No frame for a child!");
1198 continue;
1201 int32_t nextOffset = GetChildOffset(childIdx);
1202 if (nextOffset >= static_cast<int32_t>(endOffset)) {
1203 bounds.UnionRect(
1204 bounds, GetBoundsInFrame(frame, offset1, endOffset - prevOffset));
1205 break;
1208 bounds.UnionRect(bounds,
1209 GetBoundsInFrame(frame, offset1, nextOffset - prevOffset));
1211 prevOffset = nextOffset;
1212 offset1 = 0;
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);
1231 return bounds;
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
1237 // editor
1238 Accessible* ancestor = Parent();
1239 while (ancestor) {
1240 HyperTextAccessible* hyperText = ancestor->AsHyperText();
1241 if (hyperText) {
1242 // Recursion will stop at container doc because it has its own impl
1243 // of GetEditor()
1244 return hyperText->GetEditor();
1247 ancestor = ancestor->Parent();
1250 return nullptr;
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,
1269 int32_t aEndPos) {
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
1303 // accessible.
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));
1318 return NS_OK;
1321 int32_t HyperTextAccessible::CaretOffset() const {
1322 // Not focused focusable accessible except document accessible doesn't have
1323 // a caret.
1324 if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
1325 (InteractiveState() & states::FOCUSABLE)) {
1326 return -1;
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
1350 // offset.
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))
1366 return -1;
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
1400 // line iterator
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");
1432 return lineNumber;
1435 LayoutDeviceIntRect HyperTextAccessible::GetCaretRect(nsIWidget** aWidget) {
1436 *aWidget = nullptr;
1438 RefPtr<nsCaret> caret = mDoc->PresShellPtr()->GetCaret();
1439 NS_ENSURE_TRUE(caret, LayoutDeviceIntRect());
1441 bool isVisible = caret->IsVisible();
1442 if (!isVisible) return LayoutDeviceIntRect();
1444 nsRect rect;
1445 nsIFrame* frame = caret->GetGeometry(&rect);
1446 if (!frame || rect.IsEmpty()) return LayoutDeviceIntRect();
1448 nsPoint offset;
1449 // Offset from widget origin to the frame origin, which includes chrome
1450 // on the widget.
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()));
1457 // clang-format off
1458 // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
1459 // clang-format on
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());
1472 return caretRect;
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)
1481 return;
1483 dom::Selection* domSel = frameSelection->GetSelection(aSelectionType);
1484 if (!domSel) return;
1486 nsINode* startNode = GetNode();
1488 RefPtr<TextEditor> textEditor = GetEditor();
1489 if (textEditor) {
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);
1505 --numRanges;
1506 --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))
1527 return false;
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);
1541 if (!order) {
1542 MOZ_ASSERT_UNREACHABLE();
1543 return false;
1546 if (*order < 0) {
1547 nsINode* tempNode = startNode;
1548 startNode = endNode;
1549 endNode = tempNode;
1550 int32_t tempOffset = startOffset;
1551 startOffset = endOffset;
1552 endOffset = tempOffset;
1555 if (!startNode->IsInclusiveDescendantOf(mContent))
1556 *aStartOffset = 0;
1557 else
1558 *aStartOffset = DOMPointToOffset(startNode, startOffset);
1560 if (!endNode->IsInclusiveDescendantOf(mContent))
1561 *aEndOffset = CharacterCount();
1562 else
1563 *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
1564 return 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");
1575 return false;
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);
1585 } else {
1586 range = domSel->GetRangeAt(aSelectionNum);
1589 if (!range) return false;
1591 if (!OffsetsToDOMRange(std::min(startOffset, endOffset),
1592 std::max(startOffset, endOffset), range))
1593 return false;
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,
1599 IgnoreErrors());
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);
1609 return true;
1612 return false;
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()))
1621 return false;
1623 domSel->RemoveRangeAndUnselectFramesAndNotifyListeners(
1624 *domSel->GetRangeAt(aSelectionNum), IgnoreErrors());
1625 return true;
1628 void HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset,
1629 int32_t aEndOffset,
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,
1637 int32_t aEndOffset,
1638 uint32_t aCoordinateType,
1639 int32_t aX, int32_t aY) {
1640 nsIFrame* frame = GetFrame();
1641 if (!frame) return;
1643 nsIntPoint coords =
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;
1680 } else {
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());
1696 } else {
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();
1706 if (!sel) return;
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) {
1717 continue;
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();
1737 if (ht) {
1738 aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount());
1739 return;
1742 Accessible* child = aChild;
1743 Accessible* parent = nullptr;
1744 while ((parent = child->Parent()) && !(ht = parent->AsHyperText()))
1745 child = parent;
1747 // If no text then return collapsed text range, otherwise return a range
1748 // containing the text enclosed by the given child.
1749 if (ht) {
1750 int32_t childIdx = child->IndexInParent();
1751 int32_t startOffset = ht->GetChildOffset(childIdx);
1752 int32_t endOffset =
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);
1761 if (!child) return;
1763 Accessible* parent = nullptr;
1764 while ((parent = child->Parent()) && !parent->IsHyperText()) child = parent;
1766 // Return collapsed text range for the point.
1767 if (parent) {
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() {
1801 mOffsets.Clear();
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;
1815 if (count > 0) {
1816 mOffsets.RemoveElementsAt(aIndex, count);
1818 return AccessibleWrap::InsertChildAt(aIndex, aChild);
1821 Relation HyperTextAccessible::RelationByType(RelationType aType) const {
1822 Relation rel = Accessible::RelationByType(aType);
1824 switch (aType) {
1825 case RelationType::NODE_CHILD_OF:
1826 if (HasOwnContent() && mContent->IsMathMLElement()) {
1827 Accessible* parent = Parent();
1828 if (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);
1837 break;
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);
1848 break;
1849 default:
1850 break;
1853 return rel;
1856 ////////////////////////////////////////////////////////////////////////////////
1857 // HyperTextAccessible public static
1859 nsresult HyperTextAccessible::ContentToRenderedOffset(
1860 nsIFrame* aFrame, int32_t aContentOffset, uint32_t* aRenderedOffset) const {
1861 if (!aFrame) {
1862 // Current frame not rendered -- this can happen if text is set on
1863 // something with display: none
1864 *aRenderedOffset = 0;
1865 return NS_OK;
1868 if (IsTextField()) {
1869 *aRenderedOffset = aContentOffset;
1870 return NS_OK;
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;
1883 return NS_OK;
1886 nsresult HyperTextAccessible::RenderedToContentOffset(
1887 nsIFrame* aFrame, uint32_t aRenderedOffset, int32_t* aContentOffset) const {
1888 if (IsTextField()) {
1889 *aContentOffset = aRenderedOffset;
1890 return NS_OK;
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;
1906 return NS_OK;
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();
1917 return aChildIndex;
1920 int32_t count = mOffsets.Length() - aChildIndex;
1921 if (count > 0) {
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) {
1946 size_t index;
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;
1965 return -1;
1968 ////////////////////////////////////////////////////////////////////////////////
1969 // HyperTextAccessible protected
1971 nsresult HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame,
1972 int32_t aOffset,
1973 Accessible* aAccessible,
1974 DOMPoint* aPoint) {
1975 NS_ENSURE_ARG(aAccessible);
1977 if (!aFrame) {
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();
1996 nsresult rv =
1997 RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx));
1998 NS_ENSURE_SUCCESS(rv, rv);
2000 aPoint->node = content;
2002 } else {
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;
2013 return NS_OK;
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();
2021 if (!fs) return;
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)) {
2041 continue;
2044 if (*order >= 0) {
2045 continue;
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,
2055 aNodeOffset);
2056 if (!order) {
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();
2061 return;
2064 if (*order <= 0) {
2065 startOffset = DOMPointToOffset(startNode, startNodeOffset);
2067 endOffset = DOMPointToOffset(endNode, endNodeOffset);
2069 if (startOffset > *aStartOffset) *aStartOffset = startOffset;
2071 if (endOffset < *aEndOffset) *aEndOffset = endOffset;
2073 if (aAttributes) {
2074 nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
2075 NS_LITERAL_STRING("spelling"));
2078 return;
2081 // This range came after the point.
2082 endOffset = DOMPointToOffset(startNode, startNodeOffset);
2084 if (idx > 0) {
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;
2098 return;
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);
2106 startOffset =
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))
2123 return false;
2125 return true;