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 "nsAccessibilityService.h"
10 #include "nsIAccessibleTypes.h"
11 #include "AccAttributes.h"
12 #include "HTMLListAccessible.h"
13 #include "LocalAccessible-inl.h"
15 #include "mozilla/a11y/Role.h"
17 #include "TextAttrs.h"
18 #include "TextRange.h"
19 #include "TreeWalker.h"
22 #include "nsContentUtils.h"
24 #include "nsFocusManager.h"
25 #include "nsIEditingSession.h"
26 #include "nsContainerFrame.h"
27 #include "nsFrameSelection.h"
28 #include "nsILineIterator.h"
29 #include "nsIScrollableFrame.h"
30 #include "nsIMathMLFrame.h"
31 #include "nsLayoutUtils.h"
33 #include "mozilla/Assertions.h"
34 #include "mozilla/EditorBase.h"
35 #include "mozilla/HTMLEditor.h"
36 #include "mozilla/IntegerRange.h"
37 #include "mozilla/PresShell.h"
38 #include "mozilla/dom/Element.h"
39 #include "mozilla/dom/HTMLBRElement.h"
40 #include "mozilla/dom/Selection.h"
41 #include "gfxSkipChars.h"
43 using namespace mozilla
;
44 using namespace mozilla::a11y
;
46 ////////////////////////////////////////////////////////////////////////////////
47 // HyperTextAccessible
48 ////////////////////////////////////////////////////////////////////////////////
50 HyperTextAccessible::HyperTextAccessible(nsIContent
* aNode
, DocAccessible
* aDoc
)
51 : AccessibleWrap(aNode
, aDoc
) {
52 mType
= eHyperTextType
;
53 mGenericTypes
|= eHyperText
;
56 role
HyperTextAccessible::NativeRole() const {
57 a11y::role r
= GetAccService()->MarkupRole(mContent
);
58 if (r
!= roles::NOTHING
) return r
;
60 nsIFrame
* frame
= GetFrame();
61 if (frame
&& frame
->IsInlineFrame()) return roles::TEXT
;
63 return roles::TEXT_CONTAINER
;
66 uint64_t HyperTextAccessible::NativeState() const {
67 uint64_t states
= AccessibleWrap::NativeState();
70 states
|= states::EDITABLE
;
72 } else if (mContent
->IsHTMLElement(nsGkAtoms::article
)) {
73 // We want <article> to behave like a document in terms of readonly state.
74 states
|= states::READONLY
;
77 nsIFrame
* frame
= GetFrame();
78 if ((states
& states::EDITABLE
) || (frame
&& frame
->IsSelectable(nullptr))) {
79 // If the accessible is editable the layout selectable state only disables
80 // mouse selection, but keyboard (shift+arrow) selection is still possible.
81 states
|= states::SELECTABLE_TEXT
;
87 bool HyperTextAccessible::IsEditable() const {
91 return mContent
->AsElement()->State().HasState(dom::ElementState::READWRITE
);
94 uint32_t HyperTextAccessible::DOMPointToOffset(nsINode
* aNode
,
96 bool aIsEndOffset
) const {
100 nsINode
* findNode
= nullptr;
102 if (aNodeOffset
== -1) {
105 } else if (aNode
->IsText()) {
106 // For text nodes, aNodeOffset comes in as a character offset
107 // Text offset will be added at the end, if we find the offset in this
108 // hypertext We want the "skipped" offset into the text (rendered text
109 // without the extra whitespace)
110 nsIFrame
* frame
= aNode
->AsContent()->GetPrimaryFrame();
111 NS_ENSURE_TRUE(frame
, 0);
113 nsresult rv
= ContentToRenderedOffset(frame
, aNodeOffset
, &offset
);
114 NS_ENSURE_SUCCESS(rv
, 0);
119 // findNode could be null if aNodeOffset == # of child nodes, which means
120 // one of two things:
121 // 1) there are no children, and the passed-in node is not mContent -- use
122 // parentContent for the node to find
123 // 2) there are no children and the passed-in node is mContent, which means
124 // we're an empty nsIAccessibleText
125 // 3) there are children and we're at the end of the children
127 findNode
= aNode
->GetChildAt_Deprecated(aNodeOffset
);
129 if (aNodeOffset
== 0) {
130 if (aNode
== GetNode()) {
131 // Case #1: this accessible has no children and thus has empty text,
132 // we can only be at hypertext offset 0.
136 // Case #2: there are no children, we're at this node.
138 } else if (aNodeOffset
== static_cast<int32_t>(aNode
->GetChildCount())) {
139 // Case #3: we're after the last child, get next node to this one.
140 for (nsINode
* tmpNode
= aNode
;
141 !findNode
&& tmpNode
&& tmpNode
!= mContent
;
142 tmpNode
= tmpNode
->GetParent()) {
143 findNode
= tmpNode
->GetNextSibling();
149 // Get accessible for this findNode, or if that node isn't accessible, use the
150 // accessible for the next DOM node which has one (based on forward depth
152 LocalAccessible
* descendant
= nullptr;
154 dom::HTMLBRElement
* brElement
= dom::HTMLBRElement::FromNode(findNode
);
155 if (brElement
&& brElement
->IsPaddingForEmptyEditor()) {
156 // This <br> is the hacky "padding <br> element" used when there is no
157 // text in the editor.
161 descendant
= mDoc
->GetAccessible(findNode
);
162 if (!descendant
&& findNode
->IsContent()) {
163 LocalAccessible
* container
= mDoc
->GetContainerAccessible(findNode
);
165 TreeWalker
walker(container
, findNode
->AsContent(),
166 TreeWalker::eWalkContextTree
);
167 descendant
= walker
.Next();
168 if (!descendant
) descendant
= container
;
173 return TransformOffset(descendant
, offset
, aIsEndOffset
);
176 uint32_t HyperTextAccessible::TransformOffset(LocalAccessible
* aDescendant
,
178 bool aIsEndOffset
) const {
179 // From the descendant, go up and get the immediate child of this hypertext.
180 uint32_t offset
= aOffset
;
181 LocalAccessible
* descendant
= aDescendant
;
183 LocalAccessible
* parent
= descendant
->LocalParent();
184 if (parent
== this) return GetChildOffset(descendant
) + offset
;
186 // This offset no longer applies because the passed-in text object is not
187 // a child of the hypertext. This happens when there are nested hypertexts,
188 // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
189 // to make it relative the hypertext.
190 // If the end offset is not supposed to be inclusive and the original point
191 // is not at 0 offset then the returned offset should be after an embedded
192 // character the original point belongs to.
194 // Similar to our special casing in FindOffset, we add handling for
195 // bulleted lists here because PeekOffset returns the inner text node
196 // for a list when it should return the list bullet.
197 // We manually set the offset so the error doesn't propagate up.
198 if (offset
== 0 && parent
&& parent
->IsHTMLListItem() &&
199 descendant
->LocalPrevSibling() &&
200 descendant
->LocalPrevSibling() ==
201 parent
->AsHTMLListItem()->Bullet()) {
204 offset
= (offset
> 0 || descendant
->IndexInParent() > 0) ? 1 : 0;
213 // If the given a11y point cannot be mapped into offset relative this
214 // hypertext offset then return length as fallback value.
215 return CharacterCount();
218 DOMPoint
HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset
) const {
219 // 0 offset is valid even if no children. In this case the associated editor
220 // is empty so return a DOM point for editor root element.
222 RefPtr
<EditorBase
> editorBase
= GetEditor();
224 if (editorBase
->IsEmpty()) {
225 return DOMPoint(editorBase
->GetRoot(), 0);
230 int32_t childIdx
= GetChildIndexAtOffset(aOffset
);
231 if (childIdx
== -1) return DOMPoint();
233 LocalAccessible
* child
= LocalChildAt(childIdx
);
234 int32_t innerOffset
= aOffset
- GetChildOffset(childIdx
);
237 if (child
->IsTextLeaf()) {
238 // The point is inside the text node. This is always true for any text leaf
239 // except a last child one. See assertion below.
240 if (aOffset
< GetChildOffset(childIdx
+ 1)) {
241 nsIContent
* content
= child
->GetContent();
243 if (NS_FAILED(RenderedToContentOffset(content
->GetPrimaryFrame(),
244 innerOffset
, &idx
))) {
248 return DOMPoint(content
, idx
);
251 // Set the DOM point right after the text node.
252 MOZ_ASSERT(static_cast<uint32_t>(aOffset
) == CharacterCount());
256 // Case of embedded object. The point is either before or after the element.
257 NS_ASSERTION(innerOffset
== 0 || innerOffset
== 1, "A wrong inner offset!");
258 nsINode
* node
= child
->GetNode();
259 nsINode
* parentNode
= node
->GetParentNode();
260 return parentNode
? DOMPoint(parentNode
,
261 parentNode
->ComputeIndexOf_Deprecated(node
) +
266 already_AddRefed
<AccAttributes
> HyperTextAccessible::DefaultTextAttributes() {
267 RefPtr
<AccAttributes
> attributes
= new AccAttributes();
269 TextAttrsMgr
textAttrsMgr(this);
270 textAttrsMgr
.GetAttributes(attributes
);
271 return attributes
.forget();
274 void HyperTextAccessible::SetMathMLXMLRoles(AccAttributes
* aAttributes
) {
275 // Add MathML xmlroles based on the position inside the parent.
276 LocalAccessible
* parent
= LocalParent();
278 switch (parent
->Role()) {
279 case roles::MATHML_CELL
:
280 case roles::MATHML_ENCLOSED
:
281 case roles::MATHML_ERROR
:
282 case roles::MATHML_MATH
:
283 case roles::MATHML_ROW
:
284 case roles::MATHML_SQUARE_ROOT
:
285 case roles::MATHML_STYLE
:
286 if (Role() == roles::MATHML_OPERATOR
) {
287 // This is an operator inside an <mrow> (or an inferred <mrow>).
288 // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
289 // XXX We should probably do something similar for MATHML_FENCED, but
290 // operators do not appear in the accessible tree. See bug 1175747.
291 nsIMathMLFrame
* mathMLFrame
= do_QueryFrame(GetFrame());
293 nsEmbellishData embellishData
;
294 mathMLFrame
->GetEmbellishData(embellishData
);
295 if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData
.flags
)) {
296 if (!LocalPrevSibling()) {
297 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
298 nsGkAtoms::open_fence
);
299 } else if (!LocalNextSibling()) {
300 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
301 nsGkAtoms::close_fence
);
304 if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData
.flags
)) {
305 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
306 nsGkAtoms::separator_
);
311 case roles::MATHML_FRACTION
:
312 aAttributes
->SetAttribute(
313 nsGkAtoms::xmlroles
, IndexInParent() == 0 ? nsGkAtoms::numerator
314 : nsGkAtoms::denominator
);
316 case roles::MATHML_ROOT
:
317 aAttributes
->SetAttribute(
319 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::root_index
);
321 case roles::MATHML_SUB
:
322 aAttributes
->SetAttribute(
324 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::subscript
);
326 case roles::MATHML_SUP
:
327 aAttributes
->SetAttribute(
329 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::superscript
);
331 case roles::MATHML_SUB_SUP
: {
332 int32_t index
= IndexInParent();
333 aAttributes
->SetAttribute(
337 : (index
== 1 ? nsGkAtoms::subscript
: nsGkAtoms::superscript
));
339 case roles::MATHML_UNDER
:
340 aAttributes
->SetAttribute(
342 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::underscript
);
344 case roles::MATHML_OVER
:
345 aAttributes
->SetAttribute(
347 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::overscript
);
349 case roles::MATHML_UNDER_OVER
: {
350 int32_t index
= IndexInParent();
351 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
354 : (index
== 1 ? nsGkAtoms::underscript
355 : nsGkAtoms::overscript
));
357 case roles::MATHML_MULTISCRIPTS
: {
358 // Get the <multiscripts> base.
360 bool baseFound
= false;
361 for (child
= parent
->GetContent()->GetFirstChild(); child
;
362 child
= child
->GetNextSibling()) {
363 if (child
->IsMathMLElement()) {
369 nsIContent
* content
= GetContent();
370 if (child
== content
) {
372 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
, nsGkAtoms::base
);
374 // Browse the list of scripts to find us and determine our type.
375 bool postscript
= true;
376 bool subscript
= true;
377 for (child
= child
->GetNextSibling(); child
;
378 child
= child
->GetNextSibling()) {
379 if (!child
->IsMathMLElement()) continue;
380 if (child
->IsMathMLElement(nsGkAtoms::mprescripts_
)) {
385 if (child
== content
) {
387 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
388 subscript
? nsGkAtoms::subscript
389 : nsGkAtoms::superscript
);
391 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
393 ? nsGkAtoms::presubscript
394 : nsGkAtoms::presuperscript
);
398 subscript
= !subscript
;
409 already_AddRefed
<AccAttributes
> HyperTextAccessible::NativeAttributes() {
410 RefPtr
<AccAttributes
> attributes
= AccessibleWrap::NativeAttributes();
412 // 'formatting' attribute is deprecated, 'display' attribute should be
414 nsIFrame
* frame
= GetFrame();
415 if (frame
&& frame
->IsBlockFrame()) {
416 attributes
->SetAttribute(nsGkAtoms::formatting
, nsGkAtoms::block
);
419 if (FocusMgr()->IsFocused(this)) {
420 int32_t lineNumber
= CaretLineNumber();
421 if (lineNumber
>= 1) {
422 attributes
->SetAttribute(nsGkAtoms::lineNumber
, lineNumber
);
426 if (HasOwnContent()) {
427 GetAccService()->MarkupAttributes(this, attributes
);
428 if (mContent
->IsMathMLElement()) SetMathMLXMLRoles(attributes
);
431 return attributes
.forget();
434 int32_t HyperTextAccessible::OffsetAtPoint(int32_t aX
, int32_t aY
,
435 uint32_t aCoordType
) {
436 nsIFrame
* hyperFrame
= GetFrame();
437 if (!hyperFrame
) return -1;
439 LayoutDeviceIntPoint coords
=
440 nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordType
, this);
442 nsPresContext
* presContext
= mDoc
->PresContext();
443 nsPoint coordsInAppUnits
= LayoutDeviceIntPoint::ToAppUnits(
444 coords
, presContext
->AppUnitsPerDevPixel());
446 nsRect frameScreenRect
= hyperFrame
->GetScreenRectInAppUnits();
447 if (!frameScreenRect
.Contains(coordsInAppUnits
.x
, coordsInAppUnits
.y
)) {
448 return -1; // Not found
451 nsPoint
pointInHyperText(coordsInAppUnits
.x
- frameScreenRect
.X(),
452 coordsInAppUnits
.y
- frameScreenRect
.Y());
454 // Go through the frames to check if each one has the point.
455 // When one does, add up the character offsets until we have a match
457 // We have an point in an accessible child of this, now we need to add up the
458 // offsets before it to what we already have
460 uint32_t childCount
= ChildCount();
461 for (uint32_t childIdx
= 0; childIdx
< childCount
; childIdx
++) {
462 LocalAccessible
* childAcc
= mChildren
[childIdx
];
464 nsIFrame
* primaryFrame
= childAcc
->GetFrame();
465 NS_ENSURE_TRUE(primaryFrame
, -1);
467 nsIFrame
* frame
= primaryFrame
;
469 nsIContent
* content
= frame
->GetContent();
470 NS_ENSURE_TRUE(content
, -1);
471 nsPoint pointInFrame
= pointInHyperText
- frame
->GetOffsetTo(hyperFrame
);
472 nsSize frameSize
= frame
->GetSize();
473 if (pointInFrame
.x
< frameSize
.width
&&
474 pointInFrame
.y
< frameSize
.height
) {
476 if (frame
->IsTextFrame()) {
477 nsIFrame::ContentOffsets contentOffsets
=
478 frame
->GetContentOffsetsFromPointExternal(
479 pointInFrame
, nsIFrame::IGNORE_SELECTION_STYLE
);
480 if (contentOffsets
.IsNull() || contentOffsets
.content
!= content
) {
481 return -1; // Not found
483 uint32_t addToOffset
;
484 nsresult rv
= ContentToRenderedOffset(
485 primaryFrame
, contentOffsets
.offset
, &addToOffset
);
486 NS_ENSURE_SUCCESS(rv
, -1);
487 offset
+= addToOffset
;
491 frame
= frame
->GetNextContinuation();
494 offset
+= nsAccUtils::TextLength(childAcc
);
497 return -1; // Not found
500 already_AddRefed
<EditorBase
> HyperTextAccessible::GetEditor() const {
501 if (!mContent
->HasFlag(NODE_IS_EDITABLE
)) {
502 // If we're inside an editable container, then return that container's
504 LocalAccessible
* ancestor
= LocalParent();
506 HyperTextAccessible
* hyperText
= ancestor
->AsHyperText();
508 // Recursion will stop at container doc because it has its own impl
510 return hyperText
->GetEditor();
513 ancestor
= ancestor
->LocalParent();
519 nsCOMPtr
<nsIDocShell
> docShell
= nsCoreUtils::GetDocShellFor(mContent
);
520 nsCOMPtr
<nsIEditingSession
> editingSession
;
521 docShell
->GetEditingSession(getter_AddRefs(editingSession
));
522 if (!editingSession
) return nullptr; // No editing session interface
524 dom::Document
* docNode
= mDoc
->DocumentNode();
525 RefPtr
<HTMLEditor
> htmlEditor
=
526 editingSession
->GetHTMLEditorForWindow(docNode
->GetWindow());
527 return htmlEditor
.forget();
531 * =================== Caret & Selection ======================
534 nsresult
HyperTextAccessible::SetSelectionRange(int32_t aStartPos
,
536 // Before setting the selection range, we need to ensure that the editor
537 // is initialized. (See bug 804927.)
538 // Otherwise, it's possible that lazy editor initialization will override
539 // the selection we set here and leave the caret at the end of the text.
540 // By calling GetEditor here, we ensure that editor initialization is
541 // completed before we set the selection.
542 RefPtr
<EditorBase
> editorBase
= GetEditor();
544 bool isFocusable
= InteractiveState() & states::FOCUSABLE
;
546 // If accessible is focusable then focus it before setting the selection to
547 // neglect control's selection changes on focus if any (for example, inputs
548 // that do select all on focus).
549 // some input controls
550 if (isFocusable
) TakeFocus();
552 RefPtr
<dom::Selection
> domSel
= DOMSelection();
553 NS_ENSURE_STATE(domSel
);
555 // Set up the selection.
556 for (const uint32_t idx
: Reversed(IntegerRange(1u, domSel
->RangeCount()))) {
557 MOZ_ASSERT(domSel
->RangeCount() == idx
+ 1);
558 RefPtr
<nsRange
> range
{domSel
->GetRangeAt(idx
)};
560 break; // The range count has been changed by somebody else.
562 domSel
->RemoveRangeAndUnselectFramesAndNotifyListeners(*range
,
565 SetSelectionBoundsAt(0, aStartPos
, aEndPos
);
567 // Make sure it is visible
568 domSel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
569 ScrollAxis(), ScrollAxis(),
570 dom::Selection::SCROLL_FOR_CARET_MOVE
|
571 dom::Selection::SCROLL_OVERFLOW_HIDDEN
);
573 // When selection is done, move the focus to the selection if accessible is
574 // not focusable. That happens when selection is set within hypertext
576 if (isFocusable
) return NS_OK
;
578 nsFocusManager
* DOMFocusManager
= nsFocusManager::GetFocusManager();
579 if (DOMFocusManager
) {
580 NS_ENSURE_TRUE(mDoc
, NS_ERROR_FAILURE
);
581 dom::Document
* docNode
= mDoc
->DocumentNode();
582 NS_ENSURE_TRUE(docNode
, NS_ERROR_FAILURE
);
583 nsCOMPtr
<nsPIDOMWindowOuter
> window
= docNode
->GetWindow();
584 RefPtr
<dom::Element
> result
;
585 DOMFocusManager
->MoveFocus(
586 window
, nullptr, nsIFocusManager::MOVEFOCUS_CARET
,
587 nsIFocusManager::FLAG_BYMOVEFOCUS
, getter_AddRefs(result
));
593 int32_t HyperTextAccessible::CaretOffset() const {
594 // Not focused focusable accessible except document accessible doesn't have
596 if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
597 (InteractiveState() & states::FOCUSABLE
)) {
601 // Check cached value.
602 int32_t caretOffset
= -1;
603 HyperTextAccessible
* text
= SelectionMgr()->AccessibleWithCaret(&caretOffset
);
605 // Use cached value if it corresponds to this accessible.
606 if (caretOffset
!= -1) {
607 if (text
== this) return caretOffset
;
609 nsINode
* textNode
= text
->GetNode();
610 // Ignore offset if cached accessible isn't a text leaf.
611 if (nsCoreUtils::IsAncestorOf(GetNode(), textNode
)) {
612 return TransformOffset(text
, textNode
->IsText() ? caretOffset
: 0, false);
616 // No caret if the focused node is not inside this DOM node and this DOM node
617 // is not inside of focused node.
618 FocusManager::FocusDisposition focusDisp
=
619 FocusMgr()->IsInOrContainsFocus(this);
620 if (focusDisp
== FocusManager::eNone
) return -1;
622 // Turn the focus node and offset of the selection into caret hypretext
624 dom::Selection
* domSel
= DOMSelection();
625 NS_ENSURE_TRUE(domSel
, -1);
627 nsINode
* focusNode
= domSel
->GetFocusNode();
628 uint32_t focusOffset
= domSel
->FocusOffset();
630 // No caret if this DOM node is inside of focused node but the selection's
631 // focus point is not inside of this DOM node.
632 if (focusDisp
== FocusManager::eContainedByFocus
) {
633 nsINode
* resultNode
=
634 nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode
, focusOffset
);
636 nsINode
* thisNode
= GetNode();
637 if (resultNode
!= thisNode
&&
638 !nsCoreUtils::IsAncestorOf(thisNode
, resultNode
)) {
643 return DOMPointToOffset(focusNode
, focusOffset
);
646 int32_t HyperTextAccessible::CaretLineNumber() {
647 // Provide the line number for the caret, relative to the
648 // currently focused node. Use a 1-based index
649 RefPtr
<nsFrameSelection
> frameSelection
= FrameSelection();
650 if (!frameSelection
) return -1;
652 dom::Selection
* domSel
= frameSelection
->GetSelection(SelectionType::eNormal
);
653 if (!domSel
) return -1;
655 nsINode
* caretNode
= domSel
->GetFocusNode();
656 if (!caretNode
|| !caretNode
->IsContent()) return -1;
658 nsIContent
* caretContent
= caretNode
->AsContent();
659 if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent
)) return -1;
661 int32_t returnOffsetUnused
;
662 uint32_t caretOffset
= domSel
->FocusOffset();
663 CaretAssociationHint hint
= frameSelection
->GetHint();
664 nsIFrame
* caretFrame
= frameSelection
->GetFrameForNodeOffset(
665 caretContent
, caretOffset
, hint
, &returnOffsetUnused
);
666 NS_ENSURE_TRUE(caretFrame
, -1);
668 AutoAssertNoDomMutations guard
; // The nsILineIterators below will break if
669 // the DOM is modified while they're in use!
670 int32_t lineNumber
= 1;
671 nsILineIterator
* lineIterForCaret
= nullptr;
672 nsIContent
* hyperTextContent
= IsContent() ? mContent
.get() : nullptr;
674 if (hyperTextContent
== caretFrame
->GetContent()) {
675 return lineNumber
; // Must be in a single line hyper text, there is no
678 nsContainerFrame
* parentFrame
= caretFrame
->GetParent();
679 if (!parentFrame
) break;
681 // Add lines for the sibling frames before the caret
682 nsIFrame
* sibling
= parentFrame
->PrincipalChildList().FirstChild();
683 while (sibling
&& sibling
!= caretFrame
) {
684 nsILineIterator
* lineIterForSibling
= sibling
->GetLineIterator();
685 if (lineIterForSibling
) {
686 // For the frames before that grab all the lines
687 int32_t addLines
= lineIterForSibling
->GetNumLines();
688 lineNumber
+= addLines
;
690 sibling
= sibling
->GetNextSibling();
693 // Get the line number relative to the container with lines
694 if (!lineIterForCaret
) { // Add the caret line just once
695 lineIterForCaret
= parentFrame
->GetLineIterator();
696 if (lineIterForCaret
) {
698 int32_t addLines
= lineIterForCaret
->FindLineContaining(caretFrame
);
699 lineNumber
+= addLines
;
703 caretFrame
= parentFrame
;
706 MOZ_ASSERT_UNREACHABLE(
707 "DOM ancestry had this hypertext but frame ancestry didn't");
711 LayoutDeviceIntRect
HyperTextAccessible::GetCaretRect(nsIWidget
** aWidget
) {
714 RefPtr
<nsCaret
> caret
= mDoc
->PresShellPtr()->GetCaret();
715 NS_ENSURE_TRUE(caret
, LayoutDeviceIntRect());
717 bool isVisible
= caret
->IsVisible();
718 if (!isVisible
) return LayoutDeviceIntRect();
721 nsIFrame
* frame
= caret
->GetGeometry(&rect
);
722 if (!frame
|| rect
.IsEmpty()) return LayoutDeviceIntRect();
724 PresShell
* presShell
= mDoc
->PresShellPtr();
725 // Transform rect to be relative to the root frame.
726 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
727 rect
= nsLayoutUtils::TransformFrameRectToAncestor(frame
, rect
, rootFrame
);
728 // We need to inverse translate with the offset of the edge of the visual
729 // viewport from top edge of the layout viewport.
730 nsPoint viewportOffset
= presShell
->GetVisualViewportOffset() -
731 presShell
->GetLayoutViewportOffset();
732 rect
.MoveBy(-viewportOffset
);
733 // We need to take into account a non-1 resolution set on the presshell.
734 // This happens with async pinch zooming. Here we scale the bounds before
735 // adding the screen-relative offset.
736 rect
.ScaleRoundOut(presShell
->GetResolution());
737 // Now we need to put the rect in absolute screen coords.
738 nsRect rootScreenRect
= rootFrame
->GetScreenRectInAppUnits();
739 rect
.MoveBy(rootScreenRect
.TopLeft());
740 // Finally, convert from app units.
741 auto caretRect
= LayoutDeviceIntRect::FromAppUnitsToNearest(
742 rect
, presShell
->GetPresContext()->AppUnitsPerDevPixel());
744 // Correct for character size, so that caret always matches the size of
745 // the character. This is important for font size transitions, and is
746 // necessary because the Gecko caret uses the previous character's size as
747 // the user moves forward in the text by character.
748 int32_t caretOffset
= CaretOffset();
749 if (NS_WARN_IF(caretOffset
== -1)) {
750 // The caret offset will be -1 if this Accessible isn't focused. Note that
751 // the DOM node contaning the caret might be focused, but the Accessible
752 // might not be; e.g. due to an autocomplete popup suggestion having a11y
754 return LayoutDeviceIntRect();
756 LayoutDeviceIntRect charRect
= CharBounds(
757 caretOffset
, nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
);
758 if (!charRect
.IsEmpty()) {
759 caretRect
.SetTopEdge(charRect
.Y());
762 *aWidget
= frame
->GetNearestWidget();
766 void HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType
,
767 nsTArray
<nsRange
*>* aRanges
) {
768 // Ignore selection if it is not visible.
769 RefPtr
<nsFrameSelection
> frameSelection
= FrameSelection();
770 if (!frameSelection
|| frameSelection
->GetDisplaySelection() <=
771 nsISelectionController::SELECTION_HIDDEN
) {
775 dom::Selection
* domSel
= frameSelection
->GetSelection(aSelectionType
);
778 nsINode
* startNode
= GetNode();
780 RefPtr
<EditorBase
> editorBase
= GetEditor();
782 startNode
= editorBase
->GetRoot();
785 if (!startNode
) return;
787 uint32_t childCount
= startNode
->GetChildCount();
788 nsresult rv
= domSel
->GetDynamicRangesForIntervalArray(
789 startNode
, 0, startNode
, childCount
, true, aRanges
);
790 NS_ENSURE_SUCCESS_VOID(rv
);
792 // Remove collapsed ranges
793 aRanges
->RemoveElementsBy(
794 [](const auto& range
) { return range
->Collapsed(); });
797 int32_t HyperTextAccessible::SelectionCount() {
798 nsTArray
<nsRange
*> ranges
;
799 GetSelectionDOMRanges(SelectionType::eNormal
, &ranges
);
800 return ranges
.Length();
803 bool HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum
,
804 int32_t* aStartOffset
,
805 int32_t* aEndOffset
) {
806 *aStartOffset
= *aEndOffset
= 0;
808 nsTArray
<nsRange
*> ranges
;
809 GetSelectionDOMRanges(SelectionType::eNormal
, &ranges
);
811 uint32_t rangeCount
= ranges
.Length();
812 if (aSelectionNum
< 0 || aSelectionNum
>= static_cast<int32_t>(rangeCount
)) {
816 nsRange
* range
= ranges
[aSelectionNum
];
818 // Get start and end points.
819 nsINode
* startNode
= range
->GetStartContainer();
820 nsINode
* endNode
= range
->GetEndContainer();
821 uint32_t startOffset
= range
->StartOffset();
822 uint32_t endOffset
= range
->EndOffset();
824 // Make sure start is before end, by swapping DOM points. This occurs when
825 // the user selects backwards in the text.
826 const Maybe
<int32_t> order
=
827 nsContentUtils::ComparePoints(endNode
, endOffset
, startNode
, startOffset
);
830 MOZ_ASSERT_UNREACHABLE();
835 std::swap(startNode
, endNode
);
836 std::swap(startOffset
, endOffset
);
839 if (!startNode
->IsInclusiveDescendantOf(mContent
)) {
843 DOMPointToOffset(startNode
, AssertedCast
<int32_t>(startOffset
));
846 if (!endNode
->IsInclusiveDescendantOf(mContent
)) {
847 *aEndOffset
= CharacterCount();
850 DOMPointToOffset(endNode
, AssertedCast
<int32_t>(endOffset
), true);
855 bool HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum
) {
856 RefPtr
<dom::Selection
> domSel
= DOMSelection();
857 if (!domSel
) return false;
859 if (aSelectionNum
< 0 ||
860 aSelectionNum
>= static_cast<int32_t>(domSel
->RangeCount())) {
864 const RefPtr
<nsRange
> range
{
865 domSel
->GetRangeAt(static_cast<uint32_t>(aSelectionNum
))};
866 domSel
->RemoveRangeAndUnselectFramesAndNotifyListeners(*range
,
871 void HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset
,
873 uint32_t aCoordinateType
,
874 int32_t aX
, int32_t aY
) {
875 nsIFrame
* frame
= GetFrame();
878 LayoutDeviceIntPoint coords
=
879 nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordinateType
, this);
881 RefPtr
<nsRange
> domRange
= nsRange::Create(mContent
);
882 TextRange
range(this, this, aStartOffset
, this, aEndOffset
);
883 if (!range
.AssignDOMRange(domRange
)) {
887 nsPresContext
* presContext
= frame
->PresContext();
888 nsPoint coordsInAppUnits
= LayoutDeviceIntPoint::ToAppUnits(
889 coords
, presContext
->AppUnitsPerDevPixel());
891 bool initialScrolled
= false;
892 nsIFrame
* parentFrame
= frame
;
893 while ((parentFrame
= parentFrame
->GetParent())) {
894 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(parentFrame
);
895 if (scrollableFrame
) {
896 if (!initialScrolled
) {
897 // Scroll substring to the given point. Turn the point into percents
898 // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
899 nsRect frameRect
= parentFrame
->GetScreenRectInAppUnits();
900 nscoord offsetPointX
= coordsInAppUnits
.x
- frameRect
.X();
901 nscoord offsetPointY
= coordsInAppUnits
.y
- frameRect
.Y();
903 nsSize
size(parentFrame
->GetSize());
905 // avoid divide by zero
906 size
.width
= size
.width
? size
.width
: 1;
907 size
.height
= size
.height
? size
.height
: 1;
909 int16_t hPercent
= offsetPointX
* 100 / size
.width
;
910 int16_t vPercent
= offsetPointY
* 100 / size
.height
;
912 nsresult rv
= nsCoreUtils::ScrollSubstringTo(
914 ScrollAxis(WhereToScroll(vPercent
), WhenToScroll::Always
),
915 ScrollAxis(WhereToScroll(hPercent
), WhenToScroll::Always
));
916 if (NS_FAILED(rv
)) return;
918 initialScrolled
= true;
920 // Substring was scrolled to the given point already inside its closest
921 // scrollable area. If there are nested scrollable areas then make
922 // sure we scroll lower areas to the given point inside currently
923 // traversed scrollable area.
924 nsCoreUtils::ScrollFrameToPoint(parentFrame
, frame
, coords
);
931 void HyperTextAccessible::SelectionRanges(
932 nsTArray
<a11y::TextRange
>* aRanges
) const {
933 dom::Selection
* sel
= DOMSelection();
938 TextRange::TextRangesFromSelection(sel
, aRanges
);
941 void HyperTextAccessible::ReplaceText(const nsAString
& aText
) {
942 if (aText
.Length() == 0) {
943 DeleteText(0, CharacterCount());
947 SetSelectionRange(0, CharacterCount());
949 RefPtr
<EditorBase
> editorBase
= GetEditor();
954 DebugOnly
<nsresult
> rv
= editorBase
->InsertTextAsAction(aText
);
955 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to insert the new text");
958 void HyperTextAccessible::InsertText(const nsAString
& aText
,
960 RefPtr
<EditorBase
> editorBase
= GetEditor();
962 SetSelectionRange(aPosition
, aPosition
);
963 DebugOnly
<nsresult
> rv
= editorBase
->InsertTextAsAction(aText
);
964 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to insert the text");
968 void HyperTextAccessible::CopyText(int32_t aStartPos
, int32_t aEndPos
) {
969 RefPtr
<EditorBase
> editorBase
= GetEditor();
971 SetSelectionRange(aStartPos
, aEndPos
);
976 void HyperTextAccessible::CutText(int32_t aStartPos
, int32_t aEndPos
) {
977 RefPtr
<EditorBase
> editorBase
= GetEditor();
979 SetSelectionRange(aStartPos
, aEndPos
);
984 void HyperTextAccessible::DeleteText(int32_t aStartPos
, int32_t aEndPos
) {
985 RefPtr
<EditorBase
> editorBase
= GetEditor();
989 SetSelectionRange(aStartPos
, aEndPos
);
990 DebugOnly
<nsresult
> rv
=
991 editorBase
->DeleteSelectionAsAction(nsIEditor::eNone
, nsIEditor::eStrip
);
992 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to delete text");
995 void HyperTextAccessible::PasteText(int32_t aPosition
) {
996 RefPtr
<EditorBase
> editorBase
= GetEditor();
998 SetSelectionRange(aPosition
, aPosition
);
999 editorBase
->PasteAsAction(nsIClipboard::kGlobalClipboard
,
1000 EditorBase::DispatchPasteEvent::Yes
);
1004 ////////////////////////////////////////////////////////////////////////////////
1005 // LocalAccessible public
1007 // LocalAccessible protected
1008 ENameValueFlag
HyperTextAccessible::NativeName(nsString
& aName
) const {
1009 // Check @alt attribute for invalid img elements.
1010 if (mContent
->IsHTMLElement(nsGkAtoms::img
)) {
1011 mContent
->AsElement()->GetAttr(nsGkAtoms::alt
, aName
);
1012 if (!aName
.IsEmpty()) return eNameOK
;
1015 ENameValueFlag nameFlag
= AccessibleWrap::NativeName(aName
);
1016 if (!aName
.IsEmpty()) return nameFlag
;
1018 // Get name from title attribute for HTML abbr and acronym elements making it
1019 // a valid name from markup. Otherwise their name isn't picked up by recursive
1020 // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
1021 if (IsAbbreviation() && mContent
->AsElement()->GetAttr(
1022 kNameSpaceID_None
, nsGkAtoms::title
, aName
)) {
1023 aName
.CompressWhitespace();
1029 void HyperTextAccessible::Shutdown() {
1031 AccessibleWrap::Shutdown();
1034 bool HyperTextAccessible::RemoveChild(LocalAccessible
* aAccessible
) {
1035 const int32_t childIndex
= aAccessible
->IndexInParent();
1036 if (childIndex
< static_cast<int32_t>(mOffsets
.Length())) {
1037 mOffsets
.RemoveLastElements(mOffsets
.Length() - childIndex
);
1040 return AccessibleWrap::RemoveChild(aAccessible
);
1043 bool HyperTextAccessible::InsertChildAt(uint32_t aIndex
,
1044 LocalAccessible
* aChild
) {
1045 if (aIndex
< mOffsets
.Length()) {
1046 mOffsets
.RemoveLastElements(mOffsets
.Length() - aIndex
);
1049 return AccessibleWrap::InsertChildAt(aIndex
, aChild
);
1052 Relation
HyperTextAccessible::RelationByType(RelationType aType
) const {
1053 Relation rel
= LocalAccessible::RelationByType(aType
);
1056 case RelationType::NODE_CHILD_OF
:
1057 if (HasOwnContent() && mContent
->IsMathMLElement()) {
1058 LocalAccessible
* parent
= LocalParent();
1060 nsIContent
* parentContent
= parent
->GetContent();
1061 if (parentContent
&&
1062 parentContent
->IsMathMLElement(nsGkAtoms::mroot_
)) {
1063 // Add a relation pointing to the parent <mroot>.
1064 rel
.AppendTarget(parent
);
1069 case RelationType::NODE_PARENT_OF
:
1070 if (HasOwnContent() && mContent
->IsMathMLElement(nsGkAtoms::mroot_
)) {
1071 LocalAccessible
* base
= LocalChildAt(0);
1072 LocalAccessible
* index
= LocalChildAt(1);
1073 if (base
&& index
) {
1074 // Append the <mroot> children in the order index, base.
1075 rel
.AppendTarget(index
);
1076 rel
.AppendTarget(base
);
1087 ////////////////////////////////////////////////////////////////////////////////
1088 // HyperTextAccessible public static
1090 nsresult
HyperTextAccessible::ContentToRenderedOffset(
1091 nsIFrame
* aFrame
, int32_t aContentOffset
, uint32_t* aRenderedOffset
) const {
1093 // Current frame not rendered -- this can happen if text is set on
1094 // something with display: none
1095 *aRenderedOffset
= 0;
1099 if (IsTextField()) {
1100 *aRenderedOffset
= aContentOffset
;
1104 NS_ASSERTION(aFrame
->IsTextFrame(), "Need text frame for offset conversion");
1105 NS_ASSERTION(aFrame
->GetPrevContinuation() == nullptr,
1106 "Call on primary frame only");
1108 nsIFrame::RenderedText text
=
1109 aFrame
->GetRenderedText(aContentOffset
, aContentOffset
+ 1,
1110 nsIFrame::TextOffsetType::OffsetsInContentText
,
1111 nsIFrame::TrailingWhitespace::DontTrim
);
1112 *aRenderedOffset
= text
.mOffsetWithinNodeRenderedText
;
1117 nsresult
HyperTextAccessible::RenderedToContentOffset(
1118 nsIFrame
* aFrame
, uint32_t aRenderedOffset
, int32_t* aContentOffset
) const {
1119 if (IsTextField()) {
1120 *aContentOffset
= aRenderedOffset
;
1124 *aContentOffset
= 0;
1125 NS_ENSURE_TRUE(aFrame
, NS_ERROR_FAILURE
);
1127 NS_ASSERTION(aFrame
->IsTextFrame(), "Need text frame for offset conversion");
1128 NS_ASSERTION(aFrame
->GetPrevContinuation() == nullptr,
1129 "Call on primary frame only");
1131 nsIFrame::RenderedText text
=
1132 aFrame
->GetRenderedText(aRenderedOffset
, aRenderedOffset
+ 1,
1133 nsIFrame::TextOffsetType::OffsetsInRenderedText
,
1134 nsIFrame::TrailingWhitespace::DontTrim
);
1135 *aContentOffset
= text
.mOffsetWithinNodeText
;