Bug 460926 A11y hierachy is broken on Ubuntu 8.10 (GNOME 2.24), r=Evan.Yan sr=roc
[wine-gecko.git] / accessible / src / html / nsHyperTextAccessible.cpp
blob5f273c26e703edcf4e0cad5fe27b9b9f37bf8bd4
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
13 * License.
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.
22 * Contributor(s):
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"
41 #include "nsAccessibilityAtoms.h"
42 #include "nsAccessibilityService.h"
43 #include "nsAccessibleTreeWalker.h"
44 #include "nsTextUtils.h"
46 #include "nsIClipboard.h"
47 #include "nsContentCID.h"
48 #include "nsIDOMAbstractView.h"
49 #include "nsIDOMCharacterData.h"
50 #include "nsIDOMDocument.h"
51 #include "nsPIDOMWindow.h"
52 #include "nsIDOMDocumentView.h"
53 #include "nsIDOMRange.h"
54 #include "nsIDOMNSRange.h"
55 #include "nsIDOMWindowInternal.h"
56 #include "nsIDOMXULDocument.h"
57 #include "nsIEditingSession.h"
58 #include "nsIEditor.h"
59 #include "nsIFontMetrics.h"
60 #include "nsIFrame.h"
61 #include "nsFrameSelection.h"
62 #include "nsILineIterator.h"
63 #include "nsIInterfaceRequestorUtils.h"
64 #include "nsIPlaintextEditor.h"
65 #include "nsIScrollableFrame.h"
66 #include "nsISelection2.h"
67 #include "nsISelectionPrivate.h"
68 #include "nsIServiceManager.h"
69 #include "nsTextFragment.h"
70 #include "gfxSkipChars.h"
72 static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID);
74 // ------------
75 // nsHyperTextAccessible
76 // ------------
78 NS_IMPL_ADDREF_INHERITED(nsHyperTextAccessible, nsAccessibleWrap)
79 NS_IMPL_RELEASE_INHERITED(nsHyperTextAccessible, nsAccessibleWrap)
81 nsresult nsHyperTextAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr)
83 *aInstancePtr = nsnull;
85 nsCOMPtr<nsIDOMXULDocument> xulDoc(do_QueryInterface(mDOMNode));
86 if (mDOMNode && !xulDoc) {
87 // We need XUL doc check for now because for now nsDocAccessible must
88 // inherit from nsHyperTextAccessible in order for HTML document accessibles
89 // to get support for these interfaces.
90 // However at some point we may push <body> to implement the interfaces and
91 // return nsDocAccessible to inherit from nsAccessibleWrap.
93 if (aIID.Equals(NS_GET_IID(nsHyperTextAccessible))) {
94 *aInstancePtr = static_cast<nsHyperTextAccessible*>(this);
95 NS_ADDREF_THIS();
96 return NS_OK;
99 if (mRoleMapEntry &&
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);
111 NS_ADDREF_THIS();
112 return NS_OK;
115 if (aIID.Equals(NS_GET_IID(nsIAccessibleHyperText))) {
116 *aInstancePtr = static_cast<nsIAccessibleHyperText*>(this);
117 NS_ADDREF_THIS();
118 return NS_OK;
121 if (aIID.Equals(NS_GET_IID(nsIAccessibleEditableText))) {
122 *aInstancePtr = static_cast<nsIAccessibleEditableText*>(this);
123 NS_ADDREF_THIS();
124 return NS_OK;
128 return nsAccessible::QueryInterface(aIID, aInstancePtr);
131 nsHyperTextAccessible::nsHyperTextAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell):
132 nsAccessibleWrap(aNode, aShell)
136 NS_IMETHODIMP nsHyperTextAccessible::GetRole(PRUint32 *aRole)
138 nsCOMPtr<nsIContent> content = do_QueryInterface(mDOMNode);
139 if (!content) {
140 return NS_ERROR_FAILURE;
143 nsIAtom *tag = content->Tag();
145 if (tag == nsAccessibilityAtoms::form) {
146 *aRole = nsIAccessibleRole::ROLE_FORM;
148 else if (tag == nsAccessibilityAtoms::div ||
149 tag == nsAccessibilityAtoms::blockquote) {
150 *aRole = nsIAccessibleRole::ROLE_SECTION;
152 else if (tag == nsAccessibilityAtoms::h1 ||
153 tag == nsAccessibilityAtoms::h2 ||
154 tag == nsAccessibilityAtoms::h3 ||
155 tag == nsAccessibilityAtoms::h4 ||
156 tag == nsAccessibilityAtoms::h5 ||
157 tag == nsAccessibilityAtoms::h6) {
158 *aRole = nsIAccessibleRole::ROLE_HEADING;
160 else {
161 nsIFrame *frame = GetFrame();
162 if (frame && frame->GetType() == nsAccessibilityAtoms::blockFrame) {
163 *aRole = nsIAccessibleRole::ROLE_PARAGRAPH;
165 else {
166 *aRole = nsIAccessibleRole::ROLE_TEXT_CONTAINER; // In ATK this works
169 return NS_OK;
172 nsresult
173 nsHyperTextAccessible::GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState)
175 nsresult rv = nsAccessibleWrap::GetStateInternal(aState, aExtraState);
176 NS_ENSURE_A11Y_SUCCESS(rv, rv);
178 if (!aExtraState)
179 return NS_OK;
181 nsCOMPtr<nsIEditor> editor;
182 GetAssociatedEditor(getter_AddRefs(editor));
183 if (editor) {
184 PRUint32 flags;
185 editor->GetFlags(&flags);
186 if (0 == (flags & nsIPlaintextEditor::eEditorReadonlyMask)) {
187 *aExtraState |= nsIAccessibleStates::EXT_STATE_EDITABLE;
191 PRInt32 childCount;
192 GetChildCount(&childCount);
193 if (childCount > 0) {
194 *aExtraState |= nsIAccessibleStates::EXT_STATE_SELECTABLE_TEXT;
197 return NS_OK;
200 void nsHyperTextAccessible::CacheChildren()
202 if (!mWeakShell) {
203 // This node has been shut down
204 mAccChildCount = eChildCountUninitialized;
205 return;
208 // Special case for text entry fields, go directly to editor's root for children
209 if (mAccChildCount == eChildCountUninitialized) {
210 PRUint32 role;
211 GetRole(&role);
212 if (role != nsIAccessibleRole::ROLE_ENTRY && role != nsIAccessibleRole::ROLE_PASSWORD_TEXT) {
213 nsAccessible::CacheChildren();
214 return;
216 nsCOMPtr<nsIEditor> editor;
217 GetAssociatedEditor(getter_AddRefs(editor));
218 if (!editor) {
219 nsAccessible::CacheChildren();
220 return;
222 mAccChildCount = 0; // Avoid reentry
223 nsCOMPtr<nsIDOMElement> editorRoot;
224 editor->GetRootElement(getter_AddRefs(editorRoot));
225 nsCOMPtr<nsIDOMNode> editorRootDOMNode = do_QueryInterface(editorRoot);
226 if (!editorRootDOMNode) {
227 return;
229 nsAccessibleTreeWalker walker(mWeakShell, editorRootDOMNode, PR_TRUE);
230 nsCOMPtr<nsPIAccessible> privatePrevAccessible;
231 PRInt32 childCount = 0;
232 walker.GetFirstChild();
233 SetFirstChild(walker.mState.accessible);
235 while (walker.mState.accessible) {
236 ++ childCount;
237 privatePrevAccessible = do_QueryInterface(walker.mState.accessible);
238 privatePrevAccessible->SetParent(this);
239 walker.GetNextSibling();
240 privatePrevAccessible->SetNextSibling(walker.mState.accessible);
242 mAccChildCount = childCount;
246 // Substring must be entirely within the same text node
247 nsIntRect nsHyperTextAccessible::GetBoundsForString(nsIFrame *aFrame, PRUint32 aStartRenderedOffset,
248 PRUint32 aEndRenderedOffset)
250 nsIntRect screenRect;
251 NS_ENSURE_TRUE(aFrame, screenRect);
252 if (aFrame->GetType() != nsAccessibilityAtoms::textFrame) {
253 // XXX fallback for non-text frames, happens for bullets right now
254 // but in the future bullets will have proper text frames
255 return aFrame->GetScreenRectExternal();
258 PRInt32 startContentOffset, endContentOffset;
259 nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset);
260 NS_ENSURE_SUCCESS(rv, screenRect);
261 rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset);
262 NS_ENSURE_SUCCESS(rv, screenRect);
264 nsIFrame *frame;
265 PRInt32 startContentOffsetInFrame;
266 // Get the right frame continuation -- not really a child, but a sibling of
267 // the primary frame passed in
268 rv = aFrame->GetChildFrameContainingOffset(startContentOffset, PR_FALSE,
269 &startContentOffsetInFrame, &frame);
270 NS_ENSURE_SUCCESS(rv, screenRect);
272 nsCOMPtr<nsIPresShell> shell = GetPresShell();
273 NS_ENSURE_TRUE(shell, screenRect);
275 nsPresContext *context = shell->GetPresContext();
277 while (frame && startContentOffset < endContentOffset) {
278 // Start with this frame's screen rect, which we will
279 // shrink based on the substring we care about within it.
280 // We will then add that frame to the total screenRect we
281 // are returning.
282 nsIntRect frameScreenRect = frame->GetScreenRectExternal();
284 // Get the length of the substring in this frame that we want the bounds for
285 PRInt32 startFrameTextOffset, endFrameTextOffset;
286 frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
287 PRInt32 frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
288 PRInt32 seekLength = endContentOffset - startContentOffset;
289 PRInt32 frameSubStringLength = PR_MIN(frameTotalTextLength - startContentOffsetInFrame, seekLength);
291 // Add the point where the string starts to the frameScreenRect
292 nsPoint frameTextStartPoint;
293 rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
294 NS_ENSURE_SUCCESS(rv, nsRect());
295 frameScreenRect.x += context->AppUnitsToDevPixels(frameTextStartPoint.x);
297 // Use the point for the end offset to calculate the width
298 nsPoint frameTextEndPoint;
299 rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
300 NS_ENSURE_SUCCESS(rv, nsRect());
301 frameScreenRect.width = context->AppUnitsToDevPixels(frameTextEndPoint.x - frameTextStartPoint.x);
303 screenRect.UnionRect(frameScreenRect, screenRect);
305 // Get ready to loop back for next frame continuation
306 startContentOffset += frameSubStringLength;
307 startContentOffsetInFrame = 0;
308 frame = frame->GetNextContinuation();
311 return screenRect;
315 * Gets the specified text.
317 nsIFrame*
318 nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& aEndOffset,
319 nsAString *aText, nsIFrame **aEndFrame,
320 nsIntRect *aBoundsRect,
321 nsIAccessible **aStartAcc,
322 nsIAccessible **aEndAcc)
324 if (aStartOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
325 GetCharacterCount(&aStartOffset);
327 if (aStartOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
328 GetCaretOffset(&aStartOffset);
330 if (aEndOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
331 GetCharacterCount(&aEndOffset);
333 if (aEndOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
334 GetCaretOffset(&aEndOffset);
337 PRInt32 startOffset = aStartOffset;
338 PRInt32 endOffset = aEndOffset;
339 // XXX this prevents text interface usage on <input type="password">
340 PRBool isPassword =
341 (nsAccUtils::Role(this) == nsIAccessibleRole::ROLE_PASSWORD_TEXT);
343 // Clear out parameters and set up loop
344 if (aText) {
345 aText->Truncate();
347 if (endOffset < 0) {
348 const PRInt32 kMaxTextLength = 32767;
349 endOffset = kMaxTextLength; // Max end offset
351 else if (startOffset > endOffset) {
352 return nsnull;
355 nsIFrame *startFrame = nsnull;
356 if (aEndFrame) {
357 *aEndFrame = nsnull;
359 if (aBoundsRect) {
360 aBoundsRect->Empty();
362 if (aStartAcc)
363 *aStartAcc = nsnull;
364 if (aEndAcc)
365 *aEndAcc = nsnull;
367 nsIntRect unionRect;
368 nsCOMPtr<nsIAccessible> accessible, lastAccessible;
370 gfxSkipChars skipChars;
371 gfxSkipCharsIterator iter;
373 // Loop through children and collect valid offsets, text and bounds
374 // depending on what we need for out parameters
375 while (NextChild(accessible)) {
376 lastAccessible = accessible;
377 nsRefPtr<nsAccessNode> accessNode = nsAccUtils::QueryAccessNode(accessible);
379 nsIFrame *frame = accessNode->GetFrame();
380 if (!frame) {
381 continue;
383 nsIFrame *primaryFrame = frame;
384 if (nsAccUtils::IsText(accessible)) {
385 // We only need info up to rendered offset -- that is what we're
386 // converting to content offset
387 PRInt32 substringEndOffset = -1;
388 PRUint32 ourRenderedStart = 0;
389 PRInt32 ourContentStart = 0;
390 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
391 nsresult rv = frame->GetRenderedText(nsnull, &skipChars, &iter);
392 if (NS_SUCCEEDED(rv)) {
393 ourRenderedStart = iter.GetSkippedOffset();
394 ourContentStart = iter.GetOriginalOffset();
395 substringEndOffset =
396 iter.ConvertOriginalToSkipped(skipChars.GetOriginalCharCount() +
397 ourContentStart) - ourRenderedStart;
400 if (substringEndOffset < 0) {
401 // XXX for non-textframe text like list bullets,
402 // should go away after list bullet rewrite
403 substringEndOffset = nsAccUtils::TextLength(accessible);
405 if (startOffset < substringEndOffset) {
406 // Our start is within this substring
407 if (startOffset > 0 || endOffset < substringEndOffset) {
408 // We don't want the whole string for this accessible
409 // Get out the continuing text frame with this offset
410 PRInt32 outStartLineUnused;
411 PRInt32 contentOffset;
412 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
413 contentOffset = iter.ConvertSkippedToOriginal(startOffset) +
414 ourRenderedStart - ourContentStart;
416 else {
417 contentOffset = startOffset;
419 frame->GetChildFrameContainingOffset(contentOffset, PR_TRUE,
420 &outStartLineUnused, &frame);
421 if (aEndFrame) {
422 *aEndFrame = frame; // We ended in the current frame
423 if (aEndAcc)
424 NS_ADDREF(*aEndAcc = accessible);
426 if (substringEndOffset > endOffset) {
427 // Need to stop before the end of the available text
428 substringEndOffset = endOffset;
430 aEndOffset = endOffset;
432 if (aText) {
433 if (isPassword) {
434 for (PRInt32 count = startOffset; count < substringEndOffset; count ++)
435 *aText += '*'; // Show *'s only for password text
437 else {
438 nsCOMPtr<nsPIAccessible> pAcc(do_QueryInterface(accessible));
439 pAcc->AppendTextTo(*aText, startOffset,
440 substringEndOffset - startOffset);
443 if (aBoundsRect) { // Caller wants the bounds of the text
444 aBoundsRect->UnionRect(*aBoundsRect,
445 GetBoundsForString(primaryFrame, startOffset,
446 substringEndOffset));
448 if (!startFrame) {
449 startFrame = frame;
450 aStartOffset = startOffset;
451 if (aStartAcc)
452 NS_ADDREF(*aStartAcc = accessible);
454 // We already started copying in this accessible's string,
455 // for the next accessible we'll start at offset 0
456 startOffset = 0;
458 else {
459 // We have not found the start position yet, get the new startOffset
460 // that is relative to next accessible
461 startOffset -= substringEndOffset;
463 // The endOffset needs to be relative to the new startOffset
464 endOffset -= substringEndOffset;
466 else {
467 // Embedded object, append marker
468 // XXX Append \n for <br>'s
469 if (startOffset >= 1) {
470 -- startOffset;
472 else {
473 if (endOffset > 0) {
474 if (aText) {
475 if (frame->GetType() == nsAccessibilityAtoms::brFrame) {
476 *aText += kForcedNewLineChar;
477 } else if (nsAccUtils::MustPrune(this)) {
478 *aText += kImaginaryEmbeddedObjectChar;
479 // Expose imaginary embedded object character if the accessible
480 // hans't children.
481 } else {
482 *aText += kEmbeddedObjectChar;
485 if (aBoundsRect) {
486 aBoundsRect->UnionRect(*aBoundsRect,
487 frame->GetScreenRectExternal());
490 if (!startFrame) {
491 startFrame = frame;
492 aStartOffset = 0;
493 if (aStartAcc)
494 NS_ADDREF(*aStartAcc = accessible);
497 -- endOffset;
499 if (endOffset <= 0 && startFrame) {
500 break; // If we don't have startFrame yet, get that in next loop iteration
504 if (aStartAcc && !*aStartAcc) {
505 NS_IF_ADDREF(*aStartAcc = lastAccessible);
507 if (aEndFrame && !*aEndFrame) {
508 *aEndFrame = startFrame;
509 if (aStartAcc && aEndAcc)
510 NS_IF_ADDREF(*aEndAcc = *aStartAcc);
513 return startFrame;
516 NS_IMETHODIMP nsHyperTextAccessible::GetText(PRInt32 aStartOffset, PRInt32 aEndOffset, nsAString &aText)
518 if (!mDOMNode) {
519 return NS_ERROR_FAILURE;
521 return GetPosAndText(aStartOffset, aEndOffset, &aText) ? NS_OK : NS_ERROR_FAILURE;
525 * Gets the character count.
527 NS_IMETHODIMP nsHyperTextAccessible::GetCharacterCount(PRInt32 *aCharacterCount)
529 *aCharacterCount = 0;
530 if (!mDOMNode) {
531 return NS_ERROR_FAILURE;
534 nsCOMPtr<nsIAccessible> accessible;
536 while (NextChild(accessible)) {
537 PRInt32 textLength = nsAccUtils::TextLength(accessible);
538 NS_ENSURE_TRUE(textLength >= 0, nsnull);
539 *aCharacterCount += textLength;
541 return NS_OK;
545 * Gets the specified character.
547 NS_IMETHODIMP nsHyperTextAccessible::GetCharacterAtOffset(PRInt32 aOffset, PRUnichar *aCharacter)
549 if (!mDOMNode) {
550 return NS_ERROR_FAILURE;
552 nsAutoString text;
553 nsresult rv = GetText(aOffset, aOffset + 1, text);
554 if (NS_FAILED(rv)) {
555 return rv;
558 if (text.IsEmpty()) {
559 return NS_ERROR_FAILURE;
561 *aCharacter = text.First();
562 return NS_OK;
565 nsresult nsHyperTextAccessible::DOMPointToHypertextOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset,
566 PRInt32* aHyperTextOffset,
567 nsIAccessible **aFinalAccessible,
568 PRBool aIsEndOffset)
570 // Turn a DOM Node and offset into an offset into this hypertext.
571 // On failure, return null. On success, return the DOM node which contains the offset.
572 NS_ENSURE_ARG_POINTER(aHyperTextOffset);
573 *aHyperTextOffset = 0;
575 if (!aNode) {
576 return NS_ERROR_FAILURE;
578 if (aFinalAccessible) {
579 *aFinalAccessible = nsnull;
582 PRUint32 addTextOffset = 0;
583 nsCOMPtr<nsIDOMNode> findNode;
585 unsigned short nodeType;
586 aNode->GetNodeType(&nodeType);
587 if (aNodeOffset == -1) {
588 findNode = aNode;
590 else if (nodeType == nsIDOMNode::TEXT_NODE) {
591 // For text nodes, aNodeOffset comes in as a character offset
592 // Text offset will be added at the end, if we find the offset in this hypertext
593 // We want the "skipped" offset into the text (rendered text without the extra whitespace)
594 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
595 NS_ASSERTION(content, "No nsIContent for dom node");
596 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
597 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
598 nsIFrame *frame = presShell->GetPrimaryFrameFor(content);
599 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
600 nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &addTextOffset);
601 NS_ENSURE_SUCCESS(rv, rv);
602 // Get the child node and
603 findNode = aNode;
605 else {
606 // For non-text nodes, aNodeOffset comes in as a child node index
607 nsCOMPtr<nsIContent> parentContent(do_QueryInterface(aNode));
608 // Should not happen, but better to protect against crash if doc node is somehow passed in
609 NS_ENSURE_TRUE(parentContent, NS_ERROR_FAILURE);
610 // findNode could be null if aNodeOffset == # of child nodes, which means one of two things:
611 // 1) we're at the end of the children, keep findNode = null, so that we get the last possible offset
612 // 2) there are no children and the passed-in node is mDOMNode, which means we're an aempty nsIAccessibleText
613 // 3) there are no children, and the passed-in node is not mDOMNode -- use parentContent for the node to find
615 findNode = do_QueryInterface(parentContent->GetChildAt(aNodeOffset));
616 if (!findNode && !aNodeOffset) {
617 if (SameCOMIdentity(parentContent, mDOMNode)) {
618 // There are no children, which means this is an empty nsIAccessibleText, in which
619 // case we can only be at hypertext offset 0
620 *aHyperTextOffset = 0;
621 return NS_OK;
623 findNode = do_QueryInterface(parentContent); // Case #2: there are no children
627 // Get accessible for this findNode, or if that node isn't accessible, use the
628 // accessible for the next DOM node which has one (based on forward depth first search)
629 nsCOMPtr<nsIAccessible> descendantAccessible;
630 if (findNode) {
631 nsCOMPtr<nsIContent> findContent = do_QueryInterface(findNode);
632 if (findContent->IsNodeOfType(nsINode::eHTML) &&
633 findContent->NodeInfo()->Equals(nsAccessibilityAtoms::br)) {
634 nsIContent *parent = findContent->GetParent();
635 if (parent &&
636 parent->IsRootOfNativeAnonymousSubtree() &&
637 parent->GetChildCount() == 1) {
638 // This <br> is the only node in a text control, therefore it is the hacky
639 // "bogus node" used when there is no text in a control
640 *aHyperTextOffset = 0;
641 return NS_OK;
644 descendantAccessible = GetFirstAvailableAccessible(findNode);
646 // From the descendant, go up and get the immediate child of this hypertext
647 nsCOMPtr<nsIAccessible> childAccessible;
648 while (descendantAccessible) {
649 nsCOMPtr<nsIAccessible> parentAccessible;
650 descendantAccessible->GetParent(getter_AddRefs(parentAccessible));
651 if (this == parentAccessible) {
652 childAccessible = descendantAccessible;
653 break;
655 // This offset no longer applies because the passed-in text object is not a child
656 // of the hypertext. This happens when there are nested hypertexts, e.g.
657 // <div>abc<h1>def</h1>ghi</div>
658 // If the passed-in DOM point was not on a direct child of the hypertext, we will
659 // return the offset for that entire hypertext
660 if (aIsEndOffset) {
661 // Not inclusive, the indicated char comes at index before this offset
662 // If the end offset is after the first character of the passed in object, use 1 for
663 // addTextOffset, to put us after the embedded object char. We'll only treat the offset as
664 // before the embedded object char if we end at the very beginning of the child.
665 addTextOffset = addTextOffset > 0;
667 else {
668 // Start offset, inclusive
669 // Make sure the offset lands on the embedded object character in order to indicate
670 // the true inner offset is inside the subtree for that link
671 addTextOffset =
672 (nsAccUtils::TextLength(descendantAccessible) == static_cast<PRInt32>(addTextOffset)) ? 1 : 0;
674 descendantAccessible = parentAccessible;
677 // Loop through, adding offsets until we reach childAccessible
678 // If childAccessible is null we will end up adding up the entire length of
679 // the hypertext, which is good -- it just means our offset node
680 // came after the last accessible child's node
681 nsCOMPtr<nsIAccessible> accessible;
682 while (NextChild(accessible) && accessible != childAccessible) {
683 PRInt32 textLength = nsAccUtils::TextLength(accessible);
684 NS_ENSURE_TRUE(textLength >= 0, nsnull);
685 *aHyperTextOffset += textLength;
687 if (accessible) {
688 *aHyperTextOffset += addTextOffset;
689 NS_ASSERTION(accessible == childAccessible, "These should be equal whenever we exit loop and accessible != nsnull");
690 if (aFinalAccessible &&
691 (NextChild(accessible) ||
692 static_cast<PRInt32>(addTextOffset) < nsAccUtils::TextLength(childAccessible))) {
693 // If not at end of last text node, we will return the accessible we were in
694 NS_ADDREF(*aFinalAccessible = childAccessible);
698 return NS_OK;
701 nsresult
702 nsHyperTextAccessible::HypertextOffsetToDOMPoint(PRInt32 aHTOffset,
703 nsIDOMNode **aNode,
704 PRInt32 *aOffset)
706 nsCOMPtr<nsIDOMNode> endNode;
707 PRInt32 endOffset;
709 return HypertextOffsetsToDOMRange(aHTOffset, aHTOffset, aNode, aOffset,
710 getter_AddRefs(endNode), &endOffset);
713 nsresult
714 nsHyperTextAccessible::HypertextOffsetsToDOMRange(PRInt32 aStartHTOffset,
715 PRInt32 aEndHTOffset,
716 nsIDOMNode **aStartNode,
717 PRInt32 *aStartOffset,
718 nsIDOMNode **aEndNode,
719 PRInt32 *aEndOffset)
721 NS_ENSURE_ARG_POINTER(aStartNode);
722 *aStartNode = nsnull;
724 NS_ENSURE_ARG_POINTER(aStartOffset);
725 *aStartOffset = -1;
727 NS_ENSURE_ARG_POINTER(aEndNode);
728 *aEndNode = nsnull;
730 NS_ENSURE_ARG_POINTER(aEndOffset);
731 *aEndOffset = -1;
733 // If the given offsets are 0 and associated editor is empty then return
734 // collapsed range with editor root element as range container.
735 if (aStartHTOffset == 0 && aEndHTOffset == 0) {
736 nsCOMPtr<nsIEditor> editor;
737 GetAssociatedEditor(getter_AddRefs(editor));
738 if (editor) {
739 PRBool isEmpty = PR_FALSE;
740 editor->GetDocumentIsEmpty(&isEmpty);
741 if (isEmpty) {
742 nsCOMPtr<nsIDOMElement> editorRootElm;
743 editor->GetRootElement(getter_AddRefs(editorRootElm));
745 nsCOMPtr<nsIDOMNode> editorRoot(do_QueryInterface(editorRootElm));
746 if (editorRoot) {
747 *aStartOffset = *aEndOffset = 0;
748 NS_ADDREF(*aStartNode = editorRoot);
749 NS_ADDREF(*aEndNode = editorRoot);
751 return NS_OK;
757 nsCOMPtr<nsIAccessible> startAcc, endAcc;
758 PRInt32 startOffset = aStartHTOffset, endOffset = aEndHTOffset;
759 nsIFrame *startFrame = nsnull, *endFrame = nsnull;
761 startFrame = GetPosAndText(startOffset, endOffset, nsnull, &endFrame, nsnull,
762 getter_AddRefs(startAcc), getter_AddRefs(endAcc));
763 if (!startAcc || !endAcc)
764 return NS_ERROR_FAILURE;
766 nsCOMPtr<nsIDOMNode> startNode, endNode;
767 nsresult rv = GetDOMPointByFrameOffset(startFrame, startOffset, startAcc,
768 getter_AddRefs(startNode),
769 &startOffset);
770 NS_ENSURE_SUCCESS(rv, rv);
772 if (aStartHTOffset != aEndHTOffset) {
773 rv = GetDOMPointByFrameOffset(endFrame, endOffset, endAcc,
774 getter_AddRefs(endNode), &endOffset);
775 NS_ENSURE_SUCCESS(rv, rv);
776 } else {
777 endNode = startNode;
778 endOffset = startOffset;
781 NS_ADDREF(*aStartNode = startNode);
782 *aStartOffset = startOffset;
784 NS_ADDREF(*aEndNode = endNode);
785 *aEndOffset = endOffset;
787 return NS_OK;
790 PRInt32
791 nsHyperTextAccessible::GetRelativeOffset(nsIPresShell *aPresShell,
792 nsIFrame *aFromFrame,
793 PRInt32 aFromOffset,
794 nsIAccessible *aFromAccessible,
795 nsSelectionAmount aAmount,
796 nsDirection aDirection,
797 PRBool aNeedsStart)
799 const PRBool kIsJumpLinesOk = PR_TRUE; // okay to jump lines
800 const PRBool kIsScrollViewAStop = PR_FALSE; // do not stop at scroll views
801 const PRBool kIsKeyboardSelect = PR_TRUE; // is keyboard selection
802 const PRBool kIsVisualBidi = PR_FALSE; // use visual order for bidi text
804 EWordMovementType wordMovementType = aNeedsStart ? eStartWord : eEndWord;
805 if (aAmount == eSelectLine) {
806 aAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
809 // Ask layout for the new node and offset, after moving the appropriate amount
810 nsPeekOffsetStruct pos;
812 nsresult rv;
813 PRInt32 contentOffset = aFromOffset;
814 if (nsAccUtils::IsText(aFromAccessible)) {
815 nsRefPtr<nsAccessNode> accessNode =
816 nsAccUtils::QueryAccessNode(aFromAccessible);
818 nsIFrame *frame = accessNode->GetFrame();
819 NS_ENSURE_TRUE(frame, -1);
821 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
822 rv = RenderedToContentOffset(frame, aFromOffset, &contentOffset);
823 NS_ENSURE_SUCCESS(rv, -1);
827 pos.SetData(aAmount, aDirection, contentOffset,
828 0, kIsJumpLinesOk, kIsScrollViewAStop, kIsKeyboardSelect, kIsVisualBidi,
829 wordMovementType);
830 rv = aFromFrame->PeekOffset(&pos);
831 if (NS_FAILED(rv)) {
832 if (aDirection == eDirPrevious) {
833 // Use passed-in frame as starting point in failure case for now,
834 // this is a hack to deal with starting on a list bullet frame,
835 // which fails in PeekOffset() because the line iterator doesn't see it.
836 // XXX Need to look at our overall handling of list bullets, which are an odd case
837 pos.mResultContent = aFromFrame->GetContent();
838 PRInt32 endOffsetUnused;
839 aFromFrame->GetOffsets(pos.mContentOffset, endOffsetUnused);
841 else {
842 return -1;
846 // Turn the resulting node and offset into a hyperTextOffset
847 PRInt32 hyperTextOffset;
848 nsCOMPtr<nsIDOMNode> resultNode = do_QueryInterface(pos.mResultContent);
849 NS_ENSURE_TRUE(resultNode, -1);
851 nsCOMPtr<nsIAccessible> finalAccessible;
852 rv = DOMPointToHypertextOffset(resultNode, pos.mContentOffset, &hyperTextOffset,
853 getter_AddRefs(finalAccessible),
854 aDirection == eDirNext);
855 // If finalAccessible == nsnull, then DOMPointToHypertextOffset() searched through the hypertext
856 // children without finding the node/offset position
857 NS_ENSURE_SUCCESS(rv, -1);
859 if (!finalAccessible && aDirection == eDirPrevious) {
860 // If we reached the end during search, this means we didn't find the DOM point
861 // and we're actually at the start of the paragraph
862 hyperTextOffset = 0;
864 else if (aAmount == eSelectBeginLine) {
865 // For line selection with needsStart, set start of line exactly to line break
866 if (pos.mContentOffset == 0 && mFirstChild &&
867 nsAccUtils::Role(mFirstChild) == nsIAccessibleRole::ROLE_STATICTEXT &&
868 nsAccUtils::TextLength(mFirstChild) == hyperTextOffset) {
869 // XXX Bullet hack -- we should remove this once list bullets use anonymous content
870 hyperTextOffset = 0;
872 if (!aNeedsStart && hyperTextOffset > 0) {
873 -- hyperTextOffset;
876 else if (aAmount == eSelectEndLine && finalAccessible) {
877 // If not at very end of hypertext, we may need change the end of line offset by 1,
878 // to make sure we are in the right place relative to the line ending
879 if (nsAccUtils::Role(finalAccessible) == nsIAccessibleRole::ROLE_WHITESPACE) { // Landed on <br> hard line break
880 // if aNeedsStart, set end of line exactly 1 character past line break
881 // XXX It would be cleaner if we did not have to have the hard line break check,
882 // and just got the correct results from PeekOffset() for the <br> case -- the returned offset should
883 // come after the new line, as it does in other cases.
884 ++ hyperTextOffset; // Get past hard line break
886 // We are now 1 character past the line break
887 if (!aNeedsStart) {
888 -- hyperTextOffset;
892 return hyperTextOffset;
896 Gets the specified text relative to aBoundaryType, which means:
897 BOUNDARY_CHAR The character before/at/after the offset is returned.
898 BOUNDARY_WORD_START From the word start before/at/after the offset to the next word start.
899 BOUNDARY_WORD_END From the word end before/at/after the offset to the next work end.
900 BOUNDARY_LINE_START From the line start before/at/after the offset to the next line start.
901 BOUNDARY_LINE_END From the line end before/at/after the offset to the next line start.
904 nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTextBoundary aBoundaryType,
905 PRInt32 aOffset, PRInt32 *aStartOffset, PRInt32 *aEndOffset,
906 nsAString &aText)
908 aText.Truncate();
910 NS_ENSURE_ARG_POINTER(aStartOffset);
911 NS_ENSURE_ARG_POINTER(aEndOffset);
912 *aStartOffset = *aEndOffset = 0;
914 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
915 if (!presShell) {
916 return NS_ERROR_FAILURE;
919 if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
920 GetCharacterCount(&aOffset);
922 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
923 GetCaretOffset(&aOffset);
924 if (aOffset > 0 && (aBoundaryType == BOUNDARY_LINE_START ||
925 aBoundaryType == BOUNDARY_LINE_END)) {
926 // It is the same character offset when the caret is visually at the very end of a line
927 // or the start of a new line. Getting text at the line should provide the line with the visual caret,
928 // otherwise screen readers will announce the wrong line as the user presses up or down arrow and land
929 // at the end of a line.
930 nsCOMPtr<nsISelection> domSel;
931 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
932 nsnull, getter_AddRefs(domSel));
933 NS_ENSURE_SUCCESS(rv, rv);
935 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSel));
936 nsCOMPtr<nsFrameSelection> frameSelection;
937 rv = privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
938 NS_ENSURE_SUCCESS(rv, rv);
940 if (frameSelection->GetHint() == nsFrameSelection::HINTLEFT) {
941 -- aOffset; // We are at the start of a line
945 else if (aOffset < 0) {
946 return NS_ERROR_FAILURE;
949 nsSelectionAmount amount;
950 PRBool needsStart = PR_FALSE;
951 switch (aBoundaryType) {
952 case BOUNDARY_CHAR:
953 amount = eSelectCharacter;
954 if (aType == eGetAt)
955 aType = eGetAfter; // Avoid returning 2 characters
956 break;
958 case BOUNDARY_WORD_START:
959 needsStart = PR_TRUE;
960 amount = eSelectWord;
961 break;
963 case BOUNDARY_WORD_END:
964 amount = eSelectWord;
965 break;
967 case BOUNDARY_LINE_START:
968 // Newlines are considered at the end of a line. Since getting
969 // the BOUNDARY_LINE_START gets the text from the line-start to the next
970 // line-start, the newline is included at the end of the string.
971 needsStart = PR_TRUE;
972 amount = eSelectLine;
973 break;
975 case BOUNDARY_LINE_END:
976 // Newlines are considered at the end of a line. Since getting
977 // the BOUNDARY_END_START gets the text from the line-end to the next
978 //line-end, the newline is included at the beginning of the string.
979 amount = eSelectLine;
980 break;
982 case BOUNDARY_ATTRIBUTE_RANGE:
984 nsresult rv = GetTextAttributes(PR_FALSE, aOffset,
985 aStartOffset, aEndOffset, nsnull);
986 NS_ENSURE_SUCCESS(rv, rv);
988 return GetText(*aStartOffset, *aEndOffset, aText);
991 default: // Note, sentence support is deprecated and falls through to here
992 return NS_ERROR_INVALID_ARG;
995 PRInt32 startOffset = aOffset + (aBoundaryType == BOUNDARY_LINE_END); // Avoid getting the previous line
996 PRInt32 endOffset = startOffset;
998 // Convert offsets to frame-relative
999 nsCOMPtr<nsIAccessible> startAcc;
1000 nsIFrame *startFrame = GetPosAndText(startOffset, endOffset, nsnull, nsnull,
1001 nsnull, getter_AddRefs(startAcc));
1003 if (!startFrame) {
1004 PRInt32 textLength;
1005 GetCharacterCount(&textLength);
1006 if (aBoundaryType == BOUNDARY_LINE_START && aOffset > 0 && aOffset == textLength) {
1007 // Asking for start of line, while on last character
1008 if (startAcc) {
1009 nsRefPtr<nsAccessNode> startAccessNode =
1010 nsAccUtils::QueryAccessNode(startAcc);
1011 startFrame = startAccessNode->GetFrame();
1014 if (!startFrame) {
1015 return aOffset > textLength ? NS_ERROR_FAILURE : NS_OK;
1017 else {
1018 // We're on the last continuation since we're on the last character
1019 startFrame = startFrame->GetLastContinuation();
1023 PRInt32 finalStartOffset, finalEndOffset;
1025 // If aType == eGetAt we'll change both the start and end offset from
1026 // the original offset
1027 if (aType == eGetAfter) {
1028 finalStartOffset = aOffset;
1030 else {
1031 finalStartOffset = GetRelativeOffset(presShell, startFrame, startOffset,
1032 startAcc, amount, eDirPrevious,
1033 needsStart);
1034 NS_ENSURE_TRUE(finalStartOffset >= 0, NS_ERROR_FAILURE);
1037 if (aType == eGetBefore) {
1038 endOffset = aOffset;
1040 else {
1041 // Start moving forward from the start so that we don't get
1042 // 2 words/lines if the offset occured on whitespace boundary
1043 // Careful, startOffset and endOffset are passed by reference to GetPosAndText() and changed
1044 // For BOUNDARY_LINE_END, make sure we start of this line
1045 startOffset = endOffset = finalStartOffset + (aBoundaryType == BOUNDARY_LINE_END);
1046 nsCOMPtr<nsIAccessible> endAcc;
1047 nsIFrame *endFrame = GetPosAndText(startOffset, endOffset, nsnull, nsnull,
1048 nsnull, getter_AddRefs(endAcc));
1049 if (nsAccUtils::Role(endAcc) == nsIAccessibleRole::ROLE_STATICTEXT) {
1050 // Static text like list bullets will ruin our forward calculation,
1051 // since the caret cannot be in the static text. Start just after the static text.
1052 startOffset = endOffset = finalStartOffset +
1053 (aBoundaryType == BOUNDARY_LINE_END) +
1054 nsAccUtils::TextLength(endAcc);
1056 endFrame = GetPosAndText(startOffset, endOffset, nsnull, nsnull,
1057 nsnull, getter_AddRefs(endAcc));
1059 if (!endFrame) {
1060 return NS_ERROR_FAILURE;
1062 finalEndOffset = GetRelativeOffset(presShell, endFrame, endOffset, endAcc,
1063 amount, eDirNext, needsStart);
1064 NS_ENSURE_TRUE(endOffset >= 0, NS_ERROR_FAILURE);
1065 if (finalEndOffset == aOffset) {
1066 if (aType == eGetAt && amount == eSelectWord) {
1067 // Fix word error for the first character in word: PeekOffset() will return the previous word when
1068 // aOffset points to the first character of the word, but accessibility APIs want the current word
1069 // that the first character is in
1070 return GetTextHelper(eGetAfter, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
1072 PRInt32 textLength;
1073 GetCharacterCount(&textLength);
1074 if (finalEndOffset < textLength) {
1075 // This happens sometimes when current character at finalStartOffset
1076 // is an embedded object character representing another hypertext, that
1077 // the AT really needs to dig into separately
1078 ++ finalEndOffset;
1083 *aStartOffset = finalStartOffset;
1084 *aEndOffset = finalEndOffset;
1086 NS_ASSERTION((finalStartOffset < aOffset && finalEndOffset >= aOffset) || aType != eGetBefore, "Incorrect results for GetTextHelper");
1087 NS_ASSERTION((finalStartOffset <= aOffset && finalEndOffset > aOffset) || aType == eGetBefore, "Incorrect results for GetTextHelper");
1089 GetPosAndText(finalStartOffset, finalEndOffset, &aText);
1090 return NS_OK;
1094 * nsIAccessibleText impl.
1096 NS_IMETHODIMP nsHyperTextAccessible::GetTextBeforeOffset(PRInt32 aOffset, nsAccessibleTextBoundary aBoundaryType,
1097 PRInt32 *aStartOffset, PRInt32 *aEndOffset, nsAString & aText)
1099 return GetTextHelper(eGetBefore, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
1102 NS_IMETHODIMP nsHyperTextAccessible::GetTextAtOffset(PRInt32 aOffset, nsAccessibleTextBoundary aBoundaryType,
1103 PRInt32 *aStartOffset, PRInt32 *aEndOffset, nsAString & aText)
1105 return GetTextHelper(eGetAt, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
1108 NS_IMETHODIMP nsHyperTextAccessible::GetTextAfterOffset(PRInt32 aOffset, nsAccessibleTextBoundary aBoundaryType,
1109 PRInt32 *aStartOffset, PRInt32 *aEndOffset, nsAString & aText)
1111 return GetTextHelper(eGetAfter, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
1114 // nsIPersistentProperties
1115 // nsIAccessibleText::getTextAttributes(in boolean includeDefAttrs,
1116 // in long offset,
1117 // out long rangeStartOffset,
1118 // out long rangeEndOffset);
1119 NS_IMETHODIMP
1120 nsHyperTextAccessible::GetTextAttributes(PRBool aIncludeDefAttrs,
1121 PRInt32 aOffset,
1122 PRInt32 *aStartOffset,
1123 PRInt32 *aEndOffset,
1124 nsIPersistentProperties **aAttributes)
1126 // 1. First we get spell check, then language, then the set of CSS-based
1127 // attributes.
1128 // 2. As we get each new attribute, we pass the current start and end offsets
1129 // as in/out parameters. In other words, as attributes are collected,
1130 // the attribute range itself can only stay the same or get smaller.
1132 // Example:
1133 // Current: range 5-10
1134 // Adding: range 7-12
1135 // Result: range 7-10
1137 NS_ENSURE_ARG_POINTER(aStartOffset);
1138 *aStartOffset = 0;
1140 NS_ENSURE_ARG_POINTER(aEndOffset);
1141 nsresult rv = GetCharacterCount(aEndOffset);
1142 NS_ENSURE_SUCCESS(rv, rv);
1144 if (aAttributes) {
1145 *aAttributes = nsnull;
1147 nsCOMPtr<nsIPersistentProperties> attributes =
1148 do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
1149 NS_ENSURE_TRUE(attributes, NS_ERROR_OUT_OF_MEMORY);
1151 NS_ADDREF(*aAttributes = attributes);
1154 if (!mDOMNode)
1155 return NS_ERROR_FAILURE;
1157 nsCOMPtr<nsIDOMNode> node;
1158 PRInt32 nodeOffset = 0;
1159 rv = HypertextOffsetToDOMPoint(aOffset, getter_AddRefs(node), &nodeOffset);
1160 NS_ENSURE_SUCCESS(rv, rv);
1162 // Set 'misspelled' text attribute.
1163 rv = GetSpellTextAttribute(node, nodeOffset, aStartOffset, aEndOffset,
1164 aAttributes ? *aAttributes : nsnull);
1165 NS_ENSURE_SUCCESS(rv, rv);
1167 nsCOMPtr<nsIContent> content(do_QueryInterface(node));
1168 if (content && content->IsNodeOfType(nsINode::eELEMENT))
1169 node = do_QueryInterface(content->GetChildAt(nodeOffset));
1171 if (!node)
1172 return NS_OK;
1174 // Set 'lang' text attribute.
1175 rv = GetLangTextAttributes(aIncludeDefAttrs, node,
1176 aStartOffset, aEndOffset,
1177 aAttributes ? *aAttributes : nsnull);
1178 NS_ENSURE_SUCCESS(rv, rv);
1180 // Set CSS based text attributes.
1181 rv = GetCSSTextAttributes(aIncludeDefAttrs, node,
1182 aStartOffset, aEndOffset,
1183 aAttributes ? *aAttributes : nsnull);
1184 return rv;
1187 // nsIPersistentProperties
1188 // nsIAccessibleText::defaultTextAttributes
1189 NS_IMETHODIMP
1190 nsHyperTextAccessible::GetDefaultTextAttributes(nsIPersistentProperties **aAttributes)
1192 NS_ENSURE_ARG_POINTER(aAttributes);
1193 *aAttributes = nsnull;
1195 nsCOMPtr<nsIPersistentProperties> attributes =
1196 do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
1197 NS_ENSURE_TRUE(attributes, NS_ERROR_OUT_OF_MEMORY);
1199 NS_ADDREF(*aAttributes = attributes);
1201 if (!mDOMNode)
1202 return NS_ERROR_FAILURE;
1204 nsCOMPtr<nsIDOMElement> element = nsCoreUtils::GetDOMElementFor(mDOMNode);
1206 nsCSSTextAttr textAttr(PR_TRUE, element, nsnull);
1207 while (textAttr.Iterate()) {
1208 nsCAutoString name;
1209 nsAutoString value, oldValue;
1210 if (textAttr.Get(name, value))
1211 attributes->SetStringProperty(name, value, oldValue);
1214 nsIFrame *sourceFrame = nsCoreUtils::GetFrameFor(element);
1215 if (sourceFrame) {
1216 nsBackgroundTextAttr backgroundTextAttr(sourceFrame, nsnull);
1218 nsAutoString value;
1219 if (backgroundTextAttr.Get(value)) {
1220 nsAccUtils::SetAccAttr(attributes,
1221 nsAccessibilityAtoms::backgroundColor, value);
1225 return NS_OK;
1228 nsresult
1229 nsHyperTextAccessible::GetAttributesInternal(nsIPersistentProperties *aAttributes)
1231 if (!mDOMNode) {
1232 return NS_ERROR_FAILURE; // Node already shut down
1235 nsresult rv = nsAccessibleWrap::GetAttributesInternal(aAttributes);
1236 NS_ENSURE_SUCCESS(rv, rv);
1238 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
1239 NS_ENSURE_TRUE(content, NS_ERROR_UNEXPECTED);
1240 nsIAtom *tag = content->Tag();
1242 PRInt32 headLevel = 0;
1243 if (tag == nsAccessibilityAtoms::h1)
1244 headLevel = 1;
1245 else if (tag == nsAccessibilityAtoms::h2)
1246 headLevel = 2;
1247 else if (tag == nsAccessibilityAtoms::h3)
1248 headLevel = 3;
1249 else if (tag == nsAccessibilityAtoms::h4)
1250 headLevel = 4;
1251 else if (tag == nsAccessibilityAtoms::h5)
1252 headLevel = 5;
1253 else if (tag == nsAccessibilityAtoms::h6)
1254 headLevel = 6;
1256 if (headLevel) {
1257 nsAutoString strHeadLevel;
1258 strHeadLevel.AppendInt(headLevel);
1259 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::level,
1260 strHeadLevel);
1263 // Indicate when the current object uses block-level formatting
1264 // via formatting: block
1265 // XXX: 'formatting' attribute is deprecated and will be removed in Mozilla2,
1266 // use 'display' attribute instead.
1267 nsIFrame *frame = GetFrame();
1268 if (frame && frame->GetType() == nsAccessibilityAtoms::blockFrame) {
1269 nsAutoString oldValueUnused;
1270 aAttributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"), NS_LITERAL_STRING("block"),
1271 oldValueUnused);
1274 if (gLastFocusedNode == mDOMNode) {
1275 PRInt32 lineNumber = GetCaretLineNumber();
1276 if (lineNumber >= 1) {
1277 nsAutoString strLineNumber;
1278 strLineNumber.AppendInt(lineNumber);
1279 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::lineNumber,
1280 strLineNumber);
1284 return NS_OK;
1288 * Given an offset, the x, y, width, and height values are filled appropriately.
1290 NS_IMETHODIMP nsHyperTextAccessible::GetCharacterExtents(PRInt32 aOffset, PRInt32 *aX, PRInt32 *aY,
1291 PRInt32 *aWidth, PRInt32 *aHeight,
1292 PRUint32 aCoordType)
1294 return GetRangeExtents(aOffset, aOffset + 1, aX, aY, aWidth, aHeight, aCoordType);
1298 * Given a start & end offset, the x, y, width, and height values are filled appropriately.
1300 NS_IMETHODIMP nsHyperTextAccessible::GetRangeExtents(PRInt32 aStartOffset, PRInt32 aEndOffset,
1301 PRInt32 *aX, PRInt32 *aY,
1302 PRInt32 *aWidth, PRInt32 *aHeight,
1303 PRUint32 aCoordType)
1305 nsIntRect boundsRect;
1306 nsIFrame *endFrameUnused;
1307 if (!GetPosAndText(aStartOffset, aEndOffset, nsnull, &endFrameUnused, &boundsRect) ||
1308 boundsRect.IsEmpty()) {
1309 return NS_ERROR_FAILURE;
1312 *aX = boundsRect.x;
1313 *aY = boundsRect.y;
1314 *aWidth = boundsRect.width;
1315 *aHeight = boundsRect.height;
1317 return nsAccUtils::ConvertScreenCoordsTo(aX, aY, aCoordType, this);
1321 * Gets the offset of the character located at coordinates x and y. x and y are interpreted as being relative to
1322 * the screen or this widget's window depending on coords.
1324 NS_IMETHODIMP
1325 nsHyperTextAccessible::GetOffsetAtPoint(PRInt32 aX, PRInt32 aY,
1326 PRUint32 aCoordType, PRInt32 *aOffset)
1328 *aOffset = -1;
1329 nsCOMPtr<nsIPresShell> shell = GetPresShell();
1330 if (!shell) {
1331 return NS_ERROR_FAILURE;
1333 nsIFrame *hyperFrame = GetFrame();
1334 if (!hyperFrame) {
1335 return NS_ERROR_FAILURE;
1337 nsIntRect frameScreenRect = hyperFrame->GetScreenRectExternal();
1339 nsIntPoint coords;
1340 nsresult rv = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType,
1341 this, &coords);
1342 NS_ENSURE_SUCCESS(rv, rv);
1344 // coords are currently screen coordinates, and we need to turn them into
1345 // frame coordinates relative to the current accessible
1346 if (!frameScreenRect.Contains(coords.x, coords.y)) {
1347 return NS_OK; // Not found, will return -1
1349 nsPoint pointInHyperText(coords.x - frameScreenRect.x,
1350 coords.y - frameScreenRect.y);
1351 nsPresContext *context = GetPresContext();
1352 NS_ENSURE_TRUE(context, NS_ERROR_FAILURE);
1353 pointInHyperText.x = context->DevPixelsToAppUnits(pointInHyperText.x);
1354 pointInHyperText.y = context->DevPixelsToAppUnits(pointInHyperText.y);
1356 // Go through the frames to check if each one has the point.
1357 // When one does, add up the character offsets until we have a match
1359 // We have an point in an accessible child of this, now we need to add up the
1360 // offsets before it to what we already have
1361 nsCOMPtr<nsIAccessible> accessible;
1362 PRInt32 offset = 0;
1364 while (NextChild(accessible)) {
1365 nsRefPtr<nsAccessNode> accessNode = nsAccUtils::QueryAccessNode(accessible);
1367 nsIFrame *primaryFrame = accessNode->GetFrame();
1368 NS_ENSURE_TRUE(primaryFrame, NS_ERROR_FAILURE);
1370 nsIFrame *frame = primaryFrame;
1371 while (frame) {
1372 nsIContent *content = frame->GetContent();
1373 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1374 nsPoint pointInFrame = pointInHyperText - frame->GetOffsetToExternal(hyperFrame);
1375 nsSize frameSize = frame->GetSize();
1376 if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) {
1377 // Finished
1378 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
1379 nsIFrame::ContentOffsets contentOffsets = frame->GetContentOffsetsFromPointExternal(pointInFrame, PR_TRUE);
1380 if (contentOffsets.IsNull() || contentOffsets.content != content) {
1381 return NS_OK; // Not found, will return -1
1383 PRUint32 addToOffset;
1384 nsresult rv = ContentToRenderedOffset(primaryFrame,
1385 contentOffsets.offset,
1386 &addToOffset);
1387 NS_ENSURE_SUCCESS(rv, rv);
1388 offset += addToOffset;
1390 *aOffset = offset;
1391 return NS_OK;
1393 frame = frame->GetNextContinuation();
1395 PRInt32 textLength = nsAccUtils::TextLength(accessible);
1396 NS_ENSURE_TRUE(textLength >= 0, NS_ERROR_FAILURE);
1397 offset += textLength;
1400 return NS_OK; // Not found, will return -1
1403 // ------- nsIAccessibleHyperText ---------------
1404 NS_IMETHODIMP
1405 nsHyperTextAccessible::GetLinkCount(PRInt32 *aLinkCount)
1407 NS_ENSURE_ARG_POINTER(aLinkCount);
1408 *aLinkCount = 0;
1409 if (!mDOMNode) {
1410 return NS_ERROR_FAILURE;
1413 nsCOMPtr<nsIAccessible> accessible;
1415 while (NextChild(accessible)) {
1416 if (nsAccUtils::IsEmbeddedObject(accessible))
1417 ++*aLinkCount;
1419 return NS_OK;
1423 NS_IMETHODIMP
1424 nsHyperTextAccessible::GetLink(PRInt32 aLinkIndex, nsIAccessibleHyperLink **aLink)
1426 NS_ENSURE_ARG_POINTER(aLink);
1427 *aLink = nsnull;
1429 if (IsDefunct())
1430 return NS_ERROR_FAILURE;
1432 PRInt32 linkIndex = aLinkIndex;
1433 nsCOMPtr<nsIAccessible> accessible;
1434 while (NextChild(accessible)) {
1435 if (nsAccUtils::IsEmbeddedObject(accessible) && linkIndex-- == 0)
1436 return CallQueryInterface(accessible, aLink);
1439 return NS_ERROR_INVALID_ARG;
1442 NS_IMETHODIMP
1443 nsHyperTextAccessible::GetLinkIndex(PRInt32 aCharIndex, PRInt32 *aLinkIndex)
1445 NS_ENSURE_ARG_POINTER(aLinkIndex);
1446 *aLinkIndex = -1; // API says this magic value means 'not found'
1448 PRInt32 characterCount = 0;
1449 PRInt32 linkIndex = 0;
1450 if (!mDOMNode) {
1451 return NS_ERROR_FAILURE;
1454 nsCOMPtr<nsIAccessible> accessible;
1456 while (NextChild(accessible) && characterCount <= aCharIndex) {
1457 PRUint32 role = nsAccUtils::Role(accessible);
1458 if (role == nsIAccessibleRole::ROLE_TEXT_LEAF ||
1459 role == nsIAccessibleRole::ROLE_STATICTEXT) {
1460 PRInt32 textLength = nsAccUtils::TextLength(accessible);
1461 NS_ENSURE_TRUE(textLength >= 0, NS_ERROR_FAILURE);
1462 characterCount += textLength;
1464 else {
1465 if (characterCount ++ == aCharIndex) {
1466 *aLinkIndex = linkIndex;
1467 break;
1469 if (role != nsIAccessibleRole::ROLE_WHITESPACE) {
1470 ++ linkIndex;
1474 return NS_OK;
1478 * nsIAccessibleEditableText impl.
1480 NS_IMETHODIMP nsHyperTextAccessible::SetAttributes(PRInt32 aStartPos, PRInt32 aEndPos,
1481 nsISupports *aAttributes)
1483 return NS_ERROR_NOT_IMPLEMENTED;
1486 NS_IMETHODIMP nsHyperTextAccessible::SetTextContents(const nsAString &aText)
1488 PRInt32 numChars;
1489 GetCharacterCount(&numChars);
1490 if (numChars == 0 || NS_SUCCEEDED(DeleteText(0, numChars))) {
1491 return InsertText(aText, 0);
1493 return NS_ERROR_FAILURE;
1496 NS_IMETHODIMP nsHyperTextAccessible::InsertText(const nsAString &aText, PRInt32 aPosition)
1498 if (NS_SUCCEEDED(SetCaretOffset(aPosition))) {
1499 nsCOMPtr<nsIEditor> editor;
1500 GetAssociatedEditor(getter_AddRefs(editor));
1501 nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
1502 return peditor ? peditor->InsertText(aText) : NS_ERROR_FAILURE;
1505 return NS_ERROR_FAILURE;
1508 NS_IMETHODIMP nsHyperTextAccessible::CopyText(PRInt32 aStartPos, PRInt32 aEndPos)
1510 nsCOMPtr<nsIEditor> editor;
1511 GetAssociatedEditor(getter_AddRefs(editor));
1512 if (editor && NS_SUCCEEDED(SetSelectionRange(aStartPos, aEndPos)))
1513 return editor->Copy();
1515 return NS_ERROR_FAILURE;
1518 NS_IMETHODIMP nsHyperTextAccessible::CutText(PRInt32 aStartPos, PRInt32 aEndPos)
1520 nsCOMPtr<nsIEditor> editor;
1521 GetAssociatedEditor(getter_AddRefs(editor));
1522 if (editor && NS_SUCCEEDED(SetSelectionRange(aStartPos, aEndPos)))
1523 return editor->Cut();
1525 return NS_ERROR_FAILURE;
1528 NS_IMETHODIMP nsHyperTextAccessible::DeleteText(PRInt32 aStartPos, PRInt32 aEndPos)
1530 nsCOMPtr<nsIEditor> editor;
1531 GetAssociatedEditor(getter_AddRefs(editor));
1532 if (editor && NS_SUCCEEDED(SetSelectionRange(aStartPos, aEndPos)))
1533 return editor->DeleteSelection(nsIEditor::eNone);
1535 return NS_ERROR_FAILURE;
1538 NS_IMETHODIMP nsHyperTextAccessible::PasteText(PRInt32 aPosition)
1540 nsCOMPtr<nsIEditor> editor;
1541 GetAssociatedEditor(getter_AddRefs(editor));
1542 if (editor && NS_SUCCEEDED(SetCaretOffset(aPosition)))
1543 return editor->Paste(nsIClipboard::kGlobalClipboard);
1545 return NS_ERROR_FAILURE;
1548 NS_IMETHODIMP
1549 nsHyperTextAccessible::GetAssociatedEditor(nsIEditor **aEditor)
1551 NS_ENSURE_ARG_POINTER(aEditor);
1553 *aEditor = nsnull;
1554 nsCOMPtr<nsIContent> content = do_QueryInterface(mDOMNode);
1555 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1557 if (!content->HasFlag(NODE_IS_EDITABLE)) {
1558 // If we're inside an editable container, then return that container's editor
1559 nsCOMPtr<nsIAccessible> ancestor, current = this;
1560 while (NS_SUCCEEDED(current->GetParent(getter_AddRefs(ancestor))) && ancestor) {
1561 nsRefPtr<nsHyperTextAccessible> ancestorTextAccessible;
1562 ancestor->QueryInterface(NS_GET_IID(nsHyperTextAccessible),
1563 getter_AddRefs(ancestorTextAccessible));
1564 if (ancestorTextAccessible) {
1565 // Recursion will stop at container doc because it has its own impl
1566 // of GetAssociatedEditor()
1567 return ancestorTextAccessible->GetAssociatedEditor(aEditor);
1569 current = ancestor;
1571 return NS_OK;
1574 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
1575 nsCoreUtils::GetDocShellTreeItemFor(mDOMNode);
1576 nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(docShellTreeItem));
1577 if (!editingSession)
1578 return NS_OK; // No editing session interface
1580 nsCOMPtr<nsIPresShell> shell = GetPresShell();
1581 NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
1583 nsCOMPtr<nsIDocument> doc = shell->GetDocument();
1584 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
1586 nsCOMPtr<nsIEditor> editor;
1587 return editingSession->GetEditorForWindow(doc->GetWindow(), aEditor);
1591 * =================== Caret & Selection ======================
1594 nsresult nsHyperTextAccessible::SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos)
1596 // Set the selection
1597 nsresult rv = 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));
1606 if (domSel) {
1607 PRInt32 numRanges;
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);
1617 if (selCon) {
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, PR_FALSE);
1624 return NS_OK;
1627 NS_IMETHODIMP nsHyperTextAccessible::SetCaretOffset(PRInt32 aCaretOffset)
1629 return SetSelectionRange(aCaretOffset, aCaretOffset);
1633 * Gets the offset position of the caret (cursor).
1635 NS_IMETHODIMP nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset)
1637 *aCaretOffset = 0;
1639 nsCOMPtr<nsISelection> domSel;
1640 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1641 nsnull, getter_AddRefs(domSel));
1642 NS_ENSURE_SUCCESS(rv, rv);
1644 nsCOMPtr<nsIDOMNode> caretNode;
1645 rv = domSel->GetFocusNode(getter_AddRefs(caretNode));
1646 NS_ENSURE_SUCCESS(rv, rv);
1648 PRInt32 caretOffset;
1649 domSel->GetFocusOffset(&caretOffset);
1651 return DOMPointToHypertextOffset(caretNode, caretOffset, aCaretOffset);
1654 PRInt32 nsHyperTextAccessible::GetCaretLineNumber()
1656 // Provide the line number for the caret, relative to the
1657 // currently focused node. Use a 1-based index
1658 nsCOMPtr<nsISelection> domSel;
1659 GetSelections(nsISelectionController::SELECTION_NORMAL, nsnull,
1660 getter_AddRefs(domSel));
1661 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSel));
1662 NS_ENSURE_TRUE(privateSelection, -1);
1663 nsCOMPtr<nsFrameSelection> frameSelection;
1664 privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
1665 NS_ENSURE_TRUE(frameSelection, -1);
1667 nsCOMPtr<nsIDOMNode> caretNode;
1668 domSel->GetFocusNode(getter_AddRefs(caretNode));
1669 nsCOMPtr<nsIContent> caretContent = do_QueryInterface(caretNode);
1670 if (!caretContent || !nsCoreUtils::IsAncestorOf(mDOMNode, caretNode)) {
1671 return -1;
1674 PRInt32 caretOffset, returnOffsetUnused;
1675 domSel->GetFocusOffset(&caretOffset);
1676 nsFrameSelection::HINT hint = frameSelection->GetHint();
1677 nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset,
1678 hint, &returnOffsetUnused);
1679 NS_ENSURE_TRUE(caretFrame, -1);
1681 PRInt32 lineNumber = 1;
1682 nsAutoLineIterator lineIterForCaret;
1683 nsCOMPtr<nsIContent> hyperTextContent = do_QueryInterface(mDOMNode);
1684 while (caretFrame) {
1685 if (hyperTextContent == caretFrame->GetContent()) {
1686 return lineNumber; // Must be in a single line hyper text, there is no line iterator
1688 nsIFrame *parentFrame = caretFrame->GetParent();
1689 if (!parentFrame)
1690 break;
1692 // Add lines for the sibling frames before the caret
1693 nsIFrame *sibling = parentFrame->GetFirstChild(nsnull);
1694 while (sibling && sibling != caretFrame) {
1695 nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator();
1696 if (lineIterForSibling) {
1697 // For the frames before that grab all the lines
1698 PRInt32 addLines = lineIterForSibling->GetNumLines();
1699 lineNumber += addLines;
1701 sibling = sibling->GetNextSibling();
1704 // Get the line number relative to the container with lines
1705 if (!lineIterForCaret) { // Add the caret line just once
1706 lineIterForCaret = parentFrame->GetLineIterator();
1707 if (lineIterForCaret) {
1708 // Ancestor of caret
1709 PRInt32 addLines = lineIterForCaret->FindLineContaining(caretFrame);
1710 lineNumber += addLines;
1714 caretFrame = parentFrame;
1717 NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
1718 return lineNumber;
1721 nsresult
1722 nsHyperTextAccessible::GetSelections(PRInt16 aType,
1723 nsISelectionController **aSelCon,
1724 nsISelection **aDomSel,
1725 nsCOMArray<nsIDOMRange>* aRanges)
1727 if (!mDOMNode) {
1728 return NS_ERROR_FAILURE;
1730 if (aSelCon) {
1731 *aSelCon = nsnull;
1733 if (aDomSel) {
1734 *aDomSel = nsnull;
1736 if (aRanges) {
1737 aRanges->Clear();
1740 nsCOMPtr<nsISelection> domSel;
1741 nsCOMPtr<nsISelectionController> selCon;
1743 nsCOMPtr<nsIEditor> editor;
1744 GetAssociatedEditor(getter_AddRefs(editor));
1745 nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
1746 if (peditor) {
1747 // Case 1: plain text editor
1748 // This is for form controls which have their own
1749 // selection controller separate from the document, for example
1750 // HTML:input, HTML:textarea, XUL:textbox, etc.
1751 editor->GetSelectionController(getter_AddRefs(selCon));
1753 else {
1754 // Case 2: rich content subtree (can be rich editor)
1755 // This uses the selection controller from the entire document
1756 nsIFrame *frame = GetFrame();
1757 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
1759 // Get the selection and selection controller
1760 frame->GetSelectionController(GetPresContext(),
1761 getter_AddRefs(selCon));
1763 NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
1765 selCon->GetSelection(aType, getter_AddRefs(domSel));
1766 NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE);
1768 if (aSelCon) {
1769 NS_ADDREF(*aSelCon = selCon);
1771 if (aDomSel) {
1772 NS_ADDREF(*aDomSel = domSel);
1775 if (aRanges) {
1776 nsCOMPtr<nsISelection2> selection2(do_QueryInterface(domSel));
1777 NS_ENSURE_TRUE(selection2, NS_ERROR_FAILURE);
1779 nsCOMPtr<nsIDOMNode> startNode(mDOMNode);
1780 if (peditor) {
1781 nsCOMPtr<nsIDOMElement> editorRoot;
1782 editor->GetRootElement(getter_AddRefs(editorRoot));
1783 startNode = do_QueryInterface(editorRoot);
1785 NS_ENSURE_STATE(startNode);
1787 nsCOMPtr<nsIDOMNodeList> childNodes;
1788 nsresult rv = startNode->GetChildNodes(getter_AddRefs(childNodes));
1789 NS_ENSURE_SUCCESS(rv, rv);
1790 PRUint32 numChildren;
1791 rv = childNodes->GetLength(&numChildren);
1792 NS_ENSURE_SUCCESS(rv, rv);
1793 rv = selection2->GetRangesForIntervalCOMArray(startNode, 0,
1794 startNode, numChildren,
1795 PR_TRUE, aRanges);
1796 NS_ENSURE_SUCCESS(rv, rv);
1797 // Remove collapsed ranges
1798 PRInt32 numRanges = aRanges->Count();
1799 for (PRInt32 count = 0; count < numRanges; count ++) {
1800 PRBool isCollapsed;
1801 (*aRanges)[count]->GetCollapsed(&isCollapsed);
1802 if (isCollapsed) {
1803 aRanges->RemoveObjectAt(count);
1804 -- numRanges;
1805 -- count;
1810 return NS_OK;
1814 * Gets the number of selected regions.
1816 NS_IMETHODIMP nsHyperTextAccessible::GetSelectionCount(PRInt32 *aSelectionCount)
1818 nsCOMPtr<nsISelection> domSel;
1819 nsCOMArray<nsIDOMRange> ranges;
1820 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1821 nsnull, nsnull, &ranges);
1822 NS_ENSURE_SUCCESS(rv, rv);
1824 *aSelectionCount = ranges.Count();
1826 return NS_OK;
1830 * Gets the start and end offset of the specified selection.
1832 NS_IMETHODIMP nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum, PRInt32 *aStartOffset, PRInt32 *aEndOffset)
1834 *aStartOffset = *aEndOffset = 0;
1836 nsCOMPtr<nsISelection> domSel;
1837 nsCOMArray<nsIDOMRange> ranges;
1838 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1839 nsnull, getter_AddRefs(domSel), &ranges);
1840 NS_ENSURE_SUCCESS(rv, rv);
1842 PRInt32 rangeCount = ranges.Count();
1843 if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
1844 return NS_ERROR_INVALID_ARG;
1846 nsCOMPtr<nsIDOMRange> range = ranges[aSelectionNum];
1848 // Get start point
1849 nsCOMPtr<nsIDOMNode> startNode;
1850 range->GetStartContainer(getter_AddRefs(startNode));
1851 PRInt32 startOffset;
1852 range->GetStartOffset(&startOffset);
1854 // Get end point
1855 nsCOMPtr<nsIDOMNode> endNode;
1856 range->GetEndContainer(getter_AddRefs(endNode));
1857 PRInt32 endOffset;
1858 range->GetEndOffset(&endOffset);
1860 PRInt16 rangeCompareResult;
1861 rv = range->CompareBoundaryPoints(nsIDOMRange::START_TO_END, range, &rangeCompareResult);
1862 NS_ENSURE_SUCCESS(rv, rv);
1864 if (rangeCompareResult < 0) {
1865 // Make sure start is before end, by swapping offsets
1866 // This occurs when the user selects backwards in the text
1867 startNode.swap(endNode);
1868 PRInt32 tempOffset = startOffset;
1869 startOffset = endOffset;
1870 endOffset = tempOffset;
1873 nsCOMPtr<nsIAccessible> startAccessible;
1874 rv = DOMPointToHypertextOffset(startNode, startOffset, aStartOffset, getter_AddRefs(startAccessible));
1875 NS_ENSURE_SUCCESS(rv, rv);
1876 if (!startAccessible) {
1877 *aStartOffset = 0; // Could not find start point within this hypertext, so starts before
1880 return DOMPointToHypertextOffset(endNode, endOffset, aEndOffset, nsnull, PR_TRUE);
1884 * Changes the start and end offset of the specified selection.
1886 NS_IMETHODIMP
1887 nsHyperTextAccessible::SetSelectionBounds(PRInt32 aSelectionNum,
1888 PRInt32 aStartOffset,
1889 PRInt32 aEndOffset)
1891 nsCOMPtr<nsISelection> domSel;
1892 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1893 nsnull, getter_AddRefs(domSel));
1894 NS_ENSURE_SUCCESS(rv, rv);
1896 // Caret is a collapsed selection
1897 PRBool isOnlyCaret = (aStartOffset == aEndOffset);
1899 PRInt32 rangeCount;
1900 domSel->GetRangeCount(&rangeCount);
1901 nsCOMPtr<nsIDOMRange> range;
1902 if (aSelectionNum == rangeCount) { // Add a range
1903 range = do_CreateInstance(kRangeCID);
1904 NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY);
1906 else if (aSelectionNum < 0 || aSelectionNum > rangeCount) {
1907 return NS_ERROR_INVALID_ARG;
1909 else {
1910 domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
1911 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
1914 PRInt32 startOffset, endOffset;
1915 nsCOMPtr<nsIDOMNode> startNode, endNode;
1917 rv = HypertextOffsetsToDOMRange(aStartOffset, aEndOffset,
1918 getter_AddRefs(startNode), &startOffset,
1919 getter_AddRefs(endNode), &endOffset);
1920 NS_ENSURE_SUCCESS(rv, rv);
1922 rv = range->SetStart(startNode, startOffset);
1923 NS_ENSURE_SUCCESS(rv, rv);
1925 rv = isOnlyCaret ? range->Collapse(PR_TRUE) :
1926 range->SetEnd(endNode, endOffset);
1927 NS_ENSURE_SUCCESS(rv, rv);
1929 if (aSelectionNum == rangeCount) { // Add successfully created new range
1930 return domSel->AddRange(range);
1932 return NS_OK;
1936 * Adds a selection bounded by the specified offsets.
1938 NS_IMETHODIMP nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset, PRInt32 aEndOffset)
1940 nsCOMPtr<nsISelection> domSel;
1941 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1942 nsnull, getter_AddRefs(domSel));
1943 NS_ENSURE_SUCCESS(rv, rv);
1945 PRInt32 rangeCount;
1946 domSel->GetRangeCount(&rangeCount);
1948 return SetSelectionBounds(rangeCount, aStartOffset, aEndOffset);
1952 * Removes the specified selection.
1954 NS_IMETHODIMP nsHyperTextAccessible::RemoveSelection(PRInt32 aSelectionNum)
1956 nsCOMPtr<nsISelection> domSel;
1957 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1958 nsnull, getter_AddRefs(domSel));
1959 NS_ENSURE_SUCCESS(rv, rv);
1961 PRInt32 rangeCount;
1962 domSel->GetRangeCount(&rangeCount);
1963 if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
1964 return NS_ERROR_INVALID_ARG;
1966 nsCOMPtr<nsIDOMRange> range;
1967 domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
1968 return domSel->RemoveRange(range);
1971 // void nsIAccessibleText::
1972 // scrollSubstringTo(in long startIndex, in long endIndex,
1973 // in unsigned long scrollType);
1974 NS_IMETHODIMP
1975 nsHyperTextAccessible::ScrollSubstringTo(PRInt32 aStartIndex, PRInt32 aEndIndex,
1976 PRUint32 aScrollType)
1978 PRInt32 startOffset, endOffset;
1979 nsCOMPtr<nsIDOMNode> startNode, endNode;
1981 nsresult rv = HypertextOffsetsToDOMRange(aStartIndex, aEndIndex,
1982 getter_AddRefs(startNode),
1983 &startOffset,
1984 getter_AddRefs(endNode),
1985 &endOffset);
1986 NS_ENSURE_SUCCESS(rv, rv);
1988 return nsCoreUtils::ScrollSubstringTo(GetFrame(), startNode, startOffset,
1989 endNode, endOffset, aScrollType);
1992 // void nsIAccessibleText::
1993 // scrollSubstringToPoint(in long startIndex, in long endIndex,
1994 // in unsigned long coordinateType,
1995 // in long x, in long y);
1996 NS_IMETHODIMP
1997 nsHyperTextAccessible::ScrollSubstringToPoint(PRInt32 aStartIndex,
1998 PRInt32 aEndIndex,
1999 PRUint32 aCoordinateType,
2000 PRInt32 aX, PRInt32 aY)
2002 nsIFrame *frame = GetFrame();
2003 if (!frame)
2004 return NS_ERROR_FAILURE;
2006 nsIntPoint coords;
2007 nsresult rv = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
2008 this, &coords);
2009 NS_ENSURE_SUCCESS(rv, rv);
2011 PRInt32 startOffset, endOffset;
2012 nsCOMPtr<nsIDOMNode> startNode, endNode;
2014 rv = HypertextOffsetsToDOMRange(aStartIndex, aEndIndex,
2015 getter_AddRefs(startNode), &startOffset,
2016 getter_AddRefs(endNode), &endOffset);
2017 NS_ENSURE_SUCCESS(rv, rv);
2019 nsPresContext *presContext = frame->PresContext();
2021 PRBool initialScrolled = PR_FALSE;
2022 nsIFrame *parentFrame = frame;
2023 while ((parentFrame = parentFrame->GetParent())) {
2024 nsIScrollableFrame *scrollableFrame = nsnull;
2025 CallQueryInterface(parentFrame, &scrollableFrame);
2026 if (scrollableFrame) {
2027 if (!initialScrolled) {
2028 // Scroll substring to the given point. Turn the point into percents
2029 // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
2030 nsIntRect frameRect = parentFrame->GetScreenRectExternal();
2031 PRInt32 devOffsetX = coords.x - frameRect.x;
2032 PRInt32 devOffsetY = coords.y - frameRect.y;
2034 nsPoint offsetPoint(presContext->DevPixelsToAppUnits(devOffsetX),
2035 presContext->DevPixelsToAppUnits(devOffsetY));
2037 nsSize size(parentFrame->GetSize());
2038 PRInt16 hPercent = offsetPoint.x * 100 / size.width;
2039 PRInt16 vPercent = offsetPoint.y * 100 / size.height;
2041 rv = nsCoreUtils::ScrollSubstringTo(GetFrame(), startNode, startOffset,
2042 endNode, endOffset,
2043 vPercent, hPercent);
2044 NS_ENSURE_SUCCESS(rv, rv);
2046 initialScrolled = PR_TRUE;
2047 } else {
2048 // Substring was scrolled to the given point already inside its closest
2049 // scrollable area. If there are nested scrollable areas then make
2050 // sure we scroll lower areas to the given point inside currently
2051 // traversed scrollable area.
2052 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2055 frame = parentFrame;
2058 return NS_OK;
2061 nsresult nsHyperTextAccessible::ContentToRenderedOffset(nsIFrame *aFrame, PRInt32 aContentOffset,
2062 PRUint32 *aRenderedOffset)
2064 if (!aFrame) {
2065 // Current frame not rendered -- this can happen if text is set on
2066 // something with display: none
2067 *aRenderedOffset = 0;
2068 return NS_OK;
2070 NS_ASSERTION(aFrame->GetType() == nsAccessibilityAtoms::textFrame,
2071 "Need text frame for offset conversion");
2072 NS_ASSERTION(aFrame->GetPrevContinuation() == nsnull,
2073 "Call on primary frame only");
2075 gfxSkipChars skipChars;
2076 gfxSkipCharsIterator iter;
2077 // Only get info up to original ofset, we know that will be larger than skipped offset
2078 nsresult rv = aFrame->GetRenderedText(nsnull, &skipChars, &iter, 0, aContentOffset);
2079 NS_ENSURE_SUCCESS(rv, rv);
2081 PRUint32 ourRenderedStart = iter.GetSkippedOffset();
2082 PRInt32 ourContentStart = iter.GetOriginalOffset();
2084 *aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) -
2085 ourRenderedStart;
2087 return NS_OK;
2090 nsresult nsHyperTextAccessible::RenderedToContentOffset(nsIFrame *aFrame, PRUint32 aRenderedOffset,
2091 PRInt32 *aContentOffset)
2093 *aContentOffset = 0;
2094 NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
2096 NS_ASSERTION(aFrame->GetType() == nsAccessibilityAtoms::textFrame,
2097 "Need text frame for offset conversion");
2098 NS_ASSERTION(aFrame->GetPrevContinuation() == nsnull,
2099 "Call on primary frame only");
2101 gfxSkipChars skipChars;
2102 gfxSkipCharsIterator iter;
2103 // We only need info up to skipped offset -- that is what we're converting to original offset
2104 nsresult rv = aFrame->GetRenderedText(nsnull, &skipChars, &iter, 0, aRenderedOffset);
2105 NS_ENSURE_SUCCESS(rv, rv);
2107 PRUint32 ourRenderedStart = iter.GetSkippedOffset();
2108 PRInt32 ourContentStart = iter.GetOriginalOffset();
2110 *aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart;
2112 return NS_OK;
2115 nsresult
2116 nsHyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame *aFrame,
2117 PRInt32 aOffset,
2118 nsIAccessible *aAccessible,
2119 nsIDOMNode **aNode,
2120 PRInt32 *aNodeOffset)
2122 NS_ENSURE_ARG(aAccessible);
2124 nsCOMPtr<nsIDOMNode> node;
2126 if (!aFrame) {
2127 // If the given frame is null then set offset after the DOM node of the
2128 // given accessible.
2129 nsCOMPtr<nsIAccessNode> accessNode(do_QueryInterface(aAccessible));
2131 nsCOMPtr<nsIDOMNode> DOMNode;
2132 accessNode->GetDOMNode(getter_AddRefs(DOMNode));
2133 nsCOMPtr<nsIContent> content(do_QueryInterface(DOMNode));
2134 NS_ENSURE_STATE(content);
2136 nsCOMPtr<nsIContent> parent(content->GetParent());
2137 NS_ENSURE_STATE(parent);
2139 *aNodeOffset = parent->IndexOf(content) + 1;
2140 node = do_QueryInterface(parent);
2142 } else if (aFrame->GetType() == nsAccessibilityAtoms::textFrame) {
2143 nsCOMPtr<nsIContent> content(aFrame->GetContent());
2144 NS_ENSURE_STATE(content);
2146 nsCOMPtr<nsIPresShell> shell(GetPresShell());
2147 NS_ENSURE_STATE(shell);
2149 nsIFrame *primaryFrame = shell->GetPrimaryFrameFor(content);
2150 nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, aNodeOffset);
2151 NS_ENSURE_SUCCESS(rv, rv);
2153 node = do_QueryInterface(content);
2155 } else {
2156 nsCOMPtr<nsIContent> content(aFrame->GetContent());
2157 NS_ENSURE_STATE(content);
2159 nsCOMPtr<nsIContent> parent(content->GetParent());
2160 NS_ENSURE_STATE(parent);
2162 *aNodeOffset = parent->IndexOf(content);
2163 node = do_QueryInterface(parent);
2166 NS_IF_ADDREF(*aNode = node);
2167 return NS_OK;
2170 // nsHyperTextAccessible
2171 nsresult
2172 nsHyperTextAccessible::DOMRangeBoundToHypertextOffset(nsIDOMRange *aRange,
2173 PRBool aIsStartBound,
2174 PRBool aIsStartHTOffset,
2175 PRInt32 *aHTOffset)
2177 nsCOMPtr<nsIDOMNode> node;
2178 PRInt32 nodeOffset = 0;
2180 nsresult rv;
2181 if (aIsStartBound) {
2182 rv = aRange->GetStartContainer(getter_AddRefs(node));
2183 NS_ENSURE_SUCCESS(rv, rv);
2185 rv = aRange->GetStartOffset(&nodeOffset);
2186 NS_ENSURE_SUCCESS(rv, rv);
2187 } else {
2188 rv = aRange->GetEndContainer(getter_AddRefs(node));
2189 NS_ENSURE_SUCCESS(rv, rv);
2191 rv = aRange->GetEndOffset(&nodeOffset);
2192 NS_ENSURE_SUCCESS(rv, rv);
2195 nsCOMPtr<nsIAccessible> startAcc;
2196 rv = DOMPointToHypertextOffset(node, nodeOffset, aHTOffset,
2197 getter_AddRefs(startAcc));
2198 NS_ENSURE_SUCCESS(rv, rv);
2200 if (aIsStartHTOffset && !startAcc)
2201 *aHTOffset = 0;
2203 return NS_OK;
2206 // nsHyperTextAccessible
2207 nsresult
2208 nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode,
2209 PRInt32 aNodeOffset,
2210 PRInt32 *aHTStartOffset,
2211 PRInt32 *aHTEndOffset,
2212 nsIPersistentProperties *aAttributes)
2214 nsCOMArray<nsIDOMRange> ranges;
2215 nsresult rv = GetSelections(nsISelectionController::SELECTION_SPELLCHECK,
2216 nsnull, nsnull, &ranges);
2217 NS_ENSURE_SUCCESS(rv, rv);
2219 PRInt32 rangeCount = ranges.Count();
2220 if (!rangeCount)
2221 return NS_OK;
2223 for (PRInt32 index = 0; index < rangeCount; index++) {
2224 nsCOMPtr<nsIDOMRange> range = ranges[index];
2225 nsCOMPtr<nsIDOMNSRange> nsrange(do_QueryInterface(range));
2226 NS_ENSURE_STATE(nsrange);
2228 PRInt16 result;
2229 rv = nsrange->ComparePoint(aNode, aNodeOffset, &result);
2230 NS_ENSURE_SUCCESS(rv, rv);
2231 // ComparePoint checks boundary points, but we need to check that
2232 // text at aNodeOffset is inside the range.
2233 // See also bug 460690.
2234 if (result == 0) {
2235 nsCOMPtr<nsIDOMNode> end;
2236 rv = range->GetEndContainer(getter_AddRefs(end));
2237 NS_ENSURE_SUCCESS(rv, rv);
2238 PRInt32 endOffset;
2239 rv = range->GetEndOffset(&endOffset);
2240 NS_ENSURE_SUCCESS(rv, rv);
2241 if (aNode == end && aNodeOffset == endOffset) {
2242 result = 1;
2246 if (result == 1) { // range is before point
2247 PRInt32 startHTOffset = 0;
2248 rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_TRUE,
2249 &startHTOffset);
2250 NS_ENSURE_SUCCESS(rv, rv);
2252 if (startHTOffset > *aHTStartOffset)
2253 *aHTStartOffset = startHTOffset;
2255 } else if (result == -1) { // range is after point
2256 PRInt32 endHTOffset = 0;
2257 rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_FALSE,
2258 &endHTOffset);
2259 NS_ENSURE_SUCCESS(rv, rv);
2261 if (endHTOffset < *aHTEndOffset)
2262 *aHTEndOffset = endHTOffset;
2264 } else { // point is in range
2265 PRInt32 startHTOffset = 0;
2266 rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_TRUE,
2267 &startHTOffset);
2268 NS_ENSURE_SUCCESS(rv, rv);
2270 PRInt32 endHTOffset = 0;
2271 rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_FALSE,
2272 &endHTOffset);
2273 NS_ENSURE_SUCCESS(rv, rv);
2275 if (startHTOffset > *aHTStartOffset)
2276 *aHTStartOffset = startHTOffset;
2277 if (endHTOffset < *aHTEndOffset)
2278 *aHTEndOffset = endHTOffset;
2280 if (aAttributes) {
2281 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::invalid,
2282 NS_LITERAL_STRING("spelling"));
2285 return NS_OK;
2289 return NS_OK;
2292 // nsHyperTextAccessible
2293 nsresult
2294 nsHyperTextAccessible::GetLangTextAttributes(PRBool aIncludeDefAttrs,
2295 nsIDOMNode *aSourceNode,
2296 PRInt32 *aStartHTOffset,
2297 PRInt32 *aEndHTOffset,
2298 nsIPersistentProperties *aAttributes)
2300 nsCOMPtr<nsIDOMElement> sourceElm(nsCoreUtils::GetDOMElementFor(aSourceNode));
2302 nsCOMPtr<nsIContent> content(do_QueryInterface(sourceElm));
2303 nsCOMPtr<nsIContent> rootContent(do_QueryInterface(mDOMNode));
2305 nsAutoString lang;
2306 nsCoreUtils::GetLanguageFor(content, rootContent, lang);
2308 nsAutoString rootLang;
2309 nsresult rv = GetLanguage(rootLang);
2310 NS_ENSURE_SUCCESS(rv, rv);
2312 if (aAttributes) {
2313 // Expose 'language' text attribute if the DOM 'lang' attribute is
2314 // presented and it's different from the 'lang' attribute on the root
2315 // element or we should include default values of text attribute.
2316 const nsAString& resultLang = lang.IsEmpty() ? rootLang : lang;
2317 if (!resultLang.IsEmpty() && (aIncludeDefAttrs || lang != rootLang))
2318 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::language,
2319 resultLang);
2322 nsLangTextAttr textAttr(lang, rootContent);
2323 return GetRangeForTextAttr(aSourceNode, &textAttr,
2324 aStartHTOffset, aEndHTOffset);
2327 // nsHyperTextAccessible
2328 nsresult
2329 nsHyperTextAccessible::GetCSSTextAttributes(PRBool aIncludeDefAttrs,
2330 nsIDOMNode *aSourceNode,
2331 PRInt32 *aStartHTOffset,
2332 PRInt32 *aEndHTOffset,
2333 nsIPersistentProperties *aAttributes)
2335 nsCOMPtr<nsIDOMElement> sourceElm(nsCoreUtils::GetDOMElementFor(aSourceNode));
2336 nsCOMPtr<nsIDOMElement> rootElm(nsCoreUtils::GetDOMElementFor(mDOMNode));
2338 nsCSSTextAttr textAttr(aIncludeDefAttrs, sourceElm, rootElm);
2339 while (textAttr.Iterate()) {
2340 nsCAutoString name;
2341 nsAutoString value, oldValue;
2342 if (aAttributes && textAttr.Get(name, value))
2343 aAttributes->SetStringProperty(name, value, oldValue);
2345 nsresult rv = GetRangeForTextAttr(aSourceNode, &textAttr,
2346 aStartHTOffset, aEndHTOffset);
2347 NS_ENSURE_SUCCESS(rv, rv);
2350 nsIFrame *sourceFrame = nsCoreUtils::GetFrameFor(sourceElm);
2351 if (sourceFrame) {
2352 nsIFrame *rootFrame = nsnull;
2354 if (!aIncludeDefAttrs)
2355 rootFrame = nsCoreUtils::GetFrameFor(rootElm);
2357 nsBackgroundTextAttr backgroundTextAttr(sourceFrame, rootFrame);
2358 nsAutoString value;
2359 if (backgroundTextAttr.Get(value)) {
2360 nsAccUtils::SetAccAttr(aAttributes,
2361 nsAccessibilityAtoms::backgroundColor, value);
2364 nsresult rv = GetRangeForTextAttr(aSourceNode, &backgroundTextAttr,
2365 aStartHTOffset, aEndHTOffset);
2366 return rv;
2369 return NS_OK;
2372 // nsHyperTextAccessible
2373 nsresult
2374 nsHyperTextAccessible::GetRangeForTextAttr(nsIDOMNode *aNode,
2375 nsTextAttr *aComparer,
2376 PRInt32 *aStartHTOffset,
2377 PRInt32 *aEndHTOffset)
2379 nsCOMPtr<nsIDOMElement> rootElm(nsCoreUtils::GetDOMElementFor(mDOMNode));
2380 NS_ENSURE_STATE(rootElm);
2382 nsCOMPtr<nsIDOMNode> tmpNode(aNode);
2383 nsCOMPtr<nsIDOMNode> currNode(aNode);
2385 // Navigate backwards and forwards from current node to the root node to
2386 // calculate range bounds for the text attribute. Navigation sequence is the
2387 // following:
2388 // 1. Navigate through the siblings.
2389 // 2. If the traversed sibling has children then navigate from its leaf child
2390 // to it through whole tree of the traversed sibling.
2391 // 3. Get the parent and cycle algorithm until the root node.
2393 // Navigate backwards (find the start offset).
2394 while (currNode && currNode != rootElm) {
2395 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(currNode));
2396 NS_ENSURE_STATE(currElm);
2398 if (currNode != aNode && !aComparer->Equal(currElm)) {
2399 PRInt32 startHTOffset = 0;
2400 nsCOMPtr<nsIAccessible> startAcc;
2401 nsresult rv = DOMPointToHypertextOffset(tmpNode, -1, &startHTOffset,
2402 getter_AddRefs(startAcc));
2403 NS_ENSURE_SUCCESS(rv, rv);
2405 if (!startAcc)
2406 startHTOffset = 0;
2408 if (startHTOffset > *aStartHTOffset)
2409 *aStartHTOffset = startHTOffset;
2411 break;
2414 currNode->GetPreviousSibling(getter_AddRefs(tmpNode));
2415 if (tmpNode) {
2416 // Navigate through the subtree of traversed children to calculate
2417 // left bound of the range.
2418 FindStartOffsetInSubtree(tmpNode, currNode, aComparer, aStartHTOffset);
2421 currNode->GetParentNode(getter_AddRefs(tmpNode));
2422 currNode.swap(tmpNode);
2425 // Navigate forwards (find the end offset).
2426 PRBool moveIntoSubtree = PR_TRUE;
2427 currNode = aNode;
2428 while (currNode && currNode != rootElm) {
2429 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(currNode));
2430 NS_ENSURE_STATE(currElm);
2432 // Stop new end offset searching if the given text attribute changes its
2433 // value.
2434 if (!aComparer->Equal(currElm)) {
2435 PRInt32 endHTOffset = 0;
2436 nsresult rv = DOMPointToHypertextOffset(currNode, -1, &endHTOffset);
2437 NS_ENSURE_SUCCESS(rv, rv);
2439 if (endHTOffset < *aEndHTOffset)
2440 *aEndHTOffset = endHTOffset;
2442 break;
2445 if (moveIntoSubtree) {
2446 // Navigate through subtree of traversed node. We use 'moveIntoSubtree'
2447 // flag to avoid traversing the same subtree twice.
2448 currNode->GetFirstChild(getter_AddRefs(tmpNode));
2449 if (tmpNode)
2450 FindEndOffsetInSubtree(tmpNode, aComparer, aEndHTOffset);
2453 currNode->GetNextSibling(getter_AddRefs(tmpNode));
2454 moveIntoSubtree = PR_TRUE;
2455 if (!tmpNode) {
2456 currNode->GetParentNode(getter_AddRefs(tmpNode));
2457 moveIntoSubtree = PR_FALSE;
2460 currNode.swap(tmpNode);
2463 return NS_OK;
2467 PRBool
2468 nsHyperTextAccessible::FindEndOffsetInSubtree(nsIDOMNode *aCurrNode,
2469 nsTextAttr *aComparer,
2470 PRInt32 *aHTOffset)
2472 if (!aCurrNode)
2473 return PR_FALSE;
2475 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(aCurrNode));
2476 NS_ENSURE_STATE(currElm);
2478 // If the given text attribute (pointed by nsTextAttr object) changes its
2479 // value on the traversed element then fit the end of range.
2480 if (!aComparer->Equal(currElm)) {
2481 PRInt32 endHTOffset = 0;
2482 nsresult rv = DOMPointToHypertextOffset(aCurrNode, -1, &endHTOffset);
2483 NS_ENSURE_SUCCESS(rv, rv);
2485 if (endHTOffset < *aHTOffset)
2486 *aHTOffset = endHTOffset;
2488 return PR_TRUE;
2491 // Deeply traverse into the tree to fit the end of range.
2492 nsCOMPtr<nsIDOMNode> nextNode;
2493 aCurrNode->GetFirstChild(getter_AddRefs(nextNode));
2494 if (nextNode) {
2495 PRBool res = FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset);
2496 if (res)
2497 return res;
2500 aCurrNode->GetNextSibling(getter_AddRefs(nextNode));
2501 if (nextNode) {
2502 if (FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset))
2503 return PR_TRUE;
2506 return PR_FALSE;
2509 PRBool
2510 nsHyperTextAccessible::FindStartOffsetInSubtree(nsIDOMNode *aCurrNode,
2511 nsIDOMNode *aPrevNode,
2512 nsTextAttr *aComparer,
2513 PRInt32 *aHTOffset)
2515 if (!aCurrNode)
2516 return PR_FALSE;
2518 // Find the closest element back to the traversed element.
2519 nsCOMPtr<nsIDOMNode> nextNode;
2520 aCurrNode->GetLastChild(getter_AddRefs(nextNode));
2521 if (nextNode) {
2522 if (FindStartOffsetInSubtree(nextNode, aPrevNode, aComparer, aHTOffset))
2523 return PR_TRUE;
2526 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(aCurrNode));
2527 NS_ENSURE_STATE(currElm);
2529 // If the given text attribute (pointed by nsTextAttr object) changes its
2530 // value on the traversed element then fit the start of range.
2531 if (!aComparer->Equal(currElm)) {
2532 PRInt32 startHTOffset = 0;
2533 nsCOMPtr<nsIAccessible> startAcc;
2534 nsresult rv = DOMPointToHypertextOffset(aPrevNode, -1, &startHTOffset,
2535 getter_AddRefs(startAcc));
2536 NS_ENSURE_SUCCESS(rv, rv);
2538 if (!startAcc)
2539 startHTOffset = 0;
2541 if (startHTOffset > *aHTOffset)
2542 *aHTOffset = startHTOffset;
2544 return PR_TRUE;
2547 // Moving backwards to find the start of range.
2548 aCurrNode->GetPreviousSibling(getter_AddRefs(nextNode));
2549 if (nextNode) {
2550 if (FindStartOffsetInSubtree(nextNode, aCurrNode, aComparer, aHTOffset))
2551 return PR_TRUE;
2554 return PR_FALSE;