Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / accessible / src / html / nsHyperTextAccessible.cpp
blob1e522ec5f325d0302439369a881d0b443953769c
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
1636 nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset)
1638 *aCaretOffset = -1;
1640 // No caret if the focused node is not inside this DOM node and this DOM node
1641 // is not inside of focused node.
1642 PRBool isInsideOfFocusedNode =
1643 nsCoreUtils::IsAncestorOf(gLastFocusedNode, mDOMNode);
1645 if (!isInsideOfFocusedNode && mDOMNode != gLastFocusedNode &&
1646 !nsCoreUtils::IsAncestorOf(mDOMNode, gLastFocusedNode))
1647 return NS_OK;
1649 // Turn the focus node and offset of the selection into caret hypretext
1650 // offset.
1651 nsCOMPtr<nsISelection> domSel;
1652 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1653 nsnull, getter_AddRefs(domSel));
1654 NS_ENSURE_SUCCESS(rv, rv);
1656 nsCOMPtr<nsIDOMNode> focusNode;
1657 rv = domSel->GetFocusNode(getter_AddRefs(focusNode));
1658 NS_ENSURE_SUCCESS(rv, rv);
1660 PRInt32 focusOffset;
1661 rv = domSel->GetFocusOffset(&focusOffset);
1662 NS_ENSURE_SUCCESS(rv, rv);
1664 // No caret if this DOM node is inside of focused node but the selection's
1665 // focus point is not inside of this DOM node.
1666 if (isInsideOfFocusedNode) {
1667 nsCOMPtr<nsIDOMNode> resultNode =
1668 nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
1669 if (resultNode != mDOMNode &&
1670 !nsCoreUtils::IsAncestorOf(mDOMNode, resultNode))
1671 return NS_OK;
1674 return DOMPointToHypertextOffset(focusNode, focusOffset, aCaretOffset);
1677 PRInt32 nsHyperTextAccessible::GetCaretLineNumber()
1679 // Provide the line number for the caret, relative to the
1680 // currently focused node. Use a 1-based index
1681 nsCOMPtr<nsISelection> domSel;
1682 GetSelections(nsISelectionController::SELECTION_NORMAL, nsnull,
1683 getter_AddRefs(domSel));
1684 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSel));
1685 NS_ENSURE_TRUE(privateSelection, -1);
1686 nsCOMPtr<nsFrameSelection> frameSelection;
1687 privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
1688 NS_ENSURE_TRUE(frameSelection, -1);
1690 nsCOMPtr<nsIDOMNode> caretNode;
1691 domSel->GetFocusNode(getter_AddRefs(caretNode));
1692 nsCOMPtr<nsIContent> caretContent = do_QueryInterface(caretNode);
1693 if (!caretContent || !nsCoreUtils::IsAncestorOf(mDOMNode, caretNode)) {
1694 return -1;
1697 PRInt32 caretOffset, returnOffsetUnused;
1698 domSel->GetFocusOffset(&caretOffset);
1699 nsFrameSelection::HINT hint = frameSelection->GetHint();
1700 nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset,
1701 hint, &returnOffsetUnused);
1702 NS_ENSURE_TRUE(caretFrame, -1);
1704 PRInt32 lineNumber = 1;
1705 nsAutoLineIterator lineIterForCaret;
1706 nsCOMPtr<nsIContent> hyperTextContent = do_QueryInterface(mDOMNode);
1707 while (caretFrame) {
1708 if (hyperTextContent == caretFrame->GetContent()) {
1709 return lineNumber; // Must be in a single line hyper text, there is no line iterator
1711 nsIFrame *parentFrame = caretFrame->GetParent();
1712 if (!parentFrame)
1713 break;
1715 // Add lines for the sibling frames before the caret
1716 nsIFrame *sibling = parentFrame->GetFirstChild(nsnull);
1717 while (sibling && sibling != caretFrame) {
1718 nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator();
1719 if (lineIterForSibling) {
1720 // For the frames before that grab all the lines
1721 PRInt32 addLines = lineIterForSibling->GetNumLines();
1722 lineNumber += addLines;
1724 sibling = sibling->GetNextSibling();
1727 // Get the line number relative to the container with lines
1728 if (!lineIterForCaret) { // Add the caret line just once
1729 lineIterForCaret = parentFrame->GetLineIterator();
1730 if (lineIterForCaret) {
1731 // Ancestor of caret
1732 PRInt32 addLines = lineIterForCaret->FindLineContaining(caretFrame);
1733 lineNumber += addLines;
1737 caretFrame = parentFrame;
1740 NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
1741 return lineNumber;
1744 nsresult
1745 nsHyperTextAccessible::GetSelections(PRInt16 aType,
1746 nsISelectionController **aSelCon,
1747 nsISelection **aDomSel,
1748 nsCOMArray<nsIDOMRange>* aRanges)
1750 if (!mDOMNode) {
1751 return NS_ERROR_FAILURE;
1753 if (aSelCon) {
1754 *aSelCon = nsnull;
1756 if (aDomSel) {
1757 *aDomSel = nsnull;
1759 if (aRanges) {
1760 aRanges->Clear();
1763 nsCOMPtr<nsISelection> domSel;
1764 nsCOMPtr<nsISelectionController> selCon;
1766 nsCOMPtr<nsIEditor> editor;
1767 GetAssociatedEditor(getter_AddRefs(editor));
1768 nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
1769 if (peditor) {
1770 // Case 1: plain text editor
1771 // This is for form controls which have their own
1772 // selection controller separate from the document, for example
1773 // HTML:input, HTML:textarea, XUL:textbox, etc.
1774 editor->GetSelectionController(getter_AddRefs(selCon));
1776 else {
1777 // Case 2: rich content subtree (can be rich editor)
1778 // This uses the selection controller from the entire document
1779 nsIFrame *frame = GetFrame();
1780 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
1782 // Get the selection and selection controller
1783 frame->GetSelectionController(GetPresContext(),
1784 getter_AddRefs(selCon));
1786 NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
1788 selCon->GetSelection(aType, getter_AddRefs(domSel));
1789 NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE);
1791 if (aSelCon) {
1792 NS_ADDREF(*aSelCon = selCon);
1794 if (aDomSel) {
1795 NS_ADDREF(*aDomSel = domSel);
1798 if (aRanges) {
1799 nsCOMPtr<nsISelection2> selection2(do_QueryInterface(domSel));
1800 NS_ENSURE_TRUE(selection2, NS_ERROR_FAILURE);
1802 nsCOMPtr<nsIDOMNode> startNode(mDOMNode);
1803 if (peditor) {
1804 nsCOMPtr<nsIDOMElement> editorRoot;
1805 editor->GetRootElement(getter_AddRefs(editorRoot));
1806 startNode = do_QueryInterface(editorRoot);
1808 NS_ENSURE_STATE(startNode);
1810 nsCOMPtr<nsIDOMNodeList> childNodes;
1811 nsresult rv = startNode->GetChildNodes(getter_AddRefs(childNodes));
1812 NS_ENSURE_SUCCESS(rv, rv);
1813 PRUint32 numChildren;
1814 rv = childNodes->GetLength(&numChildren);
1815 NS_ENSURE_SUCCESS(rv, rv);
1816 rv = selection2->GetRangesForIntervalCOMArray(startNode, 0,
1817 startNode, numChildren,
1818 PR_TRUE, aRanges);
1819 NS_ENSURE_SUCCESS(rv, rv);
1820 // Remove collapsed ranges
1821 PRInt32 numRanges = aRanges->Count();
1822 for (PRInt32 count = 0; count < numRanges; count ++) {
1823 PRBool isCollapsed;
1824 (*aRanges)[count]->GetCollapsed(&isCollapsed);
1825 if (isCollapsed) {
1826 aRanges->RemoveObjectAt(count);
1827 -- numRanges;
1828 -- count;
1833 return NS_OK;
1837 * Gets the number of selected regions.
1839 NS_IMETHODIMP nsHyperTextAccessible::GetSelectionCount(PRInt32 *aSelectionCount)
1841 nsCOMPtr<nsISelection> domSel;
1842 nsCOMArray<nsIDOMRange> ranges;
1843 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1844 nsnull, nsnull, &ranges);
1845 NS_ENSURE_SUCCESS(rv, rv);
1847 *aSelectionCount = ranges.Count();
1849 return NS_OK;
1853 * Gets the start and end offset of the specified selection.
1855 NS_IMETHODIMP nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum, PRInt32 *aStartOffset, PRInt32 *aEndOffset)
1857 *aStartOffset = *aEndOffset = 0;
1859 nsCOMPtr<nsISelection> domSel;
1860 nsCOMArray<nsIDOMRange> ranges;
1861 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1862 nsnull, getter_AddRefs(domSel), &ranges);
1863 NS_ENSURE_SUCCESS(rv, rv);
1865 PRInt32 rangeCount = ranges.Count();
1866 if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
1867 return NS_ERROR_INVALID_ARG;
1869 nsCOMPtr<nsIDOMRange> range = ranges[aSelectionNum];
1871 // Get start point
1872 nsCOMPtr<nsIDOMNode> startNode;
1873 range->GetStartContainer(getter_AddRefs(startNode));
1874 PRInt32 startOffset;
1875 range->GetStartOffset(&startOffset);
1877 // Get end point
1878 nsCOMPtr<nsIDOMNode> endNode;
1879 range->GetEndContainer(getter_AddRefs(endNode));
1880 PRInt32 endOffset;
1881 range->GetEndOffset(&endOffset);
1883 PRInt16 rangeCompareResult;
1884 rv = range->CompareBoundaryPoints(nsIDOMRange::START_TO_END, range, &rangeCompareResult);
1885 NS_ENSURE_SUCCESS(rv, rv);
1887 if (rangeCompareResult < 0) {
1888 // Make sure start is before end, by swapping offsets
1889 // This occurs when the user selects backwards in the text
1890 startNode.swap(endNode);
1891 PRInt32 tempOffset = startOffset;
1892 startOffset = endOffset;
1893 endOffset = tempOffset;
1896 nsCOMPtr<nsIAccessible> startAccessible;
1897 rv = DOMPointToHypertextOffset(startNode, startOffset, aStartOffset, getter_AddRefs(startAccessible));
1898 NS_ENSURE_SUCCESS(rv, rv);
1899 if (!startAccessible) {
1900 *aStartOffset = 0; // Could not find start point within this hypertext, so starts before
1903 return DOMPointToHypertextOffset(endNode, endOffset, aEndOffset, nsnull, PR_TRUE);
1907 * Changes the start and end offset of the specified selection.
1909 NS_IMETHODIMP
1910 nsHyperTextAccessible::SetSelectionBounds(PRInt32 aSelectionNum,
1911 PRInt32 aStartOffset,
1912 PRInt32 aEndOffset)
1914 nsCOMPtr<nsISelection> domSel;
1915 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1916 nsnull, getter_AddRefs(domSel));
1917 NS_ENSURE_SUCCESS(rv, rv);
1919 // Caret is a collapsed selection
1920 PRBool isOnlyCaret = (aStartOffset == aEndOffset);
1922 PRInt32 rangeCount;
1923 domSel->GetRangeCount(&rangeCount);
1924 nsCOMPtr<nsIDOMRange> range;
1925 if (aSelectionNum == rangeCount) { // Add a range
1926 range = do_CreateInstance(kRangeCID);
1927 NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY);
1929 else if (aSelectionNum < 0 || aSelectionNum > rangeCount) {
1930 return NS_ERROR_INVALID_ARG;
1932 else {
1933 domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
1934 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
1937 PRInt32 startOffset, endOffset;
1938 nsCOMPtr<nsIDOMNode> startNode, endNode;
1940 rv = HypertextOffsetsToDOMRange(aStartOffset, aEndOffset,
1941 getter_AddRefs(startNode), &startOffset,
1942 getter_AddRefs(endNode), &endOffset);
1943 NS_ENSURE_SUCCESS(rv, rv);
1945 rv = range->SetStart(startNode, startOffset);
1946 NS_ENSURE_SUCCESS(rv, rv);
1948 rv = isOnlyCaret ? range->Collapse(PR_TRUE) :
1949 range->SetEnd(endNode, endOffset);
1950 NS_ENSURE_SUCCESS(rv, rv);
1952 if (aSelectionNum == rangeCount) { // Add successfully created new range
1953 return domSel->AddRange(range);
1955 return NS_OK;
1959 * Adds a selection bounded by the specified offsets.
1961 NS_IMETHODIMP nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset, PRInt32 aEndOffset)
1963 nsCOMPtr<nsISelection> domSel;
1964 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1965 nsnull, getter_AddRefs(domSel));
1966 NS_ENSURE_SUCCESS(rv, rv);
1968 PRInt32 rangeCount;
1969 domSel->GetRangeCount(&rangeCount);
1971 return SetSelectionBounds(rangeCount, aStartOffset, aEndOffset);
1975 * Removes the specified selection.
1977 NS_IMETHODIMP nsHyperTextAccessible::RemoveSelection(PRInt32 aSelectionNum)
1979 nsCOMPtr<nsISelection> domSel;
1980 nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
1981 nsnull, getter_AddRefs(domSel));
1982 NS_ENSURE_SUCCESS(rv, rv);
1984 PRInt32 rangeCount;
1985 domSel->GetRangeCount(&rangeCount);
1986 if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
1987 return NS_ERROR_INVALID_ARG;
1989 nsCOMPtr<nsIDOMRange> range;
1990 domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
1991 return domSel->RemoveRange(range);
1994 // void nsIAccessibleText::
1995 // scrollSubstringTo(in long startIndex, in long endIndex,
1996 // in unsigned long scrollType);
1997 NS_IMETHODIMP
1998 nsHyperTextAccessible::ScrollSubstringTo(PRInt32 aStartIndex, PRInt32 aEndIndex,
1999 PRUint32 aScrollType)
2001 PRInt32 startOffset, endOffset;
2002 nsCOMPtr<nsIDOMNode> startNode, endNode;
2004 nsresult rv = HypertextOffsetsToDOMRange(aStartIndex, aEndIndex,
2005 getter_AddRefs(startNode),
2006 &startOffset,
2007 getter_AddRefs(endNode),
2008 &endOffset);
2009 NS_ENSURE_SUCCESS(rv, rv);
2011 return nsCoreUtils::ScrollSubstringTo(GetFrame(), startNode, startOffset,
2012 endNode, endOffset, aScrollType);
2015 // void nsIAccessibleText::
2016 // scrollSubstringToPoint(in long startIndex, in long endIndex,
2017 // in unsigned long coordinateType,
2018 // in long x, in long y);
2019 NS_IMETHODIMP
2020 nsHyperTextAccessible::ScrollSubstringToPoint(PRInt32 aStartIndex,
2021 PRInt32 aEndIndex,
2022 PRUint32 aCoordinateType,
2023 PRInt32 aX, PRInt32 aY)
2025 nsIFrame *frame = GetFrame();
2026 if (!frame)
2027 return NS_ERROR_FAILURE;
2029 nsIntPoint coords;
2030 nsresult rv = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
2031 this, &coords);
2032 NS_ENSURE_SUCCESS(rv, rv);
2034 PRInt32 startOffset, endOffset;
2035 nsCOMPtr<nsIDOMNode> startNode, endNode;
2037 rv = HypertextOffsetsToDOMRange(aStartIndex, aEndIndex,
2038 getter_AddRefs(startNode), &startOffset,
2039 getter_AddRefs(endNode), &endOffset);
2040 NS_ENSURE_SUCCESS(rv, rv);
2042 nsPresContext *presContext = frame->PresContext();
2044 PRBool initialScrolled = PR_FALSE;
2045 nsIFrame *parentFrame = frame;
2046 while ((parentFrame = parentFrame->GetParent())) {
2047 nsIScrollableFrame *scrollableFrame = nsnull;
2048 CallQueryInterface(parentFrame, &scrollableFrame);
2049 if (scrollableFrame) {
2050 if (!initialScrolled) {
2051 // Scroll substring to the given point. Turn the point into percents
2052 // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
2053 nsIntRect frameRect = parentFrame->GetScreenRectExternal();
2054 PRInt32 devOffsetX = coords.x - frameRect.x;
2055 PRInt32 devOffsetY = coords.y - frameRect.y;
2057 nsPoint offsetPoint(presContext->DevPixelsToAppUnits(devOffsetX),
2058 presContext->DevPixelsToAppUnits(devOffsetY));
2060 nsSize size(parentFrame->GetSize());
2061 PRInt16 hPercent = offsetPoint.x * 100 / size.width;
2062 PRInt16 vPercent = offsetPoint.y * 100 / size.height;
2064 rv = nsCoreUtils::ScrollSubstringTo(GetFrame(), startNode, startOffset,
2065 endNode, endOffset,
2066 vPercent, hPercent);
2067 NS_ENSURE_SUCCESS(rv, rv);
2069 initialScrolled = PR_TRUE;
2070 } else {
2071 // Substring was scrolled to the given point already inside its closest
2072 // scrollable area. If there are nested scrollable areas then make
2073 // sure we scroll lower areas to the given point inside currently
2074 // traversed scrollable area.
2075 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2078 frame = parentFrame;
2081 return NS_OK;
2084 nsresult nsHyperTextAccessible::ContentToRenderedOffset(nsIFrame *aFrame, PRInt32 aContentOffset,
2085 PRUint32 *aRenderedOffset)
2087 if (!aFrame) {
2088 // Current frame not rendered -- this can happen if text is set on
2089 // something with display: none
2090 *aRenderedOffset = 0;
2091 return NS_OK;
2093 NS_ASSERTION(aFrame->GetType() == nsAccessibilityAtoms::textFrame,
2094 "Need text frame for offset conversion");
2095 NS_ASSERTION(aFrame->GetPrevContinuation() == nsnull,
2096 "Call on primary frame only");
2098 gfxSkipChars skipChars;
2099 gfxSkipCharsIterator iter;
2100 // Only get info up to original ofset, we know that will be larger than skipped offset
2101 nsresult rv = aFrame->GetRenderedText(nsnull, &skipChars, &iter, 0, aContentOffset);
2102 NS_ENSURE_SUCCESS(rv, rv);
2104 PRUint32 ourRenderedStart = iter.GetSkippedOffset();
2105 PRInt32 ourContentStart = iter.GetOriginalOffset();
2107 *aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) -
2108 ourRenderedStart;
2110 return NS_OK;
2113 nsresult nsHyperTextAccessible::RenderedToContentOffset(nsIFrame *aFrame, PRUint32 aRenderedOffset,
2114 PRInt32 *aContentOffset)
2116 *aContentOffset = 0;
2117 NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
2119 NS_ASSERTION(aFrame->GetType() == nsAccessibilityAtoms::textFrame,
2120 "Need text frame for offset conversion");
2121 NS_ASSERTION(aFrame->GetPrevContinuation() == nsnull,
2122 "Call on primary frame only");
2124 gfxSkipChars skipChars;
2125 gfxSkipCharsIterator iter;
2126 // We only need info up to skipped offset -- that is what we're converting to original offset
2127 nsresult rv = aFrame->GetRenderedText(nsnull, &skipChars, &iter, 0, aRenderedOffset);
2128 NS_ENSURE_SUCCESS(rv, rv);
2130 PRUint32 ourRenderedStart = iter.GetSkippedOffset();
2131 PRInt32 ourContentStart = iter.GetOriginalOffset();
2133 *aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart;
2135 return NS_OK;
2138 nsresult
2139 nsHyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame *aFrame,
2140 PRInt32 aOffset,
2141 nsIAccessible *aAccessible,
2142 nsIDOMNode **aNode,
2143 PRInt32 *aNodeOffset)
2145 NS_ENSURE_ARG(aAccessible);
2147 nsCOMPtr<nsIDOMNode> node;
2149 if (!aFrame) {
2150 // If the given frame is null then set offset after the DOM node of the
2151 // given accessible.
2152 nsCOMPtr<nsIAccessNode> accessNode(do_QueryInterface(aAccessible));
2154 nsCOMPtr<nsIDOMNode> DOMNode;
2155 accessNode->GetDOMNode(getter_AddRefs(DOMNode));
2156 nsCOMPtr<nsIContent> content(do_QueryInterface(DOMNode));
2157 NS_ENSURE_STATE(content);
2159 nsCOMPtr<nsIContent> parent(content->GetParent());
2160 NS_ENSURE_STATE(parent);
2162 *aNodeOffset = parent->IndexOf(content) + 1;
2163 node = do_QueryInterface(parent);
2165 } else if (aFrame->GetType() == nsAccessibilityAtoms::textFrame) {
2166 nsCOMPtr<nsIContent> content(aFrame->GetContent());
2167 NS_ENSURE_STATE(content);
2169 nsCOMPtr<nsIPresShell> shell(GetPresShell());
2170 NS_ENSURE_STATE(shell);
2172 nsIFrame *primaryFrame = shell->GetPrimaryFrameFor(content);
2173 nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, aNodeOffset);
2174 NS_ENSURE_SUCCESS(rv, rv);
2176 node = do_QueryInterface(content);
2178 } else {
2179 nsCOMPtr<nsIContent> content(aFrame->GetContent());
2180 NS_ENSURE_STATE(content);
2182 nsCOMPtr<nsIContent> parent(content->GetParent());
2183 NS_ENSURE_STATE(parent);
2185 *aNodeOffset = parent->IndexOf(content);
2186 node = do_QueryInterface(parent);
2189 NS_IF_ADDREF(*aNode = node);
2190 return NS_OK;
2193 // nsHyperTextAccessible
2194 nsresult
2195 nsHyperTextAccessible::DOMRangeBoundToHypertextOffset(nsIDOMRange *aRange,
2196 PRBool aIsStartBound,
2197 PRBool aIsStartHTOffset,
2198 PRInt32 *aHTOffset)
2200 nsCOMPtr<nsIDOMNode> node;
2201 PRInt32 nodeOffset = 0;
2203 nsresult rv;
2204 if (aIsStartBound) {
2205 rv = aRange->GetStartContainer(getter_AddRefs(node));
2206 NS_ENSURE_SUCCESS(rv, rv);
2208 rv = aRange->GetStartOffset(&nodeOffset);
2209 NS_ENSURE_SUCCESS(rv, rv);
2210 } else {
2211 rv = aRange->GetEndContainer(getter_AddRefs(node));
2212 NS_ENSURE_SUCCESS(rv, rv);
2214 rv = aRange->GetEndOffset(&nodeOffset);
2215 NS_ENSURE_SUCCESS(rv, rv);
2218 nsCOMPtr<nsIAccessible> startAcc;
2219 rv = DOMPointToHypertextOffset(node, nodeOffset, aHTOffset,
2220 getter_AddRefs(startAcc));
2221 NS_ENSURE_SUCCESS(rv, rv);
2223 if (aIsStartHTOffset && !startAcc)
2224 *aHTOffset = 0;
2226 return NS_OK;
2229 // nsHyperTextAccessible
2230 nsresult
2231 nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode,
2232 PRInt32 aNodeOffset,
2233 PRInt32 *aHTStartOffset,
2234 PRInt32 *aHTEndOffset,
2235 nsIPersistentProperties *aAttributes)
2237 nsCOMArray<nsIDOMRange> ranges;
2238 nsresult rv = GetSelections(nsISelectionController::SELECTION_SPELLCHECK,
2239 nsnull, nsnull, &ranges);
2240 NS_ENSURE_SUCCESS(rv, rv);
2242 PRInt32 rangeCount = ranges.Count();
2243 if (!rangeCount)
2244 return NS_OK;
2246 for (PRInt32 index = 0; index < rangeCount; index++) {
2247 nsCOMPtr<nsIDOMRange> range = ranges[index];
2248 nsCOMPtr<nsIDOMNSRange> nsrange(do_QueryInterface(range));
2249 NS_ENSURE_STATE(nsrange);
2251 PRInt16 result;
2252 rv = nsrange->ComparePoint(aNode, aNodeOffset, &result);
2253 NS_ENSURE_SUCCESS(rv, rv);
2254 // ComparePoint checks boundary points, but we need to check that
2255 // text at aNodeOffset is inside the range.
2256 // See also bug 460690.
2257 if (result == 0) {
2258 nsCOMPtr<nsIDOMNode> end;
2259 rv = range->GetEndContainer(getter_AddRefs(end));
2260 NS_ENSURE_SUCCESS(rv, rv);
2261 PRInt32 endOffset;
2262 rv = range->GetEndOffset(&endOffset);
2263 NS_ENSURE_SUCCESS(rv, rv);
2264 if (aNode == end && aNodeOffset == endOffset) {
2265 result = 1;
2269 if (result == 1) { // range is before point
2270 PRInt32 startHTOffset = 0;
2271 rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_TRUE,
2272 &startHTOffset);
2273 NS_ENSURE_SUCCESS(rv, rv);
2275 if (startHTOffset > *aHTStartOffset)
2276 *aHTStartOffset = startHTOffset;
2278 } else if (result == -1) { // range is after point
2279 PRInt32 endHTOffset = 0;
2280 rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_FALSE,
2281 &endHTOffset);
2282 NS_ENSURE_SUCCESS(rv, rv);
2284 if (endHTOffset < *aHTEndOffset)
2285 *aHTEndOffset = endHTOffset;
2287 } else { // point is in range
2288 PRInt32 startHTOffset = 0;
2289 rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_TRUE,
2290 &startHTOffset);
2291 NS_ENSURE_SUCCESS(rv, rv);
2293 PRInt32 endHTOffset = 0;
2294 rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_FALSE,
2295 &endHTOffset);
2296 NS_ENSURE_SUCCESS(rv, rv);
2298 if (startHTOffset > *aHTStartOffset)
2299 *aHTStartOffset = startHTOffset;
2300 if (endHTOffset < *aHTEndOffset)
2301 *aHTEndOffset = endHTOffset;
2303 if (aAttributes) {
2304 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::invalid,
2305 NS_LITERAL_STRING("spelling"));
2308 return NS_OK;
2312 return NS_OK;
2315 // nsHyperTextAccessible
2316 nsresult
2317 nsHyperTextAccessible::GetLangTextAttributes(PRBool aIncludeDefAttrs,
2318 nsIDOMNode *aSourceNode,
2319 PRInt32 *aStartHTOffset,
2320 PRInt32 *aEndHTOffset,
2321 nsIPersistentProperties *aAttributes)
2323 nsCOMPtr<nsIDOMElement> sourceElm(nsCoreUtils::GetDOMElementFor(aSourceNode));
2325 nsCOMPtr<nsIContent> content(do_QueryInterface(sourceElm));
2326 nsCOMPtr<nsIContent> rootContent(do_QueryInterface(mDOMNode));
2328 nsAutoString lang;
2329 nsCoreUtils::GetLanguageFor(content, rootContent, lang);
2331 nsAutoString rootLang;
2332 nsresult rv = GetLanguage(rootLang);
2333 NS_ENSURE_SUCCESS(rv, rv);
2335 if (aAttributes) {
2336 // Expose 'language' text attribute if the DOM 'lang' attribute is
2337 // presented and it's different from the 'lang' attribute on the root
2338 // element or we should include default values of text attribute.
2339 const nsAString& resultLang = lang.IsEmpty() ? rootLang : lang;
2340 if (!resultLang.IsEmpty() && (aIncludeDefAttrs || lang != rootLang))
2341 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::language,
2342 resultLang);
2345 nsLangTextAttr textAttr(lang, rootContent);
2346 return GetRangeForTextAttr(aSourceNode, &textAttr,
2347 aStartHTOffset, aEndHTOffset);
2350 // nsHyperTextAccessible
2351 nsresult
2352 nsHyperTextAccessible::GetCSSTextAttributes(PRBool aIncludeDefAttrs,
2353 nsIDOMNode *aSourceNode,
2354 PRInt32 *aStartHTOffset,
2355 PRInt32 *aEndHTOffset,
2356 nsIPersistentProperties *aAttributes)
2358 nsCOMPtr<nsIDOMElement> sourceElm(nsCoreUtils::GetDOMElementFor(aSourceNode));
2359 nsCOMPtr<nsIDOMElement> rootElm(nsCoreUtils::GetDOMElementFor(mDOMNode));
2361 nsCSSTextAttr textAttr(aIncludeDefAttrs, sourceElm, rootElm);
2362 while (textAttr.Iterate()) {
2363 nsCAutoString name;
2364 nsAutoString value, oldValue;
2365 if (aAttributes && textAttr.Get(name, value))
2366 aAttributes->SetStringProperty(name, value, oldValue);
2368 nsresult rv = GetRangeForTextAttr(aSourceNode, &textAttr,
2369 aStartHTOffset, aEndHTOffset);
2370 NS_ENSURE_SUCCESS(rv, rv);
2373 nsIFrame *sourceFrame = nsCoreUtils::GetFrameFor(sourceElm);
2374 if (sourceFrame) {
2375 nsIFrame *rootFrame = nsnull;
2377 if (!aIncludeDefAttrs)
2378 rootFrame = nsCoreUtils::GetFrameFor(rootElm);
2380 nsBackgroundTextAttr backgroundTextAttr(sourceFrame, rootFrame);
2381 nsAutoString value;
2382 if (backgroundTextAttr.Get(value)) {
2383 nsAccUtils::SetAccAttr(aAttributes,
2384 nsAccessibilityAtoms::backgroundColor, value);
2387 nsresult rv = GetRangeForTextAttr(aSourceNode, &backgroundTextAttr,
2388 aStartHTOffset, aEndHTOffset);
2389 return rv;
2392 return NS_OK;
2395 // nsHyperTextAccessible
2396 nsresult
2397 nsHyperTextAccessible::GetRangeForTextAttr(nsIDOMNode *aNode,
2398 nsTextAttr *aComparer,
2399 PRInt32 *aStartHTOffset,
2400 PRInt32 *aEndHTOffset)
2402 nsCOMPtr<nsIDOMElement> rootElm(nsCoreUtils::GetDOMElementFor(mDOMNode));
2403 NS_ENSURE_STATE(rootElm);
2405 nsCOMPtr<nsIDOMNode> tmpNode(aNode);
2406 nsCOMPtr<nsIDOMNode> currNode(aNode);
2408 // Navigate backwards and forwards from current node to the root node to
2409 // calculate range bounds for the text attribute. Navigation sequence is the
2410 // following:
2411 // 1. Navigate through the siblings.
2412 // 2. If the traversed sibling has children then navigate from its leaf child
2413 // to it through whole tree of the traversed sibling.
2414 // 3. Get the parent and cycle algorithm until the root node.
2416 // Navigate backwards (find the start offset).
2417 while (currNode && currNode != rootElm) {
2418 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(currNode));
2419 NS_ENSURE_STATE(currElm);
2421 if (currNode != aNode && !aComparer->Equal(currElm)) {
2422 PRInt32 startHTOffset = 0;
2423 nsCOMPtr<nsIAccessible> startAcc;
2424 nsresult rv = DOMPointToHypertextOffset(tmpNode, -1, &startHTOffset,
2425 getter_AddRefs(startAcc));
2426 NS_ENSURE_SUCCESS(rv, rv);
2428 if (!startAcc)
2429 startHTOffset = 0;
2431 if (startHTOffset > *aStartHTOffset)
2432 *aStartHTOffset = startHTOffset;
2434 break;
2437 currNode->GetPreviousSibling(getter_AddRefs(tmpNode));
2438 if (tmpNode) {
2439 // Navigate through the subtree of traversed children to calculate
2440 // left bound of the range.
2441 FindStartOffsetInSubtree(tmpNode, currNode, aComparer, aStartHTOffset);
2444 currNode->GetParentNode(getter_AddRefs(tmpNode));
2445 currNode.swap(tmpNode);
2448 // Navigate forwards (find the end offset).
2449 PRBool moveIntoSubtree = PR_TRUE;
2450 currNode = aNode;
2451 while (currNode && currNode != rootElm) {
2452 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(currNode));
2453 NS_ENSURE_STATE(currElm);
2455 // Stop new end offset searching if the given text attribute changes its
2456 // value.
2457 if (!aComparer->Equal(currElm)) {
2458 PRInt32 endHTOffset = 0;
2459 nsresult rv = DOMPointToHypertextOffset(currNode, -1, &endHTOffset);
2460 NS_ENSURE_SUCCESS(rv, rv);
2462 if (endHTOffset < *aEndHTOffset)
2463 *aEndHTOffset = endHTOffset;
2465 break;
2468 if (moveIntoSubtree) {
2469 // Navigate through subtree of traversed node. We use 'moveIntoSubtree'
2470 // flag to avoid traversing the same subtree twice.
2471 currNode->GetFirstChild(getter_AddRefs(tmpNode));
2472 if (tmpNode)
2473 FindEndOffsetInSubtree(tmpNode, aComparer, aEndHTOffset);
2476 currNode->GetNextSibling(getter_AddRefs(tmpNode));
2477 moveIntoSubtree = PR_TRUE;
2478 if (!tmpNode) {
2479 currNode->GetParentNode(getter_AddRefs(tmpNode));
2480 moveIntoSubtree = PR_FALSE;
2483 currNode.swap(tmpNode);
2486 return NS_OK;
2490 PRBool
2491 nsHyperTextAccessible::FindEndOffsetInSubtree(nsIDOMNode *aCurrNode,
2492 nsTextAttr *aComparer,
2493 PRInt32 *aHTOffset)
2495 if (!aCurrNode)
2496 return PR_FALSE;
2498 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(aCurrNode));
2499 NS_ENSURE_STATE(currElm);
2501 // If the given text attribute (pointed by nsTextAttr object) changes its
2502 // value on the traversed element then fit the end of range.
2503 if (!aComparer->Equal(currElm)) {
2504 PRInt32 endHTOffset = 0;
2505 nsresult rv = DOMPointToHypertextOffset(aCurrNode, -1, &endHTOffset);
2506 NS_ENSURE_SUCCESS(rv, rv);
2508 if (endHTOffset < *aHTOffset)
2509 *aHTOffset = endHTOffset;
2511 return PR_TRUE;
2514 // Deeply traverse into the tree to fit the end of range.
2515 nsCOMPtr<nsIDOMNode> nextNode;
2516 aCurrNode->GetFirstChild(getter_AddRefs(nextNode));
2517 if (nextNode) {
2518 PRBool res = FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset);
2519 if (res)
2520 return res;
2523 aCurrNode->GetNextSibling(getter_AddRefs(nextNode));
2524 if (nextNode) {
2525 if (FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset))
2526 return PR_TRUE;
2529 return PR_FALSE;
2532 PRBool
2533 nsHyperTextAccessible::FindStartOffsetInSubtree(nsIDOMNode *aCurrNode,
2534 nsIDOMNode *aPrevNode,
2535 nsTextAttr *aComparer,
2536 PRInt32 *aHTOffset)
2538 if (!aCurrNode)
2539 return PR_FALSE;
2541 // Find the closest element back to the traversed element.
2542 nsCOMPtr<nsIDOMNode> nextNode;
2543 aCurrNode->GetLastChild(getter_AddRefs(nextNode));
2544 if (nextNode) {
2545 if (FindStartOffsetInSubtree(nextNode, aPrevNode, aComparer, aHTOffset))
2546 return PR_TRUE;
2549 nsCOMPtr<nsIDOMElement> currElm(nsCoreUtils::GetDOMElementFor(aCurrNode));
2550 NS_ENSURE_STATE(currElm);
2552 // If the given text attribute (pointed by nsTextAttr object) changes its
2553 // value on the traversed element then fit the start of range.
2554 if (!aComparer->Equal(currElm)) {
2555 PRInt32 startHTOffset = 0;
2556 nsCOMPtr<nsIAccessible> startAcc;
2557 nsresult rv = DOMPointToHypertextOffset(aPrevNode, -1, &startHTOffset,
2558 getter_AddRefs(startAcc));
2559 NS_ENSURE_SUCCESS(rv, rv);
2561 if (!startAcc)
2562 startHTOffset = 0;
2564 if (startHTOffset > *aHTOffset)
2565 *aHTOffset = startHTOffset;
2567 return PR_TRUE;
2570 // Moving backwards to find the start of range.
2571 aCurrNode->GetPreviousSibling(getter_AddRefs(nextNode));
2572 if (nextNode) {
2573 if (FindStartOffsetInSubtree(nextNode, aCurrNode, aComparer, aHTOffset))
2574 return PR_TRUE;
2577 return PR_FALSE;