Bug 460926 A11y hierachy is broken on Ubuntu 8.10 (GNOME 2.24), r=Evan.Yan sr=roc
[wine-gecko.git] / accessible / src / base / nsAccessible.cpp
bloba9c1c74d919261a36dc0022c248c887d6c98676b
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 Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * John Gaunt (jgaunt@netscape.com)
24 * Aaron Leventhal (aaronl@netscape.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 "nsAccessible.h"
41 #include "nsAccessibleRelation.h"
42 #include "nsHyperTextAccessibleWrap.h"
43 #include "nsNameUtils.h"
45 #include "nsIAccessibleDocument.h"
46 #include "nsIAccessibleHyperText.h"
47 #include "nsAccessibleTreeWalker.h"
49 #include "nsIDOMElement.h"
50 #include "nsIDOMDocument.h"
51 #include "nsIDOMDocumentXBL.h"
52 #include "nsIDOMDocumentTraversal.h"
53 #include "nsIDOMHTMLDocument.h"
54 #include "nsIDOMHTMLFormElement.h"
55 #include "nsIDOMNodeFilter.h"
56 #include "nsIDOMNSHTMLElement.h"
57 #include "nsIDOMTreeWalker.h"
58 #include "nsIDOMXULButtonElement.h"
59 #include "nsIDOMXULDocument.h"
60 #include "nsIDOMXULElement.h"
61 #include "nsIDOMXULLabelElement.h"
62 #include "nsIDOMXULSelectCntrlEl.h"
63 #include "nsIDOMXULSelectCntrlItemEl.h"
64 #include "nsPIDOMWindow.h"
66 #include "nsIDocument.h"
67 #include "nsIContent.h"
68 #include "nsIForm.h"
69 #include "nsIFormControl.h"
71 #include "nsIPresShell.h"
72 #include "nsPresContext.h"
73 #include "nsIFrame.h"
74 #include "nsIViewManager.h"
75 #include "nsIDocShellTreeItem.h"
76 #include "nsIScrollableFrame.h"
78 #include "nsXPIDLString.h"
79 #include "nsUnicharUtils.h"
80 #include "nsReadableUtils.h"
81 #include "prdtoa.h"
82 #include "nsIAtom.h"
83 #include "nsIPrefService.h"
84 #include "nsIPrefBranch.h"
85 #include "nsIURI.h"
86 #include "nsITimer.h"
87 #include "nsIMutableArray.h"
88 #include "nsIObserverService.h"
89 #include "nsIServiceManager.h"
90 #include "nsWhitespaceTokenizer.h"
91 #include "nsAttrName.h"
92 #include "nsNetUtil.h"
94 #ifdef NS_DEBUG
95 #include "nsIFrameDebug.h"
96 #include "nsIDOMCharacterData.h"
97 #endif
99 /**
100 * nsAccessibleDOMStringList implementation
102 nsAccessibleDOMStringList::nsAccessibleDOMStringList()
106 nsAccessibleDOMStringList::~nsAccessibleDOMStringList()
110 NS_IMPL_ISUPPORTS1(nsAccessibleDOMStringList, nsIDOMDOMStringList)
112 NS_IMETHODIMP
113 nsAccessibleDOMStringList::Item(PRUint32 aIndex, nsAString& aResult)
115 if (aIndex >= (PRUint32)mNames.Count()) {
116 SetDOMStringToNull(aResult);
117 } else {
118 mNames.StringAt(aIndex, aResult);
121 return NS_OK;
124 NS_IMETHODIMP
125 nsAccessibleDOMStringList::GetLength(PRUint32 *aLength)
127 *aLength = (PRUint32)mNames.Count();
129 return NS_OK;
132 NS_IMETHODIMP
133 nsAccessibleDOMStringList::Contains(const nsAString& aString, PRBool *aResult)
135 *aResult = mNames.IndexOf(aString) > -1;
137 return NS_OK;
141 * Class nsAccessible
144 ////////////////////////////////////////////////////////////////////////////////
145 // nsAccessible. nsISupports
147 NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccessible)
149 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsAccessible, nsAccessNode)
150 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mParent)
151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstChild)
152 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNextSibling)
153 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
155 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsAccessible, nsAccessNode)
156 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mParent)
157 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstChild)
158 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNextSibling)
159 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
161 NS_IMPL_ADDREF_INHERITED(nsAccessible, nsAccessNode)
162 NS_IMPL_RELEASE_INHERITED(nsAccessible, nsAccessNode)
164 nsresult nsAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr)
166 // Custom-built QueryInterface() knows when we support nsIAccessibleSelectable
167 // based on role attribute and aria-multiselectable
168 *aInstancePtr = nsnull;
170 if (aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) {
171 *aInstancePtr = &NS_CYCLE_COLLECTION_NAME(nsAccessible);
172 return NS_OK;
175 if (aIID.Equals(NS_GET_IID(nsIAccessible))) {
176 *aInstancePtr = static_cast<nsIAccessible*>(this);
177 NS_ADDREF_THIS();
178 return NS_OK;
181 if(aIID.Equals(NS_GET_IID(nsPIAccessible))) {
182 *aInstancePtr = static_cast<nsPIAccessible*>(this);
183 NS_ADDREF_THIS();
184 return NS_OK;
187 if (aIID.Equals(NS_GET_IID(nsAccessible))) {
188 *aInstancePtr = static_cast<nsAccessible*>(this);
189 NS_ADDREF_THIS();
190 return NS_OK;
193 if (aIID.Equals(NS_GET_IID(nsIAccessibleSelectable))) {
194 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
195 if (!content) {
196 return NS_ERROR_FAILURE; // This accessible has been shut down
198 if (content->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::role)) {
199 // If we have an XHTML role attribute present and the
200 // aria-multiselectable attribute is true, then we need
201 // to support nsIAccessibleSelectable
202 // If either attribute (role or multiselectable) change, then we'll
203 // destroy this accessible so that we can follow COM identity rules.
204 nsAutoString multiselectable;
205 if (content->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::aria_multiselectable,
206 nsAccessibilityAtoms::_true, eCaseMatters)) {
207 *aInstancePtr = static_cast<nsIAccessibleSelectable*>(this);
208 NS_ADDREF_THIS();
209 return NS_OK;
214 if (aIID.Equals(NS_GET_IID(nsIAccessibleValue))) {
215 if (mRoleMapEntry && mRoleMapEntry->valueRule != eNoValue) {
216 *aInstancePtr = static_cast<nsIAccessibleValue*>(this);
217 NS_ADDREF_THIS();
218 return NS_OK;
222 if (aIID.Equals(NS_GET_IID(nsIAccessibleHyperLink))) {
223 nsCOMPtr<nsIAccessible> parent(GetParent());
224 nsCOMPtr<nsIAccessibleHyperText> hyperTextParent(do_QueryInterface(parent));
225 if (hyperTextParent) {
226 *aInstancePtr = static_cast<nsIAccessibleHyperLink*>(this);
227 NS_ADDREF_THIS();
228 return NS_OK;
230 return NS_ERROR_NO_INTERFACE;
233 return nsAccessNodeWrap::QueryInterface(aIID, aInstancePtr);
236 nsAccessible::nsAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell): nsAccessNodeWrap(aNode, aShell),
237 mParent(nsnull), mFirstChild(nsnull), mNextSibling(nsnull), mRoleMapEntry(nsnull),
238 mAccChildCount(eChildCountUninitialized)
240 #ifdef NS_DEBUG_X
242 nsCOMPtr<nsIPresShell> shell(do_QueryReferent(aShell));
243 printf(">>> %p Created Acc - DOM: %p PS: %p",
244 (void*)static_cast<nsIAccessible*>(this), (void*)aNode,
245 (void*)shell.get());
246 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
247 if (content) {
248 nsAutoString buf;
249 if (content->NodeInfo())
250 content->NodeInfo()->GetQualifiedName(buf);
251 printf(" Con: %s@%p", NS_ConvertUTF16toUTF8(buf).get(), (void *)content.get());
252 if (NS_SUCCEEDED(GetName(buf))) {
253 printf(" Name:[%s]", NS_ConvertUTF16toUTF8(buf).get());
256 printf("\n");
258 #endif
261 //-----------------------------------------------------
262 // destruction
263 //-----------------------------------------------------
264 nsAccessible::~nsAccessible()
268 NS_IMETHODIMP nsAccessible::SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry)
270 mRoleMapEntry = aRoleMapEntry;
271 return NS_OK;
274 NS_IMETHODIMP
275 nsAccessible::GetName(nsAString& aName)
277 aName.Truncate();
279 if (IsDefunct())
280 return NS_ERROR_FAILURE;
282 GetARIAName(aName);
283 if (!aName.IsEmpty())
284 return NS_OK;
286 nsresult rv = GetNameInternal(aName);
287 NS_ENSURE_SUCCESS(rv, rv);
289 if (!aName.IsEmpty())
290 return NS_OK;
292 // In the end get the name from tooltip.
293 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
294 if (!content)
295 return NS_OK;
297 nsIAtom *tooltipAttr = nsnull;
299 if (content->IsNodeOfType(nsINode::eHTML))
300 tooltipAttr = nsAccessibilityAtoms::title;
301 else if (content->IsNodeOfType(nsINode::eXUL))
302 tooltipAttr = nsAccessibilityAtoms::tooltiptext;
303 else
304 return NS_OK;
306 // XXX: if CompressWhiteSpace worked on nsAString we could avoid a copy.
307 nsAutoString name;
308 if (content->GetAttr(kNameSpaceID_None, tooltipAttr, name)) {
309 name.CompressWhitespace();
310 aName = name;
311 } else {
312 aName.SetIsVoid(PR_TRUE);
315 return NS_OK;
318 NS_IMETHODIMP nsAccessible::GetDescription(nsAString& aDescription)
320 // There are 4 conditions that make an accessible have no accDescription:
321 // 1. it's a text node; or
322 // 2. It has no DHTML describedby property
323 // 3. it doesn't have an accName; or
324 // 4. its title attribute already equals to its accName nsAutoString name;
325 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
326 if (!content) {
327 return NS_ERROR_FAILURE; // Node shut down
329 if (!content->IsNodeOfType(nsINode::eTEXT)) {
330 nsAutoString description;
331 nsresult rv = GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
332 if (NS_FAILED(rv)) {
333 PRBool isXUL = content->IsNodeOfType(nsINode::eXUL);
334 if (isXUL) {
335 // Try XUL <description control="[id]">description text</description>
336 nsIContent *descriptionContent =
337 nsCoreUtils::FindNeighbourPointingToNode(content,
338 nsAccessibilityAtoms::control,
339 nsAccessibilityAtoms::description);
341 if (descriptionContent) {
342 // We have a description content node
343 AppendFlatStringFromSubtree(descriptionContent, &description);
346 if (description.IsEmpty()) {
347 nsIAtom *descAtom = isXUL ? nsAccessibilityAtoms::tooltiptext :
348 nsAccessibilityAtoms::title;
349 if (content->GetAttr(kNameSpaceID_None, descAtom, description)) {
350 nsAutoString name;
351 GetName(name);
352 if (name.IsEmpty() || description == name) {
353 // Don't use tooltip for a description if this object
354 // has no name or the tooltip is the same as the name
355 description.Truncate();
360 description.CompressWhitespace();
361 aDescription = description;
364 return NS_OK;
367 // mask values for ui.key.chromeAccess and ui.key.contentAccess
368 #define NS_MODIFIER_SHIFT 1
369 #define NS_MODIFIER_CONTROL 2
370 #define NS_MODIFIER_ALT 4
371 #define NS_MODIFIER_META 8
373 // returns the accesskey modifier mask used in the given node's context
374 // (i.e. chrome or content), or 0 if an error occurs
375 static PRInt32
376 GetAccessModifierMask(nsIContent* aContent)
378 nsCOMPtr<nsIPrefBranch> prefBranch =
379 do_GetService(NS_PREFSERVICE_CONTRACTID);
380 if (!prefBranch)
381 return 0;
383 // use ui.key.generalAccessKey (unless it is -1)
384 PRInt32 accessKey;
385 nsresult rv = prefBranch->GetIntPref("ui.key.generalAccessKey", &accessKey);
386 if (NS_SUCCEEDED(rv) && accessKey != -1) {
387 switch (accessKey) {
388 case nsIDOMKeyEvent::DOM_VK_SHIFT: return NS_MODIFIER_SHIFT;
389 case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL;
390 case nsIDOMKeyEvent::DOM_VK_ALT: return NS_MODIFIER_ALT;
391 case nsIDOMKeyEvent::DOM_VK_META: return NS_MODIFIER_META;
392 default: return 0;
396 // get the docShell to this DOMNode, return 0 on failure
397 nsCOMPtr<nsIDocument> document = aContent->GetCurrentDoc();
398 if (!document)
399 return 0;
400 nsCOMPtr<nsISupports> container = document->GetContainer();
401 if (!container)
402 return 0;
403 nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(container));
404 if (!treeItem)
405 return 0;
407 // determine the access modifier used in this context
408 PRInt32 itemType, accessModifierMask = 0;
409 treeItem->GetItemType(&itemType);
410 switch (itemType) {
412 case nsIDocShellTreeItem::typeChrome:
413 rv = prefBranch->GetIntPref("ui.key.chromeAccess", &accessModifierMask);
414 break;
416 case nsIDocShellTreeItem::typeContent:
417 rv = prefBranch->GetIntPref("ui.key.contentAccess", &accessModifierMask);
418 break;
421 return NS_SUCCEEDED(rv) ? accessModifierMask : 0;
424 NS_IMETHODIMP
425 nsAccessible::GetKeyboardShortcut(nsAString& aAccessKey)
427 aAccessKey.Truncate();
429 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
430 if (!content)
431 return NS_ERROR_FAILURE;
433 PRUint32 key = nsCoreUtils::GetAccessKeyFor(content);
434 if (!key && content->IsNodeOfType(nsIContent::eELEMENT)) {
435 // Copy access key from label node unless it is labeled
436 // via an ancestor <label>, in which case that would be redundant
437 nsCOMPtr<nsIContent> labelContent(nsCoreUtils::GetLabelContent(content));
438 nsCOMPtr<nsIDOMNode> labelNode = do_QueryInterface(labelContent);
439 if (labelNode && !nsCoreUtils::IsAncestorOf(labelNode, mDOMNode))
440 key = nsCoreUtils::GetAccessKeyFor(labelContent);
443 if (!key)
444 return NS_OK;
446 nsAutoString accesskey(key);
448 // Append the modifiers in reverse order, result: Control+Alt+Shift+Meta+<key>
449 nsAutoString propertyKey;
450 PRInt32 modifierMask = GetAccessModifierMask(content);
451 if (modifierMask & NS_MODIFIER_META) {
452 propertyKey.AssignLiteral("VK_META");
453 nsAccessible::GetFullKeyName(propertyKey, accesskey, accesskey);
455 if (modifierMask & NS_MODIFIER_SHIFT) {
456 propertyKey.AssignLiteral("VK_SHIFT");
457 nsAccessible::GetFullKeyName(propertyKey, accesskey, accesskey);
459 if (modifierMask & NS_MODIFIER_ALT) {
460 propertyKey.AssignLiteral("VK_ALT");
461 nsAccessible::GetFullKeyName(propertyKey, accesskey, accesskey);
463 if (modifierMask & NS_MODIFIER_CONTROL) {
464 propertyKey.AssignLiteral("VK_CONTROL");
465 nsAccessible::GetFullKeyName(propertyKey, accesskey, accesskey);
468 aAccessKey = accesskey;
469 return NS_OK;
472 NS_IMETHODIMP nsAccessible::SetParent(nsIAccessible *aParent)
474 if (mParent != aParent) {
475 // Adopt a child -- we allow this now. the new parent
476 // may be a dom node which wasn't previously accessible but now is.
477 // The old parent's children now need to be invalidated, since
478 // it no longer owns the child, the new parent does
479 nsCOMPtr<nsPIAccessible> privOldParent = do_QueryInterface(mParent);
480 if (privOldParent) {
481 privOldParent->InvalidateChildren();
485 mParent = aParent;
486 return NS_OK;
489 NS_IMETHODIMP nsAccessible::SetFirstChild(nsIAccessible *aFirstChild)
491 mFirstChild = aFirstChild;
493 return NS_OK;
496 NS_IMETHODIMP nsAccessible::SetNextSibling(nsIAccessible *aNextSibling)
498 mNextSibling = aNextSibling;
499 return NS_OK;
502 nsresult
503 nsAccessible::Shutdown()
505 mNextSibling = nsnull;
507 // Invalidate the child count and pointers to other accessibles, also make
508 // sure none of its children point to this parent
509 InvalidateChildren();
510 if (mParent) {
511 nsCOMPtr<nsPIAccessible> privateParent(do_QueryInterface(mParent));
512 privateParent->InvalidateChildren();
513 mParent = nsnull;
516 return nsAccessNodeWrap::Shutdown();
519 NS_IMETHODIMP nsAccessible::InvalidateChildren()
521 // Document has transformed, reset our invalid children and child count
523 // Reset the sibling pointers, they will be set up again the next time
524 // CacheChildren() is called.
525 // Note: we don't want to start creating accessibles at this point,
526 // so don't use GetNextSibling() here. (bug 387252)
527 nsAccessible* child = static_cast<nsAccessible*>(mFirstChild.get());
528 while (child) {
529 child->mParent = nsnull;
531 nsCOMPtr<nsIAccessible> next = child->mNextSibling;
532 child->mNextSibling = nsnull;
533 child = static_cast<nsAccessible*>(next.get());
536 mAccChildCount = eChildCountUninitialized;
537 mFirstChild = nsnull;
538 return NS_OK;
541 NS_IMETHODIMP nsAccessible::GetParent(nsIAccessible ** aParent)
543 nsresult rv = GetCachedParent(aParent);
544 if (NS_FAILED(rv) || *aParent) {
545 return rv;
548 nsCOMPtr<nsIAccessibleDocument> docAccessible(GetDocAccessible());
549 NS_ENSURE_TRUE(docAccessible, NS_ERROR_FAILURE);
551 return docAccessible->GetAccessibleInParentChain(mDOMNode, PR_TRUE, aParent);
554 NS_IMETHODIMP nsAccessible::GetCachedParent(nsIAccessible ** aParent)
556 *aParent = nsnull;
557 if (!mWeakShell) {
558 // This node has been shut down
559 return NS_ERROR_FAILURE;
561 NS_IF_ADDREF(*aParent = mParent);
562 return NS_OK;
565 NS_IMETHODIMP nsAccessible::GetCachedFirstChild(nsIAccessible ** aFirstChild)
567 *aFirstChild = nsnull;
568 if (!mWeakShell) {
569 // This node has been shut down
570 return NS_ERROR_FAILURE;
572 NS_IF_ADDREF(*aFirstChild = mFirstChild);
573 return NS_OK;
576 /* readonly attribute nsIAccessible nextSibling; */
577 NS_IMETHODIMP nsAccessible::GetNextSibling(nsIAccessible * *aNextSibling)
579 *aNextSibling = nsnull;
580 if (!mWeakShell) {
581 // This node has been shut down
582 return NS_ERROR_FAILURE;
584 if (!mParent) {
585 nsCOMPtr<nsIAccessible> parent(GetParent());
586 if (parent) {
587 PRInt32 numChildren;
588 parent->GetChildCount(&numChildren); // Make sure we cache all of the children
592 if (mNextSibling || !mParent) {
593 // If no parent, don't try to calculate a new sibling
594 // It either means we're at the root or shutting down the parent
595 NS_IF_ADDREF(*aNextSibling = mNextSibling);
597 return NS_OK;
600 return NS_ERROR_FAILURE;
603 /* readonly attribute nsIAccessible previousSibling; */
604 NS_IMETHODIMP nsAccessible::GetPreviousSibling(nsIAccessible * *aPreviousSibling)
606 *aPreviousSibling = nsnull;
608 if (!mWeakShell) {
609 // This node has been shut down
610 return NS_ERROR_FAILURE;
613 nsCOMPtr<nsIAccessible> parent;
614 if (NS_FAILED(GetParent(getter_AddRefs(parent))) || !parent) {
615 return NS_ERROR_FAILURE;
618 nsCOMPtr<nsIAccessible> testAccessible, prevSibling;
619 parent->GetFirstChild(getter_AddRefs(testAccessible));
620 while (testAccessible && this != testAccessible) {
621 prevSibling = testAccessible;
622 prevSibling->GetNextSibling(getter_AddRefs(testAccessible));
625 if (!prevSibling) {
626 return NS_ERROR_FAILURE;
629 NS_ADDREF(*aPreviousSibling = prevSibling);
630 return NS_OK;
633 /* readonly attribute nsIAccessible firstChild; */
634 NS_IMETHODIMP nsAccessible::GetFirstChild(nsIAccessible * *aFirstChild)
636 if (gIsCacheDisabled) {
637 InvalidateChildren();
639 PRInt32 numChildren;
640 GetChildCount(&numChildren); // Make sure we cache all of the children
642 #ifdef DEBUG
643 nsCOMPtr<nsPIAccessible> firstChild(do_QueryInterface(mFirstChild));
644 if (firstChild) {
645 nsCOMPtr<nsIAccessible> realParent;
646 firstChild->GetCachedParent(getter_AddRefs(realParent));
647 NS_ASSERTION(!realParent || realParent == this,
648 "Two accessibles have the same first child accessible.");
650 #endif
652 NS_IF_ADDREF(*aFirstChild = mFirstChild);
654 return NS_OK;
657 /* readonly attribute nsIAccessible lastChild; */
658 NS_IMETHODIMP nsAccessible::GetLastChild(nsIAccessible * *aLastChild)
660 GetChildAt(-1, aLastChild);
661 return NS_OK;
664 NS_IMETHODIMP nsAccessible::GetChildAt(PRInt32 aChildNum, nsIAccessible **aChild)
666 // aChildNum is a zero-based index
668 PRInt32 numChildren;
669 GetChildCount(&numChildren);
671 // If no children or aChildNum is larger than numChildren, return null
672 if (aChildNum >= numChildren || numChildren == 0 || !mWeakShell) {
673 *aChild = nsnull;
674 return NS_ERROR_FAILURE;
675 // If aChildNum is less than zero, set aChild to last index
676 } else if (aChildNum < 0) {
677 aChildNum = numChildren - 1;
680 nsCOMPtr<nsIAccessible> current(mFirstChild), nextSibling;
681 PRInt32 index = 0;
683 while (current) {
684 nextSibling = current;
685 if (++index > aChildNum) {
686 break;
688 nextSibling->GetNextSibling(getter_AddRefs(current));
691 NS_IF_ADDREF(*aChild = nextSibling);
693 return NS_OK;
696 // readonly attribute nsIArray children;
697 NS_IMETHODIMP nsAccessible::GetChildren(nsIArray **aOutChildren)
699 *aOutChildren = nsnull;
700 nsCOMPtr<nsIMutableArray> children = do_CreateInstance(NS_ARRAY_CONTRACTID);
701 if (!children)
702 return NS_ERROR_FAILURE;
704 nsCOMPtr<nsIAccessible> curChild;
705 while (NextChild(curChild)) {
706 children->AppendElement(curChild, PR_FALSE);
709 NS_ADDREF(*aOutChildren = children);
710 return NS_OK;
713 nsIAccessible *nsAccessible::NextChild(nsCOMPtr<nsIAccessible>& aAccessible)
715 nsCOMPtr<nsIAccessible> nextChild;
716 if (!aAccessible) {
717 GetFirstChild(getter_AddRefs(nextChild));
719 else {
720 aAccessible->GetNextSibling(getter_AddRefs(nextChild));
722 return (aAccessible = nextChild);
725 void nsAccessible::CacheChildren()
727 if (!mWeakShell) {
728 // This node has been shut down
729 mAccChildCount = eChildCountUninitialized;
730 return;
733 if (mAccChildCount == eChildCountUninitialized) {
734 mAccChildCount = 0;// Prevent reentry
735 PRBool allowsAnonChildren = PR_FALSE;
736 GetAllowsAnonChildAccessibles(&allowsAnonChildren);
737 nsAccessibleTreeWalker walker(mWeakShell, mDOMNode, allowsAnonChildren);
738 // Seed the frame hint early while we're still on a container node.
739 // This is better than doing the GetPrimaryFrameFor() later on
740 // a text node, because text nodes aren't in the frame map.
741 walker.mState.frame = GetFrame();
743 nsCOMPtr<nsPIAccessible> privatePrevAccessible;
744 PRInt32 childCount = 0;
745 walker.GetFirstChild();
746 SetFirstChild(walker.mState.accessible);
748 while (walker.mState.accessible) {
749 ++ childCount;
750 privatePrevAccessible = do_QueryInterface(walker.mState.accessible);
751 privatePrevAccessible->SetParent(this);
752 walker.GetNextSibling();
753 privatePrevAccessible->SetNextSibling(walker.mState.accessible);
755 mAccChildCount = childCount;
759 NS_IMETHODIMP nsAccessible::GetAllowsAnonChildAccessibles(PRBool *aAllowsAnonChildren)
761 *aAllowsAnonChildren = PR_TRUE;
762 return NS_OK;
765 /* readonly attribute long childCount; */
766 NS_IMETHODIMP nsAccessible::GetChildCount(PRInt32 *aAccChildCount)
768 CacheChildren();
769 *aAccChildCount = mAccChildCount;
770 return NS_OK;
773 /* readonly attribute long indexInParent; */
774 NS_IMETHODIMP nsAccessible::GetIndexInParent(PRInt32 *aIndexInParent)
776 *aIndexInParent = -1;
777 if (!mWeakShell) {
778 return NS_ERROR_FAILURE;
781 nsCOMPtr<nsIAccessible> parent;
782 GetParent(getter_AddRefs(parent));
783 if (!parent) {
784 return NS_ERROR_FAILURE;
787 nsCOMPtr<nsIAccessible> sibling;
788 parent->GetFirstChild(getter_AddRefs(sibling));
789 if (!sibling) {
790 return NS_ERROR_FAILURE;
793 *aIndexInParent = 0;
794 while (sibling != this) {
795 NS_ASSERTION(sibling, "Never ran into the same child that we started from");
797 if (!sibling)
798 return NS_ERROR_FAILURE;
800 ++*aIndexInParent;
801 nsCOMPtr<nsIAccessible> tempAccessible;
802 sibling->GetNextSibling(getter_AddRefs(tempAccessible));
803 sibling = tempAccessible;
806 return NS_OK;
809 NS_IMETHODIMP nsAccessible::TestChildCache(nsIAccessible *aCachedChild)
811 #ifndef DEBUG_A11Y
812 return NS_OK;
813 #else
814 // All cached accessible nodes should be in the parent
815 // It will assert if not all the children were created
816 // when they were first cached, and no invalidation
817 // ever corrected parent accessible's child cache.
818 if (mAccChildCount <= 0) {
819 return NS_OK;
821 nsCOMPtr<nsIAccessible> sibling = mFirstChild;
823 while (sibling != aCachedChild) {
824 NS_ASSERTION(sibling, "[TestChildCache] Never ran into the same child that we started from");
825 if (!sibling)
826 return NS_ERROR_FAILURE;
828 nsCOMPtr<nsIAccessible> tempAccessible;
829 sibling->GetNextSibling(getter_AddRefs(tempAccessible));
830 sibling = tempAccessible;
832 return NS_OK;
833 #endif
836 nsresult nsAccessible::GetTranslatedString(const nsAString& aKey, nsAString& aStringOut)
838 nsXPIDLString xsValue;
840 if (!gStringBundle ||
841 NS_FAILED(gStringBundle->GetStringFromName(PromiseFlatString(aKey).get(), getter_Copies(xsValue))))
842 return NS_ERROR_FAILURE;
844 aStringOut.Assign(xsValue);
845 return NS_OK;
848 nsresult nsAccessible::GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut)
850 nsXPIDLString modifierName, separator;
852 if (!gKeyStringBundle ||
853 NS_FAILED(gKeyStringBundle->GetStringFromName(PromiseFlatString(aModifierName).get(),
854 getter_Copies(modifierName))) ||
855 NS_FAILED(gKeyStringBundle->GetStringFromName(PromiseFlatString(NS_LITERAL_STRING("MODIFIER_SEPARATOR")).get(),
856 getter_Copies(separator)))) {
857 return NS_ERROR_FAILURE;
860 aStringOut = modifierName + separator + aKeyName;
861 return NS_OK;
864 PRBool nsAccessible::IsVisible(PRBool *aIsOffscreen)
866 // We need to know if at least a kMinPixels around the object is visible
867 // Otherwise it will be marked nsIAccessibleStates::STATE_OFFSCREEN
868 // The STATE_INVISIBLE flag is for elements which are programmatically hidden
870 *aIsOffscreen = PR_TRUE;
871 if (!mDOMNode) {
872 return PR_FALSE; // Defunct object
875 const PRUint16 kMinPixels = 12;
876 // Set up the variables we need, return false if we can't get at them all
877 nsCOMPtr<nsIPresShell> shell(GetPresShell());
878 if (!shell)
879 return PR_FALSE;
881 nsIViewManager* viewManager = shell->GetViewManager();
882 if (!viewManager)
883 return PR_FALSE;
885 nsIFrame *frame = GetFrame();
886 if (!frame) {
887 return PR_FALSE;
890 // If visibility:hidden or visibility:collapsed then mark with STATE_INVISIBLE
891 if (!frame->GetStyleVisibility()->IsVisible())
893 return PR_FALSE;
896 nsPresContext *presContext = shell->GetPresContext();
897 if (!presContext)
898 return PR_FALSE;
900 // Get the bounds of the current frame, relative to the current view.
901 // We don't use the more accurate GetBoundsRect, because that is more expensive
902 // and the STATE_OFFSCREEN flag that this is used for only needs to be a rough
903 // indicator
905 nsRect relFrameRect = frame->GetRect();
906 nsIView *containingView = frame->GetViewExternal();
907 if (containingView) {
908 // When frame itself has a view, it has the same bounds as the view
909 relFrameRect.x = relFrameRect.y = 0;
911 else {
912 nsPoint frameOffset;
913 frame->GetOffsetFromView(frameOffset, &containingView);
914 if (!containingView)
915 return PR_FALSE; // no view -- not visible
916 relFrameRect.x = frameOffset.x;
917 relFrameRect.y = frameOffset.y;
920 nsRectVisibility rectVisibility;
921 viewManager->GetRectVisibility(containingView, relFrameRect,
922 nsPresContext::CSSPixelsToAppUnits(kMinPixels),
923 &rectVisibility);
925 if (rectVisibility == nsRectVisibility_kZeroAreaRect) {
926 nsIAtom *frameType = frame->GetType();
927 if (frameType == nsAccessibilityAtoms::textFrame) {
928 // Zero area rects can occur in the first frame of a multi-frame text flow,
929 // in which case the rendered text is not empty and the frame should not be marked invisible
930 nsAutoString renderedText;
931 frame->GetRenderedText (&renderedText, nsnull, nsnull, 0, 1);
932 if (!renderedText.IsEmpty()) {
933 rectVisibility = nsRectVisibility_kVisible;
936 else if (frameType == nsAccessibilityAtoms::inlineFrame) {
937 // Yuck. Unfortunately inline frames can contain larger frames inside of them,
938 // so we can't really believe this is a zero area rect without checking more deeply.
939 // GetBounds() will do that for us.
940 PRInt32 x, y, width, height;
941 GetBounds(&x, &y, &width, &height);
942 if (width > 0 && height > 0) {
943 rectVisibility = nsRectVisibility_kVisible;
948 if (rectVisibility == nsRectVisibility_kZeroAreaRect && frame &&
949 0 == (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
950 // Consider zero area objects hidden unless they are absoultely positioned
951 // or floating and may have descendants that have a non-zero size
952 return PR_FALSE;
955 // Currently one of:
956 // nsRectVisibility_kVisible,
957 // nsRectVisibility_kAboveViewport,
958 // nsRectVisibility_kBelowViewport,
959 // nsRectVisibility_kLeftOfViewport,
960 // nsRectVisibility_kRightOfViewport
961 // This view says it is visible, but we need to check the parent view chain :(
962 nsCOMPtr<nsIDOMDocument> domDoc;
963 mDOMNode->GetOwnerDocument(getter_AddRefs(domDoc));
964 nsCOMPtr<nsIDocument> doc(do_QueryInterface(domDoc));
965 if (!doc) {
966 return PR_FALSE;
969 PRBool isVisible = CheckVisibilityInParentChain(doc, containingView);
970 if (isVisible && rectVisibility == nsRectVisibility_kVisible) {
971 *aIsOffscreen = PR_FALSE;
973 return isVisible;
976 nsresult
977 nsAccessible::GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState)
979 *aState = 0;
981 if (IsDefunct()) {
982 if (aExtraState)
983 *aExtraState = nsIAccessibleStates::EXT_STATE_DEFUNCT;
985 return NS_OK_DEFUNCT_OBJECT;
988 if (aExtraState)
989 *aExtraState = 0;
991 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
992 if (!content) {
993 return NS_OK; // On document, this is not an error
996 // Set STATE_UNAVAILABLE state based on disabled attribute
997 // The disabled attribute is mostly used in XUL elements and HTML forms, but
998 // if someone sets it on another attribute,
999 // it seems reasonable to consider it unavailable
1000 PRBool isDisabled;
1001 if (content->IsNodeOfType(nsINode::eHTML)) {
1002 // In HTML, just the presence of the disabled attribute means it is disabled,
1003 // therefore disabled="false" indicates disabled!
1004 isDisabled = content->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::disabled);
1006 else {
1007 isDisabled = content->AttrValueIs(kNameSpaceID_None,
1008 nsAccessibilityAtoms::disabled,
1009 nsAccessibilityAtoms::_true,
1010 eCaseMatters);
1012 if (isDisabled) {
1013 *aState |= nsIAccessibleStates::STATE_UNAVAILABLE;
1015 else if (content->IsNodeOfType(nsINode::eELEMENT)) {
1016 nsIFrame *frame = GetFrame();
1017 if (frame && frame->IsFocusable()) {
1018 *aState |= nsIAccessibleStates::STATE_FOCUSABLE;
1021 if (gLastFocusedNode == mDOMNode) {
1022 *aState |= nsIAccessibleStates::STATE_FOCUSED;
1026 // Check if nsIAccessibleStates::STATE_INVISIBLE and
1027 // STATE_OFFSCREEN flags should be turned on for this object.
1028 PRBool isOffscreen;
1029 if (!IsVisible(&isOffscreen)) {
1030 *aState |= nsIAccessibleStates::STATE_INVISIBLE;
1032 if (isOffscreen) {
1033 *aState |= nsIAccessibleStates::STATE_OFFSCREEN;
1036 nsIFrame *frame = GetFrame();
1037 if (frame && (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW))
1038 *aState |= nsIAccessibleStates::STATE_FLOATING;
1040 // Add 'linked' state for simple xlink.
1041 if (nsCoreUtils::IsXLink(content))
1042 *aState |= nsIAccessibleStates::STATE_LINKED;
1044 return NS_OK;
1047 /* readonly attribute boolean focusedChild; */
1048 NS_IMETHODIMP nsAccessible::GetFocusedChild(nsIAccessible **aFocusedChild)
1050 nsCOMPtr<nsIAccessible> focusedChild;
1051 if (gLastFocusedNode == mDOMNode) {
1052 focusedChild = this;
1054 else if (gLastFocusedNode) {
1055 nsCOMPtr<nsIAccessibilityService> accService =
1056 do_GetService("@mozilla.org/accessibilityService;1");
1057 NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
1059 accService->GetAccessibleFor(gLastFocusedNode,
1060 getter_AddRefs(focusedChild));
1061 if (focusedChild) {
1062 nsCOMPtr<nsIAccessible> focusedParentAccessible;
1063 focusedChild->GetParent(getter_AddRefs(focusedParentAccessible));
1064 if (focusedParentAccessible != this) {
1065 focusedChild = nsnull;
1070 NS_IF_ADDREF(*aFocusedChild = focusedChild);
1071 return NS_OK;
1074 // nsIAccessible getDeepestChildAtPoint(in long x, in long y)
1075 NS_IMETHODIMP
1076 nsAccessible::GetDeepestChildAtPoint(PRInt32 aX, PRInt32 aY,
1077 nsIAccessible **aAccessible)
1079 NS_ENSURE_ARG_POINTER(aAccessible);
1080 *aAccessible = nsnull;
1082 if (!mDOMNode) {
1083 return NS_ERROR_FAILURE; // Already shut down
1086 // If we can't find the point in a child, we will return the fallback answer:
1087 // we return |this| if the point is within it, otherwise nsnull
1088 nsCOMPtr<nsIAccessible> fallbackAnswer;
1089 PRInt32 x, y, width, height;
1090 GetBounds(&x, &y, &width, &height);
1091 if (aX >= x && aX < x + width &&
1092 aY >= y && aY < y + height) {
1093 fallbackAnswer = this;
1095 if (nsAccUtils::MustPrune(this)) { // Do not dig any further
1096 NS_IF_ADDREF(*aAccessible = fallbackAnswer);
1097 return NS_OK;
1100 // Search an accessible at the given point starting from accessible document
1101 // because containing block (see CSS2) for out of flow element (for example,
1102 // absolutely positioned element) may be different from its DOM parent and
1103 // therefore accessible for containing block may be different from accessible
1104 // for DOM parent but GetFrameForPoint() should be called for containing block
1105 // to get an out of flow element.
1106 nsCOMPtr<nsIAccessibleDocument> accDocument;
1107 nsresult rv = GetAccessibleDocument(getter_AddRefs(accDocument));
1108 NS_ENSURE_SUCCESS(rv, rv);
1109 NS_ENSURE_TRUE(accDocument, NS_ERROR_FAILURE);
1111 nsRefPtr<nsAccessNode> docAccessNode =
1112 nsAccUtils::QueryAccessNode(accDocument);
1114 nsIFrame *frame = docAccessNode->GetFrame();
1115 NS_ENSURE_STATE(frame);
1117 nsPresContext *presContext = frame->PresContext();
1119 nsIntRect screenRect = frame->GetScreenRectExternal();
1120 nsPoint offset(presContext->DevPixelsToAppUnits(aX - screenRect.x),
1121 presContext->DevPixelsToAppUnits(aY - screenRect.y));
1123 nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
1124 nsIFrame *foundFrame = presShell->GetFrameForPoint(frame, offset);
1125 nsCOMPtr<nsIContent> content;
1126 if (!foundFrame || !(content = foundFrame->GetContent())) {
1127 NS_IF_ADDREF(*aAccessible = fallbackAnswer);
1128 return NS_OK;
1131 nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content));
1132 nsCOMPtr<nsIAccessibilityService> accService = GetAccService();
1134 nsCOMPtr<nsIDOMNode> relevantNode;
1135 accService->GetRelevantContentNodeFor(node, getter_AddRefs(relevantNode));
1136 if (!relevantNode) {
1137 NS_IF_ADDREF(*aAccessible = fallbackAnswer);
1138 return NS_OK;
1141 nsCOMPtr<nsIAccessible> accessible;
1142 accService->GetAccessibleFor(relevantNode, getter_AddRefs(accessible));
1143 if (!accessible) {
1144 // No accessible for the node with the point, so find the first
1145 // accessible in the DOM parent chain
1146 accDocument->GetAccessibleInParentChain(relevantNode, PR_TRUE,
1147 getter_AddRefs(accessible));
1148 if (!accessible) {
1149 NS_IF_ADDREF(*aAccessible = fallbackAnswer);
1150 return NS_OK;
1154 if (accessible == this) {
1155 // Manually walk through accessible children and see if
1156 // the are within this point.
1157 // This takes care of cases where layout won't walk into
1158 // things for us, such as image map areas and sub documents
1159 nsCOMPtr<nsIAccessible> child;
1160 while (NextChild(child)) {
1161 PRInt32 childX, childY, childWidth, childHeight;
1162 child->GetBounds(&childX, &childY, &childWidth, &childHeight);
1163 if (aX >= childX && aX < childX + childWidth &&
1164 aY >= childY && aY < childY + childHeight &&
1165 (nsAccUtils::State(child) & nsIAccessibleStates::STATE_INVISIBLE) == 0) {
1166 // Don't walk into offscreen or invisible items
1167 NS_IF_ADDREF(*aAccessible = child);
1168 return NS_OK;
1171 // Fall through -- the point is in this accessible but not in a child
1172 // We are allowed to return |this| as the answer
1175 NS_IF_ADDREF(*aAccessible = accessible);
1176 return NS_OK;
1179 // nsIAccessible getChildAtPoint(in long x, in long y)
1180 NS_IMETHODIMP
1181 nsAccessible::GetChildAtPoint(PRInt32 aX, PRInt32 aY,
1182 nsIAccessible **aAccessible)
1184 nsresult rv = GetDeepestChildAtPoint(aX, aY, aAccessible);
1185 NS_ENSURE_SUCCESS(rv, rv);
1187 if (!*aAccessible || *aAccessible == this)
1188 return NS_OK;
1190 // Get direct child containing the deepest child at the given point.
1191 nsCOMPtr<nsIAccessible> parent, accessible(*aAccessible);
1192 while (PR_TRUE) {
1193 accessible->GetParent(getter_AddRefs(parent));
1194 if (!parent) {
1195 NS_NOTREACHED("Obtained accessible isn't a child of this accessible.");
1197 // Reached the top of the hierarchy. These bounds were inside an
1198 // accessible that is not a descendant of this one.
1200 // If we can't find the point in a child, we will return the fallback
1201 // answer: we return |this| if the point is within it, otherwise nsnull.
1202 PRInt32 x, y, width, height;
1203 GetBounds(&x, &y, &width, &height);
1204 if (aX >= x && aX < x + width && aY >= y && aY < y + height)
1205 NS_ADDREF(*aAccessible = this);
1207 return NS_OK;
1210 if (parent == this) {
1211 // We reached |this|, so |accessible| is the child we want to return.
1212 NS_ADDREF(*aAccessible = accessible);
1213 return NS_OK;
1215 accessible.swap(parent);
1218 return NS_OK;
1221 void nsAccessible::GetBoundsRect(nsRect& aTotalBounds, nsIFrame** aBoundingFrame)
1224 * This method is used to determine the bounds of a content node.
1225 * Because HTML wraps and links are not always rectangular, this
1226 * method uses the following algorithm:
1228 * 1) Start with an empty rectangle
1229 * 2) Add the rect for the primary frame from for the DOM node.
1230 * 3) For each next frame at the same depth with the same DOM node, add that rect to total
1231 * 4) If that frame is an inline frame, search deeper at that point in the tree, adding all rects
1234 // Initialization area
1235 *aBoundingFrame = nsnull;
1236 nsIFrame *firstFrame = GetBoundsFrame();
1237 if (!firstFrame)
1238 return;
1240 // Find common relative parent
1241 // This is an ancestor frame that will incompass all frames for this content node.
1242 // We need the relative parent so we can get absolute screen coordinates
1243 nsIFrame *ancestorFrame = firstFrame;
1245 while (ancestorFrame) {
1246 *aBoundingFrame = ancestorFrame;
1247 // If any other frame type, we only need to deal with the primary frame
1248 // Otherwise, there may be more frames attached to the same content node
1249 if (!nsCoreUtils::IsCorrectFrameType(ancestorFrame,
1250 nsAccessibilityAtoms::inlineFrame) &&
1251 !nsCoreUtils::IsCorrectFrameType(ancestorFrame,
1252 nsAccessibilityAtoms::textFrame))
1253 break;
1254 ancestorFrame = ancestorFrame->GetParent();
1257 nsIFrame *iterFrame = firstFrame;
1258 nsCOMPtr<nsIContent> firstContent(do_QueryInterface(mDOMNode));
1259 nsIContent* iterContent = firstContent;
1260 PRInt32 depth = 0;
1262 // Look only at frames below this depth, or at this depth (if we're still on the content node we started with)
1263 while (iterContent == firstContent || depth > 0) {
1264 // Coordinates will come back relative to parent frame
1265 nsRect currFrameBounds = iterFrame->GetRect();
1267 // Make this frame's bounds relative to common parent frame
1268 currFrameBounds +=
1269 iterFrame->GetParent()->GetOffsetToExternal(*aBoundingFrame);
1271 // Add this frame's bounds to total
1272 aTotalBounds.UnionRect(aTotalBounds, currFrameBounds);
1274 nsIFrame *iterNextFrame = nsnull;
1276 if (nsCoreUtils::IsCorrectFrameType(iterFrame,
1277 nsAccessibilityAtoms::inlineFrame)) {
1278 // Only do deeper bounds search if we're on an inline frame
1279 // Inline frames can contain larger frames inside of them
1280 iterNextFrame = iterFrame->GetFirstChild(nsnull);
1283 if (iterNextFrame)
1284 ++depth; // Child was found in code above this: We are going deeper in this iteration of the loop
1285 else {
1286 // Use next sibling if it exists, or go back up the tree to get the first next-in-flow or next-sibling
1287 // within our search
1288 while (iterFrame) {
1289 iterNextFrame = iterFrame->GetNextContinuation();
1290 if (!iterNextFrame)
1291 iterNextFrame = iterFrame->GetNextSibling();
1292 if (iterNextFrame || --depth < 0)
1293 break;
1294 iterFrame = iterFrame->GetParent();
1298 // Get ready for the next round of our loop
1299 iterFrame = iterNextFrame;
1300 if (iterFrame == nsnull)
1301 break;
1302 iterContent = nsnull;
1303 if (depth == 0)
1304 iterContent = iterFrame->GetContent();
1309 /* void getBounds (out long x, out long y, out long width, out long height); */
1310 NS_IMETHODIMP nsAccessible::GetBounds(PRInt32 *x, PRInt32 *y, PRInt32 *width, PRInt32 *height)
1312 // This routine will get the entire rectange for all the frames in this node
1313 // -------------------------------------------------------------------------
1314 // Primary Frame for node
1315 // Another frame, same node <- Example
1316 // Another frame, same node
1318 nsPresContext *presContext = GetPresContext();
1319 if (!presContext)
1321 *x = *y = *width = *height = 0;
1322 return NS_ERROR_FAILURE;
1325 nsRect unionRectTwips;
1326 nsIFrame* aBoundingFrame = nsnull;
1327 GetBoundsRect(unionRectTwips, &aBoundingFrame); // Unions up all primary frames for this node and all siblings after it
1328 if (!aBoundingFrame) {
1329 *x = *y = *width = *height = 0;
1330 return NS_ERROR_FAILURE;
1333 *x = presContext->AppUnitsToDevPixels(unionRectTwips.x);
1334 *y = presContext->AppUnitsToDevPixels(unionRectTwips.y);
1335 *width = presContext->AppUnitsToDevPixels(unionRectTwips.width);
1336 *height = presContext->AppUnitsToDevPixels(unionRectTwips.height);
1338 // We have the union of the rectangle, now we need to put it in absolute screen coords
1340 nsRect orgRectPixels = aBoundingFrame->GetScreenRectExternal();
1341 *x += orgRectPixels.x;
1342 *y += orgRectPixels.y;
1344 return NS_OK;
1347 // helpers
1349 nsIFrame* nsAccessible::GetBoundsFrame()
1351 return GetFrame();
1354 /* void removeSelection (); */
1355 NS_IMETHODIMP nsAccessible::SetSelected(PRBool aSelect)
1357 // Add or remove selection
1358 if (!mDOMNode) {
1359 return NS_ERROR_FAILURE;
1362 PRUint32 state = nsAccUtils::State(this);
1363 if (state & nsIAccessibleStates::STATE_SELECTABLE) {
1364 nsCOMPtr<nsIAccessible> multiSelect =
1365 nsAccUtils::GetMultiSelectFor(mDOMNode);
1366 if (!multiSelect) {
1367 return aSelect ? TakeFocus() : NS_ERROR_FAILURE;
1369 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
1370 NS_ASSERTION(content, "Called for dead accessible");
1372 if (mRoleMapEntry) {
1373 if (aSelect) {
1374 return content->SetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_selected,
1375 NS_LITERAL_STRING("true"), PR_TRUE);
1377 return content->UnsetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_selected, PR_TRUE);
1381 return NS_ERROR_FAILURE;
1384 /* void takeSelection (); */
1385 NS_IMETHODIMP nsAccessible::TakeSelection()
1387 // Select only this item
1388 if (!mDOMNode) {
1389 return NS_ERROR_FAILURE;
1392 PRUint32 state = nsAccUtils::State(this);
1393 if (state & nsIAccessibleStates::STATE_SELECTABLE) {
1394 nsCOMPtr<nsIAccessible> multiSelect =
1395 nsAccUtils::GetMultiSelectFor(mDOMNode);
1396 if (multiSelect) {
1397 nsCOMPtr<nsIAccessibleSelectable> selectable = do_QueryInterface(multiSelect);
1398 selectable->ClearSelection();
1400 return SetSelected(PR_TRUE);
1403 return NS_ERROR_FAILURE;
1406 /* void takeFocus (); */
1407 NS_IMETHODIMP
1408 nsAccessible::TakeFocus()
1410 if (IsDefunct())
1411 return NS_ERROR_FAILURE;
1413 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
1415 nsIFrame *frame = GetFrame();
1416 NS_ENSURE_STATE(frame);
1418 // If the current element can't take real DOM focus and if it has an ID and
1419 // ancestor with a the aria-activedescendant attribute present, then set DOM
1420 // focus to that ancestor and set aria-activedescendant on the ancestor to
1421 // the ID of the desired element.
1422 if (!frame->IsFocusable()) {
1423 nsAutoString id;
1424 if (content && nsCoreUtils::GetID(content, id)) {
1426 nsCOMPtr<nsIContent> ancestorContent = content;
1427 while ((ancestorContent = ancestorContent->GetParent()) &&
1428 !ancestorContent->HasAttr(kNameSpaceID_None,
1429 nsAccessibilityAtoms::aria_activedescendant));
1431 if (ancestorContent) {
1432 nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
1433 if (presShell) {
1434 nsIFrame *frame = presShell->GetPrimaryFrameFor(ancestorContent);
1435 if (frame && frame->IsFocusable()) {
1437 content = ancestorContent;
1438 content->SetAttr(kNameSpaceID_None,
1439 nsAccessibilityAtoms::aria_activedescendant,
1440 id, PR_TRUE);
1447 nsCOMPtr<nsIDOMNSHTMLElement> htmlElement(do_QueryInterface(content));
1448 if (htmlElement) {
1449 // HTML Elements also set the caret position
1450 // in order to affect tabbing order
1451 return htmlElement->Focus();
1454 content->SetFocus(GetPresContext());
1455 return NS_OK;
1458 nsresult nsAccessible::AppendStringWithSpaces(nsAString *aFlatString, const nsAString& textEquivalent)
1460 // Insert spaces to insure that words from controls aren't jammed together
1461 if (!textEquivalent.IsEmpty()) {
1462 if (!aFlatString->IsEmpty())
1463 aFlatString->Append(PRUnichar(' '));
1464 aFlatString->Append(textEquivalent);
1465 aFlatString->Append(PRUnichar(' '));
1467 return NS_OK;
1470 nsresult nsAccessible::AppendNameFromAccessibleFor(nsIContent *aContent,
1471 nsAString *aFlatString,
1472 PRBool aFromValue)
1474 nsAutoString textEquivalent, value;
1476 nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(aContent));
1477 nsCOMPtr<nsIAccessible> accessible;
1478 if (domNode == mDOMNode) {
1479 accessible = this;
1480 if (!aFromValue) {
1481 // prevent recursive call GetName()
1482 return NS_OK;
1485 else {
1486 nsCOMPtr<nsIAccessibilityService> accService =
1487 do_GetService("@mozilla.org/accessibilityService;1");
1488 NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
1489 accService->GetAccessibleInWeakShell(domNode, mWeakShell, getter_AddRefs(accessible));
1491 if (accessible) {
1492 if (aFromValue) {
1493 accessible->GetValue(textEquivalent);
1495 else {
1496 accessible->GetName(textEquivalent);
1500 textEquivalent.CompressWhitespace();
1501 return AppendStringWithSpaces(aFlatString, textEquivalent);
1505 * AppendFlatStringFromContentNode and AppendFlatStringFromSubtree
1507 * This method will glean useful text, in whatever form it exists, from any content node given to it.
1508 * It is used by any decendant of nsAccessible that needs to get text from a single node, as
1509 * well as by nsAccessible::AppendFlatStringFromSubtree, which gleans and concatenates text from any node and
1510 * that node's decendants.
1513 nsresult nsAccessible::AppendFlatStringFromContentNode(nsIContent *aContent, nsAString *aFlatString)
1515 if (aContent->IsNodeOfType(nsINode::eTEXT)) {
1516 // If it's a text node, append the text
1517 PRBool isHTMLBlock = PR_FALSE;
1518 nsCOMPtr<nsIPresShell> shell = GetPresShell();
1519 if (!shell) {
1520 return NS_ERROR_FAILURE;
1523 nsIContent *parentContent = aContent->GetParent();
1524 nsCOMPtr<nsIContent> appendedSubtreeStart(do_QueryInterface(mDOMNode));
1525 if (parentContent && parentContent != appendedSubtreeStart) {
1526 nsIFrame *frame = shell->GetPrimaryFrameFor(parentContent);
1527 if (frame) {
1528 // If this text is inside a block level frame (as opposed to span level), we need to add spaces around that
1529 // block's text, so we don't get words jammed together in final name
1530 // Extra spaces will be trimmed out later
1531 const nsStyleDisplay* display = frame->GetStyleDisplay();
1532 if (display->IsBlockOutside() ||
1533 display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) {
1534 isHTMLBlock = PR_TRUE;
1535 if (!aFlatString->IsEmpty()) {
1536 aFlatString->Append(PRUnichar(' '));
1541 if (aContent->TextLength() > 0) {
1542 nsIFrame *frame = shell->GetPrimaryFrameFor(aContent);
1543 if (frame) {
1544 nsresult rv = frame->GetRenderedText(aFlatString);
1545 NS_ENSURE_SUCCESS(rv, rv);
1546 } else {
1547 //if aContent is an object that is display: none, we have no a frame
1548 aContent->AppendTextTo(*aFlatString);
1550 if (isHTMLBlock && !aFlatString->IsEmpty()) {
1551 aFlatString->Append(PRUnichar(' '));
1554 return NS_OK;
1557 nsAutoString textEquivalent;
1558 if (!aContent->IsNodeOfType(nsINode::eHTML)) {
1559 if (aContent->IsNodeOfType(nsINode::eXUL)) {
1560 nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl(do_QueryInterface(aContent));
1561 if (labeledEl) {
1562 labeledEl->GetLabel(textEquivalent);
1564 else {
1565 if (aContent->NodeInfo()->Equals(nsAccessibilityAtoms::label, kNameSpaceID_XUL)) {
1566 aContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::value, textEquivalent);
1568 if (textEquivalent.IsEmpty()) {
1569 aContent->GetAttr(kNameSpaceID_None,
1570 nsAccessibilityAtoms::tooltiptext, textEquivalent);
1573 AppendNameFromAccessibleFor(aContent, &textEquivalent, PR_TRUE /* use value */);
1575 return AppendStringWithSpaces(aFlatString, textEquivalent);
1577 return NS_OK; // Not HTML and not XUL -- we don't handle it yet
1580 nsCOMPtr<nsIAtom> tag = aContent->Tag();
1581 if (tag == nsAccessibilityAtoms::img) {
1582 return AppendNameFromAccessibleFor(aContent, aFlatString);
1585 if (tag == nsAccessibilityAtoms::input) {
1586 static nsIContent::AttrValuesArray strings[] =
1587 {&nsAccessibilityAtoms::button, &nsAccessibilityAtoms::submit,
1588 &nsAccessibilityAtoms::reset, &nsAccessibilityAtoms::image, nsnull};
1589 if (aContent->FindAttrValueIn(kNameSpaceID_None, nsAccessibilityAtoms::type,
1590 strings, eIgnoreCase) >= 0) {
1591 return AppendNameFromAccessibleFor(aContent, aFlatString);
1595 if (tag == nsAccessibilityAtoms::object && !aContent->GetChildCount()) {
1596 // If object has no alternative content children, try title
1597 aContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::title, textEquivalent);
1599 else if (tag == nsAccessibilityAtoms::br) {
1600 // If it's a line break, insert a space so that words aren't jammed together
1601 aFlatString->AppendLiteral("\r\n");
1602 return NS_OK;
1604 else if (tag != nsAccessibilityAtoms::a && tag != nsAccessibilityAtoms::area) {
1605 AppendNameFromAccessibleFor(aContent, aFlatString, PR_TRUE /* use value */);
1608 textEquivalent.CompressWhitespace();
1609 return AppendStringWithSpaces(aFlatString, textEquivalent);
1613 nsresult nsAccessible::AppendFlatStringFromSubtree(nsIContent *aContent, nsAString *aFlatString)
1615 static PRBool isAlreadyHere; // Prevent recursion which can cause infinite loops
1616 if (isAlreadyHere) {
1617 return NS_OK;
1620 isAlreadyHere = PR_TRUE;
1622 nsCOMPtr<nsIPresShell> shell = GetPresShell();
1623 NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
1625 nsIFrame *frame = shell->GetPrimaryFrameFor(aContent);
1626 PRBool isHidden = (!frame || !frame->GetStyleVisibility()->IsVisible());
1627 nsresult rv = AppendFlatStringFromSubtreeRecurse(aContent, aFlatString,
1628 isHidden);
1630 isAlreadyHere = PR_FALSE;
1632 if (NS_SUCCEEDED(rv) && !aFlatString->IsEmpty()) {
1633 nsAString::const_iterator start, end;
1634 aFlatString->BeginReading(start);
1635 aFlatString->EndReading(end);
1637 PRInt32 spacesToTruncate = 0;
1638 while (-- end != start && *end == ' ')
1639 ++ spacesToTruncate;
1641 if (spacesToTruncate > 0)
1642 aFlatString->Truncate(aFlatString->Length() - spacesToTruncate);
1645 return rv;
1648 nsresult
1649 nsAccessible::AppendFlatStringFromSubtreeRecurse(nsIContent *aContent,
1650 nsAString *aFlatString,
1651 PRBool aIsRootHidden)
1653 // Depth first search for all text nodes that are decendants of content node.
1654 // Append all the text into one flat string
1655 PRUint32 numChildren = 0;
1656 nsCOMPtr<nsIDOMXULSelectControlElement> selectControlEl(do_QueryInterface(aContent));
1657 nsCOMPtr<nsIAtom> tag = aContent->Tag();
1659 if (!selectControlEl &&
1660 tag != nsAccessibilityAtoms::textarea &&
1661 tag != nsAccessibilityAtoms::select) {
1662 // Don't walk children of elements with options, just get label directly.
1663 // Don't traverse the children of a textarea, we want the value, not the
1664 // static text node.
1665 // Don't traverse the children of a select element, we only want the
1666 // current value.
1667 numChildren = aContent->GetChildCount();
1670 if (numChildren == 0) {
1671 // There are no children or they are irrelvant: get the text from the current node
1672 AppendFlatStringFromContentNode(aContent, aFlatString);
1673 return NS_OK;
1676 // There are relevant children: use them to get the text.
1677 nsCOMPtr<nsIPresShell> shell = GetPresShell();
1678 NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
1680 PRUint32 index;
1681 for (index = 0; index < numChildren; index++) {
1682 nsCOMPtr<nsIContent> childContent = aContent->GetChildAt(index);
1684 // Walk into hidden subtree if the the root parent is also hidden. This
1685 // happens when the author explictly uses a hidden label or description.
1686 if (!aIsRootHidden) {
1687 nsIFrame *childFrame = shell->GetPrimaryFrameFor(childContent);
1688 if (!childFrame || !childFrame->GetStyleVisibility()->IsVisible())
1689 continue;
1692 AppendFlatStringFromSubtreeRecurse(childContent, aFlatString,
1693 aIsRootHidden);
1696 return NS_OK;
1699 nsresult nsAccessible::GetTextFromRelationID(nsIAtom *aIDProperty, nsString &aName)
1701 // Get DHTML name from content subtree pointed to by ID attribute
1702 aName.Truncate();
1703 NS_ASSERTION(mDOMNode, "Called from shutdown accessible");
1704 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
1705 if (!content)
1706 return NS_OK;
1708 nsAutoString ids;
1709 if (!content->GetAttr(kNameSpaceID_None, aIDProperty, ids))
1710 return NS_OK;
1712 ids.CompressWhitespace(PR_TRUE, PR_TRUE);
1714 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->GetOwnerDoc());
1715 NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
1717 // Support idlist as in aria-labelledby="id1 id2 id3"
1718 while (!ids.IsEmpty()) {
1719 nsAutoString id;
1720 PRInt32 idLength = ids.FindChar(' ');
1721 NS_ASSERTION(idLength != 0, "Should not be 0 because of CompressWhitespace() call above");
1722 if (idLength == kNotFound) {
1723 id = ids;
1724 ids.Truncate();
1725 } else {
1726 id = Substring(ids, 0, idLength);
1727 ids.Cut(0, idLength + 1);
1730 if (!aName.IsEmpty()) {
1731 aName += ' '; // Need whitespace between multiple labels or descriptions
1733 nsCOMPtr<nsIDOMElement> labelElement;
1734 domDoc->GetElementById(id, getter_AddRefs(labelElement));
1735 content = do_QueryInterface(labelElement);
1736 if (!content) {
1737 return NS_OK;
1739 // We have a label content
1740 nsresult rv = AppendFlatStringFromSubtree(content, &aName);
1741 if (NS_SUCCEEDED(rv)) {
1742 aName.CompressWhitespace();
1746 return NS_OK;
1749 nsresult
1750 nsAccessible::GetHTMLName(nsAString& aLabel)
1752 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
1753 if (!content) {
1754 aLabel.SetIsVoid(PR_TRUE);
1755 return NS_OK;
1758 nsIContent *labelContent = nsCoreUtils::GetHTMLLabelContent(content);
1759 if (labelContent) {
1760 nsAutoString label;
1761 nsresult rv = AppendFlatStringFromSubtree(labelContent, &label);
1762 NS_ENSURE_SUCCESS(rv, rv);
1764 label.CompressWhitespace();
1765 if (!label.IsEmpty()) {
1766 aLabel = label;
1767 return NS_OK;
1771 PRUint32 role = nsAccUtils::Role(this);
1772 PRUint32 canAggregateName =
1773 nsNameUtils::gRoleToNameRulesMap[role] & eFromSubtree;
1775 if (canAggregateName) {
1776 // Don't use AppendFlatStringFromSubtree for container widgets like menulist
1777 nsresult rv = AppendFlatStringFromSubtree(content, &aLabel);
1778 NS_ENSURE_SUCCESS(rv, rv);
1780 if (!aLabel.IsEmpty())
1781 return NS_OK;
1784 return NS_OK;
1788 * 3 main cases for XUL Controls to be labeled
1789 * 1 - control contains label="foo"
1790 * 2 - control has, as a child, a label element
1791 * - label has either value="foo" or children
1792 * 3 - non-child label contains control="controlID"
1793 * - label has either value="foo" or children
1794 * Once a label is found, the search is discontinued, so a control
1795 * that has a label child as well as having a label external to
1796 * the control that uses the control="controlID" syntax will use
1797 * the child label for its Name.
1799 nsresult
1800 nsAccessible::GetXULName(nsAString& aLabel)
1802 // CASE #1 (via label attribute) -- great majority of the cases
1803 nsresult rv = NS_OK;
1805 nsAutoString label;
1806 nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl(do_QueryInterface(mDOMNode));
1807 if (labeledEl) {
1808 rv = labeledEl->GetLabel(label);
1810 else {
1811 nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl(do_QueryInterface(mDOMNode));
1812 if (itemEl) {
1813 rv = itemEl->GetLabel(label);
1815 else {
1816 nsCOMPtr<nsIDOMXULSelectControlElement> select(do_QueryInterface(mDOMNode));
1817 // Use label if this is not a select control element which
1818 // uses label attribute to indicate which option is selected
1819 if (!select) {
1820 nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(mDOMNode));
1821 if (xulEl) {
1822 rv = xulEl->GetAttribute(NS_LITERAL_STRING("label"), label);
1828 // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label>
1829 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
1830 if (!content)
1831 return NS_OK;
1833 if (NS_FAILED(rv) || label.IsEmpty()) {
1834 label.Truncate();
1835 nsIContent *labelContent =
1836 nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::control,
1837 nsAccessibilityAtoms::label);
1839 nsCOMPtr<nsIDOMXULLabelElement> xulLabel(do_QueryInterface(labelContent));
1840 // Check if label's value attribute is used
1841 if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(label)) && label.IsEmpty()) {
1842 // If no value attribute, a non-empty label must contain
1843 // children that define its text -- possibly using HTML
1844 AppendFlatStringFromSubtree(labelContent, &label);
1848 // XXX If CompressWhiteSpace worked on nsAString we could avoid a copy
1849 label.CompressWhitespace();
1850 if (!label.IsEmpty()) {
1851 aLabel = label;
1852 return NS_OK;
1855 // Can get text from title of <toolbaritem> if we're a child of a <toolbaritem>
1856 nsIContent *bindingParent = content->GetBindingParent();
1857 nsIContent *parent = bindingParent? bindingParent->GetParent() :
1858 content->GetParent();
1859 while (parent) {
1860 if (parent->Tag() == nsAccessibilityAtoms::toolbaritem &&
1861 parent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::title, label)) {
1862 label.CompressWhitespace();
1863 aLabel = label;
1864 return NS_OK;
1866 parent = parent->GetParent();
1869 PRUint32 role = nsAccUtils::Role(this);
1870 PRUint32 canAggregateName =
1871 nsNameUtils::gRoleToNameRulesMap[role] & eFromSubtree;
1873 return canAggregateName ?
1874 AppendFlatStringFromSubtree(content, &aLabel) : NS_OK;
1877 NS_IMETHODIMP
1878 nsAccessible::FireAccessibleEvent(nsIAccessibleEvent *aEvent)
1880 NS_ENSURE_ARG_POINTER(aEvent);
1881 nsCOMPtr<nsIDOMNode> eventNode;
1882 aEvent->GetDOMNode(getter_AddRefs(eventNode));
1883 NS_ENSURE_TRUE(nsAccUtils::IsNodeRelevant(eventNode), NS_ERROR_FAILURE);
1885 nsCOMPtr<nsIObserverService> obsService =
1886 do_GetService("@mozilla.org/observer-service;1");
1887 NS_ENSURE_TRUE(obsService, NS_ERROR_FAILURE);
1889 return obsService->NotifyObservers(aEvent, NS_ACCESSIBLE_EVENT_TOPIC, nsnull);
1892 NS_IMETHODIMP nsAccessible::GetFinalRole(PRUint32 *aRole)
1894 NS_ENSURE_ARG_POINTER(aRole);
1895 *aRole = nsIAccessibleRole::ROLE_NOTHING;
1897 if (mRoleMapEntry) {
1898 *aRole = mRoleMapEntry->role;
1900 // These unfortunate exceptions don't fit into the ARIA table
1901 // This is where the nsIAccessible role depends on both the role and ARIA state
1902 if (*aRole == nsIAccessibleRole::ROLE_PUSHBUTTON) {
1903 nsCOMPtr<nsIContent> content = do_QueryInterface(mDOMNode);
1904 if (content) {
1905 if (content->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_pressed)) {
1906 // For aria-pressed="false" or aria-pressed="true"
1907 // For simplicity, any pressed attribute indicates it's a toggle button
1908 *aRole = nsIAccessibleRole::ROLE_TOGGLE_BUTTON;
1910 else if (content->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::aria_haspopup,
1911 nsAccessibilityAtoms::_true, eCaseMatters)) {
1912 // For button with aria-haspopup="true"
1913 *aRole = nsIAccessibleRole::ROLE_BUTTONMENU;
1917 else if (*aRole == nsIAccessibleRole::ROLE_LISTBOX) {
1918 // A listbox inside of a combo box needs a special role because of ATK mapping to menu
1919 nsCOMPtr<nsIAccessible> possibleCombo;
1920 GetParent(getter_AddRefs(possibleCombo));
1921 if (nsAccUtils::Role(possibleCombo) == nsIAccessibleRole::ROLE_COMBOBOX) {
1922 *aRole = nsIAccessibleRole::ROLE_COMBOBOX_LIST;
1924 else { // Check to see if combo owns the listbox instead
1925 GetAccessibleRelated(nsIAccessibleRelation::RELATION_NODE_CHILD_OF, getter_AddRefs(possibleCombo));
1926 if (nsAccUtils::Role(possibleCombo) == nsIAccessibleRole::ROLE_COMBOBOX)
1927 *aRole = nsIAccessibleRole::ROLE_COMBOBOX_LIST;
1930 else if (*aRole == nsIAccessibleRole::ROLE_OPTION) {
1931 nsCOMPtr<nsIAccessible> parent;
1932 GetParent(getter_AddRefs(parent));
1933 if (nsAccUtils::Role(parent) == nsIAccessibleRole::ROLE_COMBOBOX_LIST)
1934 *aRole = nsIAccessibleRole::ROLE_COMBOBOX_OPTION;
1937 // gLandmarkRoleMap: can use role of accessible class impl
1938 // gEmptyRoleMap and all others: cannot use role of accessible class impl
1939 if (mRoleMapEntry != &nsARIAMap::gLandmarkRoleMap) {
1940 // We can now expose ROLE_NOTHING when there is a role map entry, which
1941 // will cause ATK to use ROLE_UNKNOWN and MSAA to use a BSTR role with
1942 // the ARIA role or element's tag. In either case the AT can also use
1943 // the object attributes tag and xml-roles to find out more.
1944 return NS_OK;
1947 return mDOMNode ? GetRole(aRole) : NS_ERROR_FAILURE; // Node already shut down
1950 NS_IMETHODIMP
1951 nsAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
1953 NS_ENSURE_ARG_POINTER(aAttributes); // In/out param. Created if necessary.
1955 if (IsDefunct())
1956 return NS_ERROR_FAILURE;
1958 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
1959 if (!content) {
1960 return NS_ERROR_FAILURE;
1963 nsCOMPtr<nsIPersistentProperties> attributes = *aAttributes;
1964 if (!attributes) {
1965 // Create only if an array wasn't already passed in
1966 attributes = do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
1967 NS_ENSURE_TRUE(attributes, NS_ERROR_OUT_OF_MEMORY);
1968 NS_ADDREF(*aAttributes = attributes);
1971 nsresult rv = GetAttributesInternal(attributes);
1972 NS_ENSURE_SUCCESS(rv, rv);
1974 nsAutoString id;
1975 nsAutoString oldValueUnused;
1976 if (nsCoreUtils::GetID(content, id)) {
1977 // Expose ID. If an <iframe id> exists override the one on the <body> of the source doc,
1978 // because the specific instance is what makes the ID useful for scripts
1979 attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, oldValueUnused);
1982 nsAutoString xmlRoles;
1983 if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::role, xmlRoles)) {
1984 attributes->SetStringProperty(NS_LITERAL_CSTRING("xml-roles"), xmlRoles, oldValueUnused);
1987 nsCOMPtr<nsIAccessibleValue> supportsValue = do_QueryInterface(static_cast<nsIAccessible*>(this));
1988 if (supportsValue) {
1989 // We support values, so expose the string value as well, via the valuetext object attribute
1990 // We test for the value interface because we don't want to expose traditional get_accValue()
1991 // information such as URL's on links and documents, or text in an input
1992 nsAutoString valuetext;
1993 GetValue(valuetext);
1994 attributes->SetStringProperty(NS_LITERAL_CSTRING("valuetext"), valuetext, oldValueUnused);
1998 PRUint32 role = nsAccUtils::Role(this);
1999 if (role == nsIAccessibleRole::ROLE_CHECKBUTTON ||
2000 role == nsIAccessibleRole::ROLE_PUSHBUTTON ||
2001 role == nsIAccessibleRole::ROLE_MENUITEM ||
2002 role == nsIAccessibleRole::ROLE_LISTITEM ||
2003 role == nsIAccessibleRole::ROLE_OPTION ||
2004 role == nsIAccessibleRole::ROLE_RADIOBUTTON ||
2005 role == nsIAccessibleRole::ROLE_RICH_OPTION ||
2006 role == nsIAccessibleRole::ROLE_OUTLINEITEM ||
2007 content->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_checked)) {
2008 // Might be checkable -- checking role & ARIA attribute first is faster than getting state
2009 PRUint32 state = 0;
2010 GetState(&state, nsnull);
2011 if (state & nsIAccessibleStates::STATE_CHECKABLE) {
2012 // No official state for checkable, so use object attribute to expose that
2013 attributes->SetStringProperty(NS_LITERAL_CSTRING("checkable"), NS_LITERAL_STRING("true"),
2014 oldValueUnused);
2018 // Level/setsize/posinset
2019 if (!nsAccUtils::HasAccGroupAttrs(attributes)) {
2020 // The role of an accessible can be pointed by ARIA attribute but ARIA
2021 // posinset, level, setsize may be skipped. Therefore we calculate here
2022 // these properties to map them into description.
2024 // If accessible is invisible we don't want to calculate group ARIA
2025 // attributes for it.
2026 if ((role == nsIAccessibleRole::ROLE_LISTITEM ||
2027 role == nsIAccessibleRole::ROLE_MENUITEM ||
2028 role == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
2029 role == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM ||
2030 role == nsIAccessibleRole::ROLE_RADIOBUTTON ||
2031 role == nsIAccessibleRole::ROLE_PAGETAB ||
2032 role == nsIAccessibleRole::ROLE_OPTION ||
2033 role == nsIAccessibleRole::ROLE_RADIOBUTTON ||
2034 role == nsIAccessibleRole::ROLE_OUTLINEITEM) &&
2035 0 == (nsAccUtils::State(this) & nsIAccessibleStates::STATE_INVISIBLE)) {
2037 PRUint32 baseRole = role;
2038 if (role == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
2039 role == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
2040 baseRole = nsIAccessibleRole::ROLE_MENUITEM;
2042 nsCOMPtr<nsIAccessible> parent = GetParent();
2043 NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
2045 PRInt32 positionInGroup = 0;
2046 PRInt32 setSize = 0;
2048 nsCOMPtr<nsIAccessible> sibling, nextSibling;
2049 parent->GetFirstChild(getter_AddRefs(sibling));
2050 NS_ENSURE_TRUE(sibling, NS_ERROR_FAILURE);
2052 PRBool foundCurrent = PR_FALSE;
2053 PRUint32 siblingRole, siblingBaseRole;
2054 while (sibling) {
2055 sibling->GetFinalRole(&siblingRole);
2057 siblingBaseRole = siblingRole;
2058 if (siblingRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
2059 siblingRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
2060 siblingBaseRole = nsIAccessibleRole::ROLE_MENUITEM;
2062 // If sibling is visible and has the same base role.
2063 if (siblingBaseRole == baseRole &&
2064 !(nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE)) {
2065 ++ setSize;
2066 if (!foundCurrent) {
2067 ++ positionInGroup;
2068 if (sibling == this)
2069 foundCurrent = PR_TRUE;
2073 // If the sibling is separator
2074 if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) {
2075 if (foundCurrent) // the our group is ended
2076 break;
2078 // not our group, continue the searching
2079 positionInGroup = 0;
2080 setSize = 0;
2083 sibling->GetNextSibling(getter_AddRefs(nextSibling));
2084 sibling = nextSibling;
2087 PRInt32 groupLevel = 0;
2088 if (role == nsIAccessibleRole::ROLE_OUTLINEITEM) {
2089 groupLevel = 1;
2090 nsCOMPtr<nsIAccessible> nextParent;
2091 while (parent) {
2092 parent->GetFinalRole(&role);
2094 if (role == nsIAccessibleRole::ROLE_OUTLINE)
2095 break;
2096 if (role == nsIAccessibleRole::ROLE_GROUPING)
2097 ++ groupLevel;
2099 parent->GetParent(getter_AddRefs(nextParent));
2100 parent.swap(nextParent);
2104 nsAccUtils::SetAccGroupAttrs(attributes, groupLevel, positionInGroup,
2105 setSize);
2109 // Expose all ARIA attributes
2110 PRUint32 numAttrs = content->GetAttrCount();
2111 for (PRUint32 count = 0; count < numAttrs; count ++) {
2112 const nsAttrName *attr = content->GetAttrNameAt(count);
2113 if (attr && attr->NamespaceEquals(kNameSpaceID_None)) {
2114 nsIAtom *attrAtom = attr->Atom();
2115 const char *attrStr;
2116 attrAtom->GetUTF8String(&attrStr);
2117 if (PL_strncmp(attrStr, "aria-", 5))
2118 continue; // Not ARIA
2119 if (!nsAccUtils::IsARIAPropForObjectAttr(attrAtom))
2120 continue; // No need to expose obj attribute -- will be exposed some other way
2121 nsAutoString value;
2122 if (content->GetAttr(kNameSpaceID_None, attrAtom, value)) {
2123 attributes->SetStringProperty(nsDependentCString(attrStr + 5), value, oldValueUnused);
2128 return NS_OK;
2131 nsresult
2132 nsAccessible::GetAttributesInternal(nsIPersistentProperties *aAttributes)
2134 // Attributes set by this method will not be used to override attributes on a sub-document accessible
2135 // when there is a <frame>/<iframe> element that spawned the sub-document
2136 nsIContent *content = nsCoreUtils::GetRoleContent(mDOMNode);
2137 nsCOMPtr<nsIDOMElement> element(do_QueryInterface(content));
2138 NS_ENSURE_TRUE(element, NS_ERROR_UNEXPECTED);
2140 nsAutoString tagName;
2141 element->GetTagName(tagName);
2142 if (!tagName.IsEmpty()) {
2143 nsAutoString oldValueUnused;
2144 aAttributes->SetStringProperty(NS_LITERAL_CSTRING("tag"), tagName,
2145 oldValueUnused);
2148 nsAccEvent::GetLastEventAttributes(mDOMNode, aAttributes);
2150 // Expose class because it may have useful microformat information
2151 // Let the class from an iframe's document be exposed, don't override from <iframe class>
2152 nsAutoString _class;
2153 if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::_class, _class))
2154 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::_class, _class);
2156 // Get container-foo computed live region properties based on the closest container with
2157 // the live region attribute.
2158 // Inner nodes override outer nodes within the same document --
2159 // The inner nodes can be used to override live region behavior on more general outer nodes
2160 // However, nodes in outer documents override nodes in inner documents:
2161 // Outer doc author may want to override properties on a widget they used in an iframe
2162 nsCOMPtr<nsIDOMNode> startNode = mDOMNode;
2163 nsIContent *startContent = content;
2164 while (PR_TRUE) {
2165 NS_ENSURE_STATE(startContent);
2166 nsIDocument *doc = startContent->GetDocument();
2167 nsCOMPtr<nsIDOMNode> docNode = do_QueryInterface(doc);
2168 NS_ENSURE_STATE(docNode);
2169 nsIContent *topContent = nsCoreUtils::GetRoleContent(docNode);
2170 NS_ENSURE_STATE(topContent);
2171 nsAccUtils::SetLiveContainerAttributes(aAttributes, startContent,
2172 topContent);
2174 // Allow ARIA live region markup from outer documents to override
2175 nsCOMPtr<nsISupports> container = doc->GetContainer();
2176 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
2177 do_QueryInterface(container);
2178 if (!docShellTreeItem)
2179 break;
2181 nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
2182 docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
2183 if (!sameTypeParent || sameTypeParent == docShellTreeItem)
2184 break;
2186 nsIDocument *parentDoc = doc->GetParentDocument();
2187 if (!parentDoc)
2188 break;
2190 startContent = parentDoc->FindContentForSubDocument(doc);
2193 // Expose 'display' attribute.
2194 nsAutoString value;
2195 nsresult rv = GetComputedStyleValue(EmptyString(),
2196 NS_LITERAL_STRING("display"),
2197 value);
2198 if (NS_SUCCEEDED(rv))
2199 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::display,
2200 value);
2202 // Expose 'text-align' attribute.
2203 rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("text-align"),
2204 value);
2205 if (NS_SUCCEEDED(rv))
2206 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::textAlign,
2207 value);
2209 // Expose 'text-indent' attribute.
2210 rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("text-indent"),
2211 value);
2212 if (NS_SUCCEEDED(rv))
2213 nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::textIndent,
2214 value);
2216 return NS_OK;
2219 NS_IMETHODIMP
2220 nsAccessible::GroupPosition(PRInt32 *aGroupLevel,
2221 PRInt32 *aSimilarItemsInGroup,
2222 PRInt32 *aPositionInGroup)
2224 // Every element exposes level/posinset/sizeset for IAccessdible::attributes
2225 // if they make sense for it. These attributes are mapped into groupPosition.
2226 // If 'level' attribute doesn't make sense element then it isn't represented
2227 // via IAccessible::attributes and groupLevel of groupPosition method is 0.
2228 // Elements that expose 'level' attribute only (like html headings elements)
2229 // don't support this method and all arguments are equalled 0.
2231 NS_ENSURE_ARG_POINTER(aGroupLevel);
2232 NS_ENSURE_ARG_POINTER(aSimilarItemsInGroup);
2233 NS_ENSURE_ARG_POINTER(aPositionInGroup);
2235 *aGroupLevel = 0;
2236 *aSimilarItemsInGroup = 0;
2237 *aPositionInGroup = 0;
2239 nsCOMPtr<nsIPersistentProperties> attributes;
2240 nsresult rv = GetAttributes(getter_AddRefs(attributes));
2241 NS_ENSURE_SUCCESS(rv, rv);
2242 if (!attributes) {
2243 return NS_ERROR_FAILURE;
2245 PRInt32 level, posInSet, setSize;
2246 nsAccUtils::GetAccGroupAttrs(attributes, &level, &posInSet, &setSize);
2248 if (!posInSet && !setSize)
2249 return NS_OK;
2251 *aGroupLevel = level;
2253 *aPositionInGroup = posInSet;
2254 *aSimilarItemsInGroup = setSize;
2256 return NS_OK;
2259 PRBool nsAccessible::MappedAttrState(nsIContent *aContent, PRUint32 *aStateInOut,
2260 nsStateMapEntry *aStateMapEntry)
2262 // Return true if we should continue
2263 if (!aStateMapEntry->attributeName) {
2264 return PR_FALSE; // Stop looking -- no more states
2267 nsAutoString attribValue;
2268 if (aContent->GetAttr(kNameSpaceID_None, *aStateMapEntry->attributeName, attribValue)) {
2269 if (aStateMapEntry->attributeValue == kBoolState) {
2270 // No attribute value map specified in state map entry indicates state cleared
2271 if (attribValue.EqualsLiteral("false")) {
2272 *aStateInOut &= ~aStateMapEntry->state;
2274 else {
2275 *aStateInOut |= aStateMapEntry->state;
2278 else if (NS_ConvertUTF16toUTF8(attribValue).Equals(aStateMapEntry->attributeValue)) {
2279 *aStateInOut |= aStateMapEntry->state;
2283 return PR_TRUE;
2286 NS_IMETHODIMP
2287 nsAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState)
2289 NS_ENSURE_ARG_POINTER(aState);
2291 nsresult rv = GetStateInternal(aState, aExtraState);
2292 NS_ENSURE_A11Y_SUCCESS(rv, rv);
2294 // Apply ARIA states to be sure accessible states will be overriden.
2295 GetARIAState(aState);
2297 if (mRoleMapEntry && mRoleMapEntry->role == nsIAccessibleRole::ROLE_PAGETAB) {
2298 if (*aState & nsIAccessibleStates::STATE_FOCUSED) {
2299 *aState |= nsIAccessibleStates::STATE_SELECTED;
2300 } else {
2301 // Expose 'selected' state on ARIA tab if the focus is on internal element
2302 // of related tabpanel.
2303 nsCOMPtr<nsIAccessible> tabPanel;
2304 rv = GetAccessibleRelated(nsIAccessibleRelation::RELATION_LABEL_FOR,
2305 getter_AddRefs(tabPanel));
2306 NS_ENSURE_SUCCESS(rv, rv);
2308 if (nsAccUtils::Role(tabPanel) == nsIAccessibleRole::ROLE_PROPERTYPAGE) {
2309 nsCOMPtr<nsIAccessNode> tabPanelAccessNode(do_QueryInterface(tabPanel));
2310 nsCOMPtr<nsIDOMNode> tabPanelNode;
2311 tabPanelAccessNode->GetDOMNode(getter_AddRefs(tabPanelNode));
2312 NS_ENSURE_STATE(tabPanelNode);
2314 if (nsCoreUtils::IsAncestorOf(tabPanelNode, gLastFocusedNode))
2315 *aState |= nsIAccessibleStates::STATE_SELECTED;
2320 const PRUint32 kExpandCollapseStates =
2321 nsIAccessibleStates::STATE_COLLAPSED | nsIAccessibleStates::STATE_EXPANDED;
2322 if ((*aState & kExpandCollapseStates) == kExpandCollapseStates) {
2323 // Cannot be both expanded and collapsed -- this happens in ARIA expanded
2324 // combobox because of limitation of nsARIAMap.
2325 // XXX: Perhaps we will be able to make this less hacky if we support
2326 // extended states in nsARIAMap, e.g. derive COLLAPSED from
2327 // EXPANDABLE && !EXPANDED.
2328 *aState &= ~nsIAccessibleStates::STATE_COLLAPSED;
2331 // Set additional states which presence depends on another states.
2332 if (!aExtraState)
2333 return NS_OK;
2335 if (!(*aState & nsIAccessibleStates::STATE_UNAVAILABLE)) {
2336 *aExtraState |= nsIAccessibleStates::EXT_STATE_ENABLED |
2337 nsIAccessibleStates::EXT_STATE_SENSITIVE;
2340 if ((*aState & nsIAccessibleStates::STATE_COLLAPSED) ||
2341 (*aState & nsIAccessibleStates::STATE_EXPANDED))
2342 *aExtraState |= nsIAccessibleStates::EXT_STATE_EXPANDABLE;
2344 if (mRoleMapEntry) {
2345 // If an object has an ancestor with the activedescendant property
2346 // pointing at it, we mark it as ACTIVE even if it's not currently focused.
2347 // This allows screen reader virtual buffer modes to know which descendant
2348 // is the current one that would get focus if the user navigates to the container widget.
2349 nsCOMPtr<nsIContent> content = do_QueryInterface(mDOMNode);
2350 nsAutoString id;
2351 if (content && nsCoreUtils::GetID(content, id)) {
2352 nsIContent *ancestorContent = content;
2353 nsAutoString activeID;
2354 while ((ancestorContent = ancestorContent->GetParent()) != nsnull) {
2355 if (ancestorContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_activedescendant, activeID)) {
2356 if (id == activeID) {
2357 *aExtraState |= nsIAccessibleStates::EXT_STATE_ACTIVE;
2359 break;
2365 PRUint32 role;
2366 rv = GetFinalRole(&role);
2367 NS_ENSURE_SUCCESS(rv, rv);
2369 if (role == nsIAccessibleRole::ROLE_ENTRY ||
2370 role == nsIAccessibleRole::ROLE_COMBOBOX) {
2372 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
2373 NS_ENSURE_STATE(content);
2375 nsAutoString autocomplete;
2376 if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_autocomplete, autocomplete) &&
2377 (autocomplete.EqualsIgnoreCase("inline") ||
2378 autocomplete.EqualsIgnoreCase("list") ||
2379 autocomplete.EqualsIgnoreCase("both"))) {
2380 *aExtraState |= nsIAccessibleStates::EXT_STATE_SUPPORTS_AUTOCOMPLETION;
2383 // XXX We can remove this hack once we support RDF-based role & state maps
2384 if (mRoleMapEntry && mRoleMapEntry->role == nsIAccessibleRole::ROLE_ENTRY) {
2385 PRBool isMultiLine = content->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::aria_multiline,
2386 nsAccessibilityAtoms::_true, eCaseMatters);
2387 *aExtraState |= isMultiLine ? nsIAccessibleStates::EXT_STATE_MULTI_LINE :
2388 nsIAccessibleStates::EXT_STATE_SINGLE_LINE;
2389 if (0 == (*aState & nsIAccessibleStates::STATE_READONLY))
2390 *aExtraState |= nsIAccessibleStates::EXT_STATE_EDITABLE; // Not readonly
2391 else // We're readonly: make sure editable state wasn't set by impl class
2392 *aExtraState &= ~nsIAccessibleStates::EXT_STATE_EDITABLE;
2396 // For some reasons DOM node may have not a frame. We tract such accessibles
2397 // as invisible.
2398 nsIFrame *frame = GetFrame();
2399 if (!frame)
2400 return NS_OK;
2402 const nsStyleDisplay* display = frame->GetStyleDisplay();
2403 if (display && display->mOpacity == 1.0f &&
2404 !(*aState & nsIAccessibleStates::STATE_INVISIBLE)) {
2405 *aExtraState |= nsIAccessibleStates::EXT_STATE_OPAQUE;
2408 const nsStyleXUL *xulStyle = frame->GetStyleXUL();
2409 if (xulStyle) {
2410 // In XUL all boxes are either vertical or horizontal
2411 if (xulStyle->mBoxOrient == NS_STYLE_BOX_ORIENT_VERTICAL) {
2412 *aExtraState |= nsIAccessibleStates::EXT_STATE_VERTICAL;
2414 else {
2415 *aExtraState |= nsIAccessibleStates::EXT_STATE_HORIZONTAL;
2419 return NS_OK;
2422 nsresult
2423 nsAccessible::GetARIAState(PRUint32 *aState)
2425 // Test for universal states first
2426 nsIContent *content = nsCoreUtils::GetRoleContent(mDOMNode);
2427 if (!content) {
2428 return NS_OK;
2431 PRUint32 index = 0;
2432 while (MappedAttrState(content, aState, &nsARIAMap::gWAIUnivStateMap[index])) {
2433 ++ index;
2436 if (mRoleMapEntry) {
2437 // Once DHTML role is used, we're only readonly if DHTML readonly used
2438 *aState &= ~nsIAccessibleStates::STATE_READONLY;
2440 if (content->HasAttr(kNameSpaceID_None, content->GetIDAttributeName())) {
2441 // If has a role & ID and aria-activedescendant on the container, assume focusable
2442 nsIContent *ancestorContent = content;
2443 while ((ancestorContent = ancestorContent->GetParent()) != nsnull) {
2444 if (ancestorContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_activedescendant)) {
2445 // ancestor has activedescendant property, this content could be active
2446 *aState |= nsIAccessibleStates::STATE_FOCUSABLE;
2447 break;
2453 if (*aState & nsIAccessibleStates::STATE_FOCUSABLE) {
2454 // Special case: aria-disabled propagates from ancestors down to any focusable descendant
2455 nsIContent *ancestorContent = content;
2456 while ((ancestorContent = ancestorContent->GetParent()) != nsnull) {
2457 if (ancestorContent->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::aria_disabled,
2458 nsAccessibilityAtoms::_true, eCaseMatters)) {
2459 // ancestor has aria-disabled property, this is disabled
2460 *aState |= nsIAccessibleStates::STATE_UNAVAILABLE;
2461 break;
2466 if (!mRoleMapEntry)
2467 return NS_OK;
2469 *aState |= mRoleMapEntry->state;
2470 if (MappedAttrState(content, aState, &mRoleMapEntry->attributeMap1) &&
2471 MappedAttrState(content, aState, &mRoleMapEntry->attributeMap2) &&
2472 MappedAttrState(content, aState, &mRoleMapEntry->attributeMap3) &&
2473 MappedAttrState(content, aState, &mRoleMapEntry->attributeMap4) &&
2474 MappedAttrState(content, aState, &mRoleMapEntry->attributeMap5) &&
2475 MappedAttrState(content, aState, &mRoleMapEntry->attributeMap6) &&
2476 MappedAttrState(content, aState, &mRoleMapEntry->attributeMap7)) {
2477 MappedAttrState(content, aState, &mRoleMapEntry->attributeMap8);
2480 return NS_OK;
2483 // Not implemented by this class
2485 /* DOMString getValue (); */
2486 NS_IMETHODIMP
2487 nsAccessible::GetValue(nsAString& aValue)
2489 if (IsDefunct())
2490 return NS_ERROR_FAILURE;
2492 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
2493 if (!content)
2494 return NS_OK;
2496 if (mRoleMapEntry) {
2497 if (mRoleMapEntry->valueRule == eNoValue) {
2498 return NS_OK;
2501 // aria-valuenow is a number, and aria-valuetext is the optional text equivalent
2502 // For the string value, we will try the optional text equivalent first
2503 if (!content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_valuetext, aValue)) {
2504 content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_valuenow, aValue);
2508 if (!aValue.IsEmpty())
2509 return NS_OK;
2511 // Check if it's a simple xlink.
2512 if (nsCoreUtils::IsXLink(content)) {
2513 nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
2514 if (presShell)
2515 return presShell->GetLinkLocation(mDOMNode, aValue);
2518 return NS_OK;
2521 // nsIAccessibleValue
2522 NS_IMETHODIMP
2523 nsAccessible::GetMaximumValue(double *aMaximumValue)
2525 return GetAttrValue(nsAccessibilityAtoms::aria_valuemax, aMaximumValue);
2528 NS_IMETHODIMP
2529 nsAccessible::GetMinimumValue(double *aMinimumValue)
2531 return GetAttrValue(nsAccessibilityAtoms::aria_valuemin, aMinimumValue);
2534 NS_IMETHODIMP
2535 nsAccessible::GetMinimumIncrement(double *aMinIncrement)
2537 NS_ENSURE_ARG_POINTER(aMinIncrement);
2538 *aMinIncrement = 0;
2540 // No mimimum increment in dynamic content spec right now
2541 return NS_OK_NO_ARIA_VALUE;
2544 NS_IMETHODIMP
2545 nsAccessible::GetCurrentValue(double *aValue)
2547 return GetAttrValue(nsAccessibilityAtoms::aria_valuenow, aValue);
2550 NS_IMETHODIMP
2551 nsAccessible::SetCurrentValue(double aValue)
2553 if (!mDOMNode)
2554 return NS_ERROR_FAILURE; // Node already shut down
2556 if (!mRoleMapEntry || mRoleMapEntry->valueRule == eNoValue)
2557 return NS_OK_NO_ARIA_VALUE;
2559 const PRUint32 kValueCannotChange = nsIAccessibleStates::STATE_READONLY |
2560 nsIAccessibleStates::STATE_UNAVAILABLE;
2562 if (nsAccUtils::State(this) & kValueCannotChange)
2563 return NS_ERROR_FAILURE;
2565 double minValue = 0;
2566 if (NS_SUCCEEDED(GetMinimumValue(&minValue)) && aValue < minValue)
2567 return NS_ERROR_INVALID_ARG;
2569 double maxValue = 0;
2570 if (NS_SUCCEEDED(GetMaximumValue(&maxValue)) && aValue > maxValue)
2571 return NS_ERROR_INVALID_ARG;
2573 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
2574 NS_ENSURE_STATE(content);
2576 nsAutoString newValue;
2577 newValue.AppendFloat(aValue);
2578 return content->SetAttr(kNameSpaceID_None,
2579 nsAccessibilityAtoms::aria_valuenow, newValue, PR_TRUE);
2582 /* void setName (in DOMString name); */
2583 NS_IMETHODIMP nsAccessible::SetName(const nsAString& name)
2585 return NS_ERROR_NOT_IMPLEMENTED;
2588 NS_IMETHODIMP
2589 nsAccessible::GetDefaultKeyBinding(nsAString& aKeyBinding)
2591 aKeyBinding.Truncate();
2592 return NS_OK;
2595 NS_IMETHODIMP
2596 nsAccessible::GetKeyBindings(PRUint8 aActionIndex,
2597 nsIDOMDOMStringList **aKeyBindings)
2599 // Currently we support only unique key binding on element for default action.
2600 NS_ENSURE_TRUE(aActionIndex == 0, NS_ERROR_INVALID_ARG);
2602 nsAccessibleDOMStringList *keyBindings = new nsAccessibleDOMStringList();
2603 NS_ENSURE_TRUE(keyBindings, NS_ERROR_OUT_OF_MEMORY);
2605 nsAutoString defaultKey;
2606 nsresult rv = GetDefaultKeyBinding(defaultKey);
2607 NS_ENSURE_SUCCESS(rv, rv);
2609 if (!defaultKey.IsEmpty())
2610 keyBindings->Add(defaultKey);
2612 NS_ADDREF(*aKeyBindings = keyBindings);
2613 return NS_OK;
2616 /* unsigned long getRole (); */
2617 NS_IMETHODIMP nsAccessible::GetRole(PRUint32 *aRole)
2619 NS_ENSURE_ARG_POINTER(aRole);
2620 *aRole = nsIAccessibleRole::ROLE_NOTHING;
2622 if (IsDefunct())
2623 return NS_ERROR_FAILURE;
2625 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
2626 if (nsCoreUtils::IsXLink(content))
2627 *aRole = nsIAccessibleRole::ROLE_LINK;
2629 return NS_OK;
2632 // readonly attribute PRUint8 numActions
2633 NS_IMETHODIMP
2634 nsAccessible::GetNumActions(PRUint8 *aNumActions)
2636 NS_ENSURE_ARG_POINTER(aNumActions);
2637 *aNumActions = 0;
2639 if (IsDefunct())
2640 return NS_ERROR_FAILURE;
2642 PRUint32 actionRule = GetActionRule(nsAccUtils::State(this));
2643 if (actionRule == eNoAction)
2644 return NS_OK;
2646 *aNumActions = 1;
2647 return NS_OK;
2650 /* DOMString getAccActionName (in PRUint8 index); */
2651 NS_IMETHODIMP
2652 nsAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
2654 aName.Truncate();
2656 if (aIndex != 0)
2657 return NS_ERROR_INVALID_ARG;
2659 if (IsDefunct())
2660 return NS_ERROR_FAILURE;
2662 PRUint32 states = nsAccUtils::State(this);
2663 PRUint32 actionRule = GetActionRule(states);
2665 switch (actionRule) {
2666 case eActivateAction:
2667 aName.AssignLiteral("activate");
2668 return NS_OK;
2670 case eClickAction:
2671 aName.AssignLiteral("click");
2672 return NS_OK;
2674 case eCheckUncheckAction:
2675 if (states & nsIAccessibleStates::STATE_CHECKED)
2676 aName.AssignLiteral("uncheck");
2677 else
2678 aName.AssignLiteral("check");
2679 return NS_OK;
2681 case eJumpAction:
2682 aName.AssignLiteral("jump");
2683 return NS_OK;
2685 case eOpenCloseAction:
2686 if (states & nsIAccessibleStates::STATE_COLLAPSED)
2687 aName.AssignLiteral("open");
2688 else
2689 aName.AssignLiteral("close");
2690 return NS_OK;
2692 case eSelectAction:
2693 aName.AssignLiteral("select");
2694 return NS_OK;
2696 case eSwitchAction:
2697 aName.AssignLiteral("switch");
2698 return NS_OK;
2701 return NS_ERROR_INVALID_ARG;
2704 // AString getActionDescription(in PRUint8 index)
2705 NS_IMETHODIMP
2706 nsAccessible::GetActionDescription(PRUint8 aIndex, nsAString& aDescription)
2708 // default to localized action name.
2709 nsAutoString name;
2710 nsresult rv = GetActionName(aIndex, name);
2711 NS_ENSURE_SUCCESS(rv, rv);
2713 return GetTranslatedString(name, aDescription);
2716 // void doAction(in PRUint8 index)
2717 NS_IMETHODIMP
2718 nsAccessible::DoAction(PRUint8 aIndex)
2720 if (aIndex != 0)
2721 return NS_ERROR_INVALID_ARG;
2723 if (IsDefunct())
2724 return NS_ERROR_FAILURE;
2726 if (GetActionRule(nsAccUtils::State(this)) != eNoAction) {
2727 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
2728 return DoCommand(content);
2731 return NS_ERROR_INVALID_ARG;
2734 /* DOMString getHelp (); */
2735 NS_IMETHODIMP nsAccessible::GetHelp(nsAString& _retval)
2737 return NS_ERROR_NOT_IMPLEMENTED;
2740 /* nsIAccessible getAccessibleToRight(); */
2741 NS_IMETHODIMP nsAccessible::GetAccessibleToRight(nsIAccessible **_retval)
2743 return NS_ERROR_NOT_IMPLEMENTED;
2746 /* nsIAccessible getAccessibleToLeft(); */
2747 NS_IMETHODIMP nsAccessible::GetAccessibleToLeft(nsIAccessible **_retval)
2749 return NS_ERROR_NOT_IMPLEMENTED;
2752 /* nsIAccessible getAccessibleAbove(); */
2753 NS_IMETHODIMP nsAccessible::GetAccessibleAbove(nsIAccessible **_retval)
2755 return NS_ERROR_NOT_IMPLEMENTED;
2758 /* nsIAccessible getAccessibleBelow(); */
2759 NS_IMETHODIMP nsAccessible::GetAccessibleBelow(nsIAccessible **_retval)
2761 return NS_ERROR_NOT_IMPLEMENTED;
2764 nsIDOMNode* nsAccessible::GetAtomicRegion()
2766 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
2767 nsIContent *loopContent = content;
2768 nsAutoString atomic;
2769 while (loopContent && !loopContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_atomic, atomic)) {
2770 loopContent = loopContent->GetParent();
2773 nsCOMPtr<nsIDOMNode> atomicRegion;
2774 if (atomic.EqualsLiteral("true")) {
2775 atomicRegion = do_QueryInterface(loopContent);
2777 return atomicRegion;
2780 /* nsIAccessible getAccessibleRelated(); */
2781 NS_IMETHODIMP nsAccessible::GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated)
2783 // When adding support for relations, make sure to add them to
2784 // appropriate places in nsAccessibleWrap implementations
2785 *aRelated = nsnull;
2787 // Relationships are defined on the same content node
2788 // that the role would be defined on
2789 nsIContent *content = nsCoreUtils::GetRoleContent(mDOMNode);
2790 if (!content) {
2791 return NS_ERROR_FAILURE; // Node already shut down
2794 nsCOMPtr<nsIDOMNode> relatedNode;
2795 nsAutoString relatedID;
2797 // Search for the related DOM node according to the specified "relation type"
2798 switch (aRelationType)
2800 case nsIAccessibleRelation::RELATION_LABEL_FOR:
2802 if (content->Tag() == nsAccessibilityAtoms::label) {
2803 nsIAtom *relatedIDAttr = content->IsNodeOfType(nsINode::eHTML) ?
2804 nsAccessibilityAtoms::_for : nsAccessibilityAtoms::control;
2805 content->GetAttr(kNameSpaceID_None, relatedIDAttr, relatedID);
2807 if (relatedID.IsEmpty()) {
2808 relatedNode =
2809 do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_labelledby));
2811 break;
2813 case nsIAccessibleRelation::RELATION_LABELLED_BY:
2815 if (!content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_labelledby, relatedID)) {
2816 relatedNode = do_QueryInterface(nsCoreUtils::GetLabelContent(content));
2818 break;
2820 case nsIAccessibleRelation::RELATION_DESCRIBED_BY:
2822 if (!content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_describedby, relatedID)) {
2823 relatedNode = do_QueryInterface(
2824 nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::control, nsAccessibilityAtoms::description));
2826 break;
2828 case nsIAccessibleRelation::RELATION_DESCRIPTION_FOR:
2830 relatedNode =
2831 do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_describedby));
2833 if (!relatedNode && content->Tag() == nsAccessibilityAtoms::description &&
2834 content->IsNodeOfType(nsINode::eXUL)) {
2835 // This affectively adds an optional control attribute to xul:description,
2836 // which only affects accessibility, by allowing the description to be
2837 // tied to a control.
2838 content->GetAttr(kNameSpaceID_None,
2839 nsAccessibilityAtoms::control, relatedID);
2841 break;
2843 case nsIAccessibleRelation::RELATION_NODE_CHILD_OF:
2845 relatedNode =
2846 do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_owns));
2847 if (!relatedNode && mRoleMapEntry && mRoleMapEntry->role == nsIAccessibleRole::ROLE_OUTLINEITEM) {
2848 // This is an ARIA tree that doesn't use owns, so we need to get the parent the hard way
2849 nsAccUtils::GetARIATreeItemParent(this, content, aRelated);
2850 return NS_OK;
2852 // If accessible is in its own Window then we should provide NODE_CHILD_OF relation
2853 // so that MSAA clients can easily get to true parent instead of getting to oleacc's
2854 // ROLE_WINDOW accessible which will prevent us from going up further (because it is
2855 // system generated and has no idea about the hierarchy above it).
2856 nsIFrame *frame = GetFrame();
2857 if (frame) {
2858 nsIView *view = frame->GetViewExternal();
2859 if (view) {
2860 nsIScrollableFrame *scrollFrame = nsnull;
2861 CallQueryInterface(frame, &scrollFrame);
2862 if (scrollFrame || view->GetWidget()) {
2863 return GetParent(aRelated);
2867 break;
2869 case nsIAccessibleRelation::RELATION_CONTROLLED_BY:
2871 relatedNode =
2872 do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_controls));
2873 break;
2875 case nsIAccessibleRelation::RELATION_CONTROLLER_FOR:
2877 content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_controls, relatedID);
2878 break;
2880 case nsIAccessibleRelation::RELATION_FLOWS_TO:
2882 content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_flowto, relatedID);
2883 break;
2885 case nsIAccessibleRelation::RELATION_FLOWS_FROM:
2887 relatedNode =
2888 do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_flowto));
2889 break;
2891 case nsIAccessibleRelation::RELATION_DEFAULT_BUTTON:
2893 if (content->IsNodeOfType(nsINode::eHTML)) {
2894 // HTML form controls implements nsIFormControl interface.
2895 nsCOMPtr<nsIFormControl> control(do_QueryInterface(content));
2896 if (control) {
2897 nsCOMPtr<nsIDOMHTMLFormElement> htmlform;
2898 control->GetForm(getter_AddRefs(htmlform));
2899 nsCOMPtr<nsIForm> form(do_QueryInterface(htmlform));
2900 if (form)
2901 relatedNode = do_QueryInterface(form->GetDefaultSubmitElement());
2904 else {
2905 // In XUL, use first <button default="true" .../> in the document
2906 nsCOMPtr<nsIDOMXULDocument> xulDoc = do_QueryInterface(content->GetDocument());
2907 nsCOMPtr<nsIDOMXULButtonElement> buttonEl;
2908 if (xulDoc) {
2909 nsCOMPtr<nsIDOMNodeList> possibleDefaultButtons;
2910 xulDoc->GetElementsByAttribute(NS_LITERAL_STRING("default"),
2911 NS_LITERAL_STRING("true"),
2912 getter_AddRefs(possibleDefaultButtons));
2913 if (possibleDefaultButtons) {
2914 PRUint32 length;
2915 possibleDefaultButtons->GetLength(&length);
2916 nsCOMPtr<nsIDOMNode> possibleButton;
2917 // Check for button in list of default="true" elements
2918 for (PRUint32 count = 0; count < length && !buttonEl; count ++) {
2919 possibleDefaultButtons->Item(count, getter_AddRefs(possibleButton));
2920 buttonEl = do_QueryInterface(possibleButton);
2923 if (!buttonEl) { // Check for anonymous accept button in <dialog>
2924 nsCOMPtr<nsIDOMDocumentXBL> xblDoc(do_QueryInterface(xulDoc));
2925 if (xblDoc) {
2926 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(xulDoc);
2927 NS_ASSERTION(domDoc, "No DOM document");
2928 nsCOMPtr<nsIDOMElement> rootEl;
2929 domDoc->GetDocumentElement(getter_AddRefs(rootEl));
2930 if (rootEl) {
2931 nsCOMPtr<nsIDOMElement> possibleButtonEl;
2932 xblDoc->GetAnonymousElementByAttribute(rootEl,
2933 NS_LITERAL_STRING("default"),
2934 NS_LITERAL_STRING("true"),
2935 getter_AddRefs(possibleButtonEl));
2936 buttonEl = do_QueryInterface(possibleButtonEl);
2940 relatedNode = do_QueryInterface(buttonEl);
2943 break;
2945 case nsIAccessibleRelation::RELATION_MEMBER_OF:
2947 relatedNode = GetAtomicRegion();
2948 break;
2950 default:
2951 return NS_ERROR_NOT_IMPLEMENTED;
2954 if (!relatedID.IsEmpty()) {
2955 // In some cases we need to get the relatedNode from an ID-style attribute
2956 nsCOMPtr<nsIDOMDocument> domDoc;
2957 mDOMNode->GetOwnerDocument(getter_AddRefs(domDoc));
2958 NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
2959 nsCOMPtr<nsIDOMElement> relatedEl;
2960 domDoc->GetElementById(relatedID, getter_AddRefs(relatedEl));
2961 relatedNode = do_QueryInterface(relatedEl);
2964 // Return the corresponding accessible if the related DOM node is found
2965 if (relatedNode) {
2966 nsCOMPtr<nsIAccessibilityService> accService = GetAccService();
2967 NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
2968 accService->GetAccessibleInWeakShell(relatedNode, mWeakShell, aRelated);
2970 return NS_OK;
2973 NS_IMETHODIMP
2974 nsAccessible::GetRelationsCount(PRUint32 *aCount)
2976 NS_ENSURE_ARG_POINTER(aCount);
2977 *aCount = 0;
2979 nsCOMPtr<nsIArray> relations;
2980 nsresult rv = GetRelations(getter_AddRefs(relations));
2981 NS_ENSURE_SUCCESS(rv, rv);
2983 return relations->GetLength(aCount);
2986 NS_IMETHODIMP
2987 nsAccessible::GetRelation(PRUint32 aIndex, nsIAccessibleRelation **aRelation)
2989 NS_ENSURE_ARG_POINTER(aRelation);
2990 *aRelation = nsnull;
2992 nsCOMPtr<nsIArray> relations;
2993 nsresult rv = GetRelations(getter_AddRefs(relations));
2994 NS_ENSURE_SUCCESS(rv, rv);
2996 nsCOMPtr<nsIAccessibleRelation> relation;
2997 rv = relations->QueryElementAt(aIndex, NS_GET_IID(nsIAccessibleRelation),
2998 getter_AddRefs(relation));
3000 // nsIArray::QueryElementAt() returns NS_ERROR_ILLEGAL_VALUE on invalid index.
3001 if (rv == NS_ERROR_ILLEGAL_VALUE)
3002 return NS_ERROR_INVALID_ARG;
3004 NS_ENSURE_SUCCESS(rv, rv);
3006 NS_IF_ADDREF(*aRelation = relation);
3007 return NS_OK;
3010 NS_IMETHODIMP
3011 nsAccessible::GetRelations(nsIArray **aRelations)
3013 NS_ENSURE_ARG_POINTER(aRelations);
3015 nsCOMPtr<nsIMutableArray> relations = do_CreateInstance(NS_ARRAY_CONTRACTID);
3016 NS_ENSURE_TRUE(relations, NS_ERROR_OUT_OF_MEMORY);
3018 for (PRUint32 relType = nsIAccessibleRelation::RELATION_FIRST;
3019 relType < nsIAccessibleRelation::RELATION_LAST;
3020 ++relType) {
3021 nsCOMPtr<nsIAccessible> accessible;
3022 GetAccessibleRelated(relType, getter_AddRefs(accessible));
3024 if (accessible) {
3025 nsCOMPtr<nsIAccessibleRelation> relation =
3026 new nsAccessibleRelationWrap(relType, accessible);
3027 NS_ENSURE_TRUE(relation, NS_ERROR_OUT_OF_MEMORY);
3029 relations->AppendElement(relation, PR_FALSE);
3033 NS_ADDREF(*aRelations = relations);
3034 return NS_OK;
3037 /* void extendSelection (); */
3038 NS_IMETHODIMP nsAccessible::ExtendSelection()
3040 // XXX Should be implemented, but not high priority
3041 return NS_ERROR_NOT_IMPLEMENTED;
3044 /* [noscript] void getNativeInterface(out voidPtr aOutAccessible); */
3045 NS_IMETHODIMP nsAccessible::GetNativeInterface(void **aOutAccessible)
3047 return NS_ERROR_NOT_IMPLEMENTED;
3050 void nsAccessible::DoCommandCallback(nsITimer *aTimer, void *aClosure)
3052 NS_ASSERTION(gDoCommandTimer,
3053 "How did we get here if there was no gDoCommandTimer?");
3054 NS_RELEASE(gDoCommandTimer);
3056 nsCOMPtr<nsIContent> content =
3057 reinterpret_cast<nsIContent*>(aClosure);
3059 nsIDocument *doc = content->GetDocument();
3060 if (!doc)
3061 return;
3063 nsCOMPtr<nsIPresShell> presShell = doc->GetPrimaryShell();
3065 // Scroll into view.
3066 presShell->ScrollContentIntoView(content, NS_PRESSHELL_SCROLL_ANYWHERE,
3067 NS_PRESSHELL_SCROLL_ANYWHERE);
3069 // Fire mouse down and mouse up events.
3070 PRBool res = nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_DOWN, presShell,
3071 content);
3072 if (!res)
3073 return;
3075 nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_UP, presShell, content);
3079 * Use Timer to execute "Click" command of XUL/HTML element (e.g. menuitem, button...).
3081 * When "Click" is to open a "modal" dialog/window, it won't return untill the
3082 * dialog/window is closed. If executing "Click" command directly in
3083 * nsXXXAccessible::DoAction, it will block AT-Tools(e.g. GOK) that invoke
3084 * "action" of mozilla accessibles direclty.
3086 nsresult nsAccessible::DoCommand(nsIContent *aContent)
3088 nsCOMPtr<nsIContent> content = aContent;
3089 if (!content) {
3090 content = do_QueryInterface(mDOMNode);
3092 if (gDoCommandTimer) {
3093 // Already have timer going for another command
3094 NS_WARNING("Doubling up on do command timers doesn't work. This wasn't expected.");
3095 return NS_ERROR_FAILURE;
3098 nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
3099 if (!timer) {
3100 return NS_ERROR_OUT_OF_MEMORY;
3103 NS_ADDREF(gDoCommandTimer = timer);
3104 return gDoCommandTimer->InitWithFuncCallback(DoCommandCallback,
3105 (void*)content, 0,
3106 nsITimer::TYPE_ONE_SHOT);
3109 already_AddRefed<nsIAccessible>
3110 nsAccessible::GetNextWithState(nsIAccessible *aStart, PRUint32 matchState)
3112 // Return the next descendant that matches one of the states in matchState
3113 // Uses depth first search
3114 NS_ASSERTION(matchState, "GetNextWithState() not called with a state to match");
3115 NS_ASSERTION(aStart, "GetNextWithState() not called with an accessible to start with");
3116 nsCOMPtr<nsIAccessible> look, current = aStart;
3117 PRUint32 state = 0;
3118 while (0 == (state & matchState)) {
3119 current->GetFirstChild(getter_AddRefs(look));
3120 while (!look) {
3121 if (current == this) {
3122 return nsnull; // At top of subtree
3124 current->GetNextSibling(getter_AddRefs(look));
3125 if (!look) {
3126 current->GetParent(getter_AddRefs(look));
3127 current = look;
3128 look = nsnull;
3129 continue;
3132 current.swap(look);
3133 state = nsAccUtils::State(current);
3136 nsIAccessible *returnAccessible = nsnull;
3137 current.swap(returnAccessible);
3139 return returnAccessible;
3142 // nsIAccessibleSelectable
3143 NS_IMETHODIMP nsAccessible::GetSelectedChildren(nsIArray **aSelectedAccessibles)
3145 *aSelectedAccessibles = nsnull;
3147 nsCOMPtr<nsIMutableArray> selectedAccessibles =
3148 do_CreateInstance(NS_ARRAY_CONTRACTID);
3149 NS_ENSURE_STATE(selectedAccessibles);
3151 nsCOMPtr<nsIAccessible> selected = this;
3152 while ((selected = GetNextWithState(selected, nsIAccessibleStates::STATE_SELECTED)) != nsnull) {
3153 selectedAccessibles->AppendElement(selected, PR_FALSE);
3156 PRUint32 length = 0;
3157 selectedAccessibles->GetLength(&length);
3158 if (length) { // length of nsIArray containing selected options
3159 *aSelectedAccessibles = selectedAccessibles;
3160 NS_ADDREF(*aSelectedAccessibles);
3163 return NS_OK;
3166 // return the nth selected descendant nsIAccessible object
3167 NS_IMETHODIMP nsAccessible::RefSelection(PRInt32 aIndex, nsIAccessible **aSelected)
3169 *aSelected = nsnull;
3170 if (aIndex < 0) {
3171 return NS_ERROR_FAILURE;
3173 nsCOMPtr<nsIAccessible> selected = this;
3174 PRInt32 count = 0;
3175 while (count ++ <= aIndex) {
3176 selected = GetNextWithState(selected, nsIAccessibleStates::STATE_SELECTED);
3177 if (!selected) {
3178 return NS_ERROR_FAILURE; // aIndex out of range
3181 NS_IF_ADDREF(*aSelected = selected);
3182 return NS_OK;
3185 NS_IMETHODIMP nsAccessible::GetSelectionCount(PRInt32 *aSelectionCount)
3187 *aSelectionCount = 0;
3188 nsCOMPtr<nsIAccessible> selected = this;
3189 while ((selected = GetNextWithState(selected, nsIAccessibleStates::STATE_SELECTED)) != nsnull) {
3190 ++ *aSelectionCount;
3193 return NS_OK;
3196 NS_IMETHODIMP nsAccessible::AddChildToSelection(PRInt32 aIndex)
3198 // Tree views and other container widgets which may have grandchildren should
3199 // implement a selection methods for their specific interfaces, because being
3200 // able to deal with selection on a per-child basis would not be enough.
3202 NS_ENSURE_TRUE(aIndex >= 0, NS_ERROR_FAILURE);
3204 nsCOMPtr<nsIAccessible> child;
3205 GetChildAt(aIndex, getter_AddRefs(child));
3207 PRUint32 state = nsAccUtils::State(child);
3208 if (!(state & nsIAccessibleStates::STATE_SELECTABLE)) {
3209 return NS_OK;
3212 return child->SetSelected(PR_TRUE);
3215 NS_IMETHODIMP nsAccessible::RemoveChildFromSelection(PRInt32 aIndex)
3217 // Tree views and other container widgets which may have grandchildren should
3218 // implement a selection methods for their specific interfaces, because being
3219 // able to deal with selection on a per-child basis would not be enough.
3221 NS_ENSURE_TRUE(aIndex >= 0, NS_ERROR_FAILURE);
3223 nsCOMPtr<nsIAccessible> child;
3224 GetChildAt(aIndex, getter_AddRefs(child));
3226 PRUint32 state = nsAccUtils::State(child);
3227 if (!(state & nsIAccessibleStates::STATE_SELECTED)) {
3228 return NS_OK;
3231 return child->SetSelected(PR_FALSE);
3234 NS_IMETHODIMP nsAccessible::IsChildSelected(PRInt32 aIndex, PRBool *aIsSelected)
3236 // Tree views and other container widgets which may have grandchildren should
3237 // implement a selection methods for their specific interfaces, because being
3238 // able to deal with selection on a per-child basis would not be enough.
3240 *aIsSelected = PR_FALSE;
3241 NS_ENSURE_TRUE(aIndex >= 0, NS_ERROR_FAILURE);
3243 nsCOMPtr<nsIAccessible> child;
3244 GetChildAt(aIndex, getter_AddRefs(child));
3246 PRUint32 state = nsAccUtils::State(child);
3247 if (state & nsIAccessibleStates::STATE_SELECTED) {
3248 *aIsSelected = PR_TRUE;
3250 return NS_OK;
3253 NS_IMETHODIMP nsAccessible::ClearSelection()
3255 nsCOMPtr<nsIAccessible> selected = this;
3256 while ((selected = GetNextWithState(selected, nsIAccessibleStates::STATE_SELECTED)) != nsnull) {
3257 selected->SetSelected(PR_FALSE);
3259 return NS_OK;
3262 NS_IMETHODIMP nsAccessible::SelectAllSelection(PRBool *_retval)
3264 nsCOMPtr<nsIAccessible> selectable = this;
3265 while ((selectable = GetNextWithState(selectable, nsIAccessibleStates::STATE_SELECTED)) != nsnull) {
3266 selectable->SetSelected(PR_TRUE);
3268 return NS_OK;
3271 // nsIAccessibleHyperLink
3272 // Because of new-atk design, any embedded object in text can implement
3273 // nsIAccessibleHyperLink, which helps determine where it is located
3274 // within containing text
3276 // readonly attribute long nsIAccessibleHyperLink::anchorCount
3277 NS_IMETHODIMP
3278 nsAccessible::GetAnchorCount(PRInt32 *aAnchorCount)
3280 NS_ENSURE_ARG_POINTER(aAnchorCount);
3281 *aAnchorCount = 1;
3282 return NS_OK;
3285 // readonly attribute long nsIAccessibleHyperLink::startIndex
3286 NS_IMETHODIMP
3287 nsAccessible::GetStartIndex(PRInt32 *aStartIndex)
3289 NS_ENSURE_ARG_POINTER(aStartIndex);
3290 *aStartIndex = 0;
3291 PRInt32 endIndex;
3292 return GetLinkOffset(aStartIndex, &endIndex);
3295 // readonly attribute long nsIAccessibleHyperLink::endIndex
3296 NS_IMETHODIMP
3297 nsAccessible::GetEndIndex(PRInt32 *aEndIndex)
3299 NS_ENSURE_ARG_POINTER(aEndIndex);
3300 *aEndIndex = 0;
3301 PRInt32 startIndex;
3302 return GetLinkOffset(&startIndex, aEndIndex);
3305 NS_IMETHODIMP
3306 nsAccessible::GetURI(PRInt32 aIndex, nsIURI **aURI)
3308 NS_ENSURE_ARG_POINTER(aURI);
3309 *aURI = nsnull;
3311 if (aIndex != 0)
3312 return NS_ERROR_INVALID_ARG;
3314 // Check if it's a simple xlink.
3315 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
3316 if (nsCoreUtils::IsXLink(content)) {
3317 nsAutoString href;
3318 content->GetAttr(kNameSpaceID_XLink, nsAccessibilityAtoms::href, href);
3320 nsCOMPtr<nsIURI> baseURI = content->GetBaseURI();
3321 nsCOMPtr<nsIDocument> document = content->GetOwnerDoc();
3322 return NS_NewURI(aURI, href,
3323 document ? document->GetDocumentCharacterSet().get() : nsnull,
3324 baseURI);
3327 return NS_OK;
3331 NS_IMETHODIMP
3332 nsAccessible::GetAnchor(PRInt32 aIndex,
3333 nsIAccessible **aAccessible)
3335 NS_ENSURE_ARG_POINTER(aAccessible);
3336 *aAccessible = nsnull;
3338 if (aIndex != 0)
3339 return NS_ERROR_INVALID_ARG;
3341 *aAccessible = this;
3342 NS_ADDREF_THIS();
3343 return NS_OK;
3346 // readonly attribute boolean nsIAccessibleHyperLink::valid
3347 NS_IMETHODIMP
3348 nsAccessible::GetValid(PRBool *aValid)
3350 NS_ENSURE_ARG_POINTER(aValid);
3351 PRUint32 state = nsAccUtils::State(this);
3352 *aValid = (0 == (state & nsIAccessibleStates::STATE_INVALID));
3353 // XXX In order to implement this we would need to follow every link
3354 // Perhaps we can get information about invalid links from the cache
3355 // In the mean time authors can use role="link" aria-invalid="true"
3356 // to force it for links they internally know to be invalid
3357 return NS_OK;
3360 // readonly attribute boolean nsIAccessibleHyperLink::selected
3361 NS_IMETHODIMP
3362 nsAccessible::GetSelected(PRBool *aSelected)
3364 NS_ENSURE_ARG_POINTER(aSelected);
3365 *aSelected = (gLastFocusedNode == mDOMNode);
3366 return NS_OK;
3369 nsresult nsAccessible::GetLinkOffset(PRInt32* aStartOffset, PRInt32* aEndOffset)
3371 *aStartOffset = *aEndOffset = 0;
3372 nsCOMPtr<nsIAccessible> parent(GetParent());
3373 if (!parent) {
3374 return NS_ERROR_FAILURE;
3377 nsCOMPtr<nsIAccessible> accessible, nextSibling;
3378 PRInt32 characterCount = 0;
3379 parent->GetFirstChild(getter_AddRefs(accessible));
3381 while (accessible) {
3382 if (nsAccUtils::IsText(accessible))
3383 characterCount += nsAccUtils::TextLength(accessible);
3385 else if (accessible == this) {
3386 *aStartOffset = characterCount;
3387 *aEndOffset = characterCount + 1;
3388 return NS_OK;
3390 else {
3391 ++ characterCount;
3393 accessible->GetNextSibling(getter_AddRefs(nextSibling));
3394 accessible.swap(nextSibling);
3397 return NS_ERROR_FAILURE;
3400 NS_IMETHODIMP
3401 nsAccessible::AppendTextTo(nsAString& aText, PRUint32 aStartOffset, PRUint32 aLength)
3403 return NS_OK;
3406 ////////////////////////////////////////////////////////////////////////////////
3407 // nsAccessible public methods
3409 nsresult
3410 nsAccessible::GetARIAName(nsAString& aName)
3412 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
3413 if (!content)
3414 return NS_OK;
3416 // First check for label override via aria-label property
3417 nsAutoString label;
3418 if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_label, label)) {
3419 aName = label;
3420 return NS_OK;
3423 // Second check for label override via aria-labelledby relationship
3424 nsresult rv = GetTextFromRelationID(nsAccessibilityAtoms::aria_labelledby, label);
3425 if (NS_SUCCEEDED(rv))
3426 aName = label;
3428 return rv;
3431 nsresult
3432 nsAccessible::GetNameInternal(nsAString& aName)
3434 nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
3435 if (!content)
3436 return NS_OK;
3438 if (content->IsNodeOfType(nsINode::eHTML))
3439 return GetHTMLName(aName);
3441 if (content->IsNodeOfType(nsINode::eXUL))
3442 return GetXULName(aName);
3444 return NS_OK;
3447 ////////////////////////////////////////////////////////////////////////////////
3448 // nsAccessible private methods
3450 already_AddRefed<nsIAccessible>
3451 nsAccessible::GetFirstAvailableAccessible(nsIDOMNode *aStartNode, PRBool aRequireLeaf)
3453 nsIAccessibilityService *accService = GetAccService();
3454 nsCOMPtr<nsIAccessible> accessible;
3455 nsCOMPtr<nsIDOMTreeWalker> walker;
3456 nsCOMPtr<nsIDOMNode> currentNode(aStartNode);
3458 while (currentNode) {
3459 accService->GetAccessibleInWeakShell(currentNode, mWeakShell, getter_AddRefs(accessible)); // AddRef'd
3460 if (accessible && (!aRequireLeaf || nsAccUtils::IsLeaf(accessible))) {
3461 nsIAccessible *retAccessible = accessible;
3462 NS_ADDREF(retAccessible);
3463 return retAccessible;
3465 if (!walker) {
3466 // Instantiate walker lazily since we won't need it in 90% of the cases
3467 // where the first DOM node we're given provides an accessible
3468 nsCOMPtr<nsIDOMDocument> document;
3469 currentNode->GetOwnerDocument(getter_AddRefs(document));
3470 nsCOMPtr<nsIDOMDocumentTraversal> trav = do_QueryInterface(document);
3471 NS_ASSERTION(trav, "No DOM document traversal for document");
3472 NS_ENSURE_TRUE(trav, nsnull);
3473 trav->CreateTreeWalker(mDOMNode, nsIDOMNodeFilter::SHOW_ELEMENT | nsIDOMNodeFilter::SHOW_TEXT,
3474 nsnull, PR_FALSE, getter_AddRefs(walker));
3475 NS_ENSURE_TRUE(walker, nsnull);
3476 walker->SetCurrentNode(currentNode);
3479 walker->NextNode(getter_AddRefs(currentNode));
3482 return nsnull;
3485 PRBool nsAccessible::CheckVisibilityInParentChain(nsIDocument* aDocument, nsIView* aView)
3487 nsIDocument* document = aDocument;
3488 nsIView* view = aView;
3489 // both view chain and widget chain are broken between chrome and content
3490 while (document != nsnull) {
3491 while (view != nsnull) {
3492 if (view->GetVisibility() == nsViewVisibility_kHide) {
3493 return PR_FALSE;
3495 view = view->GetParent();
3498 nsIDocument* parentDoc = document->GetParentDocument();
3499 if (parentDoc != nsnull) {
3500 nsIContent* content = parentDoc->FindContentForSubDocument(document);
3501 if (content != nsnull) {
3502 nsIPresShell* shell = parentDoc->GetPrimaryShell();
3503 if (!shell) {
3504 return PR_FALSE;
3506 nsIFrame* frame = shell->GetPrimaryFrameFor(content);
3507 while (frame != nsnull && !frame->HasView()) {
3508 frame = frame->GetParent();
3511 if (frame != nsnull) {
3512 view = frame->GetViewExternal();
3517 document = parentDoc;
3520 return PR_TRUE;
3523 nsresult
3524 nsAccessible::GetAttrValue(nsIAtom *aProperty, double *aValue)
3526 NS_ENSURE_ARG_POINTER(aValue);
3527 *aValue = 0;
3529 if (!mDOMNode)
3530 return NS_ERROR_FAILURE; // Node already shut down
3532 if (!mRoleMapEntry || mRoleMapEntry->valueRule == eNoValue)
3533 return NS_OK_NO_ARIA_VALUE;
3535 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
3536 NS_ENSURE_STATE(content);
3538 PRInt32 result = NS_OK;
3539 nsAutoString value;
3540 if (content->GetAttr(kNameSpaceID_None, aProperty, value))
3541 *aValue = value.ToFloat(&result);
3543 return result;
3546 PRUint32
3547 nsAccessible::GetActionRule(PRUint32 aStates)
3549 if (aStates & nsIAccessibleStates::STATE_UNAVAILABLE)
3550 return eNoAction;
3552 // Check if it's simple xlink.
3553 nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
3554 if (nsCoreUtils::IsXLink(content))
3555 return eJumpAction;
3557 // Has registered 'click' event handler.
3558 PRBool isOnclick = nsCoreUtils::HasListener(content,
3559 NS_LITERAL_STRING("click"));
3561 if (isOnclick)
3562 return eClickAction;
3564 // Get an action based on ARIA role.
3565 if (mRoleMapEntry)
3566 return mRoleMapEntry->actionRule;
3568 return eNoAction;