/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * John Gaunt (jgaunt@netscape.com) * Aaron Leventhal (aaronl@netscape.com) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsAccessible.h" #include "nsAccessibleRelation.h" #include "nsHyperTextAccessibleWrap.h" #include "nsIAccessibleDocument.h" #include "nsIAccessibleHyperText.h" #include "nsIXBLAccessible.h" #include "nsAccessibleTreeWalker.h" #include "nsIDOMElement.h" #include "nsIDOMDocument.h" #include "nsIDOMDocumentXBL.h" #include "nsIDOMDocumentTraversal.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMHTMLFormElement.h" #include "nsIDOMNodeFilter.h" #include "nsIDOMNSHTMLElement.h" #include "nsIDOMTreeWalker.h" #include "nsIDOMXULButtonElement.h" #include "nsIDOMXULDocument.h" #include "nsIDOMXULElement.h" #include "nsIDOMXULLabelElement.h" #include "nsIDOMXULSelectCntrlEl.h" #include "nsIDOMXULSelectCntrlItemEl.h" #include "nsPIDOMWindow.h" #include "nsIDocument.h" #include "nsIContent.h" #include "nsIForm.h" #include "nsIFormControl.h" #include "nsIPresShell.h" #include "nsPresContext.h" #include "nsIFrame.h" #include "nsIViewManager.h" #include "nsIDocShellTreeItem.h" #include "nsIScrollableFrame.h" #include "nsFocusManager.h" #include "nsXPIDLString.h" #include "nsUnicharUtils.h" #include "nsReadableUtils.h" #include "prdtoa.h" #include "nsIAtom.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsIURI.h" #include "nsITimer.h" #include "nsArrayUtils.h" #include "nsIMutableArray.h" #include "nsIObserverService.h" #include "nsIServiceManager.h" #include "nsWhitespaceTokenizer.h" #include "nsAttrName.h" #include "nsNetUtil.h" #ifdef NS_DEBUG #include "nsIDOMCharacterData.h" #endif /** * nsAccessibleDOMStringList implementation */ nsAccessibleDOMStringList::nsAccessibleDOMStringList() { } nsAccessibleDOMStringList::~nsAccessibleDOMStringList() { } NS_IMPL_ISUPPORTS1(nsAccessibleDOMStringList, nsIDOMDOMStringList) NS_IMETHODIMP nsAccessibleDOMStringList::Item(PRUint32 aIndex, nsAString& aResult) { if (aIndex >= mNames.Length()) { SetDOMStringToNull(aResult); } else { aResult = mNames.ElementAt(aIndex); } return NS_OK; } NS_IMETHODIMP nsAccessibleDOMStringList::GetLength(PRUint32 *aLength) { *aLength = mNames.Length(); return NS_OK; } NS_IMETHODIMP nsAccessibleDOMStringList::Contains(const nsAString& aString, PRBool *aResult) { *aResult = mNames.Contains(aString); return NS_OK; } /* * Class nsAccessible */ //////////////////////////////////////////////////////////////////////////////// // nsAccessible. nsISupports NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsAccessible, nsAccessNode) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mParent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstChild) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNextSibling) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsAccessible, nsAccessNode) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mParent) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstChild) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNextSibling) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_ADDREF_INHERITED(nsAccessible, nsAccessNode) NS_IMPL_RELEASE_INHERITED(nsAccessible, nsAccessNode) nsresult nsAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr) { // Custom-built QueryInterface() knows when we support nsIAccessibleSelectable // based on role attribute and aria-multiselectable *aInstancePtr = nsnull; if (aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { *aInstancePtr = &NS_CYCLE_COLLECTION_NAME(nsAccessible); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsIAccessible))) { *aInstancePtr = static_cast(this); NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsAccessible))) { *aInstancePtr = static_cast(this); NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsIAccessibleSelectable))) { nsCOMPtr content(do_QueryInterface(mDOMNode)); if (!content) { return NS_ERROR_FAILURE; // This accessible has been shut down } if (content->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::role)) { // If we have an XHTML role attribute present and the // aria-multiselectable attribute is true, then we need // to support nsIAccessibleSelectable // If either attribute (role or multiselectable) change, then we'll // destroy this accessible so that we can follow COM identity rules. nsAutoString multiselectable; if (content->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::aria_multiselectable, nsAccessibilityAtoms::_true, eCaseMatters)) { *aInstancePtr = static_cast(this); NS_ADDREF_THIS(); return NS_OK; } } } if (aIID.Equals(NS_GET_IID(nsIAccessibleValue))) { if (mRoleMapEntry && mRoleMapEntry->valueRule != eNoValue) { *aInstancePtr = static_cast(this); NS_ADDREF_THIS(); return NS_OK; } } if (aIID.Equals(NS_GET_IID(nsIAccessibleHyperLink))) { nsCOMPtr parent(GetParent()); nsCOMPtr hyperTextParent(do_QueryInterface(parent)); if (hyperTextParent) { *aInstancePtr = static_cast(this); NS_ADDREF_THIS(); return NS_OK; } return NS_ERROR_NO_INTERFACE; } return nsAccessNodeWrap::QueryInterface(aIID, aInstancePtr); } nsAccessible::nsAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell): nsAccessNodeWrap(aNode, aShell), mParent(nsnull), mFirstChild(nsnull), mNextSibling(nsnull), mRoleMapEntry(nsnull), mAccChildCount(eChildCountUninitialized) { #ifdef NS_DEBUG_X { nsCOMPtr shell(do_QueryReferent(aShell)); printf(">>> %p Created Acc - DOM: %p PS: %p", (void*)static_cast(this), (void*)aNode, (void*)shell.get()); nsCOMPtr content = do_QueryInterface(aNode); if (content) { nsAutoString buf; if (content->NodeInfo()) content->NodeInfo()->GetQualifiedName(buf); printf(" Con: %s@%p", NS_ConvertUTF16toUTF8(buf).get(), (void *)content.get()); if (NS_SUCCEEDED(GetName(buf))) { printf(" Name:[%s]", NS_ConvertUTF16toUTF8(buf).get()); } } printf("\n"); } #endif } //----------------------------------------------------- // destruction //----------------------------------------------------- nsAccessible::~nsAccessible() { } void nsAccessible::SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry) { mRoleMapEntry = aRoleMapEntry; } NS_IMETHODIMP nsAccessible::GetName(nsAString& aName) { aName.Truncate(); if (IsDefunct()) return NS_ERROR_FAILURE; GetARIAName(aName); if (!aName.IsEmpty()) return NS_OK; nsCOMPtr xblAccessible(do_QueryInterface(mDOMNode)); if (xblAccessible) { xblAccessible->GetAccessibleName(aName); if (!aName.IsEmpty()) return NS_OK; } nsresult rv = GetNameInternal(aName); NS_ENSURE_SUCCESS(rv, rv); if (!aName.IsEmpty()) return NS_OK; // In the end get the name from tooltip. nsCOMPtr content = nsCoreUtils::GetRoleContent(mDOMNode); if (!content) return NS_OK; nsIAtom *tooltipAttr = nsnull; if (content->IsHTML()) tooltipAttr = nsAccessibilityAtoms::title; else if (content->IsXUL()) tooltipAttr = nsAccessibilityAtoms::tooltiptext; else return NS_OK; // XXX: if CompressWhiteSpace worked on nsAString we could avoid a copy. nsAutoString name; if (content->GetAttr(kNameSpaceID_None, tooltipAttr, name)) { name.CompressWhitespace(); aName = name; return NS_OK_NAME_FROM_TOOLTIP; } if (rv != NS_OK_EMPTY_NAME) aName.SetIsVoid(PR_TRUE); return NS_OK; } NS_IMETHODIMP nsAccessible::GetDescription(nsAString& aDescription) { if (IsDefunct()) return NS_ERROR_FAILURE; // There are 4 conditions that make an accessible have no accDescription: // 1. it's a text node; or // 2. It has no DHTML describedby property // 3. it doesn't have an accName; or // 4. its title attribute already equals to its accName nsAutoString name; nsCOMPtr content(do_QueryInterface(mDOMNode)); NS_ASSERTION(content, "No content of valid accessible!"); if (!content) return NS_ERROR_FAILURE; if (!content->IsNodeOfType(nsINode::eTEXT)) { nsAutoString description; nsresult rv = nsTextEquivUtils:: GetTextEquivFromIDRefs(this, nsAccessibilityAtoms::aria_describedby, description); NS_ENSURE_SUCCESS(rv, rv); if (description.IsEmpty()) { PRBool isXUL = content->IsXUL(); if (isXUL) { // Try XUL description text nsIContent *descriptionContent = nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::control, nsAccessibilityAtoms::description); if (descriptionContent) { // We have a description content node nsTextEquivUtils:: AppendTextEquivFromContent(this, descriptionContent, &description); } } if (description.IsEmpty()) { nsIAtom *descAtom = isXUL ? nsAccessibilityAtoms::tooltiptext : nsAccessibilityAtoms::title; if (content->GetAttr(kNameSpaceID_None, descAtom, description)) { nsAutoString name; GetName(name); if (name.IsEmpty() || description == name) { // Don't use tooltip for a description if this object // has no name or the tooltip is the same as the name description.Truncate(); } } } } description.CompressWhitespace(); aDescription = description; } return NS_OK; } // mask values for ui.key.chromeAccess and ui.key.contentAccess #define NS_MODIFIER_SHIFT 1 #define NS_MODIFIER_CONTROL 2 #define NS_MODIFIER_ALT 4 #define NS_MODIFIER_META 8 // returns the accesskey modifier mask used in the given node's context // (i.e. chrome or content), or 0 if an error occurs static PRInt32 GetAccessModifierMask(nsIContent* aContent) { nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); if (!prefBranch) return 0; // use ui.key.generalAccessKey (unless it is -1) PRInt32 accessKey; nsresult rv = prefBranch->GetIntPref("ui.key.generalAccessKey", &accessKey); if (NS_SUCCEEDED(rv) && accessKey != -1) { switch (accessKey) { case nsIDOMKeyEvent::DOM_VK_SHIFT: return NS_MODIFIER_SHIFT; case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL; case nsIDOMKeyEvent::DOM_VK_ALT: return NS_MODIFIER_ALT; case nsIDOMKeyEvent::DOM_VK_META: return NS_MODIFIER_META; default: return 0; } } // get the docShell to this DOMNode, return 0 on failure nsCOMPtr document = aContent->GetCurrentDoc(); if (!document) return 0; nsCOMPtr container = document->GetContainer(); if (!container) return 0; nsCOMPtr treeItem(do_QueryInterface(container)); if (!treeItem) return 0; // determine the access modifier used in this context PRInt32 itemType, accessModifierMask = 0; treeItem->GetItemType(&itemType); switch (itemType) { case nsIDocShellTreeItem::typeChrome: rv = prefBranch->GetIntPref("ui.key.chromeAccess", &accessModifierMask); break; case nsIDocShellTreeItem::typeContent: rv = prefBranch->GetIntPref("ui.key.contentAccess", &accessModifierMask); break; } return NS_SUCCEEDED(rv) ? accessModifierMask : 0; } NS_IMETHODIMP nsAccessible::GetKeyboardShortcut(nsAString& aAccessKey) { aAccessKey.Truncate(); nsCOMPtr content(do_QueryInterface(mDOMNode)); if (!content) return NS_ERROR_FAILURE; PRUint32 key = nsCoreUtils::GetAccessKeyFor(content); if (!key && content->IsNodeOfType(nsIContent::eELEMENT)) { // Copy access key from label node unless it is labeled // via an ancestor