1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developers of the Original Code are
18 * Sun Microsystems and IBM Corporation
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
23 * Ginn Chen (ginn.chen@sun.com)
24 * Aaron Leventhal (aleventh@us.ibm.com)
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsHyperTextAccessible.h"
42 #include "nsAccessibilityAtoms.h"
43 #include "nsAccessibilityService.h"
44 #include "nsAccUtils.h"
45 #include "nsTextAttrs.h"
47 #include "nsIClipboard.h"
48 #include "nsContentCID.h"
49 #include "nsIDOMAbstractView.h"
50 #include "nsIDOMCharacterData.h"
51 #include "nsIDOMDocument.h"
52 #include "nsPIDOMWindow.h"
53 #include "nsIDOMDocumentView.h"
54 #include "nsIDOMRange.h"
55 #include "nsIDOMNSRange.h"
56 #include "nsIDOMWindowInternal.h"
57 #include "nsIDOMXULDocument.h"
58 #include "nsIEditingSession.h"
59 #include "nsIEditor.h"
60 #include "nsIFontMetrics.h"
62 #include "nsFrameSelection.h"
63 #include "nsILineIterator.h"
64 #include "nsIInterfaceRequestorUtils.h"
65 #include "nsIPlaintextEditor.h"
66 #include "nsIScrollableFrame.h"
67 #include "nsISelection2.h"
68 #include "nsISelectionPrivate.h"
69 #include "nsIServiceManager.h"
70 #include "nsTextFragment.h"
71 #include "gfxSkipChars.h"
73 static NS_DEFINE_IID(kRangeCID
, NS_RANGE_CID
);
75 ////////////////////////////////////////////////////////////////////////////////
76 // nsHyperTextAccessible
77 ////////////////////////////////////////////////////////////////////////////////
79 nsHyperTextAccessible::
80 nsHyperTextAccessible(nsIContent
*aNode
, nsIWeakReference
*aShell
) :
81 nsAccessibleWrap(aNode
, aShell
)
83 mFlags
|= eHyperTextAccessible
;
86 NS_IMPL_ADDREF_INHERITED(nsHyperTextAccessible
, nsAccessibleWrap
)
87 NS_IMPL_RELEASE_INHERITED(nsHyperTextAccessible
, nsAccessibleWrap
)
89 nsresult
nsHyperTextAccessible::QueryInterface(REFNSIID aIID
, void** aInstancePtr
)
91 *aInstancePtr
= nsnull
;
93 if (aIID
.Equals(NS_GET_IID(nsHyperTextAccessible
))) {
94 *aInstancePtr
= static_cast<nsHyperTextAccessible
*>(this);
100 (mRoleMapEntry
->role
== nsIAccessibleRole::ROLE_GRAPHIC
||
101 mRoleMapEntry
->role
== nsIAccessibleRole::ROLE_IMAGE_MAP
||
102 mRoleMapEntry
->role
== nsIAccessibleRole::ROLE_SLIDER
||
103 mRoleMapEntry
->role
== nsIAccessibleRole::ROLE_PROGRESSBAR
||
104 mRoleMapEntry
->role
== nsIAccessibleRole::ROLE_SEPARATOR
)) {
105 // ARIA roles that these interfaces are not appropriate for
106 return nsAccessible::QueryInterface(aIID
, aInstancePtr
);
109 if (aIID
.Equals(NS_GET_IID(nsIAccessibleText
))) {
110 *aInstancePtr
= static_cast<nsIAccessibleText
*>(this);
115 if (aIID
.Equals(NS_GET_IID(nsIAccessibleHyperText
))) {
116 *aInstancePtr
= static_cast<nsIAccessibleHyperText
*>(this);
121 if (aIID
.Equals(NS_GET_IID(nsIAccessibleEditableText
))) {
122 *aInstancePtr
= static_cast<nsIAccessibleEditableText
*>(this);
127 return nsAccessible::QueryInterface(aIID
, aInstancePtr
);
131 nsHyperTextAccessible::NativeRole()
133 nsIAtom
*tag
= mContent
->Tag();
135 if (tag
== nsAccessibilityAtoms::form
)
136 return nsIAccessibleRole::ROLE_FORM
;
138 if (tag
== nsAccessibilityAtoms::blockquote
||
139 tag
== nsAccessibilityAtoms::div
||
140 tag
== nsAccessibilityAtoms::nav
)
141 return nsIAccessibleRole::ROLE_SECTION
;
143 if (tag
== nsAccessibilityAtoms::h1
||
144 tag
== nsAccessibilityAtoms::h2
||
145 tag
== nsAccessibilityAtoms::h3
||
146 tag
== nsAccessibilityAtoms::h4
||
147 tag
== nsAccessibilityAtoms::h5
||
148 tag
== nsAccessibilityAtoms::h6
)
149 return nsIAccessibleRole::ROLE_HEADING
;
151 if (tag
== nsAccessibilityAtoms::article
)
152 return nsIAccessibleRole::ROLE_DOCUMENT
;
154 // Deal with html landmark elements
155 if (tag
== nsAccessibilityAtoms::header
)
156 return nsIAccessibleRole::ROLE_HEADER
;
158 if (tag
== nsAccessibilityAtoms::footer
)
159 return nsIAccessibleRole::ROLE_FOOTER
;
161 if (tag
== nsAccessibilityAtoms::aside
)
162 return nsIAccessibleRole::ROLE_NOTE
;
164 // Treat block frames as paragraphs
165 nsIFrame
*frame
= GetFrame();
166 if (frame
&& frame
->GetType() == nsAccessibilityAtoms::blockFrame
&&
167 frame
->GetContent()->Tag() != nsAccessibilityAtoms::input
) {
168 // An html:input @type="file" is the only input that is exposed as a
169 // blockframe. It must be exposed as ROLE_TEXT_CONTAINER for JAWS.
170 return nsIAccessibleRole::ROLE_PARAGRAPH
;
173 return nsIAccessibleRole::ROLE_TEXT_CONTAINER
; // In ATK this works
177 nsHyperTextAccessible::GetStateInternal(PRUint32
*aState
, PRUint32
*aExtraState
)
179 nsresult rv
= nsAccessibleWrap::GetStateInternal(aState
, aExtraState
);
180 NS_ENSURE_A11Y_SUCCESS(rv
, rv
);
185 nsCOMPtr
<nsIEditor
> editor
;
186 GetAssociatedEditor(getter_AddRefs(editor
));
189 editor
->GetFlags(&flags
);
190 if (0 == (flags
& nsIPlaintextEditor::eEditorReadonlyMask
)) {
191 *aExtraState
|= nsIAccessibleStates::EXT_STATE_EDITABLE
;
193 } else if (mContent
->Tag() == nsAccessibilityAtoms::article
) {
194 // We want <article> to behave like a document in terms of readonly state.
195 *aState
|= nsIAccessibleStates::STATE_READONLY
;
199 GetChildCount(&childCount
);
200 if (childCount
> 0) {
201 *aExtraState
|= nsIAccessibleStates::EXT_STATE_SELECTABLE_TEXT
;
207 // Substring must be entirely within the same text node
208 nsIntRect
nsHyperTextAccessible::GetBoundsForString(nsIFrame
*aFrame
, PRUint32 aStartRenderedOffset
,
209 PRUint32 aEndRenderedOffset
)
211 nsIntRect screenRect
;
212 NS_ENSURE_TRUE(aFrame
, screenRect
);
213 if (aFrame
->GetType() != nsAccessibilityAtoms::textFrame
) {
214 // XXX fallback for non-text frames, happens for bullets right now
215 // but in the future bullets will have proper text frames
216 return aFrame
->GetScreenRectExternal();
219 PRInt32 startContentOffset
, endContentOffset
;
220 nsresult rv
= RenderedToContentOffset(aFrame
, aStartRenderedOffset
, &startContentOffset
);
221 NS_ENSURE_SUCCESS(rv
, screenRect
);
222 rv
= RenderedToContentOffset(aFrame
, aEndRenderedOffset
, &endContentOffset
);
223 NS_ENSURE_SUCCESS(rv
, screenRect
);
226 PRInt32 startContentOffsetInFrame
;
227 // Get the right frame continuation -- not really a child, but a sibling of
228 // the primary frame passed in
229 rv
= aFrame
->GetChildFrameContainingOffset(startContentOffset
, PR_FALSE
,
230 &startContentOffsetInFrame
, &frame
);
231 NS_ENSURE_SUCCESS(rv
, screenRect
);
233 nsCOMPtr
<nsIPresShell
> shell
= GetPresShell();
234 NS_ENSURE_TRUE(shell
, screenRect
);
236 nsPresContext
*context
= shell
->GetPresContext();
238 while (frame
&& startContentOffset
< endContentOffset
) {
239 // Start with this frame's screen rect, which we will
240 // shrink based on the substring we care about within it.
241 // We will then add that frame to the total screenRect we
243 nsIntRect frameScreenRect
= frame
->GetScreenRectExternal();
245 // Get the length of the substring in this frame that we want the bounds for
246 PRInt32 startFrameTextOffset
, endFrameTextOffset
;
247 frame
->GetOffsets(startFrameTextOffset
, endFrameTextOffset
);
248 PRInt32 frameTotalTextLength
= endFrameTextOffset
- startFrameTextOffset
;
249 PRInt32 seekLength
= endContentOffset
- startContentOffset
;
250 PRInt32 frameSubStringLength
= NS_MIN(frameTotalTextLength
- startContentOffsetInFrame
, seekLength
);
252 // Add the point where the string starts to the frameScreenRect
253 nsPoint frameTextStartPoint
;
254 rv
= frame
->GetPointFromOffset(startContentOffset
, &frameTextStartPoint
);
255 NS_ENSURE_SUCCESS(rv
, nsIntRect());
256 frameScreenRect
.x
+= context
->AppUnitsToDevPixels(frameTextStartPoint
.x
);
258 // Use the point for the end offset to calculate the width
259 nsPoint frameTextEndPoint
;
260 rv
= frame
->GetPointFromOffset(startContentOffset
+ frameSubStringLength
, &frameTextEndPoint
);
261 NS_ENSURE_SUCCESS(rv
, nsIntRect());
262 frameScreenRect
.width
= context
->AppUnitsToDevPixels(frameTextEndPoint
.x
- frameTextStartPoint
.x
);
264 screenRect
.UnionRect(frameScreenRect
, screenRect
);
266 // Get ready to loop back for next frame continuation
267 startContentOffset
+= frameSubStringLength
;
268 startContentOffsetInFrame
= 0;
269 frame
= frame
->GetNextContinuation();
276 * Gets the specified text.
279 nsHyperTextAccessible::GetPosAndText(PRInt32
& aStartOffset
, PRInt32
& aEndOffset
,
280 nsAString
*aText
, nsIFrame
**aEndFrame
,
281 nsIntRect
*aBoundsRect
,
282 nsAccessible
**aStartAcc
,
283 nsAccessible
**aEndAcc
)
285 if (aStartOffset
== nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT
) {
286 aStartOffset
= CharacterCount();
288 if (aStartOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
289 GetCaretOffset(&aStartOffset
);
291 if (aEndOffset
== nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT
) {
292 aEndOffset
= CharacterCount();
294 if (aEndOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
295 GetCaretOffset(&aEndOffset
);
298 PRInt32 startOffset
= aStartOffset
;
299 PRInt32 endOffset
= aEndOffset
;
300 // XXX this prevents text interface usage on <input type="password">
301 PRBool isPassword
= (Role() == nsIAccessibleRole::ROLE_PASSWORD_TEXT
);
303 // Clear out parameters and set up loop
308 const PRInt32 kMaxTextLength
= 32767;
309 endOffset
= kMaxTextLength
; // Max end offset
311 else if (startOffset
> endOffset
) {
315 nsIFrame
*startFrame
= nsnull
;
320 aBoundsRect
->Empty();
328 nsAccessible
*lastAccessible
= nsnull
;
330 gfxSkipChars skipChars
;
331 gfxSkipCharsIterator iter
;
333 // Loop through children and collect valid offsets, text and bounds
334 // depending on what we need for out parameters.
335 PRInt32 childCount
= GetChildCount();
336 for (PRInt32 childIdx
= 0; childIdx
< childCount
; childIdx
++) {
337 nsAccessible
*childAcc
= mChildren
[childIdx
];
338 lastAccessible
= childAcc
;
340 nsIFrame
*frame
= childAcc
->GetFrame();
344 nsIFrame
*primaryFrame
= frame
;
345 if (nsAccUtils::IsText(childAcc
)) {
346 // We only need info up to rendered offset -- that is what we're
347 // converting to content offset
348 PRInt32 substringEndOffset
= -1;
349 PRUint32 ourRenderedStart
= 0;
350 PRInt32 ourContentStart
= 0;
351 if (frame
->GetType() == nsAccessibilityAtoms::textFrame
) {
352 nsresult rv
= frame
->GetRenderedText(nsnull
, &skipChars
, &iter
);
353 if (NS_SUCCEEDED(rv
)) {
354 ourRenderedStart
= iter
.GetSkippedOffset();
355 ourContentStart
= iter
.GetOriginalOffset();
357 iter
.ConvertOriginalToSkipped(skipChars
.GetOriginalCharCount() +
358 ourContentStart
) - ourRenderedStart
;
361 if (substringEndOffset
< 0) {
362 // XXX for non-textframe text like list bullets,
363 // should go away after list bullet rewrite
364 substringEndOffset
= nsAccUtils::TextLength(childAcc
);
366 if (startOffset
< substringEndOffset
) {
367 // Our start is within this substring
368 if (startOffset
> 0 || endOffset
< substringEndOffset
) {
369 // We don't want the whole string for this accessible
370 // Get out the continuing text frame with this offset
371 PRInt32 outStartLineUnused
;
372 PRInt32 contentOffset
;
373 if (frame
->GetType() == nsAccessibilityAtoms::textFrame
) {
374 contentOffset
= iter
.ConvertSkippedToOriginal(startOffset
) +
375 ourRenderedStart
- ourContentStart
;
378 contentOffset
= startOffset
;
380 frame
->GetChildFrameContainingOffset(contentOffset
, PR_TRUE
,
381 &outStartLineUnused
, &frame
);
383 *aEndFrame
= frame
; // We ended in the current frame
385 NS_ADDREF(*aEndAcc
= childAcc
);
387 if (substringEndOffset
> endOffset
) {
388 // Need to stop before the end of the available text
389 substringEndOffset
= endOffset
;
391 aEndOffset
= endOffset
;
395 for (PRInt32 count
= startOffset
; count
< substringEndOffset
; count
++)
396 *aText
+= '*'; // Show *'s only for password text
399 childAcc
->AppendTextTo(*aText
, startOffset
,
400 substringEndOffset
- startOffset
);
403 if (aBoundsRect
) { // Caller wants the bounds of the text
404 aBoundsRect
->UnionRect(*aBoundsRect
,
405 GetBoundsForString(primaryFrame
, startOffset
,
406 substringEndOffset
));
410 aStartOffset
= startOffset
;
412 NS_ADDREF(*aStartAcc
= childAcc
);
414 // We already started copying in this accessible's string,
415 // for the next accessible we'll start at offset 0
419 // We have not found the start position yet, get the new startOffset
420 // that is relative to next accessible
421 startOffset
-= substringEndOffset
;
423 // The endOffset needs to be relative to the new startOffset
424 endOffset
-= substringEndOffset
;
427 // Embedded object, append marker
428 // XXX Append \n for <br>'s
429 if (startOffset
>= 1) {
435 // XXX: should use nsIAccessible::AppendTextTo.
436 if (frame
->GetType() == nsAccessibilityAtoms::brFrame
) {
437 *aText
+= kForcedNewLineChar
;
438 } else if (nsAccUtils::MustPrune(this)) {
439 *aText
+= kImaginaryEmbeddedObjectChar
;
440 // Expose imaginary embedded object character if the accessible
443 *aText
+= kEmbeddedObjectChar
;
447 aBoundsRect
->UnionRect(*aBoundsRect
,
448 frame
->GetScreenRectExternal());
455 NS_ADDREF(*aStartAcc
= childAcc
);
460 if (endOffset
<= 0 && startFrame
) {
461 break; // If we don't have startFrame yet, get that in next loop iteration
465 if (aStartAcc
&& !*aStartAcc
) {
466 NS_IF_ADDREF(*aStartAcc
= lastAccessible
);
468 if (aEndFrame
&& !*aEndFrame
) {
469 *aEndFrame
= startFrame
;
470 if (aStartAcc
&& aEndAcc
)
471 NS_IF_ADDREF(*aEndAcc
= *aStartAcc
);
478 nsHyperTextAccessible::GetText(PRInt32 aStartOffset
, PRInt32 aEndOffset
,
484 return NS_ERROR_FAILURE
;
486 PRInt32 startOffset
= ConvertMagicOffset(aStartOffset
);
487 PRInt32 startChildIdx
= GetChildIndexAtOffset(startOffset
);
488 if (startChildIdx
== -1)
489 return NS_ERROR_INVALID_ARG
;
491 PRInt32 endOffset
= ConvertMagicOffset(aEndOffset
);
492 PRInt32 endChildIdx
= GetChildIndexAtOffset(endOffset
);
493 if (endChildIdx
== -1)
494 return NS_ERROR_INVALID_ARG
;
496 if (startChildIdx
== endChildIdx
) {
497 PRInt32 childOffset
= GetChildOffset(startChildIdx
);
498 NS_ENSURE_STATE(childOffset
!= -1);
500 nsAccessible
* child
= GetChildAt(startChildIdx
);
501 child
->AppendTextTo(aText
, startOffset
- childOffset
,
502 endOffset
- startOffset
);
507 PRInt32 startChildOffset
= GetChildOffset(startChildIdx
);
508 NS_ENSURE_STATE(startChildOffset
!= -1);
510 nsAccessible
* startChild
= GetChildAt(startChildIdx
);
511 startChild
->AppendTextTo(aText
, startOffset
- startChildOffset
);
513 for (PRInt32 childIdx
= startChildIdx
+ 1; childIdx
< endChildIdx
; childIdx
++) {
514 nsAccessible
* child
= GetChildAt(childIdx
);
515 child
->AppendTextTo(aText
);
518 PRInt32 endChildOffset
= GetChildOffset(endChildIdx
);
519 NS_ENSURE_STATE(endChildOffset
!= -1);
521 nsAccessible
* endChild
= GetChildAt(endChildIdx
);
522 endChild
->AppendTextTo(aText
, 0, endOffset
- endChildOffset
);
528 * Gets the character count.
530 NS_IMETHODIMP
nsHyperTextAccessible::GetCharacterCount(PRInt32
*aCharacterCount
)
532 NS_ENSURE_ARG_POINTER(aCharacterCount
);
533 *aCharacterCount
= 0;
536 return NS_ERROR_FAILURE
;
538 *aCharacterCount
= CharacterCount();
543 * Gets the specified character.
545 NS_IMETHODIMP
nsHyperTextAccessible::GetCharacterAtOffset(PRInt32 aOffset
, PRUnichar
*aCharacter
)
547 NS_ENSURE_ARG_POINTER(aCharacter
);
548 *aCharacter
= nsnull
;
551 return NS_ERROR_FAILURE
;
553 nsAutoString character
;
554 if (GetCharAt(aOffset
, eGetAt
, character
)) {
555 *aCharacter
= character
.First();
559 return NS_ERROR_INVALID_ARG
;
563 nsHyperTextAccessible::DOMPointToHypertextOffset(nsINode
*aNode
,
565 PRInt32
*aHyperTextOffset
,
568 if (!aHyperTextOffset
)
570 *aHyperTextOffset
= 0;
575 PRUint32 addTextOffset
= 0;
576 nsINode
* findNode
= nsnull
;
578 if (aNodeOffset
== -1) {
581 } else if (aNode
->IsNodeOfType(nsINode::eTEXT
)) {
582 // For text nodes, aNodeOffset comes in as a character offset
583 // Text offset will be added at the end, if we find the offset in this hypertext
584 // We want the "skipped" offset into the text (rendered text without the extra whitespace)
585 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aNode
);
586 NS_ASSERTION(content
, "No nsIContent for dom node");
587 nsIFrame
*frame
= content
->GetPrimaryFrame();
588 NS_ENSURE_TRUE(frame
, nsnull
);
589 nsresult rv
= ContentToRenderedOffset(frame
, aNodeOffset
, &addTextOffset
);
590 NS_ENSURE_SUCCESS(rv
, nsnull
);
591 // Get the child node and
595 // findNode could be null if aNodeOffset == # of child nodes, which means
596 // one of two things:
597 // 1) we're at the end of the children, keep findNode = null, so that we get
598 // the last possible offset
599 // 2) there are no children and the passed-in node is mContent, which means
600 // we're an aempty nsIAccessibleText
601 // 3) there are no children, and the passed-in node is not mContent -- use
602 // parentContent for the node to find
604 findNode
= aNode
->GetChildAt(aNodeOffset
);
605 if (!findNode
&& !aNodeOffset
) {
606 if (aNode
== GetNode()) {
607 // There are no children, which means this is an empty nsIAccessibleText, in which
608 // case we can only be at hypertext offset 0
609 *aHyperTextOffset
= 0;
612 findNode
= aNode
; // Case #2: there are no children
616 // Get accessible for this findNode, or if that node isn't accessible, use the
617 // accessible for the next DOM node which has one (based on forward depth first search)
618 nsAccessible
*descendantAcc
= nsnull
;
620 nsCOMPtr
<nsIContent
> findContent(do_QueryInterface(findNode
));
621 if (findContent
&& findContent
->IsHTML() &&
622 findContent
->NodeInfo()->Equals(nsAccessibilityAtoms::br
) &&
623 findContent
->AttrValueIs(kNameSpaceID_None
,
624 nsAccessibilityAtoms::mozeditorbogusnode
,
625 nsAccessibilityAtoms::_true
,
627 // This <br> is the hacky "bogus node" used when there is no text in a control
628 *aHyperTextOffset
= 0;
631 descendantAcc
= GetFirstAvailableAccessible(findNode
);
634 // From the descendant, go up and get the immediate child of this hypertext
635 nsAccessible
*childAccAtOffset
= nsnull
;
636 while (descendantAcc
) {
637 nsAccessible
*parentAcc
= descendantAcc
->GetParent();
638 if (parentAcc
== this) {
639 childAccAtOffset
= descendantAcc
;
643 // This offset no longer applies because the passed-in text object is not a child
644 // of the hypertext. This happens when there are nested hypertexts, e.g.
645 // <div>abc<h1>def</h1>ghi</div>
646 // If the passed-in DOM point was not on a direct child of the hypertext, we will
647 // return the offset for that entire hypertext
649 // Not inclusive, the indicated char comes at index before this offset
650 // If the end offset is after the first character of the passed in object, use 1 for
651 // addTextOffset, to put us after the embedded object char. We'll only treat the offset as
652 // before the embedded object char if we end at the very beginning of the child.
653 addTextOffset
= addTextOffset
> 0;
656 // Start offset, inclusive
657 // Make sure the offset lands on the embedded object character in order to indicate
658 // the true inner offset is inside the subtree for that link
660 (nsAccUtils::TextLength(descendantAcc
) == addTextOffset
) ? 1 : 0;
663 descendantAcc
= parentAcc
;
666 // Loop through, adding offsets until we reach childAccessible
667 // If childAccessible is null we will end up adding up the entire length of
668 // the hypertext, which is good -- it just means our offset node
669 // came after the last accessible child's node
670 PRInt32 childCount
= GetChildCount();
672 PRInt32 childIdx
= 0;
673 nsAccessible
*childAcc
= nsnull
;
674 for (; childIdx
< childCount
; childIdx
++) {
675 childAcc
= mChildren
[childIdx
];
676 if (childAcc
== childAccAtOffset
)
679 *aHyperTextOffset
+= nsAccUtils::TextLength(childAcc
);
682 if (childIdx
< childCount
) {
683 *aHyperTextOffset
+= addTextOffset
;
684 NS_ASSERTION(childAcc
== childAccAtOffset
,
685 "These should be equal whenever we exit loop and childAcc != nsnull");
687 if (childIdx
< childCount
- 1 ||
688 addTextOffset
< nsAccUtils::TextLength(childAccAtOffset
)) {
689 // If not at end of last text node, we will return the accessible we were in
690 return childAccAtOffset
;
698 nsHyperTextAccessible::HypertextOffsetToDOMPoint(PRInt32 aHTOffset
,
702 nsCOMPtr
<nsIDOMNode
> endNode
;
705 return HypertextOffsetsToDOMRange(aHTOffset
, aHTOffset
, aNode
, aOffset
,
706 getter_AddRefs(endNode
), &endOffset
);
710 nsHyperTextAccessible::HypertextOffsetsToDOMRange(PRInt32 aStartHTOffset
,
711 PRInt32 aEndHTOffset
,
712 nsIDOMNode
**aStartNode
,
713 PRInt32
*aStartOffset
,
714 nsIDOMNode
**aEndNode
,
717 NS_ENSURE_ARG_POINTER(aStartNode
);
718 *aStartNode
= nsnull
;
720 NS_ENSURE_ARG_POINTER(aStartOffset
);
723 NS_ENSURE_ARG_POINTER(aEndNode
);
726 NS_ENSURE_ARG_POINTER(aEndOffset
);
729 // If the given offsets are 0 and associated editor is empty then return
730 // collapsed range with editor root element as range container.
731 if (aStartHTOffset
== 0 && aEndHTOffset
== 0) {
732 nsCOMPtr
<nsIEditor
> editor
;
733 GetAssociatedEditor(getter_AddRefs(editor
));
735 PRBool isEmpty
= PR_FALSE
;
736 editor
->GetDocumentIsEmpty(&isEmpty
);
738 nsCOMPtr
<nsIDOMElement
> editorRootElm
;
739 editor
->GetRootElement(getter_AddRefs(editorRootElm
));
741 nsCOMPtr
<nsIDOMNode
> editorRoot(do_QueryInterface(editorRootElm
));
743 *aStartOffset
= *aEndOffset
= 0;
744 NS_ADDREF(*aStartNode
= editorRoot
);
745 NS_ADDREF(*aEndNode
= editorRoot
);
753 nsRefPtr
<nsAccessible
> startAcc
, endAcc
;
754 PRInt32 startOffset
= aStartHTOffset
, endOffset
= aEndHTOffset
;
755 nsIFrame
*startFrame
= nsnull
, *endFrame
= nsnull
;
757 startFrame
= GetPosAndText(startOffset
, endOffset
, nsnull
, &endFrame
, nsnull
,
758 getter_AddRefs(startAcc
), getter_AddRefs(endAcc
));
759 if (!startAcc
|| !endAcc
)
760 return NS_ERROR_FAILURE
;
762 nsCOMPtr
<nsIDOMNode
> startNode
, endNode
;
763 nsresult rv
= GetDOMPointByFrameOffset(startFrame
, startOffset
, startAcc
,
764 getter_AddRefs(startNode
),
766 NS_ENSURE_SUCCESS(rv
, rv
);
768 if (aStartHTOffset
!= aEndHTOffset
) {
769 rv
= GetDOMPointByFrameOffset(endFrame
, endOffset
, endAcc
,
770 getter_AddRefs(endNode
), &endOffset
);
771 NS_ENSURE_SUCCESS(rv
, rv
);
774 endOffset
= startOffset
;
777 NS_ADDREF(*aStartNode
= startNode
);
778 *aStartOffset
= startOffset
;
780 NS_ADDREF(*aEndNode
= endNode
);
781 *aEndOffset
= endOffset
;
787 nsHyperTextAccessible::GetRelativeOffset(nsIPresShell
*aPresShell
,
788 nsIFrame
*aFromFrame
,
790 nsAccessible
*aFromAccessible
,
791 nsSelectionAmount aAmount
,
792 nsDirection aDirection
,
795 const PRBool kIsJumpLinesOk
= PR_TRUE
; // okay to jump lines
796 const PRBool kIsScrollViewAStop
= PR_FALSE
; // do not stop at scroll views
797 const PRBool kIsKeyboardSelect
= PR_TRUE
; // is keyboard selection
798 const PRBool kIsVisualBidi
= PR_FALSE
; // use visual order for bidi text
800 EWordMovementType wordMovementType
= aNeedsStart
? eStartWord
: eEndWord
;
801 if (aAmount
== eSelectLine
) {
802 aAmount
= (aDirection
== eDirNext
) ? eSelectEndLine
: eSelectBeginLine
;
805 // Ask layout for the new node and offset, after moving the appropriate amount
806 nsPeekOffsetStruct pos
;
809 PRInt32 contentOffset
= aFromOffset
;
810 if (nsAccUtils::IsText(aFromAccessible
)) {
811 nsIFrame
*frame
= aFromAccessible
->GetFrame();
812 NS_ENSURE_TRUE(frame
, -1);
814 if (frame
->GetType() == nsAccessibilityAtoms::textFrame
) {
815 rv
= RenderedToContentOffset(frame
, aFromOffset
, &contentOffset
);
816 NS_ENSURE_SUCCESS(rv
, -1);
820 pos
.SetData(aAmount
, aDirection
, contentOffset
,
821 0, kIsJumpLinesOk
, kIsScrollViewAStop
, kIsKeyboardSelect
, kIsVisualBidi
,
823 rv
= aFromFrame
->PeekOffset(&pos
);
825 if (aDirection
== eDirPrevious
) {
826 // Use passed-in frame as starting point in failure case for now,
827 // this is a hack to deal with starting on a list bullet frame,
828 // which fails in PeekOffset() because the line iterator doesn't see it.
829 // XXX Need to look at our overall handling of list bullets, which are an odd case
830 pos
.mResultContent
= aFromFrame
->GetContent();
831 PRInt32 endOffsetUnused
;
832 aFromFrame
->GetOffsets(pos
.mContentOffset
, endOffsetUnused
);
839 // Turn the resulting node and offset into a hyperTextOffset
840 PRInt32 hyperTextOffset
;
841 if (!pos
.mResultContent
)
844 // If finalAccessible is nsnull, then DOMPointToHypertextOffset() searched
845 // through the hypertext children without finding the node/offset position.
846 nsAccessible
*finalAccessible
=
847 DOMPointToHypertextOffset(pos
.mResultContent
, pos
.mContentOffset
,
848 &hyperTextOffset
, aDirection
== eDirNext
);
850 if (!finalAccessible
&& aDirection
== eDirPrevious
) {
851 // If we reached the end during search, this means we didn't find the DOM point
852 // and we're actually at the start of the paragraph
855 else if (aAmount
== eSelectBeginLine
) {
856 nsAccessible
*firstChild
= mChildren
.SafeElementAt(0, nsnull
);
857 // For line selection with needsStart, set start of line exactly to line break
858 if (pos
.mContentOffset
== 0 && firstChild
&&
859 firstChild
->Role() == nsIAccessibleRole::ROLE_STATICTEXT
&&
860 static_cast<PRInt32
>(nsAccUtils::TextLength(firstChild
)) == hyperTextOffset
) {
861 // XXX Bullet hack -- we should remove this once list bullets use anonymous content
864 if (!aNeedsStart
&& hyperTextOffset
> 0) {
868 else if (aAmount
== eSelectEndLine
&& finalAccessible
) {
869 // If not at very end of hypertext, we may need change the end of line offset by 1,
870 // to make sure we are in the right place relative to the line ending
871 if (finalAccessible
->Role() == nsIAccessibleRole::ROLE_WHITESPACE
) { // Landed on <br> hard line break
872 // if aNeedsStart, set end of line exactly 1 character past line break
873 // XXX It would be cleaner if we did not have to have the hard line break check,
874 // and just got the correct results from PeekOffset() for the <br> case -- the returned offset should
875 // come after the new line, as it does in other cases.
876 ++ hyperTextOffset
; // Get past hard line break
878 // We are now 1 character past the line break
884 return hyperTextOffset
;
888 Gets the specified text relative to aBoundaryType, which means:
889 BOUNDARY_CHAR The character before/at/after the offset is returned.
890 BOUNDARY_WORD_START From the word start before/at/after the offset to the next word start.
891 BOUNDARY_WORD_END From the word end before/at/after the offset to the next work end.
892 BOUNDARY_LINE_START From the line start before/at/after the offset to the next line start.
893 BOUNDARY_LINE_END From the line end before/at/after the offset to the next line start.
896 nsresult
nsHyperTextAccessible::GetTextHelper(EGetTextType aType
, nsAccessibleTextBoundary aBoundaryType
,
897 PRInt32 aOffset
, PRInt32
*aStartOffset
, PRInt32
*aEndOffset
,
902 NS_ENSURE_ARG_POINTER(aStartOffset
);
903 NS_ENSURE_ARG_POINTER(aEndOffset
);
904 *aStartOffset
= *aEndOffset
= 0;
906 nsCOMPtr
<nsIPresShell
> presShell
= GetPresShell();
908 return NS_ERROR_FAILURE
;
911 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT
) {
912 aOffset
= CharacterCount();
914 if (aOffset
== nsIAccessibleText::TEXT_OFFSET_CARET
) {
915 GetCaretOffset(&aOffset
);
916 if (aOffset
> 0 && (aBoundaryType
== BOUNDARY_LINE_START
||
917 aBoundaryType
== BOUNDARY_LINE_END
)) {
918 // It is the same character offset when the caret is visually at the very end of a line
919 // or the start of a new line. Getting text at the line should provide the line with the visual caret,
920 // otherwise screen readers will announce the wrong line as the user presses up or down arrow and land
921 // at the end of a line.
922 nsCOMPtr
<nsISelection
> domSel
;
923 nsresult rv
= GetSelections(nsISelectionController::SELECTION_NORMAL
,
924 nsnull
, getter_AddRefs(domSel
));
925 NS_ENSURE_SUCCESS(rv
, rv
);
927 nsCOMPtr
<nsISelectionPrivate
> privateSelection(do_QueryInterface(domSel
));
928 nsCOMPtr
<nsFrameSelection
> frameSelection
;
929 rv
= privateSelection
->GetFrameSelection(getter_AddRefs(frameSelection
));
930 NS_ENSURE_SUCCESS(rv
, rv
);
932 if (frameSelection
->GetHint() == nsFrameSelection::HINTLEFT
) {
933 -- aOffset
; // We are at the start of a line
937 else if (aOffset
< 0) {
938 return NS_ERROR_FAILURE
;
941 nsSelectionAmount amount
;
942 PRBool needsStart
= PR_FALSE
;
943 switch (aBoundaryType
) {
945 amount
= eSelectCluster
;
947 aType
= eGetAfter
; // Avoid returning 2 characters
950 case BOUNDARY_WORD_START
:
951 needsStart
= PR_TRUE
;
952 amount
= eSelectWord
;
955 case BOUNDARY_WORD_END
:
956 amount
= eSelectWord
;
959 case BOUNDARY_LINE_START
:
960 // Newlines are considered at the end of a line. Since getting
961 // the BOUNDARY_LINE_START gets the text from the line-start to the next
962 // line-start, the newline is included at the end of the string.
963 needsStart
= PR_TRUE
;
964 amount
= eSelectLine
;
967 case BOUNDARY_LINE_END
:
968 // Newlines are considered at the end of a line. Since getting
969 // the BOUNDARY_END_START gets the text from the line-end to the next
970 //line-end, the newline is included at the beginning of the string.
971 amount
= eSelectLine
;
974 case BOUNDARY_ATTRIBUTE_RANGE
:
976 nsresult rv
= GetTextAttributes(PR_FALSE
, aOffset
,
977 aStartOffset
, aEndOffset
, nsnull
);
978 NS_ENSURE_SUCCESS(rv
, rv
);
980 return GetText(*aStartOffset
, *aEndOffset
, aText
);
983 default: // Note, sentence support is deprecated and falls through to here
984 return NS_ERROR_INVALID_ARG
;
987 PRInt32 startOffset
= aOffset
+ (aBoundaryType
== BOUNDARY_LINE_END
); // Avoid getting the previous line
988 PRInt32 endOffset
= startOffset
;
990 // Convert offsets to frame-relative
991 nsRefPtr
<nsAccessible
> startAcc
;
992 nsIFrame
*startFrame
= GetPosAndText(startOffset
, endOffset
, nsnull
, nsnull
,
993 nsnull
, getter_AddRefs(startAcc
));
996 PRInt32 textLength
= CharacterCount();
997 if (aBoundaryType
== BOUNDARY_LINE_START
&& aOffset
> 0 && aOffset
== textLength
) {
998 // Asking for start of line, while on last character
1000 startFrame
= startAcc
->GetFrame();
1003 return aOffset
> textLength
? NS_ERROR_FAILURE
: NS_OK
;
1006 // We're on the last continuation since we're on the last character
1007 startFrame
= startFrame
->GetLastContinuation();
1011 PRInt32 finalStartOffset
, finalEndOffset
;
1013 // If aType == eGetAt we'll change both the start and end offset from
1014 // the original offset
1015 if (aType
== eGetAfter
) {
1016 finalStartOffset
= aOffset
;
1019 finalStartOffset
= GetRelativeOffset(presShell
, startFrame
, startOffset
,
1020 startAcc
, amount
, eDirPrevious
,
1022 NS_ENSURE_TRUE(finalStartOffset
>= 0, NS_ERROR_FAILURE
);
1025 if (aType
== eGetBefore
) {
1026 endOffset
= aOffset
;
1029 // Start moving forward from the start so that we don't get
1030 // 2 words/lines if the offset occurred on whitespace boundary
1031 // Careful, startOffset and endOffset are passed by reference to GetPosAndText() and changed
1032 // For BOUNDARY_LINE_END, make sure we start of this line
1033 startOffset
= endOffset
= finalStartOffset
+ (aBoundaryType
== BOUNDARY_LINE_END
);
1034 nsRefPtr
<nsAccessible
> endAcc
;
1035 nsIFrame
*endFrame
= GetPosAndText(startOffset
, endOffset
, nsnull
, nsnull
,
1036 nsnull
, getter_AddRefs(endAcc
));
1037 if (endAcc
&& endAcc
->Role() == nsIAccessibleRole::ROLE_STATICTEXT
) {
1038 // Static text like list bullets will ruin our forward calculation,
1039 // since the caret cannot be in the static text. Start just after the static text.
1040 startOffset
= endOffset
= finalStartOffset
+
1041 (aBoundaryType
== BOUNDARY_LINE_END
) +
1042 nsAccUtils::TextLength(endAcc
);
1044 endFrame
= GetPosAndText(startOffset
, endOffset
, nsnull
, nsnull
,
1045 nsnull
, getter_AddRefs(endAcc
));
1048 return NS_ERROR_FAILURE
;
1050 finalEndOffset
= GetRelativeOffset(presShell
, endFrame
, endOffset
, endAcc
,
1051 amount
, eDirNext
, needsStart
);
1052 NS_ENSURE_TRUE(endOffset
>= 0, NS_ERROR_FAILURE
);
1053 if (finalEndOffset
== aOffset
) {
1054 if (aType
== eGetAt
&& amount
== eSelectWord
) {
1055 // Fix word error for the first character in word: PeekOffset() will return the previous word when
1056 // aOffset points to the first character of the word, but accessibility APIs want the current word
1057 // that the first character is in
1058 return GetTextHelper(eGetAfter
, aBoundaryType
, aOffset
, aStartOffset
, aEndOffset
, aText
);
1060 PRInt32 textLength
= CharacterCount();
1061 if (finalEndOffset
< textLength
) {
1062 // This happens sometimes when current character at finalStartOffset
1063 // is an embedded object character representing another hypertext, that
1064 // the AT really needs to dig into separately
1070 *aStartOffset
= finalStartOffset
;
1071 *aEndOffset
= finalEndOffset
;
1073 NS_ASSERTION((finalStartOffset
< aOffset
&& finalEndOffset
>= aOffset
) || aType
!= eGetBefore
, "Incorrect results for GetTextHelper");
1074 NS_ASSERTION((finalStartOffset
<= aOffset
&& finalEndOffset
> aOffset
) || aType
== eGetBefore
, "Incorrect results for GetTextHelper");
1076 GetPosAndText(finalStartOffset
, finalEndOffset
, &aText
);
1081 * nsIAccessibleText impl.
1083 NS_IMETHODIMP
nsHyperTextAccessible::GetTextBeforeOffset(PRInt32 aOffset
, nsAccessibleTextBoundary aBoundaryType
,
1084 PRInt32
*aStartOffset
, PRInt32
*aEndOffset
, nsAString
& aText
)
1086 if (aBoundaryType
== BOUNDARY_CHAR
) {
1087 GetCharAt(aOffset
, eGetBefore
, aText
, aStartOffset
, aEndOffset
);
1091 return GetTextHelper(eGetBefore
, aBoundaryType
, aOffset
, aStartOffset
, aEndOffset
, aText
);
1094 NS_IMETHODIMP
nsHyperTextAccessible::GetTextAtOffset(PRInt32 aOffset
, nsAccessibleTextBoundary aBoundaryType
,
1095 PRInt32
*aStartOffset
, PRInt32
*aEndOffset
, nsAString
& aText
)
1097 if (aBoundaryType
== BOUNDARY_CHAR
) {
1098 GetCharAt(aOffset
, eGetAt
, aText
, aStartOffset
, aEndOffset
);
1102 return GetTextHelper(eGetAt
, aBoundaryType
, aOffset
, aStartOffset
, aEndOffset
, aText
);
1105 NS_IMETHODIMP
nsHyperTextAccessible::GetTextAfterOffset(PRInt32 aOffset
, nsAccessibleTextBoundary aBoundaryType
,
1106 PRInt32
*aStartOffset
, PRInt32
*aEndOffset
, nsAString
& aText
)
1108 if (aBoundaryType
== BOUNDARY_CHAR
) {
1109 GetCharAt(aOffset
, eGetAfter
, aText
, aStartOffset
, aEndOffset
);
1113 return GetTextHelper(eGetAfter
, aBoundaryType
, aOffset
, aStartOffset
, aEndOffset
, aText
);
1116 // nsIPersistentProperties
1117 // nsIAccessibleText::getTextAttributes(in boolean includeDefAttrs,
1119 // out long rangeStartOffset,
1120 // out long rangeEndOffset);
1122 nsHyperTextAccessible::GetTextAttributes(PRBool aIncludeDefAttrs
,
1124 PRInt32
*aStartOffset
,
1125 PRInt32
*aEndOffset
,
1126 nsIPersistentProperties
**aAttributes
)
1128 // 1. Get each attribute and its ranges one after another.
1129 // 2. As we get each new attribute, we pass the current start and end offsets
1130 // as in/out parameters. In other words, as attributes are collected,
1131 // the attribute range itself can only stay the same or get smaller.
1133 NS_ENSURE_ARG_POINTER(aStartOffset
);
1136 NS_ENSURE_ARG_POINTER(aEndOffset
);
1140 return NS_ERROR_FAILURE
;
1143 *aAttributes
= nsnull
;
1145 nsCOMPtr
<nsIPersistentProperties
> attributes
=
1146 do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID
);
1147 NS_ENSURE_TRUE(attributes
, NS_ERROR_OUT_OF_MEMORY
);
1149 NS_ADDREF(*aAttributes
= attributes
);
1152 nsAccessible
* accAtOffset
= GetChildAtOffset(aOffset
);
1154 // Offset 0 is correct offset when accessible has empty text. Include
1155 // default attributes if they were requested, otherwise return empty set.
1157 if (aIncludeDefAttrs
) {
1158 nsTextAttrsMgr
textAttrsMgr(this, PR_TRUE
, nsnull
, -1);
1159 return textAttrsMgr
.GetAttributes(*aAttributes
);
1163 return NS_ERROR_INVALID_ARG
;
1166 PRInt32 accAtOffsetIdx
= accAtOffset
->GetIndexInParent();
1167 PRInt32 startOffset
= GetChildOffset(accAtOffsetIdx
);
1168 PRInt32 endOffset
= GetChildOffset(accAtOffsetIdx
+ 1);
1169 PRInt32 offsetInAcc
= aOffset
- startOffset
;
1171 nsTextAttrsMgr
textAttrsMgr(this, aIncludeDefAttrs
, accAtOffset
,
1173 nsresult rv
= textAttrsMgr
.GetAttributes(*aAttributes
, &startOffset
,
1175 NS_ENSURE_SUCCESS(rv
, rv
);
1177 // Compute spelling attributes on text accessible only.
1178 nsIFrame
*offsetFrame
= accAtOffset
->GetFrame();
1179 if (offsetFrame
&& offsetFrame
->GetType() == nsAccessibilityAtoms::textFrame
) {
1180 nsCOMPtr
<nsIDOMNode
> node
= accAtOffset
->GetDOMNode();
1182 PRInt32 nodeOffset
= 0;
1183 nsresult rv
= RenderedToContentOffset(offsetFrame
, offsetInAcc
,
1185 NS_ENSURE_SUCCESS(rv
, rv
);
1187 // Set 'misspelled' text attribute.
1188 rv
= GetSpellTextAttribute(node
, nodeOffset
, &startOffset
, &endOffset
,
1189 aAttributes
? *aAttributes
: nsnull
);
1190 NS_ENSURE_SUCCESS(rv
, rv
);
1193 *aStartOffset
= startOffset
;
1194 *aEndOffset
= endOffset
;
1198 // nsIPersistentProperties
1199 // nsIAccessibleText::defaultTextAttributes
1201 nsHyperTextAccessible::GetDefaultTextAttributes(nsIPersistentProperties
**aAttributes
)
1203 NS_ENSURE_ARG_POINTER(aAttributes
);
1204 *aAttributes
= nsnull
;
1207 return NS_ERROR_FAILURE
;
1209 nsCOMPtr
<nsIPersistentProperties
> attributes
=
1210 do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID
);
1211 NS_ENSURE_TRUE(attributes
, NS_ERROR_OUT_OF_MEMORY
);
1213 NS_ADDREF(*aAttributes
= attributes
);
1215 nsTextAttrsMgr
textAttrsMgr(this, PR_TRUE
);
1216 return textAttrsMgr
.GetAttributes(*aAttributes
);
1220 nsHyperTextAccessible::GetLevelInternal()
1222 nsIAtom
*tag
= mContent
->Tag();
1223 if (tag
== nsAccessibilityAtoms::h1
)
1225 if (tag
== nsAccessibilityAtoms::h2
)
1227 if (tag
== nsAccessibilityAtoms::h3
)
1229 if (tag
== nsAccessibilityAtoms::h4
)
1231 if (tag
== nsAccessibilityAtoms::h5
)
1233 if (tag
== nsAccessibilityAtoms::h6
)
1236 return nsAccessibleWrap::GetLevelInternal();
1240 nsHyperTextAccessible::GetAttributesInternal(nsIPersistentProperties
*aAttributes
)
1242 nsresult rv
= nsAccessibleWrap::GetAttributesInternal(aAttributes
);
1243 NS_ENSURE_SUCCESS(rv
, rv
);
1245 // Indicate when the current object uses block-level formatting
1246 // via formatting: block
1247 // XXX: 'formatting' attribute is deprecated and will be removed in Mozilla2,
1248 // use 'display' attribute instead.
1249 nsIFrame
*frame
= GetFrame();
1250 if (frame
&& frame
->GetType() == nsAccessibilityAtoms::blockFrame
) {
1251 nsAutoString oldValueUnused
;
1252 aAttributes
->SetStringProperty(NS_LITERAL_CSTRING("formatting"), NS_LITERAL_STRING("block"),
1256 if (gLastFocusedNode
== GetNode()) {
1257 PRInt32 lineNumber
= GetCaretLineNumber();
1258 if (lineNumber
>= 1) {
1259 nsAutoString strLineNumber
;
1260 strLineNumber
.AppendInt(lineNumber
);
1261 nsAccUtils::SetAccAttr(aAttributes
, nsAccessibilityAtoms::lineNumber
,
1266 // For the html landmark elements we expose them like we do aria landmarks to
1267 // make AT navigation schemes "just work".
1268 if (mContent
->Tag() == nsAccessibilityAtoms::nav
)
1269 nsAccUtils::SetAccAttr(aAttributes
, nsAccessibilityAtoms::xmlroles
,
1270 NS_LITERAL_STRING("navigation"));
1271 else if (mContent
->Tag() == nsAccessibilityAtoms::header
)
1272 nsAccUtils::SetAccAttr(aAttributes
, nsAccessibilityAtoms::xmlroles
,
1273 NS_LITERAL_STRING("banner"));
1274 else if (mContent
->Tag() == nsAccessibilityAtoms::footer
)
1275 nsAccUtils::SetAccAttr(aAttributes
, nsAccessibilityAtoms::xmlroles
,
1276 NS_LITERAL_STRING("contentinfo"));
1277 else if (mContent
->Tag() == nsAccessibilityAtoms::aside
)
1278 nsAccUtils::SetAccAttr(aAttributes
, nsAccessibilityAtoms::xmlroles
,
1279 NS_LITERAL_STRING("note"));
1285 * Given an offset, the x, y, width, and height values are filled appropriately.
1287 NS_IMETHODIMP
nsHyperTextAccessible::GetCharacterExtents(PRInt32 aOffset
, PRInt32
*aX
, PRInt32
*aY
,
1288 PRInt32
*aWidth
, PRInt32
*aHeight
,
1289 PRUint32 aCoordType
)
1291 return GetRangeExtents(aOffset
, aOffset
+ 1, aX
, aY
, aWidth
, aHeight
, aCoordType
);
1295 * Given a start & end offset, the x, y, width, and height values are filled appropriately.
1297 NS_IMETHODIMP
nsHyperTextAccessible::GetRangeExtents(PRInt32 aStartOffset
, PRInt32 aEndOffset
,
1298 PRInt32
*aX
, PRInt32
*aY
,
1299 PRInt32
*aWidth
, PRInt32
*aHeight
,
1300 PRUint32 aCoordType
)
1302 nsIntRect boundsRect
;
1303 nsIFrame
*endFrameUnused
;
1304 if (!GetPosAndText(aStartOffset
, aEndOffset
, nsnull
, &endFrameUnused
, &boundsRect
) ||
1305 boundsRect
.IsEmpty()) {
1306 return NS_ERROR_FAILURE
;
1311 *aWidth
= boundsRect
.width
;
1312 *aHeight
= boundsRect
.height
;
1314 return nsAccUtils::ConvertScreenCoordsTo(aX
, aY
, aCoordType
, this);
1318 * Gets the offset of the character located at coordinates x and y. x and y are interpreted as being relative to
1319 * the screen or this widget's window depending on coords.
1322 nsHyperTextAccessible::GetOffsetAtPoint(PRInt32 aX
, PRInt32 aY
,
1323 PRUint32 aCoordType
, PRInt32
*aOffset
)
1326 nsCOMPtr
<nsIPresShell
> shell
= GetPresShell();
1328 return NS_ERROR_FAILURE
;
1330 nsIFrame
*hyperFrame
= GetFrame();
1332 return NS_ERROR_FAILURE
;
1334 nsIntRect frameScreenRect
= hyperFrame
->GetScreenRectExternal();
1337 nsresult rv
= nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordType
,
1339 NS_ENSURE_SUCCESS(rv
, rv
);
1341 // coords are currently screen coordinates, and we need to turn them into
1342 // frame coordinates relative to the current accessible
1343 if (!frameScreenRect
.Contains(coords
.x
, coords
.y
)) {
1344 return NS_OK
; // Not found, will return -1
1346 nsIntPoint
pxInHyperText(coords
.x
- frameScreenRect
.x
,
1347 coords
.y
- frameScreenRect
.y
);
1348 nsPresContext
*context
= GetPresContext();
1349 NS_ENSURE_TRUE(context
, NS_ERROR_FAILURE
);
1350 nsPoint
pointInHyperText(context
->DevPixelsToAppUnits(pxInHyperText
.x
),
1351 context
->DevPixelsToAppUnits(pxInHyperText
.y
));
1353 // Go through the frames to check if each one has the point.
1354 // When one does, add up the character offsets until we have a match
1356 // We have an point in an accessible child of this, now we need to add up the
1357 // offsets before it to what we already have
1359 PRInt32 childCount
= GetChildCount();
1360 for (PRInt32 childIdx
= 0; childIdx
< childCount
; childIdx
++) {
1361 nsAccessible
*childAcc
= mChildren
[childIdx
];
1363 nsIFrame
*primaryFrame
= childAcc
->GetFrame();
1364 NS_ENSURE_TRUE(primaryFrame
, NS_ERROR_FAILURE
);
1366 nsIFrame
*frame
= primaryFrame
;
1368 nsIContent
*content
= frame
->GetContent();
1369 NS_ENSURE_TRUE(content
, NS_ERROR_FAILURE
);
1370 nsPoint pointInFrame
= pointInHyperText
- frame
->GetOffsetToExternal(hyperFrame
);
1371 nsSize frameSize
= frame
->GetSize();
1372 if (pointInFrame
.x
< frameSize
.width
&& pointInFrame
.y
< frameSize
.height
) {
1374 if (frame
->GetType() == nsAccessibilityAtoms::textFrame
) {
1375 nsIFrame::ContentOffsets contentOffsets
= frame
->GetContentOffsetsFromPointExternal(pointInFrame
, PR_TRUE
);
1376 if (contentOffsets
.IsNull() || contentOffsets
.content
!= content
) {
1377 return NS_OK
; // Not found, will return -1
1379 PRUint32 addToOffset
;
1380 nsresult rv
= ContentToRenderedOffset(primaryFrame
,
1381 contentOffsets
.offset
,
1383 NS_ENSURE_SUCCESS(rv
, rv
);
1384 offset
+= addToOffset
;
1389 frame
= frame
->GetNextContinuation();
1392 offset
+= nsAccUtils::TextLength(childAcc
);
1395 return NS_OK
; // Not found, will return -1
1399 ////////////////////////////////////////////////////////////////////////////////
1400 // nsIAccessibleHyperText
1403 nsHyperTextAccessible::GetLinkCount(PRInt32
*aLinkCount
)
1405 NS_ENSURE_ARG_POINTER(aLinkCount
);
1409 return NS_ERROR_FAILURE
;
1411 *aLinkCount
= GetLinkCount();
1416 nsHyperTextAccessible::GetLinkAt(PRInt32 aIndex
, nsIAccessibleHyperLink
** aLink
)
1418 NS_ENSURE_ARG_POINTER(aLink
);
1422 return NS_ERROR_FAILURE
;
1424 nsAccessible
* link
= GetLinkAt(aIndex
);
1426 CallQueryInterface(link
, aLink
);
1432 nsHyperTextAccessible::GetLinkIndex(nsIAccessibleHyperLink
* aLink
,
1435 NS_ENSURE_ARG_POINTER(aLink
);
1438 return NS_ERROR_FAILURE
;
1440 nsRefPtr
<nsAccessible
> link(do_QueryObject(aLink
));
1441 *aIndex
= GetLinkIndex(link
);
1446 nsHyperTextAccessible::GetLinkIndexAtOffset(PRInt32 aOffset
,
1447 PRInt32
* aLinkIndex
)
1449 NS_ENSURE_ARG_POINTER(aLinkIndex
);
1450 *aLinkIndex
= -1; // API says this magic value means 'not found'
1453 return NS_ERROR_FAILURE
;
1455 *aLinkIndex
= GetLinkIndexAtOffset(aOffset
);
1460 * nsIAccessibleEditableText impl.
1462 NS_IMETHODIMP
nsHyperTextAccessible::SetAttributes(PRInt32 aStartPos
, PRInt32 aEndPos
,
1463 nsISupports
*aAttributes
)
1465 return NS_ERROR_NOT_IMPLEMENTED
;
1468 NS_IMETHODIMP
nsHyperTextAccessible::SetTextContents(const nsAString
&aText
)
1470 PRInt32 numChars
= CharacterCount();
1471 if (numChars
== 0 || NS_SUCCEEDED(DeleteText(0, numChars
))) {
1472 return InsertText(aText
, 0);
1474 return NS_ERROR_FAILURE
;
1478 nsHyperTextAccessible::InsertText(const nsAString
&aText
, PRInt32 aPosition
)
1480 nsCOMPtr
<nsIEditor
> editor
;
1481 GetAssociatedEditor(getter_AddRefs(editor
));
1483 nsCOMPtr
<nsIPlaintextEditor
> peditor(do_QueryInterface(editor
));
1484 NS_ENSURE_STATE(peditor
);
1486 nsresult rv
= SetSelectionRange(aPosition
, aPosition
);
1487 NS_ENSURE_SUCCESS(rv
, rv
);
1489 return peditor
->InsertText(aText
);
1493 nsHyperTextAccessible::CopyText(PRInt32 aStartPos
, PRInt32 aEndPos
)
1495 nsCOMPtr
<nsIEditor
> editor
;
1496 GetAssociatedEditor(getter_AddRefs(editor
));
1497 NS_ENSURE_STATE(editor
);
1499 nsresult rv
= SetSelectionRange(aStartPos
, aEndPos
);
1500 NS_ENSURE_SUCCESS(rv
, rv
);
1502 return editor
->Copy();
1506 nsHyperTextAccessible::CutText(PRInt32 aStartPos
, PRInt32 aEndPos
)
1508 nsCOMPtr
<nsIEditor
> editor
;
1509 GetAssociatedEditor(getter_AddRefs(editor
));
1510 NS_ENSURE_STATE(editor
);
1512 nsresult rv
= SetSelectionRange(aStartPos
, aEndPos
);
1513 NS_ENSURE_SUCCESS(rv
, rv
);
1515 return editor
->Cut();
1519 nsHyperTextAccessible::DeleteText(PRInt32 aStartPos
, PRInt32 aEndPos
)
1521 nsCOMPtr
<nsIEditor
> editor
;
1522 GetAssociatedEditor(getter_AddRefs(editor
));
1523 NS_ENSURE_STATE(editor
);
1525 nsresult rv
= SetSelectionRange(aStartPos
, aEndPos
);
1526 NS_ENSURE_SUCCESS(rv
, rv
);
1528 return editor
->DeleteSelection(nsIEditor::eNone
);
1532 nsHyperTextAccessible::PasteText(PRInt32 aPosition
)
1534 nsCOMPtr
<nsIEditor
> editor
;
1535 GetAssociatedEditor(getter_AddRefs(editor
));
1536 NS_ENSURE_STATE(editor
);
1538 nsresult rv
= SetSelectionRange(aPosition
, aPosition
);
1539 NS_ENSURE_SUCCESS(rv
, rv
);
1541 return editor
->Paste(nsIClipboard::kGlobalClipboard
);
1545 nsHyperTextAccessible::GetAssociatedEditor(nsIEditor
**aEditor
)
1547 NS_ENSURE_ARG_POINTER(aEditor
);
1551 return NS_ERROR_FAILURE
;
1553 if (!mContent
->HasFlag(NODE_IS_EDITABLE
)) {
1554 // If we're inside an editable container, then return that container's editor
1555 nsCOMPtr
<nsIAccessible
> ancestor
, current
= this;
1556 while (NS_SUCCEEDED(current
->GetParent(getter_AddRefs(ancestor
))) && ancestor
) {
1557 nsRefPtr
<nsHyperTextAccessible
> ancestorTextAccessible
;
1558 ancestor
->QueryInterface(NS_GET_IID(nsHyperTextAccessible
),
1559 getter_AddRefs(ancestorTextAccessible
));
1560 if (ancestorTextAccessible
) {
1561 // Recursion will stop at container doc because it has its own impl
1562 // of GetAssociatedEditor()
1563 return ancestorTextAccessible
->GetAssociatedEditor(aEditor
);
1570 nsCOMPtr
<nsIDocShellTreeItem
> docShellTreeItem
=
1571 nsCoreUtils::GetDocShellTreeItemFor(mContent
);
1572 nsCOMPtr
<nsIEditingSession
> editingSession(do_GetInterface(docShellTreeItem
));
1573 if (!editingSession
)
1574 return NS_OK
; // No editing session interface
1576 nsCOMPtr
<nsIPresShell
> shell
= GetPresShell();
1577 NS_ENSURE_TRUE(shell
, NS_ERROR_FAILURE
);
1579 nsCOMPtr
<nsIDocument
> doc
= shell
->GetDocument();
1580 NS_ENSURE_TRUE(doc
, NS_ERROR_FAILURE
);
1582 nsCOMPtr
<nsIEditor
> editor
;
1583 return editingSession
->GetEditorForWindow(doc
->GetWindow(), aEditor
);
1587 * =================== Caret & Selection ======================
1591 nsHyperTextAccessible::SetSelectionRange(PRInt32 aStartPos
, PRInt32 aEndPos
)
1593 nsresult rv
= TakeFocus();
1594 NS_ENSURE_SUCCESS(rv
, rv
);
1596 // Set the selection
1597 SetSelectionBounds(0, aStartPos
, aEndPos
);
1598 NS_ENSURE_SUCCESS(rv
, rv
);
1600 // If range 0 was successfully set, clear any additional selection
1601 // ranges remaining from previous selection
1602 nsCOMPtr
<nsISelection
> domSel
;
1603 nsCOMPtr
<nsISelectionController
> selCon
;
1604 GetSelections(nsISelectionController::SELECTION_NORMAL
,
1605 getter_AddRefs(selCon
), getter_AddRefs(domSel
));
1608 domSel
->GetRangeCount(&numRanges
);
1610 for (PRInt32 count
= 0; count
< numRanges
- 1; count
++) {
1611 nsCOMPtr
<nsIDOMRange
> range
;
1612 domSel
->GetRangeAt(1, getter_AddRefs(range
));
1613 domSel
->RemoveRange(range
);
1618 // XXX I'm not sure this can do synchronous scrolling. If the last param is
1619 // set to true, this calling might flush the pending reflow. See bug 418470.
1620 selCon
->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL
,
1621 nsISelectionController::SELECTION_FOCUS_REGION
, 0);
1628 nsHyperTextAccessible::SetCaretOffset(PRInt32 aCaretOffset
)
1630 return SetSelectionRange(aCaretOffset
, aCaretOffset
);
1634 * Gets the offset position of the caret (cursor).
1637 nsHyperTextAccessible::GetCaretOffset(PRInt32
*aCaretOffset
)
1641 // No caret if the focused node is not inside this DOM node and this DOM node
1642 // is not inside of focused node.
1644 nsINode
* thisNode
= GetNode();
1645 PRBool isInsideOfFocusedNode
=
1646 nsCoreUtils::IsAncestorOf(gLastFocusedNode
, thisNode
);
1648 if (!isInsideOfFocusedNode
&& thisNode
!= gLastFocusedNode
&&
1649 !nsCoreUtils::IsAncestorOf(thisNode
, gLastFocusedNode
))
1652 // Turn the focus node and offset of the selection into caret hypretext
1654 nsCOMPtr
<nsISelection
> domSel
;
1655 nsresult rv
= GetSelections(nsISelectionController::SELECTION_NORMAL
,
1656 nsnull
, getter_AddRefs(domSel
));
1657 NS_ENSURE_SUCCESS(rv
, rv
);
1659 nsCOMPtr
<nsIDOMNode
> focusDOMNode
;
1660 rv
= domSel
->GetFocusNode(getter_AddRefs(focusDOMNode
));
1661 NS_ENSURE_SUCCESS(rv
, rv
);
1663 PRInt32 focusOffset
;
1664 rv
= domSel
->GetFocusOffset(&focusOffset
);
1665 NS_ENSURE_SUCCESS(rv
, rv
);
1667 // No caret if this DOM node is inside of focused node but the selection's
1668 // focus point is not inside of this DOM node.
1669 nsCOMPtr
<nsINode
> focusNode(do_QueryInterface(focusDOMNode
));
1670 if (isInsideOfFocusedNode
) {
1671 nsINode
*resultNode
=
1672 nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode
, focusOffset
);
1674 if (resultNode
!= thisNode
&&
1675 !nsCoreUtils::IsAncestorOf(thisNode
, resultNode
))
1679 DOMPointToHypertextOffset(focusNode
, focusOffset
, aCaretOffset
);
1683 PRInt32
nsHyperTextAccessible::GetCaretLineNumber()
1685 // Provide the line number for the caret, relative to the
1686 // currently focused node. Use a 1-based index
1687 nsCOMPtr
<nsISelection
> domSel
;
1688 GetSelections(nsISelectionController::SELECTION_NORMAL
, nsnull
,
1689 getter_AddRefs(domSel
));
1690 nsCOMPtr
<nsISelectionPrivate
> privateSelection(do_QueryInterface(domSel
));
1691 NS_ENSURE_TRUE(privateSelection
, -1);
1692 nsCOMPtr
<nsFrameSelection
> frameSelection
;
1693 privateSelection
->GetFrameSelection(getter_AddRefs(frameSelection
));
1694 NS_ENSURE_TRUE(frameSelection
, -1);
1696 nsCOMPtr
<nsIDOMNode
> caretNode
;
1697 domSel
->GetFocusNode(getter_AddRefs(caretNode
));
1698 nsCOMPtr
<nsIContent
> caretContent
= do_QueryInterface(caretNode
);
1699 if (!caretContent
|| !nsCoreUtils::IsAncestorOf(GetNode(), caretContent
))
1702 PRInt32 caretOffset
, returnOffsetUnused
;
1703 domSel
->GetFocusOffset(&caretOffset
);
1704 nsFrameSelection::HINT hint
= frameSelection
->GetHint();
1705 nsIFrame
*caretFrame
= frameSelection
->GetFrameForNodeOffset(caretContent
, caretOffset
,
1706 hint
, &returnOffsetUnused
);
1707 NS_ENSURE_TRUE(caretFrame
, -1);
1709 PRInt32 lineNumber
= 1;
1710 nsAutoLineIterator lineIterForCaret
;
1711 nsIContent
*hyperTextContent
= IsContent() ? mContent
.get() : nsnull
;
1712 while (caretFrame
) {
1713 if (hyperTextContent
== caretFrame
->GetContent()) {
1714 return lineNumber
; // Must be in a single line hyper text, there is no line iterator
1716 nsIFrame
*parentFrame
= caretFrame
->GetParent();
1720 // Add lines for the sibling frames before the caret
1721 nsIFrame
*sibling
= parentFrame
->GetFirstChild(nsnull
);
1722 while (sibling
&& sibling
!= caretFrame
) {
1723 nsAutoLineIterator lineIterForSibling
= sibling
->GetLineIterator();
1724 if (lineIterForSibling
) {
1725 // For the frames before that grab all the lines
1726 PRInt32 addLines
= lineIterForSibling
->GetNumLines();
1727 lineNumber
+= addLines
;
1729 sibling
= sibling
->GetNextSibling();
1732 // Get the line number relative to the container with lines
1733 if (!lineIterForCaret
) { // Add the caret line just once
1734 lineIterForCaret
= parentFrame
->GetLineIterator();
1735 if (lineIterForCaret
) {
1736 // Ancestor of caret
1737 PRInt32 addLines
= lineIterForCaret
->FindLineContaining(caretFrame
);
1738 lineNumber
+= addLines
;
1742 caretFrame
= parentFrame
;
1745 NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
1750 nsHyperTextAccessible::GetSelections(PRInt16 aType
,
1751 nsISelectionController
**aSelCon
,
1752 nsISelection
**aDomSel
,
1753 nsCOMArray
<nsIDOMRange
>* aRanges
)
1756 return NS_ERROR_FAILURE
;
1768 nsCOMPtr
<nsISelection
> domSel
;
1769 nsCOMPtr
<nsISelectionController
> selCon
;
1771 nsCOMPtr
<nsIEditor
> editor
;
1772 GetAssociatedEditor(getter_AddRefs(editor
));
1773 nsCOMPtr
<nsIPlaintextEditor
> peditor(do_QueryInterface(editor
));
1775 // Case 1: plain text editor
1776 // This is for form controls which have their own
1777 // selection controller separate from the document, for example
1778 // HTML:input, HTML:textarea, XUL:textbox, etc.
1779 editor
->GetSelectionController(getter_AddRefs(selCon
));
1782 // Case 2: rich content subtree (can be rich editor)
1783 // This uses the selection controller from the entire document
1784 nsIFrame
*frame
= GetFrame();
1785 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
1787 // Get the selection and selection controller
1788 frame
->GetSelectionController(GetPresContext(),
1789 getter_AddRefs(selCon
));
1791 NS_ENSURE_TRUE(selCon
, NS_ERROR_FAILURE
);
1793 selCon
->GetSelection(aType
, getter_AddRefs(domSel
));
1794 NS_ENSURE_TRUE(domSel
, NS_ERROR_FAILURE
);
1797 NS_ADDREF(*aSelCon
= selCon
);
1800 NS_ADDREF(*aDomSel
= domSel
);
1804 nsCOMPtr
<nsISelection2
> selection2(do_QueryInterface(domSel
));
1805 NS_ENSURE_TRUE(selection2
, NS_ERROR_FAILURE
);
1807 nsCOMPtr
<nsINode
> startNode
= GetNode();
1809 nsCOMPtr
<nsIDOMElement
> editorRoot
;
1810 editor
->GetRootElement(getter_AddRefs(editorRoot
));
1811 startNode
= do_QueryInterface(editorRoot
);
1813 NS_ENSURE_STATE(startNode
);
1815 PRUint32 childCount
= startNode
->GetChildCount();
1816 nsCOMPtr
<nsIDOMNode
> startDOMNode(do_QueryInterface(startNode
));
1817 nsresult rv
= selection2
->
1818 GetRangesForIntervalCOMArray(startDOMNode
, 0, startDOMNode
, childCount
,
1820 NS_ENSURE_SUCCESS(rv
, rv
);
1821 // Remove collapsed ranges
1822 PRInt32 numRanges
= aRanges
->Count();
1823 for (PRInt32 count
= 0; count
< numRanges
; count
++) {
1825 (*aRanges
)[count
]->GetCollapsed(&isCollapsed
);
1827 aRanges
->RemoveObjectAt(count
);
1838 * Gets the number of selected regions.
1840 NS_IMETHODIMP
nsHyperTextAccessible::GetSelectionCount(PRInt32
*aSelectionCount
)
1842 nsCOMPtr
<nsISelection
> domSel
;
1843 nsCOMArray
<nsIDOMRange
> ranges
;
1844 nsresult rv
= GetSelections(nsISelectionController::SELECTION_NORMAL
,
1845 nsnull
, nsnull
, &ranges
);
1846 NS_ENSURE_SUCCESS(rv
, rv
);
1848 *aSelectionCount
= ranges
.Count();
1854 * Gets the start and end offset of the specified selection.
1856 NS_IMETHODIMP
nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum
, PRInt32
*aStartOffset
, PRInt32
*aEndOffset
)
1858 *aStartOffset
= *aEndOffset
= 0;
1860 nsCOMPtr
<nsISelection
> domSel
;
1861 nsCOMArray
<nsIDOMRange
> ranges
;
1862 nsresult rv
= GetSelections(nsISelectionController::SELECTION_NORMAL
,
1863 nsnull
, getter_AddRefs(domSel
), &ranges
);
1864 NS_ENSURE_SUCCESS(rv
, rv
);
1866 PRInt32 rangeCount
= ranges
.Count();
1867 if (aSelectionNum
< 0 || aSelectionNum
>= rangeCount
)
1868 return NS_ERROR_INVALID_ARG
;
1870 nsCOMPtr
<nsIDOMRange
> range
= ranges
[aSelectionNum
];
1873 nsCOMPtr
<nsIDOMNode
> startDOMNode
;
1874 range
->GetStartContainer(getter_AddRefs(startDOMNode
));
1875 nsCOMPtr
<nsINode
> startNode(do_QueryInterface(startDOMNode
));
1876 PRInt32 startOffset
;
1877 range
->GetStartOffset(&startOffset
);
1880 nsCOMPtr
<nsIDOMNode
> endDOMNode
;
1881 range
->GetEndContainer(getter_AddRefs(endDOMNode
));
1882 nsCOMPtr
<nsINode
> endNode(do_QueryInterface(endDOMNode
));
1884 range
->GetEndOffset(&endOffset
);
1886 PRInt16 rangeCompareResult
;
1887 rv
= range
->CompareBoundaryPoints(nsIDOMRange::START_TO_END
, range
, &rangeCompareResult
);
1888 NS_ENSURE_SUCCESS(rv
, rv
);
1890 if (rangeCompareResult
< 0) {
1891 // Make sure start is before end, by swapping offsets
1892 // This occurs when the user selects backwards in the text
1893 startNode
.swap(endNode
);
1894 PRInt32 tempOffset
= startOffset
;
1895 startOffset
= endOffset
;
1896 endOffset
= tempOffset
;
1899 nsAccessible
*startAccessible
=
1900 DOMPointToHypertextOffset(startNode
, startOffset
, aStartOffset
);
1901 if (!startAccessible
) {
1902 *aStartOffset
= 0; // Could not find start point within this hypertext, so starts before
1905 DOMPointToHypertextOffset(endNode
, endOffset
, aEndOffset
, PR_TRUE
);
1910 * Changes the start and end offset of the specified selection.
1913 nsHyperTextAccessible::SetSelectionBounds(PRInt32 aSelectionNum
,
1914 PRInt32 aStartOffset
,
1917 nsCOMPtr
<nsISelection
> domSel
;
1918 nsresult rv
= GetSelections(nsISelectionController::SELECTION_NORMAL
,
1919 nsnull
, getter_AddRefs(domSel
));
1920 NS_ENSURE_SUCCESS(rv
, rv
);
1922 // Caret is a collapsed selection
1923 PRBool isOnlyCaret
= (aStartOffset
== aEndOffset
);
1926 domSel
->GetRangeCount(&rangeCount
);
1927 nsCOMPtr
<nsIDOMRange
> range
;
1928 if (aSelectionNum
== rangeCount
) { // Add a range
1929 range
= do_CreateInstance(kRangeCID
);
1930 NS_ENSURE_TRUE(range
, NS_ERROR_OUT_OF_MEMORY
);
1932 else if (aSelectionNum
< 0 || aSelectionNum
> rangeCount
) {
1933 return NS_ERROR_INVALID_ARG
;
1936 domSel
->GetRangeAt(aSelectionNum
, getter_AddRefs(range
));
1937 NS_ENSURE_TRUE(range
, NS_ERROR_FAILURE
);
1940 PRInt32 startOffset
, endOffset
;
1941 nsCOMPtr
<nsIDOMNode
> startNode
, endNode
;
1943 rv
= HypertextOffsetsToDOMRange(aStartOffset
, aEndOffset
,
1944 getter_AddRefs(startNode
), &startOffset
,
1945 getter_AddRefs(endNode
), &endOffset
);
1946 NS_ENSURE_SUCCESS(rv
, rv
);
1948 rv
= range
->SetStart(startNode
, startOffset
);
1949 NS_ENSURE_SUCCESS(rv
, rv
);
1951 rv
= isOnlyCaret
? range
->Collapse(PR_TRUE
) :
1952 range
->SetEnd(endNode
, endOffset
);
1953 NS_ENSURE_SUCCESS(rv
, rv
);
1955 if (aSelectionNum
== rangeCount
) { // Add successfully created new range
1956 return domSel
->AddRange(range
);
1962 * Adds a selection bounded by the specified offsets.
1964 NS_IMETHODIMP
nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset
, PRInt32 aEndOffset
)
1966 nsCOMPtr
<nsISelection
> domSel
;
1967 nsresult rv
= GetSelections(nsISelectionController::SELECTION_NORMAL
,
1968 nsnull
, getter_AddRefs(domSel
));
1969 NS_ENSURE_SUCCESS(rv
, rv
);
1972 domSel
->GetRangeCount(&rangeCount
);
1974 return SetSelectionBounds(rangeCount
, aStartOffset
, aEndOffset
);
1978 * Removes the specified selection.
1980 NS_IMETHODIMP
nsHyperTextAccessible::RemoveSelection(PRInt32 aSelectionNum
)
1982 nsCOMPtr
<nsISelection
> domSel
;
1983 nsresult rv
= GetSelections(nsISelectionController::SELECTION_NORMAL
,
1984 nsnull
, getter_AddRefs(domSel
));
1985 NS_ENSURE_SUCCESS(rv
, rv
);
1988 domSel
->GetRangeCount(&rangeCount
);
1989 if (aSelectionNum
< 0 || aSelectionNum
>= rangeCount
)
1990 return NS_ERROR_INVALID_ARG
;
1992 nsCOMPtr
<nsIDOMRange
> range
;
1993 domSel
->GetRangeAt(aSelectionNum
, getter_AddRefs(range
));
1994 return domSel
->RemoveRange(range
);
1997 // void nsIAccessibleText::
1998 // scrollSubstringTo(in long startIndex, in long endIndex,
1999 // in unsigned long scrollType);
2001 nsHyperTextAccessible::ScrollSubstringTo(PRInt32 aStartIndex
, PRInt32 aEndIndex
,
2002 PRUint32 aScrollType
)
2004 PRInt32 startOffset
, endOffset
;
2005 nsCOMPtr
<nsIDOMNode
> startNode
, endNode
;
2007 nsresult rv
= HypertextOffsetsToDOMRange(aStartIndex
, aEndIndex
,
2008 getter_AddRefs(startNode
),
2010 getter_AddRefs(endNode
),
2012 NS_ENSURE_SUCCESS(rv
, rv
);
2014 return nsCoreUtils::ScrollSubstringTo(GetFrame(), startNode
, startOffset
,
2015 endNode
, endOffset
, aScrollType
);
2018 // void nsIAccessibleText::
2019 // scrollSubstringToPoint(in long startIndex, in long endIndex,
2020 // in unsigned long coordinateType,
2021 // in long x, in long y);
2023 nsHyperTextAccessible::ScrollSubstringToPoint(PRInt32 aStartIndex
,
2025 PRUint32 aCoordinateType
,
2026 PRInt32 aX
, PRInt32 aY
)
2028 nsIFrame
*frame
= GetFrame();
2030 return NS_ERROR_FAILURE
;
2033 nsresult rv
= nsAccUtils::ConvertToScreenCoords(aX
, aY
, aCoordinateType
,
2035 NS_ENSURE_SUCCESS(rv
, rv
);
2037 PRInt32 startOffset
, endOffset
;
2038 nsCOMPtr
<nsIDOMNode
> startNode
, endNode
;
2040 rv
= HypertextOffsetsToDOMRange(aStartIndex
, aEndIndex
,
2041 getter_AddRefs(startNode
), &startOffset
,
2042 getter_AddRefs(endNode
), &endOffset
);
2043 NS_ENSURE_SUCCESS(rv
, rv
);
2045 nsPresContext
*presContext
= frame
->PresContext();
2047 PRBool initialScrolled
= PR_FALSE
;
2048 nsIFrame
*parentFrame
= frame
;
2049 while ((parentFrame
= parentFrame
->GetParent())) {
2050 nsIScrollableFrame
*scrollableFrame
= do_QueryFrame(parentFrame
);
2051 if (scrollableFrame
) {
2052 if (!initialScrolled
) {
2053 // Scroll substring to the given point. Turn the point into percents
2054 // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
2055 nsIntRect frameRect
= parentFrame
->GetScreenRectExternal();
2056 PRInt32 devOffsetX
= coords
.x
- frameRect
.x
;
2057 PRInt32 devOffsetY
= coords
.y
- frameRect
.y
;
2059 nsPoint
offsetPoint(presContext
->DevPixelsToAppUnits(devOffsetX
),
2060 presContext
->DevPixelsToAppUnits(devOffsetY
));
2062 nsSize
size(parentFrame
->GetSize());
2064 // avoid divide by zero
2065 size
.width
= size
.width
? size
.width
: 1;
2066 size
.height
= size
.height
? size
.height
: 1;
2068 PRInt16 hPercent
= offsetPoint
.x
* 100 / size
.width
;
2069 PRInt16 vPercent
= offsetPoint
.y
* 100 / size
.height
;
2071 rv
= nsCoreUtils::ScrollSubstringTo(GetFrame(), startNode
, startOffset
,
2073 vPercent
, hPercent
);
2074 NS_ENSURE_SUCCESS(rv
, rv
);
2076 initialScrolled
= PR_TRUE
;
2078 // Substring was scrolled to the given point already inside its closest
2079 // scrollable area. If there are nested scrollable areas then make
2080 // sure we scroll lower areas to the given point inside currently
2081 // traversed scrollable area.
2082 nsCoreUtils::ScrollFrameToPoint(parentFrame
, frame
, coords
);
2085 frame
= parentFrame
;
2091 ////////////////////////////////////////////////////////////////////////////////
2092 // nsAccessible public
2095 nsHyperTextAccessible::InvalidateChildren()
2099 nsAccessibleWrap::InvalidateChildren();
2103 nsHyperTextAccessible::RemoveChild(nsAccessible
* aAccessible
)
2105 PRInt32 childIndex
= aAccessible
->GetIndexInParent();
2106 PRInt32 count
= mOffsets
.Length() - childIndex
;
2108 mOffsets
.RemoveElementsAt(childIndex
, count
);
2110 return nsAccessible::RemoveChild(aAccessible
);
2113 ////////////////////////////////////////////////////////////////////////////////
2114 // nsHyperTextAccessible public static
2116 nsresult
nsHyperTextAccessible::ContentToRenderedOffset(nsIFrame
*aFrame
, PRInt32 aContentOffset
,
2117 PRUint32
*aRenderedOffset
)
2120 // Current frame not rendered -- this can happen if text is set on
2121 // something with display: none
2122 *aRenderedOffset
= 0;
2125 NS_ASSERTION(aFrame
->GetType() == nsAccessibilityAtoms::textFrame
,
2126 "Need text frame for offset conversion");
2127 NS_ASSERTION(aFrame
->GetPrevContinuation() == nsnull
,
2128 "Call on primary frame only");
2130 gfxSkipChars skipChars
;
2131 gfxSkipCharsIterator iter
;
2132 // Only get info up to original offset, we know that will be larger than skipped offset
2133 nsresult rv
= aFrame
->GetRenderedText(nsnull
, &skipChars
, &iter
, 0, aContentOffset
);
2134 NS_ENSURE_SUCCESS(rv
, rv
);
2136 PRUint32 ourRenderedStart
= iter
.GetSkippedOffset();
2137 PRInt32 ourContentStart
= iter
.GetOriginalOffset();
2139 *aRenderedOffset
= iter
.ConvertOriginalToSkipped(aContentOffset
+ ourContentStart
) -
2145 nsresult
nsHyperTextAccessible::RenderedToContentOffset(nsIFrame
*aFrame
, PRUint32 aRenderedOffset
,
2146 PRInt32
*aContentOffset
)
2148 *aContentOffset
= 0;
2149 NS_ENSURE_TRUE(aFrame
, NS_ERROR_FAILURE
);
2151 NS_ASSERTION(aFrame
->GetType() == nsAccessibilityAtoms::textFrame
,
2152 "Need text frame for offset conversion");
2153 NS_ASSERTION(aFrame
->GetPrevContinuation() == nsnull
,
2154 "Call on primary frame only");
2156 gfxSkipChars skipChars
;
2157 gfxSkipCharsIterator iter
;
2158 // We only need info up to skipped offset -- that is what we're converting to original offset
2159 nsresult rv
= aFrame
->GetRenderedText(nsnull
, &skipChars
, &iter
, 0, aRenderedOffset
);
2160 NS_ENSURE_SUCCESS(rv
, rv
);
2162 PRUint32 ourRenderedStart
= iter
.GetSkippedOffset();
2163 PRInt32 ourContentStart
= iter
.GetOriginalOffset();
2165 *aContentOffset
= iter
.ConvertSkippedToOriginal(aRenderedOffset
+ ourRenderedStart
) - ourContentStart
;
2170 ////////////////////////////////////////////////////////////////////////////////
2171 // nsHyperTextAccessible public
2174 nsHyperTextAccessible::GetCharAt(PRInt32 aOffset
, EGetTextType aShift
,
2175 nsAString
& aChar
, PRInt32
* aStartOffset
,
2176 PRInt32
* aEndOffset
)
2180 PRInt32 offset
= ConvertMagicOffset(aOffset
) + static_cast<PRInt32
>(aShift
);
2181 PRInt32 childIdx
= GetChildIndexAtOffset(offset
);
2185 nsAccessible
* child
= GetChildAt(childIdx
);
2186 child
->AppendTextTo(aChar
, offset
- GetChildOffset(childIdx
), 1);
2189 *aStartOffset
= offset
;
2191 *aEndOffset
= aChar
.IsEmpty() ? offset
: offset
+ 1;
2197 nsHyperTextAccessible::GetChildOffset(PRUint32 aChildIndex
,
2198 PRBool aInvalidateAfter
)
2200 if (aChildIndex
== 0) {
2201 if (aInvalidateAfter
)
2207 PRInt32 count
= mOffsets
.Length() - aChildIndex
;
2209 if (aInvalidateAfter
)
2210 mOffsets
.RemoveElementsAt(aChildIndex
, count
);
2212 return mOffsets
[aChildIndex
- 1];
2215 PRUint32 lastOffset
= mOffsets
.IsEmpty() ?
2216 0 : mOffsets
[mOffsets
.Length() - 1];
2219 while (mOffsets
.Length() < aChildIndex
) {
2220 nsAccessible
* child
= mChildren
[mOffsets
.Length()];
2221 lastOffset
+= nsAccUtils::TextLength(child
);
2222 mOffsets
.AppendElement(lastOffset
);
2225 return mOffsets
[aChildIndex
- 1];
2229 nsHyperTextAccessible::GetChildIndexAtOffset(PRUint32 aOffset
)
2231 PRUint32 lastOffset
= 0;
2232 PRUint32 offsetCount
= mOffsets
.Length();
2233 if (offsetCount
> 0) {
2234 lastOffset
= mOffsets
[offsetCount
- 1];
2235 if (aOffset
< lastOffset
) {
2236 PRUint32 low
= 0, high
= offsetCount
;
2237 while (high
> low
) {
2238 PRUint32 mid
= (high
+ low
) >> 1;
2239 if (mOffsets
[mid
] == aOffset
)
2240 return mid
< offsetCount
- 1 ? mid
+ 1 : mid
;
2242 if (mOffsets
[mid
] < aOffset
)
2247 if (high
== offsetCount
)
2254 PRUint32 childCount
= GetChildCount();
2255 while (mOffsets
.Length() < childCount
) {
2256 nsAccessible
* child
= GetChildAt(mOffsets
.Length());
2257 lastOffset
+= nsAccUtils::TextLength(child
);
2258 mOffsets
.AppendElement(lastOffset
);
2259 if (aOffset
< lastOffset
)
2260 return mOffsets
.Length() - 1;
2263 if (aOffset
== lastOffset
)
2264 return mOffsets
.Length() - 1;
2269 ////////////////////////////////////////////////////////////////////////////////
2270 // nsHyperTextAccessible protected
2273 nsHyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame
*aFrame
,
2275 nsIAccessible
*aAccessible
,
2277 PRInt32
*aNodeOffset
)
2279 NS_ENSURE_ARG(aAccessible
);
2281 nsCOMPtr
<nsIDOMNode
> node
;
2284 // If the given frame is null then set offset after the DOM node of the
2285 // given accessible.
2286 nsCOMPtr
<nsIAccessNode
> accessNode(do_QueryInterface(aAccessible
));
2288 nsCOMPtr
<nsIDOMNode
> DOMNode
;
2289 accessNode
->GetDOMNode(getter_AddRefs(DOMNode
));
2290 nsCOMPtr
<nsIContent
> content(do_QueryInterface(DOMNode
));
2291 NS_ENSURE_STATE(content
);
2293 nsCOMPtr
<nsIContent
> parent(content
->GetParent());
2294 NS_ENSURE_STATE(parent
);
2296 *aNodeOffset
= parent
->IndexOf(content
) + 1;
2297 node
= do_QueryInterface(parent
);
2299 } else if (aFrame
->GetType() == nsAccessibilityAtoms::textFrame
) {
2300 nsCOMPtr
<nsIContent
> content(aFrame
->GetContent());
2301 NS_ENSURE_STATE(content
);
2303 nsIFrame
*primaryFrame
= content
->GetPrimaryFrame();
2304 nsresult rv
= RenderedToContentOffset(primaryFrame
, aOffset
, aNodeOffset
);
2305 NS_ENSURE_SUCCESS(rv
, rv
);
2307 node
= do_QueryInterface(content
);
2310 nsCOMPtr
<nsIContent
> content(aFrame
->GetContent());
2311 NS_ENSURE_STATE(content
);
2313 nsCOMPtr
<nsIContent
> parent(content
->GetParent());
2314 NS_ENSURE_STATE(parent
);
2316 *aNodeOffset
= parent
->IndexOf(content
);
2317 node
= do_QueryInterface(parent
);
2320 NS_IF_ADDREF(*aNode
= node
);
2324 // nsHyperTextAccessible
2326 nsHyperTextAccessible::DOMRangeBoundToHypertextOffset(nsIDOMRange
*aRange
,
2327 PRBool aIsStartBound
,
2328 PRBool aIsStartHTOffset
,
2331 nsCOMPtr
<nsIDOMNode
> DOMNode
;
2332 PRInt32 nodeOffset
= 0;
2335 if (aIsStartBound
) {
2336 rv
= aRange
->GetStartContainer(getter_AddRefs(DOMNode
));
2337 NS_ENSURE_SUCCESS(rv
, rv
);
2339 rv
= aRange
->GetStartOffset(&nodeOffset
);
2340 NS_ENSURE_SUCCESS(rv
, rv
);
2342 rv
= aRange
->GetEndContainer(getter_AddRefs(DOMNode
));
2343 NS_ENSURE_SUCCESS(rv
, rv
);
2345 rv
= aRange
->GetEndOffset(&nodeOffset
);
2346 NS_ENSURE_SUCCESS(rv
, rv
);
2349 nsCOMPtr
<nsINode
> node(do_QueryInterface(DOMNode
));
2350 nsAccessible
*startAcc
=
2351 DOMPointToHypertextOffset(node
, nodeOffset
, aHTOffset
);
2353 if (aIsStartHTOffset
&& !startAcc
)
2359 // nsHyperTextAccessible
2361 nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode
*aNode
,
2362 PRInt32 aNodeOffset
,
2363 PRInt32
*aHTStartOffset
,
2364 PRInt32
*aHTEndOffset
,
2365 nsIPersistentProperties
*aAttributes
)
2367 nsCOMArray
<nsIDOMRange
> ranges
;
2368 nsresult rv
= GetSelections(nsISelectionController::SELECTION_SPELLCHECK
,
2369 nsnull
, nsnull
, &ranges
);
2370 NS_ENSURE_SUCCESS(rv
, rv
);
2372 PRInt32 rangeCount
= ranges
.Count();
2376 for (PRInt32 index
= 0; index
< rangeCount
; index
++) {
2377 nsCOMPtr
<nsIDOMRange
> range
= ranges
[index
];
2378 nsCOMPtr
<nsIDOMNSRange
> nsrange(do_QueryInterface(range
));
2379 NS_ENSURE_STATE(nsrange
);
2382 rv
= nsrange
->ComparePoint(aNode
, aNodeOffset
, &result
);
2383 NS_ENSURE_SUCCESS(rv
, rv
);
2384 // ComparePoint checks boundary points, but we need to check that
2385 // text at aNodeOffset is inside the range.
2386 // See also bug 460690.
2388 nsCOMPtr
<nsIDOMNode
> end
;
2389 rv
= range
->GetEndContainer(getter_AddRefs(end
));
2390 NS_ENSURE_SUCCESS(rv
, rv
);
2392 rv
= range
->GetEndOffset(&endOffset
);
2393 NS_ENSURE_SUCCESS(rv
, rv
);
2394 if (aNode
== end
&& aNodeOffset
== endOffset
) {
2399 if (result
== 1) { // range is before point
2400 PRInt32 startHTOffset
= 0;
2401 rv
= DOMRangeBoundToHypertextOffset(range
, PR_FALSE
, PR_TRUE
,
2403 NS_ENSURE_SUCCESS(rv
, rv
);
2405 if (startHTOffset
> *aHTStartOffset
)
2406 *aHTStartOffset
= startHTOffset
;
2408 } else if (result
== -1) { // range is after point
2409 PRInt32 endHTOffset
= 0;
2410 rv
= DOMRangeBoundToHypertextOffset(range
, PR_TRUE
, PR_FALSE
,
2412 NS_ENSURE_SUCCESS(rv
, rv
);
2414 if (endHTOffset
< *aHTEndOffset
)
2415 *aHTEndOffset
= endHTOffset
;
2417 } else { // point is in range
2418 PRInt32 startHTOffset
= 0;
2419 rv
= DOMRangeBoundToHypertextOffset(range
, PR_TRUE
, PR_TRUE
,
2421 NS_ENSURE_SUCCESS(rv
, rv
);
2423 PRInt32 endHTOffset
= 0;
2424 rv
= DOMRangeBoundToHypertextOffset(range
, PR_FALSE
, PR_FALSE
,
2426 NS_ENSURE_SUCCESS(rv
, rv
);
2428 if (startHTOffset
> *aHTStartOffset
)
2429 *aHTStartOffset
= startHTOffset
;
2430 if (endHTOffset
< *aHTEndOffset
)
2431 *aHTEndOffset
= endHTOffset
;
2434 nsAccUtils::SetAccAttr(aAttributes
, nsAccessibilityAtoms::invalid
,
2435 NS_LITERAL_STRING("spelling"));