/* -*- Mode: C++; tab-width: 4; 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): * Kyle Yuan (kyle.yuan@sun.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 "nsCOMPtr.h" #include "nsHTMLSelectAccessible.h" #include "nsIAccessibilityService.h" #include "nsIAccessibleEvent.h" #include "nsIFrame.h" #include "nsIComboboxControlFrame.h" #include "nsIDocument.h" #include "nsIDOMHTMLInputElement.h" #include "nsIDOMHTMLOptGroupElement.h" #include "nsIDOMHTMLSelectElement.h" #include "nsIListControlFrame.h" #include "nsIServiceManager.h" #include "nsIMutableArray.h" /** * Selects, Listboxes and Comboboxes, are made up of a number of different * widgets, some of which are shared between the two. This file contains * all of the widgets for both of the Selects, for HTML only. * * Listbox: * - nsHTMLSelectListAccessible * - nsHTMLSelectOptionAccessible * * Comboboxes: * - nsHTMLComboboxAccessible * - nsHTMLComboboxTextFieldAccessible (#ifdef COMBO_BOX_WITH_THREE_CHILDREN) * - nsHTMLComboboxButtonAccessible (#ifdef COMBO_BOX_WITH_THREE_CHILDREN) * - nsHTMLComboboxListAccessible [ inserted in accessible tree ] * - nsHTMLSelectOptionAccessible(s) * * XXX COMBO_BOX_WITH_THREE_CHILDREN is not currently defined. * If we start using it again, we should pass the correct frame into those accessibles. * They share a DOM node with the parent combobox. */ /** ------------------------------------------------------ */ /** Impl. of nsHTMLSelectableAccessible */ /** ------------------------------------------------------ */ // Helper class nsHTMLSelectableAccessible::iterator::iterator(nsHTMLSelectableAccessible *aParent, nsIWeakReference *aWeakShell): mWeakShell(aWeakShell), mParentSelect(aParent) { mLength = mIndex = 0; mSelCount = 0; nsCOMPtr htmlSelect(do_QueryInterface(mParentSelect->mDOMNode)); if (htmlSelect) { htmlSelect->GetOptions(getter_AddRefs(mOptions)); if (mOptions) mOptions->GetLength(&mLength); } } PRBool nsHTMLSelectableAccessible::iterator::Advance() { if (mIndex < mLength) { nsCOMPtr tempNode; if (mOptions) { mOptions->Item(mIndex, getter_AddRefs(tempNode)); mOption = do_QueryInterface(tempNode); } mIndex++; return PR_TRUE; } return PR_FALSE; } void nsHTMLSelectableAccessible::iterator::CalcSelectionCount(PRInt32 *aSelectionCount) { PRBool isSelected = PR_FALSE; if (mOption) mOption->GetSelected(&isSelected); if (isSelected) (*aSelectionCount)++; } void nsHTMLSelectableAccessible::iterator::AddAccessibleIfSelected(nsIAccessibilityService *aAccService, nsIMutableArray *aSelectedAccessibles, nsPresContext *aContext) { PRBool isSelected = PR_FALSE; nsCOMPtr tempAccess; if (mOption) { mOption->GetSelected(&isSelected); if (isSelected) { nsCOMPtr optionNode(do_QueryInterface(mOption)); aAccService->GetAccessibleInWeakShell(optionNode, mWeakShell, getter_AddRefs(tempAccess)); } } if (tempAccess) aSelectedAccessibles->AppendElement(static_cast(tempAccess), PR_FALSE); } PRBool nsHTMLSelectableAccessible::iterator::GetAccessibleIfSelected(PRInt32 aIndex, nsIAccessibilityService *aAccService, nsPresContext *aContext, nsIAccessible **aAccessible) { PRBool isSelected = PR_FALSE; *aAccessible = nsnull; if (mOption) { mOption->GetSelected(&isSelected); if (isSelected) { if (mSelCount == aIndex) { nsCOMPtr optionNode(do_QueryInterface(mOption)); aAccService->GetAccessibleInWeakShell(optionNode, mWeakShell, aAccessible); return PR_TRUE; } mSelCount++; } } return PR_FALSE; } void nsHTMLSelectableAccessible::iterator::Select(PRBool aSelect) { if (mOption) mOption->SetSelected(aSelect); } nsHTMLSelectableAccessible::nsHTMLSelectableAccessible(nsIDOMNode* aDOMNode, nsIWeakReference* aShell): nsAccessibleWrap(aDOMNode, aShell) { } NS_IMPL_ISUPPORTS_INHERITED1(nsHTMLSelectableAccessible, nsAccessible, nsIAccessibleSelectable) // Helper methods NS_IMETHODIMP nsHTMLSelectableAccessible::ChangeSelection(PRInt32 aIndex, PRUint8 aMethod, PRBool *aSelState) { *aSelState = PR_FALSE; nsCOMPtr htmlSelect(do_QueryInterface(mDOMNode)); if (!htmlSelect) return NS_ERROR_FAILURE; nsCOMPtr options; htmlSelect->GetOptions(getter_AddRefs(options)); if (!options) return NS_ERROR_FAILURE; nsCOMPtr tempNode; options->Item(aIndex, getter_AddRefs(tempNode)); nsCOMPtr tempOption(do_QueryInterface(tempNode)); if (!tempOption) return NS_ERROR_FAILURE; tempOption->GetSelected(aSelState); nsresult rv = NS_OK; if (eSelection_Add == aMethod && !(*aSelState)) rv = tempOption->SetSelected(PR_TRUE); else if (eSelection_Remove == aMethod && (*aSelState)) rv = tempOption->SetSelected(PR_FALSE); return rv; } // Interface methods NS_IMETHODIMP nsHTMLSelectableAccessible::GetSelectedChildren(nsIArray **_retval) { *_retval = nsnull; nsCOMPtr accService(do_GetService("@mozilla.org/accessibilityService;1")); if (!accService) return NS_ERROR_FAILURE; nsCOMPtr selectedAccessibles = do_CreateInstance(NS_ARRAY_CONTRACTID); NS_ENSURE_STATE(selectedAccessibles); nsPresContext *context = GetPresContext(); if (!context) return NS_ERROR_FAILURE; nsHTMLSelectableAccessible::iterator iter(this, mWeakShell); while (iter.Advance()) iter.AddAccessibleIfSelected(accService, selectedAccessibles, context); PRUint32 uLength = 0; selectedAccessibles->GetLength(&uLength); if (uLength != 0) { // length of nsIArray containing selected options *_retval = selectedAccessibles; NS_ADDREF(*_retval); } return NS_OK; } // return the nth selected child's nsIAccessible object NS_IMETHODIMP nsHTMLSelectableAccessible::RefSelection(PRInt32 aIndex, nsIAccessible **_retval) { *_retval = nsnull; nsCOMPtr accService(do_GetService("@mozilla.org/accessibilityService;1")); if (!accService) return NS_ERROR_FAILURE; nsPresContext *context = GetPresContext(); if (!context) return NS_ERROR_FAILURE; nsHTMLSelectableAccessible::iterator iter(this, mWeakShell); while (iter.Advance()) if (iter.GetAccessibleIfSelected(aIndex, accService, context, _retval)) return NS_OK; // No matched item found return NS_ERROR_FAILURE; } NS_IMETHODIMP nsHTMLSelectableAccessible::GetSelectionCount(PRInt32 *aSelectionCount) { *aSelectionCount = 0; nsHTMLSelectableAccessible::iterator iter(this, mWeakShell); while (iter.Advance()) iter.CalcSelectionCount(aSelectionCount); return NS_OK; } NS_IMETHODIMP nsHTMLSelectableAccessible::AddChildToSelection(PRInt32 aIndex) { PRBool isSelected; return ChangeSelection(aIndex, eSelection_Add, &isSelected); } NS_IMETHODIMP nsHTMLSelectableAccessible::RemoveChildFromSelection(PRInt32 aIndex) { PRBool isSelected; return ChangeSelection(aIndex, eSelection_Remove, &isSelected); } NS_IMETHODIMP nsHTMLSelectableAccessible::IsChildSelected(PRInt32 aIndex, PRBool *_retval) { *_retval = PR_FALSE; return ChangeSelection(aIndex, eSelection_GetState, _retval); } NS_IMETHODIMP nsHTMLSelectableAccessible::ClearSelection() { nsHTMLSelectableAccessible::iterator iter(this, mWeakShell); while (iter.Advance()) iter.Select(PR_FALSE); return NS_OK; } NS_IMETHODIMP nsHTMLSelectableAccessible::SelectAllSelection(PRBool *_retval) { *_retval = PR_FALSE; nsCOMPtr htmlSelect(do_QueryInterface(mDOMNode)); if (!htmlSelect) return NS_ERROR_FAILURE; htmlSelect->GetMultiple(_retval); if (*_retval) { nsHTMLSelectableAccessible::iterator iter(this, mWeakShell); while (iter.Advance()) iter.Select(PR_TRUE); } return NS_OK; } /** ------------------------------------------------------ */ /** First, the common widgets */ /** ------------------------------------------------------ */ /** ----- nsHTMLSelectListAccessible ----- */ /** Default Constructor */ nsHTMLSelectListAccessible::nsHTMLSelectListAccessible(nsIDOMNode* aDOMNode, nsIWeakReference* aShell) :nsHTMLSelectableAccessible(aDOMNode, aShell) { } /** * As a nsHTMLSelectListAccessible we can have the following states: * nsIAccessibleStates::STATE_MULTISELECTABLE * nsIAccessibleStates::STATE_EXTSELECTABLE */ NS_IMETHODIMP nsHTMLSelectListAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState) { nsresult rv = nsHTMLSelectableAccessible::GetState(aState, aExtraState); NS_ENSURE_SUCCESS(rv, rv); if (!mDOMNode) return NS_OK; nsCOMPtr select (do_QueryInterface(mDOMNode)); if (select) { if (*aState | nsIAccessibleStates::STATE_FOCUSED) { // Treat first focusable option node as actual focus, in order // to avoid confusing JAWS, which needs focus on the option nsCOMPtr focusedOption; nsHTMLSelectOptionAccessible::GetFocusedOptionNode(mDOMNode, getter_AddRefs(focusedOption)); if (focusedOption) { // Clear focused state since it is on option *aState &= ~nsIAccessibleStates::STATE_FOCUSED; } } PRBool multiple; select->GetMultiple(&multiple); if ( multiple ) *aState |= nsIAccessibleStates::STATE_MULTISELECTABLE | nsIAccessibleStates::STATE_EXTSELECTABLE; } return NS_OK; } NS_IMETHODIMP nsHTMLSelectListAccessible::GetRole(PRUint32 *aRole) { if (mParent && Role(mParent) == nsIAccessibleRole::ROLE_COMBOBOX) { *aRole = nsIAccessibleRole::ROLE_COMBOBOX_LIST; } else { *aRole = nsIAccessibleRole::ROLE_LIST; } return NS_OK; } already_AddRefed nsHTMLSelectListAccessible::AccessibleForOption(nsIAccessibilityService *aAccService, nsIContent *aContent, nsIAccessible *aLastGoodAccessible, PRInt32 *aChildCount) { nsCOMPtr domNode(do_QueryInterface(aContent)); NS_ASSERTION(domNode, "DOM node is null"); // Accessibility service will initialize & cache any accessibles created nsCOMPtr accessible; aAccService->GetAccessibleInWeakShell(domNode, mWeakShell, getter_AddRefs(accessible)); nsCOMPtr privateAccessible(do_QueryInterface(accessible)); if (!privateAccessible) { return nsnull; } ++ *aChildCount; privateAccessible->SetParent(this); nsCOMPtr privatePrevAccessible(do_QueryInterface(aLastGoodAccessible)); if (privatePrevAccessible) { privatePrevAccessible->SetNextSibling(accessible); } if (!mFirstChild) { mFirstChild = accessible; } nsIAccessible *returnAccessible = accessible; NS_ADDREF(returnAccessible); return returnAccessible; } already_AddRefed nsHTMLSelectListAccessible::CacheOptSiblings(nsIAccessibilityService *aAccService, nsIContent *aParentContent, nsIAccessible *aLastGoodAccessible, PRInt32 *aChildCount) { // Recursive helper for CacheChildren() PRUint32 numChildren = aParentContent->GetChildCount(); nsCOMPtr lastGoodAccessible(aLastGoodAccessible); nsCOMPtr newAccessible; for (PRUint32 count = 0; count < numChildren; count ++) { nsIContent *childContent = aParentContent->GetChildAt(count); if (!childContent->IsNodeOfType(nsINode::eHTML)) { continue; } nsCOMPtr tag = childContent->Tag(); if (tag == nsAccessibilityAtoms::option || tag == nsAccessibilityAtoms::optgroup) { newAccessible = AccessibleForOption(aAccService, childContent, lastGoodAccessible, aChildCount); if (newAccessible) { lastGoodAccessible = newAccessible; } if (tag == nsAccessibilityAtoms::optgroup) { newAccessible = CacheOptSiblings(aAccService, childContent, lastGoodAccessible, aChildCount); if (newAccessible) { lastGoodAccessible = newAccessible; } } } } if (lastGoodAccessible) { nsCOMPtr privateLastAcc = do_QueryInterface(lastGoodAccessible); privateLastAcc->SetNextSibling(nsnull); NS_ADDREF(aLastGoodAccessible = lastGoodAccessible); } return aLastGoodAccessible; } /** * Cache the children and child count of a Select List Accessible. We want to count * all the s and and