/* -*- 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 "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 "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 "nsIMutableArray.h" #include "nsIObserverService.h" #include "nsIServiceManager.h" #include "nsWhitespaceTokenizer.h" #include "nsAttrName.h" #include "nsNetUtil.h" #ifdef NS_DEBUG #include "nsIFrameDebug.h" #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 >= (PRUint32)mNames.Count()) { SetDOMStringToNull(aResult); } else { mNames.StringAt(aIndex, aResult); } return NS_OK; } NS_IMETHODIMP nsAccessibleDOMStringList::GetLength(PRUint32 *aLength) { *aLength = (PRUint32)mNames.Count(); return NS_OK; } NS_IMETHODIMP nsAccessibleDOMStringList::Contains(const nsAString& aString, PRBool *aResult) { *aResult = mNames.IndexOf(aString) > -1; return NS_OK; } /* * Class nsAccessible */ //----------------------------------------------------- // construction //----------------------------------------------------- NS_IMPL_ADDREF_INHERITED(nsAccessible, nsAccessNode) NS_IMPL_RELEASE_INHERITED(nsAccessible, nsAccessNode) #ifdef DEBUG_A11Y /* * static * help method. to detect whether this accessible object implements * nsIAccessibleText, when it is text or has text child node. */ PRBool nsAccessible::IsTextInterfaceSupportCorrect(nsIAccessible *aAccessible) { PRBool foundText = PR_FALSE; nsCOMPtr accDoc = do_QueryInterface(aAccessible); if (accDoc) { // Don't test for accessible docs, it makes us create accessibles too // early and fire mutation events before we need to return PR_TRUE; } nsCOMPtr child, nextSibling; aAccessible->GetFirstChild(getter_AddRefs(child)); while (child) { if (IsText(child)) { foundText = PR_TRUE; break; } child->GetNextSibling(getter_AddRefs(nextSibling)); child.swap(nextSibling); } if (foundText) { // found text child node nsCOMPtr text = do_QueryInterface(aAccessible); if (!text) { return PR_FALSE; } } return PR_TRUE; } #endif 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(nsIAccessible))) { *aInstancePtr = static_cast(this); NS_ADDREF_THIS(); return NS_OK; } if(aIID.Equals(NS_GET_IID(nsPIAccessible))) { *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() { } NS_IMETHODIMP nsAccessible::SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry) { mRoleMapEntry = aRoleMapEntry; return NS_OK; } NS_IMETHODIMP nsAccessible::GetName(nsAString& aName) { aName.Truncate(); nsCOMPtr content(do_QueryInterface(mDOMNode)); if (!content) { return NS_ERROR_FAILURE; // Node shut down } PRBool canAggregateName = mRoleMapEntry && mRoleMapEntry->nameRule == eNameOkFromChildren; if (content->IsNodeOfType(nsINode::eHTML)) { return GetHTMLName(aName, canAggregateName); } if (content->IsNodeOfType(nsINode::eXUL)) { return GetXULName(aName, canAggregateName); } return NS_OK; } NS_IMETHODIMP nsAccessible::GetDescription(nsAString& aDescription) { // 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)); if (!content) { return NS_ERROR_FAILURE; // Node shut down } if (!content->IsNodeOfType(nsINode::eTEXT)) { nsAutoString description; nsresult rv = GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description); if (NS_FAILED(rv)) { PRBool isXUL = content->IsNodeOfType(nsINode::eXUL); if (isXUL) { // Try XUL description text nsIContent *descriptionContent = nsAccUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::control, nsAccessibilityAtoms::description); if (descriptionContent) { // We have a description content node AppendFlatStringFromSubtree(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 = nsAccUtils::GetAccessKeyFor(content); if (!key && content->IsNodeOfType(nsIContent::eELEMENT)) { // Copy access key from label node unless it is labeled // via an ancestor