Bug 454376 add -lCrun -lCstd for Solaris OS_LIBS, r=bsmedberg
[wine-gecko.git] / accessible / src / html / nsHyperTextAccessible.cpp
blob982ade2106823a5468afd2e95a2b6063f626c037
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 "nsPIAccessNode.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"
61 #include "nsIFrame.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 NS_IMPL_ADDREF_INHERITED(nsHyperTextAccessible, nsAccessibleWrap)
80 NS_IMPL_RELEASE_INHERITED(nsHyperTextAccessible, nsAccessibleWrap)
82 nsresult nsHyperTextAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr)
84 *aInstancePtr = nsnull;
86 nsCOMPtr<nsIDOMXULDocument> xulDoc(do_QueryInterface(mDOMNode));
87 if (mDOMNode && !xulDoc) {
88 // We need XUL doc check for now because for now nsDocAccessible must
89 // inherit from nsHyperTextAccessible in order for HTML document accessibles
90 // to get support for these interfaces.
91 // However at some point we may push <body> to implement the interfaces and
92 // return nsDocAccessible to inherit from nsAccessibleWrap.
94 if (aIID.Equals(NS_GET_IID(nsHyperTextAccessible))) {
95 *aInstancePtr = static_cast<nsHyperTextAccessible*>(this);
96 NS_ADDREF_THIS();
97 return NS_OK;
100 if (mRoleMapEntry &&
101 (mRoleMapEntry->role == nsIAccessibleRole::ROLE_GRAPHIC ||
102 mRoleMapEntry->role == nsIAccessibleRole::ROLE_IMAGE_MAP ||
103 mRoleMapEntry->role == nsIAccessibleRole::ROLE_SLIDER ||
104 mRoleMapEntry->role == nsIAccessibleRole::ROLE_PROGRESSBAR ||
105 mRoleMapEntry->role == nsIAccessibleRole::ROLE_SEPARATOR)) {
106 // ARIA roles that these interfaces are not appropriate for
107 return nsAccessible::QueryInterface(aIID, aInstancePtr);
110 if (aIID.Equals(NS_GET_IID(nsIAccessibleText))) {
111 *aInstancePtr = static_cast<nsIAccessibleText*>(this);
112 NS_ADDREF_THIS();
113 return NS_OK;
116 if (aIID.Equals(NS_GET_IID(nsIAccessibleHyperText))) {
117 *aInstancePtr = static_cast<nsIAccessibleHyperText*>(this);
118 NS_ADDREF_THIS();
119 return NS_OK;
122 if (aIID.Equals(NS_GET_IID(nsIAccessibleEditableText))) {
123 *aInstancePtr = static_cast<nsIAccessibleEditableText*>(this);
124 NS_ADDREF_THIS();
125 return NS_OK;
129 return nsAccessible::QueryInterface(aIID, aInstancePtr);
132 nsHyperTextAccessible::nsHyperTextAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell):
133 nsAccessibleWrap(aNode, aShell)
137 NS_IMETHODIMP nsHyperTextAccessible::GetRole(PRUint32 *aRole)
139 nsCOMPtr<nsIContent> content = do_QueryInterface(mDOMNode);
140 if (!content) {
141 return NS_ERROR_FAILURE;
144 nsIAtom *tag = content->Tag();
146 if (tag == nsAccessibilityAtoms::form) {
147 *aRole = nsIAccessibleRole::ROLE_FORM;
149 else if (tag == nsAccessibilityAtoms::div ||
150 tag == nsAccessibilityAtoms::blockquote) {
151 *aRole = nsIAccessibleRole::ROLE_SECTION;
153 else if (tag == nsAccessibilityAtoms::h1 ||
154 tag == nsAccessibilityAtoms::h2 ||
155 tag == nsAccessibilityAtoms::h3 ||
156 tag == nsAccessibilityAtoms::h4 ||
157 tag == nsAccessibilityAtoms::h5 ||
158 tag == nsAccessibilityAtoms::h6) {
159 *aRole = nsIAccessibleRole::ROLE_HEADING;
161 else {
162 nsIFrame *frame = GetFrame();
163 if (frame && frame->GetType() == nsAccessibilityAtoms::blockFrame) {
164 *aRole = nsIAccessibleRole::ROLE_PARAGRAPH;
166 else {
167 *aRole = nsIAccessibleRole::ROLE_TEXT_CONTAINER; // In ATK this works
170 return NS_OK;
173 NS_IMETHODIMP
174 nsHyperTextAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState)
176 nsresult rv = nsAccessibleWrap::GetState(aState, aExtraState);
177 NS_ENSURE_SUCCESS(rv, rv);
178 if (!mDOMNode || !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 nsCOMPtr<nsIRenderingContext> rc;
276 shell->CreateRenderingContext(frame, getter_AddRefs(rc));
277 NS_ENSURE_TRUE(rc, screenRect);
279 const nsStyleFont *font = frame->GetStyleFont();
280 const nsStyleVisibility *visibility = frame->GetStyleVisibility();
282 rv = rc->SetFont(font->mFont, visibility->mLangGroup);
283 NS_ENSURE_SUCCESS(rv, screenRect);
285 nsPresContext *context = shell->GetPresContext();
287 while (frame && startContentOffset < endContentOffset) {
288 // Start with this frame's screen rect, which we will
289 // shrink based on the substring we care about within it.
290 // We will then add that frame to the total screenRect we
291 // are returning.
292 nsIntRect frameScreenRect = frame->GetScreenRectExternal();
294 // Get the length of the substring in this frame that we want the bounds for
295 PRInt32 startFrameTextOffset, endFrameTextOffset;
296 frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
297 PRInt32 frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
298 PRInt32 seekLength = endContentOffset - startContentOffset;
299 PRInt32 frameSubStringLength = PR_MIN(frameTotalTextLength - startContentOffsetInFrame, seekLength);
301 // Add the point where the string starts to the frameScreenRect
302 nsPoint frameTextStartPoint;
303 rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
304 NS_ENSURE_SUCCESS(rv, nsRect());
305 frameScreenRect.x += context->AppUnitsToDevPixels(frameTextStartPoint.x);
307 // Use the point for the end offset to calculate the width
308 nsPoint frameTextEndPoint;
309 rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
310 NS_ENSURE_SUCCESS(rv, nsRect());
311 frameScreenRect.width = context->AppUnitsToDevPixels(frameTextEndPoint.x - frameTextStartPoint.x);
313 screenRect.UnionRect(frameScreenRect, screenRect);
315 // Get ready to loop back for next frame continuation
316 startContentOffset += frameSubStringLength;
317 startContentOffsetInFrame = 0;
318 frame = frame->GetNextContinuation();
321 return screenRect;
325 * Gets the specified text.
327 nsIFrame*
328 nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& aEndOffset,
329 nsAString *aText, nsIFrame **aEndFrame,
330 nsIntRect *aBoundsRect,
331 nsIAccessible **aStartAcc,
332 nsIAccessible **aEndAcc)
334 if (aStartOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
335 GetCharacterCount(&aStartOffset);
337 if (aStartOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
338 GetCaretOffset(&aStartOffset);
340 if (aEndOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
341 GetCharacterCount(&aEndOffset);
343 if (aEndOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
344 GetCaretOffset(&aEndOffset);
347 PRInt32 startOffset = aStartOffset;
348 PRInt32 endOffset = aEndOffset;
349 // XXX this prevents text interface usage on <input type="password">
350 PRBool isPassword = (Role(this) == nsIAccessibleRole::ROLE_PASSWORD_TEXT);
352 // Clear out parameters and set up loop
353 if (aText) {
354 aText->Truncate();
356 if (endOffset < 0) {
357 const PRInt32 kMaxTextLength = 32767;
358 endOffset = kMaxTextLength; // Max end offset
360 else if (startOffset > endOffset) {
361 return nsnull;
364 nsIFrame *startFrame = nsnull;
365 if (aEndFrame) {
366 *aEndFrame = nsnull;
368 if (aBoundsRect) {
369 aBoundsRect->Empty();
371 if (aStartAcc)
372 *aStartAcc = nsnull;
373 if (aEndAcc)
374 *aEndAcc = nsnull;
376 nsIntRect unionRect;
377 nsCOMPtr<nsIAccessible> accessible, lastAccessible;
379 gfxSkipChars skipChars;
380 gfxSkipCharsIterator iter;
382 // Loop through children and collect valid offsets, text and bounds
383 // depending on what we need for out parameters
384 while (NextChild(accessible)) {
385 lastAccessible = accessible;
386 nsCOMPtr<nsPIAccessNode> accessNode(do_QueryInterface(accessible));
387 nsIFrame *frame = accessNode->GetFrame();
388 if (!frame) {
389 continue;
391 nsIFrame *primaryFrame = frame;
392 if (IsText(accessible)) {
393 // We only need info up to rendered offset -- that is what we're
394 // converting to content offset
395 PRInt32 substringEndOffset = -1;
396 PRUint32 ourRenderedStart = 0;
397 PRInt32 ourContentStart = 0;
398 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
399 nsresult rv = frame->GetRenderedText(nsnull, &skipChars, &iter);
400 if (NS_SUCCEEDED(rv)) {
401 ourRenderedStart = iter.GetSkippedOffset();
402 ourContentStart = iter.GetOriginalOffset();
403 substringEndOffset =
404 iter.ConvertOriginalToSkipped(skipChars.GetOriginalCharCount() +
405 ourContentStart) - ourRenderedStart;
408 if (substringEndOffset < 0) {
409 // XXX for non-textframe text like list bullets,
410 // should go away after list bullet rewrite
411 substringEndOffset = TextLength(accessible);
413 if (startOffset < substringEndOffset) {
414 // Our start is within this substring
415 if (startOffset > 0 || endOffset < substringEndOffset) {
416 // We don't want the whole string for this accessible
417 // Get out the continuing text frame with this offset
418 PRInt32 outStartLineUnused;
419 PRInt32 contentOffset;
420 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
421 contentOffset = iter.ConvertSkippedToOriginal(startOffset) +
422 ourRenderedStart - ourContentStart;
424 else {
425 contentOffset = startOffset;
427 frame->GetChildFrameContainingOffset(contentOffset, PR_TRUE,
428 &outStartLineUnused, &frame);
429 if (aEndFrame) {
430 *aEndFrame = frame; // We ended in the current frame
431 if (aEndAcc)
432 NS_ADDREF(*aEndAcc = accessible);
434 if (substringEndOffset > endOffset) {
435 // Need to stop before the end of the available text
436 substringEndOffset = endOffset;
438 aEndOffset = endOffset;
440 if (aText) {
441 if (isPassword) {
442 for (PRInt32 count = startOffset; count < substringEndOffset; count ++)
443 *aText += '*'; // Show *'s only for password text
445 else {
446 nsCOMPtr<nsPIAccessible> pAcc(do_QueryInterface(accessible));
447 pAcc->AppendTextTo(*aText, startOffset,
448 substringEndOffset - startOffset);
451 if (aBoundsRect) { // Caller wants the bounds of the text
452 aBoundsRect->UnionRect(*aBoundsRect,
453 GetBoundsForString(primaryFrame, startOffset,
454 substringEndOffset));
456 if (!startFrame) {
457 startFrame = frame;
458 aStartOffset = startOffset;
459 if (aStartAcc)
460 NS_ADDREF(*aStartAcc = accessible);
462 // We already started copying in this accessible's string,
463 // for the next accessible we'll start at offset 0
464 startOffset = 0;
466 else {
467 // We have not found the start position yet, get the new startOffset
468 // that is relative to next accessible
469 startOffset -= substringEndOffset;
471 // The endOffset needs to be relative to the new startOffset
472 endOffset -= substringEndOffset;
474 else {
475 // Embedded object, append marker
476 // XXX Append \n for <br>'s
477 if (startOffset >= 1) {
478 -- startOffset;
480 else {
481 if (endOffset > 0) {
482 if (aText) {
483 if (frame->GetType() == nsAccessibilityAtoms::brFrame) {
484 *aText += kForcedNewLineChar;
485 } else if (MustPrune(this)) {
486 *aText += kImaginaryEmbeddedObjectChar;
487 // Expose imaginary embedded object character if the accessible
488 // hans't children.
489 } else {
490 *aText += kEmbeddedObjectChar;
493 if (aBoundsRect) {
494 aBoundsRect->UnionRect(*aBoundsRect,
495 frame->GetScreenRectExternal());
498 if (!startFrame) {
499 startFrame = frame;
500 aStartOffset = 0;
501 if (aStartAcc)
502 NS_ADDREF(*aStartAcc = accessible);
505 -- endOffset;
507 if (endOffset <= 0 && startFrame) {
508 break; // If we don't have startFrame yet, get that in next loop iteration
512 if (aStartAcc && !*aStartAcc) {
513 NS_IF_ADDREF(*aStartAcc = lastAccessible);
515 if (aEndFrame && !*aEndFrame) {
516 *aEndFrame = startFrame;
517 if (aStartAcc && aEndAcc)
518 NS_IF_ADDREF(*aEndAcc = *aStartAcc);
521 return startFrame;
524 NS_IMETHODIMP nsHyperTextAccessible::GetText(PRInt32 aStartOffset, PRInt32 aEndOffset, nsAString &aText)
526 if (!mDOMNode) {
527 return NS_ERROR_FAILURE;
529 return GetPosAndText(aStartOffset, aEndOffset, &aText) ? NS_OK : NS_ERROR_FAILURE;
533 * Gets the character count.
535 NS_IMETHODIMP nsHyperTextAccessible::GetCharacterCount(PRInt32 *aCharacterCount)
537 *aCharacterCount = 0;
538 if (!mDOMNode) {
539 return NS_ERROR_FAILURE;
542 nsCOMPtr<nsIAccessible> accessible;
544 while (NextChild(accessible)) {
545 PRInt32 textLength = TextLength(accessible);
546 NS_ENSURE_TRUE(textLength >= 0, nsnull);
547 *aCharacterCount += textLength;
549 return NS_OK;
553 * Gets the specified character.
555 NS_IMETHODIMP nsHyperTextAccessible::GetCharacterAtOffset(PRInt32 aOffset, PRUnichar *aCharacter)
557 if (!mDOMNode) {
558 return NS_ERROR_FAILURE;
560 nsAutoString text;
561 nsresult rv = GetText(aOffset, aOffset + 1, text);
562 if (NS_FAILED(rv)) {
563 return rv;
566 if (text.IsEmpty()) {
567 return NS_ERROR_FAILURE;
569 *aCharacter = text.First();
570 return NS_OK;
573 nsresult nsHyperTextAccessible::DOMPointToHypertextOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset,
574 PRInt32* aHyperTextOffset,
575 nsIAccessible **aFinalAccessible,
576 PRBool aIsEndOffset)
578 // Turn a DOM Node and offset into an offset into this hypertext.
579 // On failure, return null. On success, return the DOM node which contains the offset.
580 NS_ENSURE_ARG_POINTER(aHyperTextOffset);
581 *aHyperTextOffset = 0;
583 if (!aNode) {
584 return NS_ERROR_FAILURE;
586 if (aFinalAccessible) {
587 *aFinalAccessible = nsnull;
590 PRUint32 addTextOffset = 0;
591 nsCOMPtr<nsIDOMNode> findNode;
593 unsigned short nodeType;
594 aNode->GetNodeType(&nodeType);
595 if (aNodeOffset == -1) {
596 findNode = aNode;
598 else if (nodeType == nsIDOMNode::TEXT_NODE) {
599 // For text nodes, aNodeOffset comes in as a character offset
600 // Text offset will be added at the end, if we find the offset in this hypertext
601 // We want the "skipped" offset into the text (rendered text without the extra whitespace)
602 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
603 NS_ASSERTION(content, "No nsIContent for dom node");
604 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
605 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
606 nsIFrame *frame = presShell->GetPrimaryFrameFor(content);
607 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
608 nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &addTextOffset);
609 NS_ENSURE_SUCCESS(rv, rv);
610 // Get the child node and
611 findNode = aNode;
613 else {
614 // For non-text nodes, aNodeOffset comes in as a child node index
615 nsCOMPtr<nsIContent> parentContent(do_QueryInterface(aNode));
616 // Should not happen, but better to protect against crash if doc node is somehow passed in
617 NS_ENSURE_TRUE(parentContent, NS_ERROR_FAILURE);
618 // findNode could be null if aNodeOffset == # of child nodes, which means one of two things:
619 // 1) we're at the end of the children, keep findNode = null, so that we get the last possible offset
620 // 2) there are no children and the passed-in node is mDOMNode, which means we're an aempty nsIAccessibleText
621 // 3) there are no children, and the passed-in node is not mDOMNode -- use parentContent for the node to find
623 findNode = do_QueryInterface(parentContent->GetChildAt(aNodeOffset));
624 if (!findNode && !aNodeOffset) {
625 if (SameCOMIdentity(parentContent, mDOMNode)) {
626 // There are no children, which means this is an empty nsIAccessibleText, in which
627 // case we can only be at hypertext offset 0
628 *aHyperTextOffset = 0;
629 return NS_OK;
631 findNode = do_QueryInterface(parentContent); // Case #2: there are no children
635 // Get accessible for this findNode, or if that node isn't accessible, use the
636 // accessible for the next DOM node which has one (based on forward depth first search)
637 nsCOMPtr<nsIAccessible> descendantAccessible;
638 if (findNode) {
639 nsCOMPtr<nsIContent> findContent = do_QueryInterface(findNode);
640 if (findContent->IsNodeOfType(nsINode::eHTML) &&
641 findContent->NodeInfo()->Equals(nsAccessibilityAtoms::br)) {
642 nsIContent *parent = findContent->GetParent();
643 if (parent &&
644 parent->IsRootOfNativeAnonymousSubtree() &&
645 parent->GetChildCount() == 1) {
646 // This <br> is the only node in a text control, therefore it is the hacky
647 // "bogus node" used when there is no text in a control
648 *aHyperTextOffset = 0;
649 return NS_OK;
652 descendantAccessible = GetFirstAvailableAccessible(findNode);
654 // From the descendant, go up and get the immediate child of this hypertext
655 nsCOMPtr<nsIAccessible> childAccessible;
656 while (descendantAccessible) {
657 nsCOMPtr<nsIAccessible> parentAccessible;
658 descendantAccessible->GetParent(getter_AddRefs(parentAccessible));
659 if (this == parentAccessible) {
660 childAccessible = descendantAccessible;
661 break;
663 // This offset no longer applies because the passed-in text object is not a child
664 // of the hypertext. This happens when there are nested hypertexts, e.g.
665 // <div>abc<h1>def</h1>ghi</div>
666 // If the passed-in DOM point was not on a direct child of the hypertext, we will
667 // return the offset for that entire hypertext
668 if (aIsEndOffset) {
669 // Not inclusive, the indicated char comes at index before this offset
670 // If the end offset is after the first character of the passed in object, use 1 for
671 // addTextOffset, to put us after the embedded object char. We'll only treat the offset as
672 // before the embedded object char if we end at the very beginning of the child.
673 addTextOffset = addTextOffset > 0;
675 else {
676 // Start offset, inclusive
677 // Make sure the offset lands on the embedded object character in order to indicate
678 // the true inner offset is inside the subtree for that link
679 addTextOffset = (TextLength(descendantAccessible) == static_cast<PRInt32>(addTextOffset)) ? 1 : 0;
681 descendantAccessible = parentAccessible;
684 // Loop through, adding offsets until we reach childAccessible
685 // If childAccessible is null we will end up adding up the entire length of
686 // the hypertext, which is good -- it just means our offset node
687 // came after the last accessible child's node
688 nsCOMPtr<nsIAccessible> accessible;
689 while (NextChild(accessible) && accessible != childAccessible) {
690 PRInt32 textLength = TextLength(accessible);
691 NS_ENSURE_TRUE(textLength >= 0, nsnull);
692 *aHyperTextOffset += textLength;
694 if (accessible) {
695 *aHyperTextOffset += addTextOffset;
696 NS_ASSERTION(accessible == childAccessible, "These should be equal whenever we exit loop and accessible != nsnull");
697 if (aFinalAccessible && (NextChild(accessible) || static_cast<PRInt32>(addTextOffset) < TextLength(childAccessible))) {
698 // If not at end of last text node, we will return the accessible we were in
699 NS_ADDREF(*aFinalAccessible = childAccessible);
703 return NS_OK;
706 nsresult
707 nsHyperTextAccessible::HypertextOffsetToDOMPoint(PRInt32 aHTOffset,
708 nsIDOMNode **aNode,
709 PRInt32 *aOffset)
711 nsCOMPtr<nsIDOMNode> endNode;
712 PRInt32 endOffset;
714 return HypertextOffsetsToDOMRange(aHTOffset, aHTOffset, aNode, aOffset,
715 getter_AddRefs(endNode), &endOffset);
718 nsresult
719 nsHyperTextAccessible::HypertextOffsetsToDOMRange(PRInt32 aStartHTOffset,
720 PRInt32 aEndHTOffset,
721 nsIDOMNode **aStartNode,
722 PRInt32 *aStartOffset,
723 nsIDOMNode **aEndNode,
724 PRInt32 *aEndOffset)
726 NS_ENSURE_ARG_POINTER(aStartNode);
727 *aStartNode = nsnull;
729 NS_ENSURE_ARG_POINTER(aStartOffset);
730 *aStartOffset = -1;
732 NS_ENSURE_ARG_POINTER(aEndNode);
733 *aEndNode = nsnull;
735 NS_ENSURE_ARG_POINTER(aEndOffset);
736 *aEndOffset = -1;
738 // If the given offsets are 0 and associated editor is empty then return
739 // collapsed range with editor root element as range container.
740 if (aStartHTOffset == 0 && aEndHTOffset == 0) {
741 nsCOMPtr<nsIEditor> editor;
742 GetAssociatedEditor(getter_AddRefs(editor));
743 if (editor) {
744 PRBool isEmpty = PR_FALSE;
745 editor->GetDocumentIsEmpty(&isEmpty);
746 if (isEmpty) {
747 nsCOMPtr<nsIDOMElement> editorRootElm;
748 editor->GetRootElement(getter_AddRefs(editorRootElm));
750 nsCOMPtr<nsIDOMNode> editorRoot(do_QueryInterface(editorRootElm));
751 if (editorRoot) {
752 *aStartOffset = *aEndOffset = 0;
753 NS_ADDREF(*aStartNode = editorRoot);
754 NS_ADDREF(*aEndNode = editorRoot);
756 return NS_OK;
762 nsCOMPtr<nsIAccessible> startAcc, endAcc;
763 PRInt32 startOffset = aStartHTOffset, endOffset = aEndHTOffset;
764 nsIFrame *startFrame = nsnull, *endFrame = nsnull;
766 startFrame = GetPosAndText(startOffset, endOffset, nsnull, &endFrame, nsnull,
767 getter_AddRefs(startAcc), getter_AddRefs(endAcc));
768 if (!startAcc || !endAcc)
769 return NS_ERROR_FAILURE;
771 nsCOMPtr<nsIDOMNode> startNode, endNode;
772 nsresult rv = GetDOMPointByFrameOffset(startFrame, startOffset, startAcc,
773 getter_AddRefs(startNode),
774 &startOffset);
775 NS_ENSURE_SUCCESS(rv, rv);
777 if (aStartHTOffset != aEndHTOffset) {
778 rv = GetDOMPointByFrameOffset(endFrame, endOffset, endAcc,
779 getter_AddRefs(endNode), &endOffset);
780 NS_ENSURE_SUCCESS(rv, rv);
781 } else {
782 endNode = startNode;
783 endOffset = startOffset;
786 NS_ADDREF(*aStartNode = startNode);
787 *aStartOffset = startOffset;
789 NS_ADDREF(*aEndNode = endNode);
790 *aEndOffset = endOffset;
792 return NS_OK;
795 PRInt32
796 nsHyperTextAccessible::GetRelativeOffset(nsIPresShell *aPresShell,
797 nsIFrame *aFromFrame,
798 PRInt32 aFromOffset,
799 nsIAccessible *aFromAccessible,
800 nsSelectionAmount aAmount,
801 nsDirection aDirection,
802 PRBool aNeedsStart)
804 const PRBool kIsJumpLinesOk = PR_TRUE; // okay to jump lines
805 const PRBool kIsScrollViewAStop = PR_FALSE; // do not stop at scroll views
806 const PRBool kIsKeyboardSelect = PR_TRUE; // is keyboard selection
807 const PRBool kIsVisualBidi = PR_FALSE; // use visual order for bidi text
809 EWordMovementType wordMovementType = aNeedsStart ? eStartWord : eEndWord;
810 if (aAmount == eSelectLine) {
811 aAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
814 // Ask layout for the new node and offset, after moving the appropriate amount
815 nsPeekOffsetStruct pos;
817 nsresult rv;
818 PRInt32 contentOffset = aFromOffset;
819 if (IsText(aFromAccessible)) {
820 nsCOMPtr<nsPIAccessNode> accessNode(do_QueryInterface(aFromAccessible));
821 NS_ASSERTION(accessNode, "nsIAccessible doesn't support nsPIAccessNode");
823 nsIFrame *frame = accessNode->GetFrame();
824 NS_ENSURE_TRUE(frame, -1);
825 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
826 rv = RenderedToContentOffset(frame, aFromOffset, &contentOffset);
827 NS_ENSURE_SUCCESS(rv, -1);
831 pos.SetData(aAmount, aDirection, contentOffset,
832 0, kIsJumpLinesOk, kIsScrollViewAStop, kIsKeyboardSelect, kIsVisualBidi,
833 wordMovementType);
834 rv = aFromFrame->PeekOffset(&pos);
835 if (NS_FAILED(rv)) {
836 if (aDirection == eDirPrevious) {
837 // Use passed-in frame as starting point in failure case for now,
838 // this is a hack to deal with starting on a list bullet frame,
839 // which fails in PeekOffset() because the line iterator doesn't see it.
840 // XXX Need to look at our overall handling of list bullets, which are an odd case
841 pos.mResultContent = aFromFrame->GetContent();
842 PRInt32 endOffsetUnused;
843 aFromFrame->GetOffsets(pos.mContentOffset, endOffsetUnused);
845 else {
846 return -1;
850 // Turn the resulting node and offset into a hyperTextOffset
851 PRInt32 hyperTextOffset;
852 nsCOMPtr<nsIDOMNode> resultNode = do_QueryInterface(pos.mResultContent);
853 NS_ENSURE_TRUE(resultNode, -1);
855 nsCOMPtr<nsIAccessible> finalAccessible;
856 rv = DOMPointToHypertextOffset(resultNode, pos.mContentOffset, &hyperTextOffset,
857 getter_AddRefs(finalAccessible),
858 aDirection == eDirNext);
859 // If finalAccessible == nsnull, then DOMPointToHypertextOffset() searched through the hypertext
860 // children without finding the node/offset position
861 NS_ENSURE_SUCCESS(rv, -1);
863 if (!finalAccessible && aDirection == eDirPrevious) {
864 // If we reached the end during search, this means we didn't find the DOM point
865 // and we're actually at the start of the paragraph
866 hyperTextOffset = 0;
868 else if (aAmount == eSelectBeginLine) {
869 // For line selection with needsStart, set start of line exactly to line break
870 if (pos.mContentOffset == 0 && mFirstChild &&
871 Role(mFirstChild) == nsIAccessibleRole::ROLE_STATICTEXT &&
872 TextLength(mFirstChild) == hyperTextOffset) {
873 // XXX Bullet hack -- we should remove this once list bullets use anonymous content
874 hyperTextOffset = 0;
876 if (!aNeedsStart && hyperTextOffset > 0) {
877 -- hyperTextOffset;
880 else if (aAmount == eSelectEndLine && finalAccessible) {
881 // If not at very end of hypertext, we may need change the end of line offset by 1,
882 // to make sure we are in the right place relative to the line ending
883 if (Role(finalAccessible) == nsIAccessibleRole::ROLE_WHITESPACE) { // Landed on <br> hard line break
884 // if aNeedsStart, set end of line exactly 1 character past line break
885 // XXX It would be cleaner if we did not have to have the hard line break check,
886 // and just got the correct results from PeekOffset() for the <br> case -- the returned offset should
887 // come after the new line, as it does in other cases.
888 ++ hyperTextOffset; // Get past hard line break
890 // We are now 1 character past the line break
891 if (!aNeedsStart) {
892 -- hyperTextOffset;
896 return hyperTextOffset;
900 Gets the specified text relative to aBoundaryType, which means:
901 BOUNDARY_CHAR The character before/at/after the offset is returned.
902 BOUNDARY_WORD_START From the word start before/at/after the offset to the next word start.
903 BOUNDARY_WORD_END From the word end before/at/after the offset to the next work end.
904 BOUNDARY_LINE_START From the line start before/at/after the offset to the next line start.
905 BOUNDARY_LINE_END From the line end before/at/after the offset to the next line start.
908 nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTextBoundary aBoundaryType,
909 PRInt32 aOffset, PRInt32 *aStartOffset, PRInt32 *aEndOffset,
910 nsAString &aText)
912 aText.Truncate();
914 NS_ENSURE_ARG_POINTER(aStartOffset);
915 NS_ENSURE_ARG_POINTER(aEndOffset);
916 *aStartOffset = *aEndOffset = 0;
918 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
919 if (!presShell) {
920 return NS_ERROR_FAILURE;
923 if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
924 GetCharacterCount(&aOffset);
926 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
927 GetCaretOffset(&aOffset);
928 if (aOffset > 0 && (aBoundaryType == BOUNDARY_LINE_START ||
929 aBoundaryType == BOUNDARY_LINE_END)) {
930 // It is the same character offset when the caret is visually at the very end of a line
931 // or the start of a new line. Getting text at the line should provide the line with the visual caret,
932 // otherwise screen readers will announce the wrong line as the user presses up or down arrow and land
933 // at the end of a line.
934 nsCOMPtr<nsISelection> domSel;
935 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
936 nsnull, getter_AddRefs(domSel));
937 NS_ENSURE_SUCCESS(rv, rv);
939 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSel));
940 nsCOMPtr<nsFrameSelection> frameSelection;
941 rv = privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
942 NS_ENSURE_SUCCESS(rv, rv);
944 if (frameSelection->GetHint() == nsFrameSelection::HINTLEFT) {
945 -- aOffset; // We are at the start of a line
949 else if (aOffset < 0) {
950 return NS_ERROR_FAILURE;
953 nsSelectionAmount amount;
954 PRBool needsStart = PR_FALSE;
955 switch (aBoundaryType) {
956 case BOUNDARY_CHAR:
957 amount = eSelectCharacter;
958 if (aType == eGetAt)
959 aType = eGetAfter; // Avoid returning 2 characters
960 break;
962 case BOUNDARY_WORD_START:
963 needsStart = PR_TRUE;
964 amount = eSelectWord;
965 break;
967 case BOUNDARY_WORD_END:
968 amount = eSelectWord;
969 break;
971 case BOUNDARY_LINE_START:
972 // Newlines are considered at the end of a line. Since getting
973 // the BOUNDARY_LINE_START gets the text from the line-start to the next
974 // line-start, the newline is included at the end of the string.
975 needsStart = PR_TRUE;
976 amount = eSelectLine;
977 break;
979 case BOUNDARY_LINE_END:
980 // Newlines are considered at the end of a line. Since getting
981 // the BOUNDARY_END_START gets the text from the line-end to the next
982 //line-end, the newline is included at the beginning of the string.
983 amount = eSelectLine;
984 break;
986 case BOUNDARY_ATTRIBUTE_RANGE:
988 nsresult rv = GetTextAttributes(PR_FALSE, aOffset,
989 aStartOffset, aEndOffset, nsnull);
990 NS_ENSURE_SUCCESS(rv, rv);
992 return GetText(*aStartOffset, *aEndOffset, aText);
995 default: // Note, sentence support is deprecated and falls through to here
996 return NS_ERROR_INVALID_ARG;
999 PRInt32 startOffset = aOffset + (aBoundaryType == BOUNDARY_LINE_END); // Avoid getting the previous line
1000 PRInt32 endOffset = startOffset;
1002 // Convert offsets to frame-relative
1003 nsCOMPtr<nsIAccessible> startAcc;
1004 nsIFrame *startFrame = GetPosAndText(startOffset, endOffset, nsnull, nsnull,
1005 nsnull, getter_AddRefs(startAcc));
1007 if (!startFrame) {
1008 PRInt32 textLength;
1009 GetCharacterCount(&textLength);
1010 if (aBoundaryType == BOUNDARY_LINE_START && aOffset > 0 && aOffset == textLength) {
1011 // Asking for start of line, while on last character
1012 nsCOMPtr<nsPIAccessNode> startAccessNode = do_QueryInterface(startAcc);
1013 if (startAccessNode) {
1014 startFrame = startAccessNode->GetFrame();
1017 if (!startFrame) {
1018 return aOffset > textLength ? NS_ERROR_FAILURE : NS_OK;
1020 else {
1021 // We're on the last continuation since we're on the last character
1022 startFrame = startFrame->GetLastContinuation();
1026 PRInt32 finalStartOffset, finalEndOffset;
1028 // If aType == eGetAt we'll change both the start and end offset from
1029 // the original offset
1030 if (aType == eGetAfter) {
1031 finalStartOffset = aOffset;
1033 else {
1034 finalStartOffset = GetRelativeOffset(presShell, startFrame, startOffset,
1035 startAcc, amount, eDirPrevious,
1036 needsStart);
1037 NS_ENSURE_TRUE(finalStartOffset >= 0, NS_ERROR_FAILURE);
1040 if (aType == eGetBefore) {
1041 endOffset = aOffset;
1043 else {
1044 // Start moving forward from the start so that we don't get
1045 // 2 words/lines if the offset occured on whitespace boundary
1046 // Careful, startOffset and endOffset are passed by reference to GetPosAndText() and changed
1047 // For BOUNDARY_LINE_END, make sure we start of this line
1048 startOffset = endOffset = finalStartOffset + (aBoundaryType == BOUNDARY_LINE_END);
1049 nsCOMPtr<nsIAccessible> endAcc;
1050 nsIFrame *endFrame = GetPosAndText(startOffset, endOffset, nsnull, nsnull,
1051 nsnull, getter_AddRefs(endAcc));
1052 if (endAcc && Role(endAcc) == nsIAccessibleRole::ROLE_STATICTEXT) {
1053 // Static text like list bullets will ruin our forward calculation,
1054 // since the caret cannot be in the static text. Start just after the static text.
1055 startOffset = endOffset = finalStartOffset + (aBoundaryType == BOUNDARY_LINE_END) + 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 = nsAccUtils::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 = nsAccUtils::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(do_QueryInterface(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 nsCOMPtr<nsPIAccessNode> accessNode(do_QueryInterface(accessible));
1366 nsIFrame *primaryFrame = accessNode->GetFrame();
1367 NS_ENSURE_TRUE(primaryFrame, NS_ERROR_FAILURE);
1369 nsIFrame *frame = primaryFrame;
1370 while (frame) {
1371 nsIContent *content = frame->GetContent();
1372 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1373 nsPoint pointInFrame = pointInHyperText - frame->GetOffsetToExternal(hyperFrame);
1374 nsSize frameSize = frame->GetSize();
1375 if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) {
1376 // Finished
1377 if (frame->GetType() == nsAccessibilityAtoms::textFrame) {
1378 nsIFrame::ContentOffsets contentOffsets = frame->GetContentOffsetsFromPointExternal(pointInFrame, PR_TRUE);
1379 if (contentOffsets.IsNull() || contentOffsets.content != content) {
1380 return NS_OK; // Not found, will return -1
1382 PRUint32 addToOffset;
1383 nsresult rv = ContentToRenderedOffset(primaryFrame,
1384 contentOffsets.offset,
1385 &addToOffset);
1386 NS_ENSURE_SUCCESS(rv, rv);
1387 offset += addToOffset;
1389 *aOffset = offset;
1390 return NS_OK;
1392 frame = frame->GetNextContinuation();
1394 PRInt32 textLength = TextLength(accessible);
1395 NS_ENSURE_TRUE(textLength >= 0, NS_ERROR_FAILURE);
1396 offset += textLength;
1399 return NS_OK; // Not found, will return -1
1402 // ------- nsIAccessibleHyperText ---------------
1403 NS_IMETHODIMP
1404 nsHyperTextAccessible::GetLinkCount(PRInt32 *aLinkCount)
1406 NS_ENSURE_ARG_POINTER(aLinkCount);
1407 *aLinkCount = 0;
1408 if (!mDOMNode) {
1409 return NS_ERROR_FAILURE;
1412 nsCOMPtr<nsIAccessible> accessible;
1414 while (NextChild(accessible)) {
1415 if (IsEmbeddedObject(accessible)) {
1416 ++*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 (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 = Role(accessible);
1458 if (role == nsIAccessibleRole::ROLE_TEXT_LEAF ||
1459 role == nsIAccessibleRole::ROLE_STATICTEXT) {
1460 PRInt32 textLength = 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 nsAccUtils::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 || !nsAccUtils::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 nsCOMPtr<nsILineIterator> 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 nsCOMPtr<nsILineIterator> lineIterForSibling = do_QueryInterface(sibling);
1696 if (lineIterForSibling) {
1697 PRInt32 addLines;
1698 // For the frames before that grab all the lines
1699 lineIterForSibling->GetNumLines(&addLines);
1700 lineNumber += addLines;
1702 sibling = sibling->GetNextSibling();
1705 // Get the line number relative to the container with lines
1706 if (!lineIterForCaret) { // Add the caret line just once
1707 lineIterForCaret = do_QueryInterface(parentFrame);
1708 if (lineIterForCaret) {
1709 // Ancestor of caret
1710 PRInt32 addLines;
1711 lineIterForCaret->FindLineContaining(caretFrame, &addLines);
1712 lineNumber += addLines;
1716 caretFrame = parentFrame;
1719 NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
1720 return lineNumber;
1723 nsresult
1724 nsHyperTextAccessible::GetSelections(PRInt16 aType,
1725 nsISelectionController **aSelCon,
1726 nsISelection **aDomSel,
1727 nsCOMArray<nsIDOMRange>* aRanges)
1729 if (!mDOMNode) {
1730 return NS_ERROR_FAILURE;
1732 if (aSelCon) {
1733 *aSelCon = nsnull;
1735 if (aDomSel) {
1736 *aDomSel = nsnull;
1738 if (aRanges) {
1739 aRanges->Clear();
1742 nsCOMPtr<nsISelection> domSel;
1743 nsCOMPtr<nsISelectionController> selCon;
1745 nsCOMPtr<nsIEditor> editor;
1746 GetAssociatedEditor(getter_AddRefs(editor));
1747 nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
1748 if (peditor) {
1749 // Case 1: plain text editor
1750 // This is for form controls which have their own
1751 // selection controller separate from the document, for example
1752 // HTML:input, HTML:textarea, XUL:textbox, etc.
1753 editor->GetSelectionController(getter_AddRefs(selCon));
1755 else {
1756 // Case 2: rich content subtree (can be rich editor)
1757 // This uses the selection controller from the entire document
1758 nsIFrame *frame = GetFrame();
1759 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
1761 // Get the selection and selection controller
1762 frame->GetSelectionController(GetPresContext(),
1763 getter_AddRefs(selCon));
1765 NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
1767 selCon->GetSelection(aType, getter_AddRefs(domSel));
1768 NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE);
1770 if (aSelCon) {
1771 NS_ADDREF(*aSelCon = selCon);
1773 if (aDomSel) {
1774 NS_ADDREF(*aDomSel = domSel);
1777 if (aRanges) {
1778 nsCOMPtr<nsISelection2> selection2(do_QueryInterface(domSel));
1779 NS_ENSURE_TRUE(selection2, NS_ERROR_FAILURE);
1781 nsCOMPtr<nsIDOMNode> startNode(mDOMNode);
1782 if (peditor) {
1783 nsCOMPtr<nsIDOMElement> editorRoot;
1784 editor->GetRootElement(getter_AddRefs(editorRoot));
1785 startNode = do_QueryInterface(editorRoot);
1787 NS_ENSURE_STATE(startNode);
1789 nsCOMPtr<nsIDOMNodeList> childNodes;
1790 nsresult rv = startNode->GetChildNodes(getter_AddRefs(childNodes));
1791 NS_ENSURE_SUCCESS(rv, rv);
1792 PRUint32 numChildren;
1793 rv = childNodes->GetLength(&numChildren);
1794 NS_ENSURE_SUCCESS(rv, rv);
1795 rv = selection2->GetRangesForIntervalCOMArray(startNode, 0,
1796 startNode, numChildren,
1797 PR_TRUE, aRanges);
1798 NS_ENSURE_SUCCESS(rv, rv);
1799 // Remove collapsed ranges
1800 PRInt32 numRanges = aRanges->Count();
1801 for (PRInt32 count = 0; count < numRanges; count ++) {
1802 PRBool isCollapsed;
1803 (*aRanges)[count]->GetCollapsed(&isCollapsed);
1804 if (isCollapsed) {
1805 aRanges->RemoveObjectAt(count);
1806 -- numRanges;
1807 -- count;
1812 return NS_OK;
1816 * Gets the number of selected regions.
1818 NS_IMETHODIMP nsHyperTextAccessible::GetSelectionCount(PRInt32 *aSelectionCount)
1820 nsCOMPtr<nsISelection> domSel;
1821 nsCOMArray<nsIDOMRange> ranges;
1822 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1823 nsnull, nsnull, &ranges);
1824 NS_ENSURE_SUCCESS(rv, rv);
1826 *aSelectionCount = ranges.Count();
1828 return NS_OK;
1832 * Gets the start and end offset of the specified selection.
1834 NS_IMETHODIMP nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum, PRInt32 *aStartOffset, PRInt32 *aEndOffset)
1836 *aStartOffset = *aEndOffset = 0;
1838 nsCOMPtr<nsISelection> domSel;
1839 nsCOMArray<nsIDOMRange> ranges;
1840 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1841 nsnull, getter_AddRefs(domSel), &ranges);
1842 NS_ENSURE_SUCCESS(rv, rv);
1844 PRInt32 rangeCount = ranges.Count();
1845 if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
1846 return NS_ERROR_INVALID_ARG;
1848 nsCOMPtr<nsIDOMRange> range = ranges[aSelectionNum];
1850 // Get start point
1851 nsCOMPtr<nsIDOMNode> startNode;
1852 range->GetStartContainer(getter_AddRefs(startNode));
1853 PRInt32 startOffset;
1854 range->GetStartOffset(&startOffset);
1856 // Get end point
1857 nsCOMPtr<nsIDOMNode> endNode;
1858 range->GetEndContainer(getter_AddRefs(endNode));
1859 PRInt32 endOffset;
1860 range->GetEndOffset(&endOffset);
1862 PRInt16 rangeCompareResult;
1863 rv = range->CompareBoundaryPoints(nsIDOMRange::START_TO_END, range, &rangeCompareResult);
1864 NS_ENSURE_SUCCESS(rv, rv);
1866 if (rangeCompareResult < 0) {
1867 // Make sure start is before end, by swapping offsets
1868 // This occurs when the user selects backwards in the text
1869 startNode.swap(endNode);
1870 PRInt32 tempOffset = startOffset;
1871 startOffset = endOffset;
1872 endOffset = tempOffset;
1875 nsCOMPtr<nsIAccessible> startAccessible;
1876 rv = DOMPointToHypertextOffset(startNode, startOffset, aStartOffset, getter_AddRefs(startAccessible));
1877 NS_ENSURE_SUCCESS(rv, rv);
1878 if (!startAccessible) {
1879 *aStartOffset = 0; // Could not find start point within this hypertext, so starts before
1882 return DOMPointToHypertextOffset(endNode, endOffset, aEndOffset, nsnull, PR_TRUE);
1886 * Changes the start and end offset of the specified selection.
1888 NS_IMETHODIMP
1889 nsHyperTextAccessible::SetSelectionBounds(PRInt32 aSelectionNum,
1890 PRInt32 aStartOffset,
1891 PRInt32 aEndOffset)
1893 nsCOMPtr<nsISelection> domSel;
1894 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1895 nsnull, getter_AddRefs(domSel));
1896 NS_ENSURE_SUCCESS(rv, rv);
1898 // Caret is a collapsed selection
1899 PRBool isOnlyCaret = (aStartOffset == aEndOffset);
1901 PRInt32 rangeCount;
1902 domSel->GetRangeCount(&rangeCount);
1903 nsCOMPtr<nsIDOMRange> range;
1904 if (aSelectionNum == rangeCount) { // Add a range
1905 range = do_CreateInstance(kRangeCID);
1906 NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY);
1908 else if (aSelectionNum < 0 || aSelectionNum > rangeCount) {
1909 return NS_ERROR_INVALID_ARG;
1911 else {
1912 domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
1913 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
1916 PRInt32 startOffset, endOffset;
1917 nsCOMPtr<nsIDOMNode> startNode, endNode;
1919 rv = HypertextOffsetsToDOMRange(aStartOffset, aEndOffset,
1920 getter_AddRefs(startNode), &startOffset,
1921 getter_AddRefs(endNode), &endOffset);
1922 NS_ENSURE_SUCCESS(rv, rv);
1924 rv = range->SetStart(startNode, startOffset);
1925 NS_ENSURE_SUCCESS(rv, rv);
1927 rv = isOnlyCaret ? range->Collapse(PR_TRUE) :
1928 range->SetEnd(endNode, endOffset);
1929 NS_ENSURE_SUCCESS(rv, rv);
1931 if (aSelectionNum == rangeCount) { // Add successfully created new range
1932 return domSel->AddRange(range);
1934 return NS_OK;
1938 * Adds a selection bounded by the specified offsets.
1940 NS_IMETHODIMP nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset, PRInt32 aEndOffset)
1942 nsCOMPtr<nsISelection> domSel;
1943 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1944 nsnull, getter_AddRefs(domSel));
1945 NS_ENSURE_SUCCESS(rv, rv);
1947 PRInt32 rangeCount;
1948 domSel->GetRangeCount(&rangeCount);
1950 return SetSelectionBounds(rangeCount, aStartOffset, aEndOffset);
1954 * Removes the specified selection.
1956 NS_IMETHODIMP nsHyperTextAccessible::RemoveSelection(PRInt32 aSelectionNum)
1958 nsCOMPtr<nsISelection> domSel;
1959 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1960 nsnull, getter_AddRefs(domSel));
1961 NS_ENSURE_SUCCESS(rv, rv);
1963 PRInt32 rangeCount;
1964 domSel->GetRangeCount(&rangeCount);
1965 if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
1966 return NS_ERROR_INVALID_ARG;
1968 nsCOMPtr<nsIDOMRange> range;
1969 domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
1970 return domSel->RemoveRange(range);
1973 // void nsIAccessibleText::
1974 // scrollSubstringTo(in long startIndex, in long endIndex,
1975 // in unsigned long scrollType);
1976 NS_IMETHODIMP
1977 nsHyperTextAccessible::ScrollSubstringTo(PRInt32 aStartIndex, PRInt32 aEndIndex,
1978 PRUint32 aScrollType)
1980 PRInt32 startOffset, endOffset;
1981 nsCOMPtr<nsIDOMNode> startNode, endNode;
1983 nsresult rv = HypertextOffsetsToDOMRange(aStartIndex, aEndIndex,
1984 getter_AddRefs(startNode),
1985 &startOffset,
1986 getter_AddRefs(endNode),
1987 &endOffset);
1988 NS_ENSURE_SUCCESS(rv, rv);
1990 return nsAccUtils::ScrollSubstringTo(GetFrame(), startNode, startOffset,
1991 endNode, endOffset, aScrollType);
1994 // void nsIAccessibleText::
1995 // scrollSubstringToPoint(in long startIndex, in long endIndex,
1996 // in unsigned long coordinateType,
1997 // in long x, in long y);
1998 NS_IMETHODIMP
1999 nsHyperTextAccessible::ScrollSubstringToPoint(PRInt32 aStartIndex,
2000 PRInt32 aEndIndex,
2001 PRUint32 aCoordinateType,
2002 PRInt32 aX, PRInt32 aY)
2004 nsIFrame *frame = GetFrame();
2005 if (!frame)
2006 return NS_ERROR_FAILURE;
2008 nsIntPoint coords;
2009 nsresult rv = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
2010 this, &coords);
2011 NS_ENSURE_SUCCESS(rv, rv);
2013 PRInt32 startOffset, endOffset;
2014 nsCOMPtr<nsIDOMNode> startNode, endNode;
2016 rv = HypertextOffsetsToDOMRange(aStartIndex, aEndIndex,
2017 getter_AddRefs(startNode), &startOffset,
2018 getter_AddRefs(endNode), &endOffset);
2019 NS_ENSURE_SUCCESS(rv, rv);
2021 nsPresContext *presContext = frame->PresContext();
2023 PRBool initialScrolled = PR_FALSE;
2024 nsIFrame *parentFrame = frame;
2025 while ((parentFrame = parentFrame->GetParent())) {
2026 nsIScrollableFrame *scrollableFrame = nsnull;
2027 CallQueryInterface(parentFrame, &scrollableFrame);
2028 if (scrollableFrame) {
2029 if (!initialScrolled) {
2030 // Scroll substring to the given point. Turn the point into percents
2031 // relative scrollable area to use nsAccUtils::ScrollSubstringTo.
2032 nsIntRect frameRect = parentFrame->GetScreenRectExternal();
2033 PRInt32 devOffsetX = coords.x - frameRect.x;
2034 PRInt32 devOffsetY = coords.y - frameRect.y;
2036 nsPoint offsetPoint(presContext->DevPixelsToAppUnits(devOffsetX),
2037 presContext->DevPixelsToAppUnits(devOffsetY));
2039 nsSize size(parentFrame->GetSize());
2040 PRInt16 hPercent = offsetPoint.x * 100 / size.width;
2041 PRInt16 vPercent = offsetPoint.y * 100 / size.height;
2043 rv = nsAccUtils::ScrollSubstringTo(GetFrame(), startNode, startOffset,
2044 endNode, endOffset,
2045 vPercent, hPercent);
2046 NS_ENSURE_SUCCESS(rv, rv);
2048 initialScrolled = PR_TRUE;
2049 } else {
2050 // Substring was scrolled to the given point already inside its closest
2051 // scrollable area. If there are nested scrollable areas then make
2052 // sure we scroll lower areas to the given point inside currently
2053 // traversed scrollable area.
2054 nsAccUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2057 frame = parentFrame;
2060 return NS_OK;
2063 nsresult nsHyperTextAccessible::ContentToRenderedOffset(nsIFrame *aFrame, PRInt32 aContentOffset,
2064 PRUint32 *aRenderedOffset)
2066 if (!aFrame) {
2067 // Current frame not rendered -- this can happen if text is set on
2068 // something with display: none
2069 *aRenderedOffset = 0;
2070 return NS_OK;
2072 NS_ASSERTION(aFrame->GetType() == nsAccessibilityAtoms::textFrame,
2073 "Need text frame for offset conversion");
2074 NS_ASSERTION(aFrame->GetPrevContinuation() == nsnull,
2075 "Call on primary frame only");
2077 gfxSkipChars skipChars;
2078 gfxSkipCharsIterator iter;
2079 // Only get info up to original ofset, we know that will be larger than skipped offset
2080 nsresult rv = aFrame->GetRenderedText(nsnull, &skipChars, &iter, 0, aContentOffset);
2081 NS_ENSURE_SUCCESS(rv, rv);
2083 PRUint32 ourRenderedStart = iter.GetSkippedOffset();
2084 PRInt32 ourContentStart = iter.GetOriginalOffset();
2086 *aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) -
2087 ourRenderedStart;
2089 return NS_OK;
2092 nsresult nsHyperTextAccessible::RenderedToContentOffset(nsIFrame *aFrame, PRUint32 aRenderedOffset,
2093 PRInt32 *aContentOffset)
2095 *aContentOffset = 0;
2096 NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
2098 NS_ASSERTION(aFrame->GetType() == nsAccessibilityAtoms::textFrame,
2099 "Need text frame for offset conversion");
2100 NS_ASSERTION(aFrame->GetPrevContinuation() == nsnull,
2101 "Call on primary frame only");
2103 gfxSkipChars skipChars;
2104 gfxSkipCharsIterator iter;
2105 // We only need info up to skipped offset -- that is what we're converting to original offset
2106 nsresult rv = aFrame->GetRenderedText(nsnull, &skipChars, &iter, 0, aRenderedOffset);
2107 NS_ENSURE_SUCCESS(rv, rv);
2109 PRUint32 ourRenderedStart = iter.GetSkippedOffset();
2110 PRInt32 ourContentStart = iter.GetOriginalOffset();
2112 *aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart;
2114 return NS_OK;
2117 nsresult
2118 nsHyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame *aFrame,
2119 PRInt32 aOffset,
2120 nsIAccessible *aAccessible,
2121 nsIDOMNode **aNode,
2122 PRInt32 *aNodeOffset)
2124 NS_ENSURE_ARG(aAccessible);
2126 nsCOMPtr<nsIDOMNode> node;
2128 if (!aFrame) {
2129 // If the given frame is null then set offset after the DOM node of the
2130 // given accessible.
2131 nsCOMPtr<nsIAccessNode> accessNode(do_QueryInterface(aAccessible));
2133 nsCOMPtr<nsIDOMNode> DOMNode;
2134 accessNode->GetDOMNode(getter_AddRefs(DOMNode));
2135 nsCOMPtr<nsIContent> content(do_QueryInterface(DOMNode));
2136 NS_ENSURE_STATE(content);
2138 nsCOMPtr<nsIContent> parent(content->GetParent());
2139 NS_ENSURE_STATE(parent);
2141 *aNodeOffset = parent->IndexOf(content) + 1;
2142 node = do_QueryInterface(parent);
2144 } else if (aFrame->GetType() == nsAccessibilityAtoms::textFrame) {
2145 nsCOMPtr<nsIContent> content(aFrame->GetContent());
2146 NS_ENSURE_STATE(content);
2148 nsCOMPtr<nsIPresShell> shell(GetPresShell());
2149 NS_ENSURE_STATE(shell);
2151 nsIFrame *primaryFrame = shell->GetPrimaryFrameFor(content);
2152 nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, aNodeOffset);
2153 NS_ENSURE_SUCCESS(rv, rv);
2155 node = do_QueryInterface(content);
2157 } else {
2158 nsCOMPtr<nsIContent> content(aFrame->GetContent());
2159 NS_ENSURE_STATE(content);
2161 nsCOMPtr<nsIContent> parent(content->GetParent());
2162 NS_ENSURE_STATE(parent);
2164 *aNodeOffset = parent->IndexOf(content);
2165 node = do_QueryInterface(parent);
2168 NS_IF_ADDREF(*aNode = node);
2169 return NS_OK;
2172 // nsHyperTextAccessible
2173 nsresult
2174 nsHyperTextAccessible::DOMRangeBoundToHypertextOffset(nsIDOMRange *aRange,
2175 PRBool aIsStartBound,
2176 PRBool aIsStartHTOffset,
2177 PRInt32 *aHTOffset)
2179 nsCOMPtr<nsIDOMNode> node;
2180 PRInt32 nodeOffset = 0;
2182 nsresult rv;
2183 if (aIsStartBound) {
2184 rv = aRange->GetStartContainer(getter_AddRefs(node));
2185 NS_ENSURE_SUCCESS(rv, rv);
2187 rv = aRange->GetStartOffset(&nodeOffset);
2188 NS_ENSURE_SUCCESS(rv, rv);
2189 } else {
2190 rv = aRange->GetEndContainer(getter_AddRefs(node));
2191 NS_ENSURE_SUCCESS(rv, rv);
2193 rv = aRange->GetEndOffset(&nodeOffset);
2194 NS_ENSURE_SUCCESS(rv, rv);
2197 nsCOMPtr<nsIAccessible> startAcc;
2198 rv = DOMPointToHypertextOffset(node, nodeOffset, aHTOffset,
2199 getter_AddRefs(startAcc));
2200 NS_ENSURE_SUCCESS(rv, rv);
2202 if (aIsStartHTOffset && !startAcc)
2203 *aHTOffset = 0;
2205 return NS_OK;
2208 // nsHyperTextAccessible
2209 nsresult
2210 nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode,
2211 PRInt32 aNodeOffset,
2212 PRInt32 *aHTStartOffset,
2213 PRInt32 *aHTEndOffset,
2214 nsIPersistentProperties *aAttributes)
2216 nsCOMArray<nsIDOMRange> ranges;
2217 nsresult rv = GetSelections(nsISelectionController::SELECTION_SPELLCHECK,
2218 nsnull, nsnull, &ranges);
2219 NS_ENSURE_SUCCESS(rv, rv);
2221 PRInt32 rangeCount = ranges.Count();
2222 if (!rangeCount)
2223 return NS_OK;
2225 for (PRInt32 index = 0; index < rangeCount; index++) {
2226 nsCOMPtr<nsIDOMRange> range = ranges[index];
2227 nsCOMPtr<nsIDOMNSRange> nsrange(do_QueryInterface(range));
2228 NS_ENSURE_STATE(nsrange);
2230 PRInt16 result;
2231 rv = nsrange->ComparePoint(aNode, aNodeOffset, &result);
2232 NS_ENSURE_SUCCESS(rv, rv);
2234 if (result == 1) { // range is before point
2235 PRInt32 startHTOffset = 0;
2236 rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_TRUE,
2237 &startHTOffset);
2238 NS_ENSURE_SUCCESS(rv, rv);
2240 if (startHTOffset > *aHTStartOffset)
2241 *aHTStartOffset = startHTOffset;
2243 } else if (result == -1) { // range is after point
2244 PRInt32 endHTOffset = 0;
2245 rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_FALSE,
2246 &endHTOffset);
2247 NS_ENSURE_SUCCESS(rv, rv);
2249 if (endHTOffset < *aHTEndOffset)
2250 *aHTEndOffset = endHTOffset;
2252 } else { // point is in range
2253 PRInt32 startHTOffset = 0;
2254 rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_TRUE,
2255 &startHTOffset);
2256 NS_ENSURE_SUCCESS(rv, rv);
2258 PRInt32 endHTOffset = 0;
2259 rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_FALSE,
2260 &endHTOffset);
2261 NS_ENSURE_SUCCESS(rv, rv);
2263 if (startHTOffset > *aHTStartOffset)
2264 *aHTStartOffset = startHTOffset;
2265 if (endHTOffset < *aHTEndOffset)
2266 *aHTEndOffset = endHTOffset;
2268 if (aAttributes) {
2269 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::invalid,
2270 NS_LITERAL_STRING("spelling"));
2273 return NS_OK;
2277 return NS_OK;
2280 // nsHyperTextAccessible
2281 nsresult
2282 nsHyperTextAccessible::GetLangTextAttributes(PRBool aIncludeDefAttrs,
2283 nsIDOMNode *aSourceNode,
2284 PRInt32 *aStartHTOffset,
2285 PRInt32 *aEndHTOffset,
2286 nsIPersistentProperties *aAttributes)
2288 nsCOMPtr<nsIDOMElement> sourceElm(nsAccUtils::GetDOMElementFor(aSourceNode));
2290 nsCOMPtr<nsIContent> content(do_QueryInterface(sourceElm));
2291 nsCOMPtr<nsIContent> rootContent(do_QueryInterface(mDOMNode));
2293 nsAutoString lang;
2294 nsAccUtils::GetLanguageFor(content, rootContent, lang);
2296 nsAutoString rootLang;
2297 nsresult rv = GetLanguage(rootLang);
2298 NS_ENSURE_SUCCESS(rv, rv);
2300 if (aAttributes) {
2301 // Expose 'language' text attribute if the DOM 'lang' attribute is
2302 // presented and it's different from the 'lang' attribute on the root
2303 // element or we should include default values of text attribute.
2304 const nsAString& resultLang = lang.IsEmpty() ? rootLang : lang;
2305 if (!resultLang.IsEmpty() && (aIncludeDefAttrs || lang != rootLang))
2306 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::language,
2307 resultLang);
2310 nsLangTextAttr textAttr(lang, rootContent);
2311 return GetRangeForTextAttr(aSourceNode, &textAttr,
2312 aStartHTOffset, aEndHTOffset);
2315 // nsHyperTextAccessible
2316 nsresult
2317 nsHyperTextAccessible::GetCSSTextAttributes(PRBool aIncludeDefAttrs,
2318 nsIDOMNode *aSourceNode,
2319 PRInt32 *aStartHTOffset,
2320 PRInt32 *aEndHTOffset,
2321 nsIPersistentProperties *aAttributes)
2323 nsCOMPtr<nsIDOMElement> sourceElm(nsAccUtils::GetDOMElementFor(aSourceNode));
2324 nsCOMPtr<nsIDOMElement> rootElm(nsAccUtils::GetDOMElementFor(mDOMNode));
2326 nsCSSTextAttr textAttr(aIncludeDefAttrs, sourceElm, rootElm);
2327 while (textAttr.Iterate()) {
2328 nsCAutoString name;
2329 nsAutoString value, oldValue;
2330 if (aAttributes && textAttr.Get(name, value))
2331 aAttributes->SetStringProperty(name, value, oldValue);
2333 nsresult rv = GetRangeForTextAttr(aSourceNode, &textAttr,
2334 aStartHTOffset, aEndHTOffset);
2335 NS_ENSURE_SUCCESS(rv, rv);
2338 nsIFrame *sourceFrame = nsAccUtils::GetFrameFor(sourceElm);
2339 if (sourceFrame) {
2340 nsIFrame *rootFrame = nsnull;
2342 if (!aIncludeDefAttrs)
2343 rootFrame = nsAccUtils::GetFrameFor(rootElm);
2345 nsBackgroundTextAttr backgroundTextAttr(sourceFrame, rootFrame);
2346 nsAutoString value;
2347 if (backgroundTextAttr.Get(value)) {
2348 nsAccUtils::SetAccAttr(aAttributes,
2349 nsAccessibilityAtoms::backgroundColor, value);
2352 nsresult rv = GetRangeForTextAttr(aSourceNode, &backgroundTextAttr,
2353 aStartHTOffset, aEndHTOffset);
2354 return rv;
2357 return NS_OK;
2360 // nsHyperTextAccessible
2361 nsresult
2362 nsHyperTextAccessible::GetRangeForTextAttr(nsIDOMNode *aNode,
2363 nsTextAttr *aComparer,
2364 PRInt32 *aStartHTOffset,
2365 PRInt32 *aEndHTOffset)
2367 nsCOMPtr<nsIDOMElement> rootElm(nsAccUtils::GetDOMElementFor(mDOMNode));
2368 NS_ENSURE_STATE(rootElm);
2370 nsCOMPtr<nsIDOMNode> tmpNode(aNode);
2371 nsCOMPtr<nsIDOMNode> currNode(aNode);
2373 // Navigate backwards and forwards from current node to the root node to
2374 // calculate range bounds for the text attribute. Navigation sequence is the
2375 // following:
2376 // 1. Navigate through the siblings.
2377 // 2. If the traversed sibling has children then navigate from its leaf child
2378 // to it through whole tree of the traversed sibling.
2379 // 3. Get the parent and cycle algorithm until the root node.
2381 // Navigate backwards (find the start offset).
2382 while (currNode && currNode != rootElm) {
2383 nsCOMPtr<nsIDOMElement> currElm(nsAccUtils::GetDOMElementFor(currNode));
2384 NS_ENSURE_STATE(currElm);
2386 if (currNode != aNode && !aComparer->Equal(currElm)) {
2387 PRInt32 startHTOffset = 0;
2388 nsCOMPtr<nsIAccessible> startAcc;
2389 nsresult rv = DOMPointToHypertextOffset(tmpNode, -1, &startHTOffset,
2390 getter_AddRefs(startAcc));
2391 NS_ENSURE_SUCCESS(rv, rv);
2393 if (!startAcc)
2394 startHTOffset = 0;
2396 if (startHTOffset > *aStartHTOffset)
2397 *aStartHTOffset = startHTOffset;
2399 break;
2402 currNode->GetPreviousSibling(getter_AddRefs(tmpNode));
2403 if (tmpNode) {
2404 // Navigate through the subtree of traversed children to calculate
2405 // left bound of the range.
2406 FindStartOffsetInSubtree(tmpNode, currNode, aComparer, aStartHTOffset);
2409 currNode->GetParentNode(getter_AddRefs(tmpNode));
2410 currNode.swap(tmpNode);
2413 // Navigate forwards (find the end offset).
2414 PRBool moveIntoSubtree = PR_TRUE;
2415 currNode = aNode;
2416 while (currNode && currNode != rootElm) {
2417 nsCOMPtr<nsIDOMElement> currElm(nsAccUtils::GetDOMElementFor(currNode));
2418 NS_ENSURE_STATE(currElm);
2420 // Stop new end offset searching if the given text attribute changes its
2421 // value.
2422 if (!aComparer->Equal(currElm)) {
2423 PRInt32 endHTOffset = 0;
2424 nsresult rv = DOMPointToHypertextOffset(currNode, -1, &endHTOffset);
2425 NS_ENSURE_SUCCESS(rv, rv);
2427 if (endHTOffset < *aEndHTOffset)
2428 *aEndHTOffset = endHTOffset;
2430 break;
2433 if (moveIntoSubtree) {
2434 // Navigate through subtree of traversed node. We use 'moveIntoSubtree'
2435 // flag to avoid traversing the same subtree twice.
2436 currNode->GetFirstChild(getter_AddRefs(tmpNode));
2437 if (tmpNode)
2438 FindEndOffsetInSubtree(tmpNode, aComparer, aEndHTOffset);
2441 currNode->GetNextSibling(getter_AddRefs(tmpNode));
2442 moveIntoSubtree = PR_TRUE;
2443 if (!tmpNode) {
2444 currNode->GetParentNode(getter_AddRefs(tmpNode));
2445 moveIntoSubtree = PR_FALSE;
2448 currNode.swap(tmpNode);
2451 return NS_OK;
2455 PRBool
2456 nsHyperTextAccessible::FindEndOffsetInSubtree(nsIDOMNode *aCurrNode,
2457 nsTextAttr *aComparer,
2458 PRInt32 *aHTOffset)
2460 if (!aCurrNode)
2461 return PR_FALSE;
2463 nsCOMPtr<nsIDOMElement> currElm(nsAccUtils::GetDOMElementFor(aCurrNode));
2464 NS_ENSURE_STATE(currElm);
2466 // If the given text attribute (pointed by nsTextAttr object) changes its
2467 // value on the traversed element then fit the end of range.
2468 if (!aComparer->Equal(currElm)) {
2469 PRInt32 endHTOffset = 0;
2470 nsresult rv = DOMPointToHypertextOffset(aCurrNode, -1, &endHTOffset);
2471 NS_ENSURE_SUCCESS(rv, rv);
2473 if (endHTOffset < *aHTOffset)
2474 *aHTOffset = endHTOffset;
2476 return PR_TRUE;
2479 // Deeply traverse into the tree to fit the end of range.
2480 nsCOMPtr<nsIDOMNode> nextNode;
2481 aCurrNode->GetFirstChild(getter_AddRefs(nextNode));
2482 if (nextNode) {
2483 PRBool res = FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset);
2484 if (res)
2485 return res;
2488 aCurrNode->GetNextSibling(getter_AddRefs(nextNode));
2489 if (nextNode) {
2490 if (FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset))
2491 return PR_TRUE;
2494 return PR_FALSE;
2497 PRBool
2498 nsHyperTextAccessible::FindStartOffsetInSubtree(nsIDOMNode *aCurrNode,
2499 nsIDOMNode *aPrevNode,
2500 nsTextAttr *aComparer,
2501 PRInt32 *aHTOffset)
2503 if (!aCurrNode)
2504 return PR_FALSE;
2506 // Find the closest element back to the traversed element.
2507 nsCOMPtr<nsIDOMNode> nextNode;
2508 aCurrNode->GetLastChild(getter_AddRefs(nextNode));
2509 if (nextNode) {
2510 if (FindStartOffsetInSubtree(nextNode, aPrevNode, aComparer, aHTOffset))
2511 return PR_TRUE;
2514 nsCOMPtr<nsIDOMElement> currElm(nsAccUtils::GetDOMElementFor(aCurrNode));
2515 NS_ENSURE_STATE(currElm);
2517 // If the given text attribute (pointed by nsTextAttr object) changes its
2518 // value on the traversed element then fit the start of range.
2519 if (!aComparer->Equal(currElm)) {
2520 PRInt32 startHTOffset = 0;
2521 nsCOMPtr<nsIAccessible> startAcc;
2522 nsresult rv = DOMPointToHypertextOffset(aPrevNode, -1, &startHTOffset,
2523 getter_AddRefs(startAcc));
2524 NS_ENSURE_SUCCESS(rv, rv);
2526 if (!startAcc)
2527 startHTOffset = 0;
2529 if (startHTOffset > *aHTOffset)
2530 *aHTOffset = startHTOffset;
2532 return PR_TRUE;
2535 // Moving backwards to find the start of range.
2536 aCurrNode->GetPreviousSibling(getter_AddRefs(nextNode));
2537 if (nextNode) {
2538 if (FindStartOffsetInSubtree(nextNode, aCurrNode, aComparer, aHTOffset))
2539 return PR_TRUE;
2542 return PR_FALSE;