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/SelectionMovementUtils.h"
39 #include "mozilla/dom/Element.h"
40 #include "mozilla/dom/HTMLBRElement.h"
41 #include "mozilla/dom/Selection.h"
42 #include "gfxSkipChars.h"
44 using namespace mozilla
;
45 using namespace mozilla::a11y
;
47 ////////////////////////////////////////////////////////////////////////////////
48 // HyperTextAccessible
49 ////////////////////////////////////////////////////////////////////////////////
51 HyperTextAccessible::HyperTextAccessible(nsIContent
* aNode
, DocAccessible
* aDoc
)
52 : AccessibleWrap(aNode
, aDoc
) {
53 mType
= eHyperTextType
;
54 mGenericTypes
|= eHyperText
;
57 role
HyperTextAccessible::NativeRole() const {
58 a11y::role r
= GetAccService()->MarkupRole(mContent
);
59 if (r
!= roles::NOTHING
) return r
;
61 nsIFrame
* frame
= GetFrame();
62 if (frame
&& frame
->IsInlineFrame()) return roles::TEXT
;
64 return roles::TEXT_CONTAINER
;
67 uint64_t HyperTextAccessible::NativeState() const {
68 uint64_t states
= AccessibleWrap::NativeState();
71 states
|= states::EDITABLE
;
73 } else if (mContent
->IsHTMLElement(nsGkAtoms::article
)) {
74 // We want <article> to behave like a document in terms of readonly state.
75 states
|= states::READONLY
;
78 nsIFrame
* frame
= GetFrame();
79 if ((states
& states::EDITABLE
) || (frame
&& frame
->IsSelectable(nullptr))) {
80 // If the accessible is editable the layout selectable state only disables
81 // mouse selection, but keyboard (shift+arrow) selection is still possible.
82 states
|= states::SELECTABLE_TEXT
;
88 bool HyperTextAccessible::IsEditable() const {
92 return mContent
->AsElement()->State().HasState(dom::ElementState::READWRITE
);
95 uint32_t HyperTextAccessible::DOMPointToOffset(nsINode
* aNode
,
97 bool aIsEndOffset
) const {
101 nsINode
* findNode
= nullptr;
103 if (aNodeOffset
== -1) {
106 } else if (aNode
->IsText()) {
107 // For text nodes, aNodeOffset comes in as a character offset
108 // Text offset will be added at the end, if we find the offset in this
109 // hypertext We want the "skipped" offset into the text (rendered text
110 // without the extra whitespace)
111 nsIFrame
* frame
= aNode
->AsContent()->GetPrimaryFrame();
112 NS_ENSURE_TRUE(frame
, 0);
114 nsresult rv
= ContentToRenderedOffset(frame
, aNodeOffset
, &offset
);
115 NS_ENSURE_SUCCESS(rv
, 0);
120 // findNode could be null if aNodeOffset == # of child nodes, which means
121 // one of two things:
122 // 1) there are no children, and the passed-in node is not mContent -- use
123 // parentContent for the node to find
124 // 2) there are no children and the passed-in node is mContent, which means
125 // we're an empty nsIAccessibleText
126 // 3) there are children and we're at the end of the children
128 findNode
= aNode
->GetChildAt_Deprecated(aNodeOffset
);
130 if (aNodeOffset
== 0) {
131 if (aNode
== GetNode()) {
132 // Case #1: this accessible has no children and thus has empty text,
133 // we can only be at hypertext offset 0.
137 // Case #2: there are no children, we're at this node.
139 } else if (aNodeOffset
== static_cast<int32_t>(aNode
->GetChildCount())) {
140 // Case #3: we're after the last child, get next node to this one.
141 for (nsINode
* tmpNode
= aNode
;
142 !findNode
&& tmpNode
&& tmpNode
!= mContent
;
143 tmpNode
= tmpNode
->GetParent()) {
144 findNode
= tmpNode
->GetNextSibling();
150 // Get accessible for this findNode, or if that node isn't accessible, use the
151 // accessible for the next DOM node which has one (based on forward depth
153 LocalAccessible
* descendant
= nullptr;
155 dom::HTMLBRElement
* brElement
= dom::HTMLBRElement::FromNode(findNode
);
156 if (brElement
&& brElement
->IsPaddingForEmptyEditor()) {
157 // This <br> is the hacky "padding <br> element" used when there is no
158 // text in the editor.
162 descendant
= mDoc
->GetAccessible(findNode
);
163 if (!descendant
&& findNode
->IsContent()) {
164 LocalAccessible
* container
= mDoc
->GetContainerAccessible(findNode
);
166 TreeWalker
walker(container
, findNode
->AsContent(),
167 TreeWalker::eWalkContextTree
);
168 descendant
= walker
.Next();
169 if (!descendant
) descendant
= container
;
174 return TransformOffset(descendant
, offset
, aIsEndOffset
);
177 uint32_t HyperTextAccessible::TransformOffset(LocalAccessible
* aDescendant
,
179 bool aIsEndOffset
) const {
180 // From the descendant, go up and get the immediate child of this hypertext.
181 uint32_t offset
= aOffset
;
182 LocalAccessible
* descendant
= aDescendant
;
184 LocalAccessible
* parent
= descendant
->LocalParent();
185 if (parent
== this) return GetChildOffset(descendant
) + offset
;
187 // This offset no longer applies because the passed-in text object is not
188 // a child of the hypertext. This happens when there are nested hypertexts,
189 // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
190 // to make it relative the hypertext.
191 // If the end offset is not supposed to be inclusive and the original point
192 // is not at 0 offset then the returned offset should be after an embedded
193 // character the original point belongs to.
195 // Similar to our special casing in FindOffset, we add handling for
196 // bulleted lists here because PeekOffset returns the inner text node
197 // for a list when it should return the list bullet.
198 // We manually set the offset so the error doesn't propagate up.
199 if (offset
== 0 && parent
&& parent
->IsHTMLListItem() &&
200 descendant
->LocalPrevSibling() &&
201 descendant
->LocalPrevSibling() ==
202 parent
->AsHTMLListItem()->Bullet()) {
205 offset
= (offset
> 0 || descendant
->IndexInParent() > 0) ? 1 : 0;
214 // If the given a11y point cannot be mapped into offset relative this
215 // hypertext offset then return length as fallback value.
216 return CharacterCount();
219 DOMPoint
HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset
) const {
220 // 0 offset is valid even if no children. In this case the associated editor
221 // is empty so return a DOM point for editor root element.
223 RefPtr
<EditorBase
> editorBase
= GetEditor();
225 if (editorBase
->IsEmpty()) {
226 return DOMPoint(editorBase
->GetRoot(), 0);
231 int32_t childIdx
= GetChildIndexAtOffset(aOffset
);
232 if (childIdx
== -1) return DOMPoint();
234 LocalAccessible
* child
= LocalChildAt(childIdx
);
235 int32_t innerOffset
= aOffset
- GetChildOffset(childIdx
);
238 if (child
->IsTextLeaf()) {
239 // The point is inside the text node. This is always true for any text leaf
240 // except a last child one. See assertion below.
241 if (aOffset
< GetChildOffset(childIdx
+ 1)) {
242 nsIContent
* content
= child
->GetContent();
244 if (NS_FAILED(RenderedToContentOffset(content
->GetPrimaryFrame(),
245 innerOffset
, &idx
))) {
249 return DOMPoint(content
, idx
);
252 // Set the DOM point right after the text node.
253 MOZ_ASSERT(static_cast<uint32_t>(aOffset
) == CharacterCount());
257 // Case of embedded object. The point is either before or after the element.
258 NS_ASSERTION(innerOffset
== 0 || innerOffset
== 1, "A wrong inner offset!");
259 nsINode
* node
= child
->GetNode();
260 nsINode
* parentNode
= node
->GetParentNode();
261 return parentNode
? DOMPoint(parentNode
,
262 parentNode
->ComputeIndexOf_Deprecated(node
) +
267 already_AddRefed
<AccAttributes
> HyperTextAccessible::DefaultTextAttributes() {
268 RefPtr
<AccAttributes
> attributes
= new AccAttributes();
270 TextAttrsMgr
textAttrsMgr(this);
271 textAttrsMgr
.GetAttributes(attributes
);
272 return attributes
.forget();
275 void HyperTextAccessible::SetMathMLXMLRoles(AccAttributes
* aAttributes
) {
276 // Add MathML xmlroles based on the position inside the parent.
277 LocalAccessible
* parent
= LocalParent();
279 switch (parent
->Role()) {
280 case roles::MATHML_CELL
:
281 case roles::MATHML_ENCLOSED
:
282 case roles::MATHML_ERROR
:
283 case roles::MATHML_MATH
:
284 case roles::MATHML_ROW
:
285 case roles::MATHML_SQUARE_ROOT
:
286 case roles::MATHML_STYLE
:
287 if (Role() == roles::MATHML_OPERATOR
) {
288 // This is an operator inside an <mrow> (or an inferred <mrow>).
289 // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
290 // XXX We should probably do something similar for MATHML_FENCED, but
291 // operators do not appear in the accessible tree. See bug 1175747.
292 nsIMathMLFrame
* mathMLFrame
= do_QueryFrame(GetFrame());
294 nsEmbellishData embellishData
;
295 mathMLFrame
->GetEmbellishData(embellishData
);
296 if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData
.flags
)) {
297 if (!LocalPrevSibling()) {
298 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
299 nsGkAtoms::open_fence
);
300 } else if (!LocalNextSibling()) {
301 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
302 nsGkAtoms::close_fence
);
305 if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData
.flags
)) {
306 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
307 nsGkAtoms::separator_
);
312 case roles::MATHML_FRACTION
:
313 aAttributes
->SetAttribute(
314 nsGkAtoms::xmlroles
, IndexInParent() == 0 ? nsGkAtoms::numerator
315 : nsGkAtoms::denominator
);
317 case roles::MATHML_ROOT
:
318 aAttributes
->SetAttribute(
320 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::root_index
);
322 case roles::MATHML_SUB
:
323 aAttributes
->SetAttribute(
325 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::subscript
);
327 case roles::MATHML_SUP
:
328 aAttributes
->SetAttribute(
330 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::superscript
);
332 case roles::MATHML_SUB_SUP
: {
333 int32_t index
= IndexInParent();
334 aAttributes
->SetAttribute(
338 : (index
== 1 ? nsGkAtoms::subscript
: nsGkAtoms::superscript
));
340 case roles::MATHML_UNDER
:
341 aAttributes
->SetAttribute(
343 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::underscript
);
345 case roles::MATHML_OVER
:
346 aAttributes
->SetAttribute(
348 IndexInParent() == 0 ? nsGkAtoms::base
: nsGkAtoms::overscript
);
350 case roles::MATHML_UNDER_OVER
: {
351 int32_t index
= IndexInParent();
352 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
355 : (index
== 1 ? nsGkAtoms::underscript
356 : nsGkAtoms::overscript
));
358 case roles::MATHML_MULTISCRIPTS
: {
359 // Get the <multiscripts> base.
361 bool baseFound
= false;
362 for (child
= parent
->GetContent()->GetFirstChild(); child
;
363 child
= child
->GetNextSibling()) {
364 if (child
->IsMathMLElement()) {
370 nsIContent
* content
= GetContent();
371 if (child
== content
) {
373 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
, nsGkAtoms::base
);
375 // Browse the list of scripts to find us and determine our type.
376 bool postscript
= true;
377 bool subscript
= true;
378 for (child
= child
->GetNextSibling(); child
;
379 child
= child
->GetNextSibling()) {
380 if (!child
->IsMathMLElement()) continue;
381 if (child
->IsMathMLElement(nsGkAtoms::mprescripts_
)) {
386 if (child
== content
) {
388 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
389 subscript
? nsGkAtoms::subscript
390 : nsGkAtoms::superscript
);
392 aAttributes
->SetAttribute(nsGkAtoms::xmlroles
,
394 ? nsGkAtoms::presubscript
395 : nsGkAtoms::presuperscript
);
399 subscript
= !subscript
;
410 already_AddRefed
<AccAttributes
> HyperTextAccessible::NativeAttributes() {
411 RefPtr
<AccAttributes
> attributes
= AccessibleWrap::NativeAttributes();
413 // 'formatting' attribute is deprecated, 'display' attribute should be
415 nsIFrame
* frame
= GetFrame();
416 if (frame
&& frame
->IsBlockFrame()) {
417 attributes
->SetAttribute(nsGkAtoms::formatting
, nsGkAtoms::block
);
420 if (FocusMgr()->IsFocused(this)) {
421 int32_t lineNumber
= CaretLineNumber();
422 if (lineNumber
>= 1) {
423 attributes
->SetAttribute(nsGkAtoms::lineNumber
, lineNumber
);
427 if (HasOwnContent()) {
428 GetAccService()->MarkupAttributes(this, attributes
);
429 if (mContent
->IsMathMLElement()) SetMathMLXMLRoles(attributes
);
432 return attributes
.forget();
435 int32_t HyperTextAccessible::OffsetAtPoint(int32_t aX
, int32_t aY
,
436 uint32_t aCoordType
) {
437 nsIFrame
* hyperFrame
= GetFrame();
438 if (!hyperFrame
) return -1;
440 LayoutDeviceIntPoint coords
=
441 nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordType
, this);
443 nsPresContext
* presContext
= mDoc
->PresContext();
444 nsPoint coordsInAppUnits
= LayoutDeviceIntPoint::ToAppUnits(
445 coords
, presContext
->AppUnitsPerDevPixel());
447 nsRect frameScreenRect
= hyperFrame
->GetScreenRectInAppUnits();
448 if (!frameScreenRect
.Contains(coordsInAppUnits
.x
, coordsInAppUnits
.y
)) {
449 return -1; // Not found
452 nsPoint
pointInHyperText(coordsInAppUnits
.x
- frameScreenRect
.X(),
453 coordsInAppUnits
.y
- frameScreenRect
.Y());
455 // Go through the frames to check if each one has the point.
456 // When one does, add up the character offsets until we have a match
458 // We have an point in an accessible child of this, now we need to add up the
459 // offsets before it to what we already have
461 uint32_t childCount
= ChildCount();
462 for (uint32_t childIdx
= 0; childIdx
< childCount
; childIdx
++) {
463 LocalAccessible
* childAcc
= mChildren
[childIdx
];
465 nsIFrame
* primaryFrame
= childAcc
->GetFrame();
466 NS_ENSURE_TRUE(primaryFrame
, -1);
468 nsIFrame
* frame
= primaryFrame
;
470 nsIContent
* content
= frame
->GetContent();
471 NS_ENSURE_TRUE(content
, -1);
472 nsPoint pointInFrame
= pointInHyperText
- frame
->GetOffsetTo(hyperFrame
);
473 nsSize frameSize
= frame
->GetSize();
474 if (pointInFrame
.x
< frameSize
.width
&&
475 pointInFrame
.y
< frameSize
.height
) {
477 if (frame
->IsTextFrame()) {
478 nsIFrame::ContentOffsets contentOffsets
=
479 frame
->GetContentOffsetsFromPointExternal(
480 pointInFrame
, nsIFrame::IGNORE_SELECTION_STYLE
);
481 if (contentOffsets
.IsNull() || contentOffsets
.content
!= content
) {
482 return -1; // Not found
484 uint32_t addToOffset
;
485 nsresult rv
= ContentToRenderedOffset(
486 primaryFrame
, contentOffsets
.offset
, &addToOffset
);
487 NS_ENSURE_SUCCESS(rv
, -1);
488 offset
+= addToOffset
;
492 frame
= frame
->GetNextContinuation();
495 offset
+= nsAccUtils::TextLength(childAcc
);
498 return -1; // Not found
501 already_AddRefed
<EditorBase
> HyperTextAccessible::GetEditor() const {
502 if (!mContent
->HasFlag(NODE_IS_EDITABLE
)) {
503 // If we're inside an editable container, then return that container's
505 LocalAccessible
* ancestor
= LocalParent();
507 HyperTextAccessible
* hyperText
= ancestor
->AsHyperText();
509 // Recursion will stop at container doc because it has its own impl
511 return hyperText
->GetEditor();
514 ancestor
= ancestor
->LocalParent();
520 nsCOMPtr
<nsIDocShell
> docShell
= nsCoreUtils::GetDocShellFor(mContent
);
521 nsCOMPtr
<nsIEditingSession
> editingSession
;
522 docShell
->GetEditingSession(getter_AddRefs(editingSession
));
523 if (!editingSession
) return nullptr; // No editing session interface
525 dom::Document
* docNode
= mDoc
->DocumentNode();
526 RefPtr
<HTMLEditor
> htmlEditor
=
527 editingSession
->GetHTMLEditorForWindow(docNode
->GetWindow());
528 return htmlEditor
.forget();
532 * =================== Caret & Selection ======================
535 nsresult
HyperTextAccessible::SetSelectionRange(int32_t aStartPos
,
537 // Before setting the selection range, we need to ensure that the editor
538 // is initialized. (See bug 804927.)
539 // Otherwise, it's possible that lazy editor initialization will override
540 // the selection we set here and leave the caret at the end of the text.
541 // By calling GetEditor here, we ensure that editor initialization is
542 // completed before we set the selection.
543 RefPtr
<EditorBase
> editorBase
= GetEditor();
545 bool isFocusable
= InteractiveState() & states::FOCUSABLE
;
547 // If accessible is focusable then focus it before setting the selection to
548 // neglect control's selection changes on focus if any (for example, inputs
549 // that do select all on focus).
550 // some input controls
551 if (isFocusable
) TakeFocus();
553 RefPtr
<dom::Selection
> domSel
= DOMSelection();
554 NS_ENSURE_STATE(domSel
);
556 // Set up the selection.
557 domSel
->RemoveAllRanges(IgnoreErrors());
558 SetSelectionBoundsAt(0, aStartPos
, aEndPos
);
560 // Make sure it is visible
561 domSel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
562 ScrollAxis(), ScrollAxis(),
563 dom::Selection::SCROLL_FOR_CARET_MOVE
|
564 dom::Selection::SCROLL_OVERFLOW_HIDDEN
);
566 // When selection is done, move the focus to the selection if accessible is
567 // not focusable. That happens when selection is set within hypertext
569 if (isFocusable
) return NS_OK
;
571 nsFocusManager
* DOMFocusManager
= nsFocusManager::GetFocusManager();
572 if (DOMFocusManager
) {
573 NS_ENSURE_TRUE(mDoc
, NS_ERROR_FAILURE
);
574 dom::Document
* docNode
= mDoc
->DocumentNode();
575 NS_ENSURE_TRUE(docNode
, NS_ERROR_FAILURE
);
576 nsCOMPtr
<nsPIDOMWindowOuter
> window
= docNode
->GetWindow();
577 RefPtr
<dom::Element
> result
;
578 DOMFocusManager
->MoveFocus(
579 window
, nullptr, nsIFocusManager::MOVEFOCUS_CARET
,
580 nsIFocusManager::FLAG_BYMOVEFOCUS
, getter_AddRefs(result
));
586 int32_t HyperTextAccessible::CaretOffset() const {
587 // Not focused focusable accessible except document accessible doesn't have
589 if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
590 (InteractiveState() & states::FOCUSABLE
)) {
594 // Check cached value.
595 int32_t caretOffset
= -1;
596 HyperTextAccessible
* text
= SelectionMgr()->AccessibleWithCaret(&caretOffset
);
598 // Use cached value if it corresponds to this accessible.
599 if (caretOffset
!= -1) {
600 if (text
== this) return caretOffset
;
602 nsINode
* textNode
= text
->GetNode();
603 // Ignore offset if cached accessible isn't a text leaf.
604 if (nsCoreUtils::IsAncestorOf(GetNode(), textNode
)) {
605 return TransformOffset(text
, textNode
->IsText() ? caretOffset
: 0, false);
609 // No caret if the focused node is not inside this DOM node and this DOM node
610 // is not inside of focused node.
611 FocusManager::FocusDisposition focusDisp
=
612 FocusMgr()->IsInOrContainsFocus(this);
613 if (focusDisp
== FocusManager::eNone
) return -1;
615 // Turn the focus node and offset of the selection into caret hypretext
617 dom::Selection
* domSel
= DOMSelection();
618 NS_ENSURE_TRUE(domSel
, -1);
620 nsINode
* focusNode
= domSel
->GetFocusNode();
621 uint32_t focusOffset
= domSel
->FocusOffset();
623 // No caret if this DOM node is inside of focused node but the selection's
624 // focus point is not inside of this DOM node.
625 if (focusDisp
== FocusManager::eContainedByFocus
) {
626 nsINode
* resultNode
=
627 nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode
, focusOffset
);
629 nsINode
* thisNode
= GetNode();
630 if (resultNode
!= thisNode
&&
631 !nsCoreUtils::IsAncestorOf(thisNode
, resultNode
)) {
636 return DOMPointToOffset(focusNode
, focusOffset
);
639 int32_t HyperTextAccessible::CaretLineNumber() {
640 // Provide the line number for the caret, relative to the
641 // currently focused node. Use a 1-based index
642 RefPtr
<nsFrameSelection
> frameSelection
= FrameSelection();
643 if (!frameSelection
) return -1;
645 dom::Selection
* domSel
= frameSelection
->GetSelection(SelectionType::eNormal
);
646 if (!domSel
) return -1;
648 nsINode
* caretNode
= domSel
->GetFocusNode();
649 if (!caretNode
|| !caretNode
->IsContent()) return -1;
651 nsIContent
* caretContent
= caretNode
->AsContent();
652 if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent
)) return -1;
654 uint32_t caretOffset
= domSel
->FocusOffset();
655 CaretAssociationHint hint
= frameSelection
->GetHint();
656 nsIFrame
* caretFrame
= SelectionMovementUtils::GetFrameForNodeOffset(
657 caretContent
, caretOffset
, hint
);
658 NS_ENSURE_TRUE(caretFrame
, -1);
660 AutoAssertNoDomMutations guard
; // The nsILineIterators below will break if
661 // the DOM is modified while they're in use!
662 int32_t lineNumber
= 1;
663 nsILineIterator
* lineIterForCaret
= nullptr;
664 nsIContent
* hyperTextContent
= IsContent() ? mContent
.get() : nullptr;
666 if (hyperTextContent
== caretFrame
->GetContent()) {
667 return lineNumber
; // Must be in a single line hyper text, there is no
670 nsContainerFrame
* parentFrame
= caretFrame
->GetParent();
671 if (!parentFrame
) break;
673 // Add lines for the sibling frames before the caret
674 nsIFrame
* sibling
= parentFrame
->PrincipalChildList().FirstChild();
675 while (sibling
&& sibling
!= caretFrame
) {
676 nsILineIterator
* lineIterForSibling
= sibling
->GetLineIterator();
677 if (lineIterForSibling
) {
678 // For the frames before that grab all the lines
679 int32_t addLines
= lineIterForSibling
->GetNumLines();
680 lineNumber
+= addLines
;
682 sibling
= sibling
->GetNextSibling();
685 // Get the line number relative to the container with lines
686 if (!lineIterForCaret
) { // Add the caret line just once
687 lineIterForCaret
= parentFrame
->GetLineIterator();
688 if (lineIterForCaret
) {
690 int32_t addLines
= lineIterForCaret
->FindLineContaining(caretFrame
);
691 lineNumber
+= addLines
;
695 caretFrame
= parentFrame
;
698 MOZ_ASSERT_UNREACHABLE(
699 "DOM ancestry had this hypertext but frame ancestry didn't");
703 LayoutDeviceIntRect
HyperTextAccessible::GetCaretRect(nsIWidget
** aWidget
) {
706 RefPtr
<nsCaret
> caret
= mDoc
->PresShellPtr()->GetCaret();
707 NS_ENSURE_TRUE(caret
, LayoutDeviceIntRect());
709 bool isVisible
= caret
->IsVisible();
710 if (!isVisible
) return LayoutDeviceIntRect();
713 nsIFrame
* frame
= caret
->GetGeometry(&rect
);
714 if (!frame
|| rect
.IsEmpty()) return LayoutDeviceIntRect();
716 PresShell
* presShell
= mDoc
->PresShellPtr();
717 // Transform rect to be relative to the root frame.
718 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
719 rect
= nsLayoutUtils::TransformFrameRectToAncestor(frame
, rect
, rootFrame
);
720 // We need to inverse translate with the offset of the edge of the visual
721 // viewport from top edge of the layout viewport.
722 nsPoint viewportOffset
= presShell
->GetVisualViewportOffset() -
723 presShell
->GetLayoutViewportOffset();
724 rect
.MoveBy(-viewportOffset
);
725 // We need to take into account a non-1 resolution set on the presshell.
726 // This happens with async pinch zooming. Here we scale the bounds before
727 // adding the screen-relative offset.
728 rect
.ScaleRoundOut(presShell
->GetResolution());
729 // Now we need to put the rect in absolute screen coords.
730 nsRect rootScreenRect
= rootFrame
->GetScreenRectInAppUnits();
731 rect
.MoveBy(rootScreenRect
.TopLeft());
732 // Finally, convert from app units.
733 auto caretRect
= LayoutDeviceIntRect::FromAppUnitsToNearest(
734 rect
, presShell
->GetPresContext()->AppUnitsPerDevPixel());
736 // Correct for character size, so that caret always matches the size of
737 // the character. This is important for font size transitions, and is
738 // necessary because the Gecko caret uses the previous character's size as
739 // the user moves forward in the text by character.
740 int32_t caretOffset
= CaretOffset();
741 if (NS_WARN_IF(caretOffset
== -1)) {
742 // The caret offset will be -1 if this Accessible isn't focused. Note that
743 // the DOM node contaning the caret might be focused, but the Accessible
744 // might not be; e.g. due to an autocomplete popup suggestion having a11y
746 return LayoutDeviceIntRect();
748 LayoutDeviceIntRect charRect
= CharBounds(
749 caretOffset
, nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
);
750 if (!charRect
.IsEmpty()) {
751 caretRect
.SetTopEdge(charRect
.Y());
754 *aWidget
= frame
->GetNearestWidget();
758 void HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType
,
759 nsTArray
<nsRange
*>* aRanges
) {
760 if (IsDoc() && !AsDoc()->HasLoadState(DocAccessible::eTreeConstructed
)) {
761 // Rarely, a client query can be handled after a DocAccessible is created
762 // but before the initial tree is constructed, since DoInitialUpdate happens
763 // during a refresh tick. In that case, there might be a DOM selection, but
764 // we can't use it. We will crash if we try due to mContent being null, etc.
765 // This should only happen in the parent process because we should never
766 // try to push the cache in a content process before the initial tree is
768 MOZ_ASSERT(XRE_IsParentProcess(), "Query before DoInitialUpdate");
771 // Ignore selection if it is not visible.
772 RefPtr
<nsFrameSelection
> frameSelection
= FrameSelection();
773 if (!frameSelection
|| frameSelection
->GetDisplaySelection() <=
774 nsISelectionController::SELECTION_HIDDEN
) {
778 dom::Selection
* domSel
= frameSelection
->GetSelection(aSelectionType
);
781 nsINode
* startNode
= GetNode();
783 RefPtr
<EditorBase
> editorBase
= GetEditor();
785 startNode
= editorBase
->GetRoot();
788 if (!startNode
) return;
790 uint32_t childCount
= startNode
->GetChildCount();
791 nsresult rv
= domSel
->GetDynamicRangesForIntervalArray(
792 startNode
, 0, startNode
, childCount
, true, aRanges
);
793 NS_ENSURE_SUCCESS_VOID(rv
);
795 // Remove collapsed ranges
796 aRanges
->RemoveElementsBy(
797 [](const auto& range
) { return range
->Collapsed(); });
800 int32_t HyperTextAccessible::SelectionCount() {
801 nsTArray
<nsRange
*> ranges
;
802 GetSelectionDOMRanges(SelectionType::eNormal
, &ranges
);
803 return ranges
.Length();
806 bool HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum
,
807 int32_t* aStartOffset
,
808 int32_t* aEndOffset
) {
809 *aStartOffset
= *aEndOffset
= 0;
811 nsTArray
<nsRange
*> ranges
;
812 GetSelectionDOMRanges(SelectionType::eNormal
, &ranges
);
814 uint32_t rangeCount
= ranges
.Length();
815 if (aSelectionNum
< 0 || aSelectionNum
>= static_cast<int32_t>(rangeCount
)) {
819 nsRange
* range
= ranges
[aSelectionNum
];
821 // Get start and end points.
822 nsINode
* startNode
= range
->GetStartContainer();
823 nsINode
* endNode
= range
->GetEndContainer();
824 uint32_t startOffset
= range
->StartOffset();
825 uint32_t endOffset
= range
->EndOffset();
827 // Make sure start is before end, by swapping DOM points. This occurs when
828 // the user selects backwards in the text.
829 const Maybe
<int32_t> order
=
830 nsContentUtils::ComparePoints(endNode
, endOffset
, startNode
, startOffset
);
833 MOZ_ASSERT_UNREACHABLE();
838 std::swap(startNode
, endNode
);
839 std::swap(startOffset
, endOffset
);
842 if (!startNode
->IsInclusiveDescendantOf(mContent
)) {
846 DOMPointToOffset(startNode
, AssertedCast
<int32_t>(startOffset
));
849 if (!endNode
->IsInclusiveDescendantOf(mContent
)) {
850 *aEndOffset
= CharacterCount();
853 DOMPointToOffset(endNode
, AssertedCast
<int32_t>(endOffset
), true);
858 bool HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum
) {
859 RefPtr
<dom::Selection
> domSel
= DOMSelection();
860 if (!domSel
) return false;
862 if (aSelectionNum
< 0 ||
863 aSelectionNum
>= static_cast<int32_t>(domSel
->RangeCount())) {
867 const RefPtr
<nsRange
> range
{
868 domSel
->GetRangeAt(static_cast<uint32_t>(aSelectionNum
))};
869 domSel
->RemoveRangeAndUnselectFramesAndNotifyListeners(*range
,
874 void HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset
,
876 uint32_t aCoordinateType
,
877 int32_t aX
, int32_t aY
) {
878 nsIFrame
* frame
= GetFrame();
881 LayoutDeviceIntPoint coords
=
882 nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordinateType
, this);
884 RefPtr
<nsRange
> domRange
= nsRange::Create(mContent
);
885 TextRange
range(this, this, aStartOffset
, this, aEndOffset
);
886 if (!range
.AssignDOMRange(domRange
)) {
890 nsPresContext
* presContext
= frame
->PresContext();
891 nsPoint coordsInAppUnits
= LayoutDeviceIntPoint::ToAppUnits(
892 coords
, presContext
->AppUnitsPerDevPixel());
894 bool initialScrolled
= false;
895 nsIFrame
* parentFrame
= frame
;
896 while ((parentFrame
= parentFrame
->GetParent())) {
897 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(parentFrame
);
898 if (scrollableFrame
) {
899 if (!initialScrolled
) {
900 // Scroll substring to the given point. Turn the point into percents
901 // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
902 nsRect frameRect
= parentFrame
->GetScreenRectInAppUnits();
903 nscoord offsetPointX
= coordsInAppUnits
.x
- frameRect
.X();
904 nscoord offsetPointY
= coordsInAppUnits
.y
- frameRect
.Y();
906 nsSize
size(parentFrame
->GetSize());
908 // avoid divide by zero
909 size
.width
= size
.width
? size
.width
: 1;
910 size
.height
= size
.height
? size
.height
: 1;
912 int16_t hPercent
= offsetPointX
* 100 / size
.width
;
913 int16_t vPercent
= offsetPointY
* 100 / size
.height
;
915 nsresult rv
= nsCoreUtils::ScrollSubstringTo(
917 ScrollAxis(WhereToScroll(vPercent
), WhenToScroll::Always
),
918 ScrollAxis(WhereToScroll(hPercent
), WhenToScroll::Always
));
919 if (NS_FAILED(rv
)) return;
921 initialScrolled
= true;
923 // Substring was scrolled to the given point already inside its closest
924 // scrollable area. If there are nested scrollable areas then make
925 // sure we scroll lower areas to the given point inside currently
926 // traversed scrollable area.
927 nsCoreUtils::ScrollFrameToPoint(parentFrame
, frame
, coords
);
934 void HyperTextAccessible::SelectionRanges(
935 nsTArray
<a11y::TextRange
>* aRanges
) const {
936 dom::Selection
* sel
= DOMSelection();
941 TextRange::TextRangesFromSelection(sel
, aRanges
);
944 void HyperTextAccessible::ReplaceText(const nsAString
& aText
) {
945 if (aText
.Length() == 0) {
946 DeleteText(0, CharacterCount());
950 SetSelectionRange(0, CharacterCount());
952 RefPtr
<EditorBase
> editorBase
= GetEditor();
957 DebugOnly
<nsresult
> rv
= editorBase
->InsertTextAsAction(aText
);
958 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to insert the new text");
961 void HyperTextAccessible::InsertText(const nsAString
& aText
,
963 RefPtr
<EditorBase
> editorBase
= GetEditor();
965 SetSelectionRange(aPosition
, aPosition
);
966 DebugOnly
<nsresult
> rv
= editorBase
->InsertTextAsAction(aText
);
967 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to insert the text");
971 void HyperTextAccessible::CopyText(int32_t aStartPos
, int32_t aEndPos
) {
972 RefPtr
<EditorBase
> editorBase
= GetEditor();
974 SetSelectionRange(aStartPos
, aEndPos
);
979 void HyperTextAccessible::CutText(int32_t aStartPos
, int32_t aEndPos
) {
980 RefPtr
<EditorBase
> editorBase
= GetEditor();
982 SetSelectionRange(aStartPos
, aEndPos
);
987 void HyperTextAccessible::DeleteText(int32_t aStartPos
, int32_t aEndPos
) {
988 RefPtr
<EditorBase
> editorBase
= GetEditor();
992 SetSelectionRange(aStartPos
, aEndPos
);
993 DebugOnly
<nsresult
> rv
=
994 editorBase
->DeleteSelectionAsAction(nsIEditor::eNone
, nsIEditor::eStrip
);
995 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to delete text");
998 void HyperTextAccessible::PasteText(int32_t aPosition
) {
999 RefPtr
<EditorBase
> editorBase
= GetEditor();
1001 SetSelectionRange(aPosition
, aPosition
);
1002 editorBase
->PasteAsAction(nsIClipboard::kGlobalClipboard
,
1003 EditorBase::DispatchPasteEvent::Yes
);
1007 ////////////////////////////////////////////////////////////////////////////////
1008 // LocalAccessible public
1010 // LocalAccessible protected
1011 ENameValueFlag
HyperTextAccessible::NativeName(nsString
& aName
) const {
1012 // Check @alt attribute for invalid img elements.
1013 if (mContent
->IsHTMLElement(nsGkAtoms::img
)) {
1014 mContent
->AsElement()->GetAttr(nsGkAtoms::alt
, aName
);
1015 if (!aName
.IsEmpty()) return eNameOK
;
1018 ENameValueFlag nameFlag
= AccessibleWrap::NativeName(aName
);
1019 if (!aName
.IsEmpty()) return nameFlag
;
1021 // Get name from title attribute for HTML abbr and acronym elements making it
1022 // a valid name from markup. Otherwise their name isn't picked up by recursive
1023 // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
1024 if (IsAbbreviation() && mContent
->AsElement()->GetAttr(
1025 kNameSpaceID_None
, nsGkAtoms::title
, aName
)) {
1026 aName
.CompressWhitespace();
1032 void HyperTextAccessible::Shutdown() {
1034 AccessibleWrap::Shutdown();
1037 bool HyperTextAccessible::RemoveChild(LocalAccessible
* aAccessible
) {
1038 const int32_t childIndex
= aAccessible
->IndexInParent();
1039 if (childIndex
< static_cast<int32_t>(mOffsets
.Length())) {
1040 mOffsets
.RemoveLastElements(mOffsets
.Length() - childIndex
);
1043 return AccessibleWrap::RemoveChild(aAccessible
);
1046 bool HyperTextAccessible::InsertChildAt(uint32_t aIndex
,
1047 LocalAccessible
* aChild
) {
1048 if (aIndex
< mOffsets
.Length()) {
1049 mOffsets
.RemoveLastElements(mOffsets
.Length() - aIndex
);
1052 return AccessibleWrap::InsertChildAt(aIndex
, aChild
);
1055 Relation
HyperTextAccessible::RelationByType(RelationType aType
) const {
1056 Relation rel
= LocalAccessible::RelationByType(aType
);
1059 case RelationType::NODE_CHILD_OF
:
1060 if (HasOwnContent() && mContent
->IsMathMLElement()) {
1061 LocalAccessible
* parent
= LocalParent();
1063 nsIContent
* parentContent
= parent
->GetContent();
1064 if (parentContent
&&
1065 parentContent
->IsMathMLElement(nsGkAtoms::mroot_
)) {
1066 // Add a relation pointing to the parent <mroot>.
1067 rel
.AppendTarget(parent
);
1072 case RelationType::NODE_PARENT_OF
:
1073 if (HasOwnContent() && mContent
->IsMathMLElement(nsGkAtoms::mroot_
)) {
1074 LocalAccessible
* base
= LocalChildAt(0);
1075 LocalAccessible
* index
= LocalChildAt(1);
1076 if (base
&& index
) {
1077 // Append the <mroot> children in the order index, base.
1078 rel
.AppendTarget(index
);
1079 rel
.AppendTarget(base
);
1090 ////////////////////////////////////////////////////////////////////////////////
1091 // HyperTextAccessible public static
1093 nsresult
HyperTextAccessible::ContentToRenderedOffset(
1094 nsIFrame
* aFrame
, int32_t aContentOffset
, uint32_t* aRenderedOffset
) const {
1096 // Current frame not rendered -- this can happen if text is set on
1097 // something with display: none
1098 *aRenderedOffset
= 0;
1102 if (IsTextField()) {
1103 *aRenderedOffset
= aContentOffset
;
1107 NS_ASSERTION(aFrame
->IsTextFrame(), "Need text frame for offset conversion");
1108 NS_ASSERTION(aFrame
->GetPrevContinuation() == nullptr,
1109 "Call on primary frame only");
1111 nsIFrame::RenderedText text
=
1112 aFrame
->GetRenderedText(aContentOffset
, aContentOffset
+ 1,
1113 nsIFrame::TextOffsetType::OffsetsInContentText
,
1114 nsIFrame::TrailingWhitespace::DontTrim
);
1115 *aRenderedOffset
= text
.mOffsetWithinNodeRenderedText
;
1120 nsresult
HyperTextAccessible::RenderedToContentOffset(
1121 nsIFrame
* aFrame
, uint32_t aRenderedOffset
, int32_t* aContentOffset
) const {
1122 if (IsTextField()) {
1123 *aContentOffset
= aRenderedOffset
;
1127 *aContentOffset
= 0;
1128 NS_ENSURE_TRUE(aFrame
, NS_ERROR_FAILURE
);
1130 NS_ASSERTION(aFrame
->IsTextFrame(), "Need text frame for offset conversion");
1131 NS_ASSERTION(aFrame
->GetPrevContinuation() == nullptr,
1132 "Call on primary frame only");
1134 nsIFrame::RenderedText text
=
1135 aFrame
->GetRenderedText(aRenderedOffset
, aRenderedOffset
+ 1,
1136 nsIFrame::TextOffsetType::OffsetsInRenderedText
,
1137 nsIFrame::TrailingWhitespace::DontTrim
);
1138 *aContentOffset
= text
.mOffsetWithinNodeText
;