Bug 589145 - dexpcom accessible event classes, r=davidb, sr=neil, a=davidb
[mozilla-central.git] / accessible / src / html / nsHTMLSelectAccessible.cpp
blob41d260e9463294f7406b6f2aeb3861730153389d
1 /* -*- Mode: C++; tab-width: 4; 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 * Kyle Yuan (kyle.yuan@sun.com)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsHTMLSelectAccessible.h"
41 #include "nsAccessibilityService.h"
42 #include "nsAccUtils.h"
43 #include "nsDocAccessible.h"
44 #include "nsEventShell.h"
45 #include "nsIAccessibleEvent.h"
46 #include "nsTextEquivUtils.h"
48 #include "nsCOMPtr.h"
49 #include "nsIFrame.h"
50 #include "nsIComboboxControlFrame.h"
51 #include "nsIDocument.h"
52 #include "nsIDOMHTMLInputElement.h"
53 #include "nsIDOMHTMLOptGroupElement.h"
54 #include "nsIDOMHTMLSelectElement.h"
55 #include "nsIListControlFrame.h"
56 #include "nsIServiceManager.h"
57 #include "nsIMutableArray.h"
59 ////////////////////////////////////////////////////////////////////////////////
60 // nsHTMLSelectableAccessible
61 ////////////////////////////////////////////////////////////////////////////////
63 ////////////////////////////////////////////////////////////////////////////////
64 // nsHTMLSelectableAccessible::iterator
66 nsHTMLSelectableAccessible::iterator::iterator(nsHTMLSelectableAccessible *aParent, nsIWeakReference *aWeakShell):
67 mWeakShell(aWeakShell), mParentSelect(aParent)
69 mLength = mIndex = 0;
70 mSelCount = 0;
72 nsCOMPtr<nsIDOMHTMLSelectElement> htmlSelect =
73 do_QueryInterface(mParentSelect->mContent);
74 if (htmlSelect) {
75 htmlSelect->GetOptions(getter_AddRefs(mOptions));
76 if (mOptions)
77 mOptions->GetLength(&mLength);
81 PRBool nsHTMLSelectableAccessible::iterator::Advance()
83 if (mIndex < mLength) {
84 nsCOMPtr<nsIDOMNode> tempNode;
85 if (mOptions) {
86 mOptions->Item(mIndex, getter_AddRefs(tempNode));
87 mOption = do_QueryInterface(tempNode);
89 mIndex++;
90 return PR_TRUE;
92 return PR_FALSE;
95 void nsHTMLSelectableAccessible::iterator::CalcSelectionCount(PRInt32 *aSelectionCount)
97 PRBool isSelected = PR_FALSE;
99 if (mOption)
100 mOption->GetSelected(&isSelected);
102 if (isSelected)
103 (*aSelectionCount)++;
106 void
107 nsHTMLSelectableAccessible::iterator::AddAccessibleIfSelected(nsIMutableArray *aSelectedAccessibles,
108 nsPresContext *aContext)
110 PRBool isSelected = PR_FALSE;
111 nsAccessible *optionAcc = nsnull;
113 if (mOption) {
114 mOption->GetSelected(&isSelected);
115 if (isSelected) {
116 nsCOMPtr<nsIContent> optionContent(do_QueryInterface(mOption));
117 optionAcc = GetAccService()->GetAccessibleInWeakShell(optionContent,
118 mWeakShell);
122 if (optionAcc)
123 aSelectedAccessibles->AppendElement(static_cast<nsIAccessible*>(optionAcc),
124 PR_FALSE);
127 PRBool
128 nsHTMLSelectableAccessible::iterator::GetAccessibleIfSelected(PRInt32 aIndex,
129 nsPresContext *aContext,
130 nsIAccessible **aAccessible)
132 PRBool isSelected = PR_FALSE;
134 *aAccessible = nsnull;
136 if (mOption) {
137 mOption->GetSelected(&isSelected);
138 if (isSelected) {
139 if (mSelCount == aIndex) {
140 nsCOMPtr<nsIContent> optionContent(do_QueryInterface(mOption));
141 nsAccessible *accessible =
142 GetAccService()->GetAccessibleInWeakShell(optionContent, mWeakShell);
143 NS_IF_ADDREF(*aAccessible = accessible);
145 return PR_TRUE;
147 mSelCount++;
151 return PR_FALSE;
154 void nsHTMLSelectableAccessible::iterator::Select(PRBool aSelect)
156 if (mOption)
157 mOption->SetSelected(aSelect);
160 ////////////////////////////////////////////////////////////////////////////////
161 // nsHTMLSelectableAccessible
163 nsHTMLSelectableAccessible::
164 nsHTMLSelectableAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
165 nsAccessibleWrap(aContent, aShell)
169 NS_IMPL_ISUPPORTS_INHERITED1(nsHTMLSelectableAccessible, nsAccessible, nsIAccessibleSelectable)
171 // Helper methods
172 NS_IMETHODIMP nsHTMLSelectableAccessible::ChangeSelection(PRInt32 aIndex, PRUint8 aMethod, PRBool *aSelState)
174 *aSelState = PR_FALSE;
176 nsCOMPtr<nsIDOMHTMLSelectElement> htmlSelect(do_QueryInterface(mContent));
177 if (!htmlSelect)
178 return NS_ERROR_FAILURE;
180 nsCOMPtr<nsIDOMHTMLOptionsCollection> options;
181 htmlSelect->GetOptions(getter_AddRefs(options));
182 if (!options)
183 return NS_ERROR_FAILURE;
185 nsCOMPtr<nsIDOMNode> tempNode;
186 options->Item(aIndex, getter_AddRefs(tempNode));
187 nsCOMPtr<nsIDOMHTMLOptionElement> tempOption(do_QueryInterface(tempNode));
188 if (!tempOption)
189 return NS_ERROR_FAILURE;
191 tempOption->GetSelected(aSelState);
192 nsresult rv = NS_OK;
193 if (eSelection_Add == aMethod && !(*aSelState))
194 rv = tempOption->SetSelected(PR_TRUE);
195 else if (eSelection_Remove == aMethod && (*aSelState))
196 rv = tempOption->SetSelected(PR_FALSE);
197 return rv;
200 // Interface methods
201 NS_IMETHODIMP nsHTMLSelectableAccessible::GetSelectedChildren(nsIArray **_retval)
203 *_retval = nsnull;
205 nsCOMPtr<nsIMutableArray> selectedAccessibles =
206 do_CreateInstance(NS_ARRAY_CONTRACTID);
207 NS_ENSURE_STATE(selectedAccessibles);
209 nsPresContext *context = GetPresContext();
210 if (!context)
211 return NS_ERROR_FAILURE;
213 nsHTMLSelectableAccessible::iterator iter(this, mWeakShell);
214 while (iter.Advance())
215 iter.AddAccessibleIfSelected(selectedAccessibles, context);
217 PRUint32 uLength = 0;
218 selectedAccessibles->GetLength(&uLength);
219 if (uLength != 0) { // length of nsIArray containing selected options
220 *_retval = selectedAccessibles;
221 NS_ADDREF(*_retval);
223 return NS_OK;
226 // return the nth selected child's nsIAccessible object
227 NS_IMETHODIMP nsHTMLSelectableAccessible::RefSelection(PRInt32 aIndex, nsIAccessible **_retval)
229 *_retval = nsnull;
231 nsPresContext *context = GetPresContext();
232 if (!context)
233 return NS_ERROR_FAILURE;
235 nsHTMLSelectableAccessible::iterator iter(this, mWeakShell);
236 while (iter.Advance())
237 if (iter.GetAccessibleIfSelected(aIndex, context, _retval))
238 return NS_OK;
240 // No matched item found
241 return NS_ERROR_FAILURE;
244 NS_IMETHODIMP nsHTMLSelectableAccessible::GetSelectionCount(PRInt32 *aSelectionCount)
246 *aSelectionCount = 0;
248 nsHTMLSelectableAccessible::iterator iter(this, mWeakShell);
249 while (iter.Advance())
250 iter.CalcSelectionCount(aSelectionCount);
251 return NS_OK;
254 NS_IMETHODIMP nsHTMLSelectableAccessible::AddChildToSelection(PRInt32 aIndex)
256 PRBool isSelected;
257 return ChangeSelection(aIndex, eSelection_Add, &isSelected);
260 NS_IMETHODIMP nsHTMLSelectableAccessible::RemoveChildFromSelection(PRInt32 aIndex)
262 PRBool isSelected;
263 return ChangeSelection(aIndex, eSelection_Remove, &isSelected);
266 NS_IMETHODIMP nsHTMLSelectableAccessible::IsChildSelected(PRInt32 aIndex, PRBool *_retval)
268 *_retval = PR_FALSE;
269 return ChangeSelection(aIndex, eSelection_GetState, _retval);
272 NS_IMETHODIMP nsHTMLSelectableAccessible::ClearSelection()
274 nsHTMLSelectableAccessible::iterator iter(this, mWeakShell);
275 while (iter.Advance())
276 iter.Select(PR_FALSE);
277 return NS_OK;
280 NS_IMETHODIMP nsHTMLSelectableAccessible::SelectAllSelection(PRBool *_retval)
282 *_retval = PR_FALSE;
284 nsCOMPtr<nsIDOMHTMLSelectElement> htmlSelect(do_QueryInterface(mContent));
285 if (!htmlSelect)
286 return NS_ERROR_FAILURE;
288 htmlSelect->GetMultiple(_retval);
289 if (*_retval) {
290 nsHTMLSelectableAccessible::iterator iter(this, mWeakShell);
291 while (iter.Advance())
292 iter.Select(PR_TRUE);
294 return NS_OK;
298 ////////////////////////////////////////////////////////////////////////////////
299 // nsHTMLSelectListAccessible
300 ////////////////////////////////////////////////////////////////////////////////
302 nsHTMLSelectListAccessible::
303 nsHTMLSelectListAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
304 nsHTMLSelectableAccessible(aContent, aShell)
308 ////////////////////////////////////////////////////////////////////////////////
309 // nsHTMLSelectListAccessible: nsAccessible public
311 nsresult
312 nsHTMLSelectListAccessible::GetStateInternal(PRUint32 *aState,
313 PRUint32 *aExtraState)
315 nsresult rv = nsHTMLSelectableAccessible::GetStateInternal(aState,
316 aExtraState);
317 NS_ENSURE_A11Y_SUCCESS(rv, rv);
319 // As a nsHTMLSelectListAccessible we can have the following states:
320 // nsIAccessibleStates::STATE_MULTISELECTABLE
321 // nsIAccessibleStates::STATE_EXTSELECTABLE
323 nsCOMPtr<nsIDOMHTMLSelectElement> select(do_QueryInterface(mContent));
324 if (select) {
325 if (*aState & nsIAccessibleStates::STATE_FOCUSED) {
326 // Treat first focusable option node as actual focus, in order
327 // to avoid confusing JAWS, which needs focus on the option
328 nsCOMPtr<nsIContent> focusedOption =
329 nsHTMLSelectOptionAccessible::GetFocusedOption(mContent);
330 if (focusedOption) { // Clear focused state since it is on option
331 *aState &= ~nsIAccessibleStates::STATE_FOCUSED;
334 PRBool multiple;
335 select->GetMultiple(&multiple);
336 if ( multiple )
337 *aState |= nsIAccessibleStates::STATE_MULTISELECTABLE |
338 nsIAccessibleStates::STATE_EXTSELECTABLE;
341 return NS_OK;
344 nsresult
345 nsHTMLSelectListAccessible::GetRoleInternal(PRUint32 *aRole)
347 if (nsAccUtils::Role(mParent) == nsIAccessibleRole::ROLE_COMBOBOX)
348 *aRole = nsIAccessibleRole::ROLE_COMBOBOX_LIST;
349 else
350 *aRole = nsIAccessibleRole::ROLE_LISTBOX;
352 return NS_OK;
355 ////////////////////////////////////////////////////////////////////////////////
356 // nsHTMLSelectListAccessible: nsAccessible protected
358 void
359 nsHTMLSelectListAccessible::CacheChildren()
361 // Cache accessibles for <optgroup> and <option> DOM decendents as children,
362 // as well as the accessibles for them. Avoid whitespace text nodes. We want
363 // to count all the <optgroup>s and <option>s as children because we want
364 // a flat tree under the Select List.
365 CacheOptSiblings(mContent);
368 ////////////////////////////////////////////////////////////////////////////////
369 // nsHTMLSelectListAccessible protected
371 void
372 nsHTMLSelectListAccessible::CacheOptSiblings(nsIContent *aParentContent)
374 nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
375 PRUint32 numChildren = aParentContent->GetChildCount();
376 for (PRUint32 count = 0; count < numChildren; count ++) {
377 nsIContent *childContent = aParentContent->GetChildAt(count);
378 if (!childContent->IsHTML()) {
379 continue;
382 nsCOMPtr<nsIAtom> tag = childContent->Tag();
383 if (tag == nsAccessibilityAtoms::option ||
384 tag == nsAccessibilityAtoms::optgroup) {
386 // Get an accessible for option or optgroup and cache it.
387 nsRefPtr<nsAccessible> accessible =
388 GetAccService()->GetOrCreateAccessible(childContent, presShell,
389 mWeakShell);
390 if (accessible)
391 AppendChild(accessible);
393 // Deep down into optgroup element.
394 if (tag == nsAccessibilityAtoms::optgroup)
395 CacheOptSiblings(childContent);
401 ////////////////////////////////////////////////////////////////////////////////
402 // nsHTMLSelectOptionAccessible
403 ////////////////////////////////////////////////////////////////////////////////
405 nsHTMLSelectOptionAccessible::
406 nsHTMLSelectOptionAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
407 nsHyperTextAccessibleWrap(aContent, aShell)
411 ////////////////////////////////////////////////////////////////////////////////
412 // nsHTMLSelectOptionAccessible: nsAccessible public
414 nsresult
415 nsHTMLSelectOptionAccessible::GetRoleInternal(PRUint32 *aRole)
417 if (nsAccUtils::Role(mParent) == nsIAccessibleRole::ROLE_COMBOBOX_LIST)
418 *aRole = nsIAccessibleRole::ROLE_COMBOBOX_OPTION;
419 else
420 *aRole = nsIAccessibleRole::ROLE_OPTION;
422 return NS_OK;
425 nsresult
426 nsHTMLSelectOptionAccessible::GetNameInternal(nsAString& aName)
428 // CASE #1 -- great majority of the cases
429 // find the label attribute - this is what the W3C says we should use
430 mContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::label, aName);
431 if (!aName.IsEmpty())
432 return NS_OK;
434 // CASE #2 -- no label parameter, get the first child,
435 // use it if it is a text node
436 nsIContent *text = mContent->GetChildAt(0);
437 if (!text)
438 return NS_OK;
440 if (text->IsNodeOfType(nsINode::eTEXT)) {
441 nsAutoString txtValue;
442 nsresult rv = nsTextEquivUtils::
443 AppendTextEquivFromTextContent(text, &txtValue);
444 NS_ENSURE_SUCCESS(rv, rv);
446 // Temp var (txtValue) needed until CompressWhitespace built for nsAString
447 txtValue.CompressWhitespace();
448 aName.Assign(txtValue);
449 return NS_OK;
452 return NS_OK;
455 // nsAccessible protected
456 nsIFrame* nsHTMLSelectOptionAccessible::GetBoundsFrame()
458 PRUint32 state = 0;
459 nsCOMPtr<nsIContent> content = GetSelectState(&state);
460 if (state & nsIAccessibleStates::STATE_COLLAPSED) {
461 if (content) {
462 return content->GetPrimaryFrame();
465 return nsnull;
468 return nsAccessible::GetBoundsFrame();
472 * As a nsHTMLSelectOptionAccessible we can have the following states:
473 * STATE_SELECTABLE
474 * STATE_SELECTED
475 * STATE_FOCUSED
476 * STATE_FOCUSABLE
477 * STATE_OFFSCREEN
479 nsresult
480 nsHTMLSelectOptionAccessible::GetStateInternal(PRUint32 *aState,
481 PRUint32 *aExtraState)
483 // Upcall to nsAccessible, but skip nsHyperTextAccessible impl
484 // because we don't want EXT_STATE_EDITABLE or EXT_STATE_SELECTABLE_TEXT
485 nsresult rv = nsAccessible::GetStateInternal(aState, aExtraState);
486 NS_ENSURE_A11Y_SUCCESS(rv, rv);
488 PRUint32 selectState = 0, selectExtState = 0;
489 nsCOMPtr<nsIContent> selectContent = GetSelectState(&selectState,
490 &selectExtState);
491 if (selectState & nsIAccessibleStates::STATE_INVISIBLE) {
492 return NS_OK;
495 NS_ENSURE_TRUE(selectContent, NS_ERROR_FAILURE);
497 // Is disabled?
498 if (0 == (*aState & nsIAccessibleStates::STATE_UNAVAILABLE)) {
499 *aState |= (nsIAccessibleStates::STATE_FOCUSABLE |
500 nsIAccessibleStates::STATE_SELECTABLE);
501 // When the list is focused but no option is actually focused,
502 // Firefox draws a focus ring around the first non-disabled option.
503 // We need to indicated STATE_FOCUSED in that case, because it
504 // prevents JAWS from ignoring the list
505 // GetFocusedOption() ensures that an option node is
506 // returned in this case, as long as some focusable option exists
507 // in the listbox
508 nsCOMPtr<nsIContent> focusedOption = GetFocusedOption(selectContent);
509 if (focusedOption == mContent)
510 *aState |= nsIAccessibleStates::STATE_FOCUSED;
513 // Are we selected?
514 PRBool isSelected = PR_FALSE;
515 nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(mContent));
516 if (option) {
517 option->GetSelected(&isSelected);
518 if ( isSelected )
519 *aState |= nsIAccessibleStates::STATE_SELECTED;
522 if (selectState & nsIAccessibleStates::STATE_OFFSCREEN) {
523 *aState |= nsIAccessibleStates::STATE_OFFSCREEN;
525 else if (selectState & nsIAccessibleStates::STATE_COLLAPSED) {
526 // <select> is COLLAPSED: add STATE_OFFSCREEN, if not the currently
527 // visible option
528 if (!isSelected) {
529 *aState |= nsIAccessibleStates::STATE_OFFSCREEN;
531 else {
532 // Clear offscreen and invisible for currently showing option
533 *aState &= ~nsIAccessibleStates::STATE_OFFSCREEN;
534 *aState &= ~nsIAccessibleStates::STATE_INVISIBLE;
535 if (aExtraState) {
536 *aExtraState |= selectExtState & nsIAccessibleStates::EXT_STATE_OPAQUE;
540 else {
541 // XXX list frames are weird, don't rely on nsAccessible's general
542 // visibility implementation unless they get reimplemented in layout
543 *aState &= ~nsIAccessibleStates::STATE_OFFSCREEN;
544 // <select> is not collapsed: compare bounds to calculate STATE_OFFSCREEN
545 nsAccessible* listAcc = GetParent();
546 if (listAcc) {
547 PRInt32 optionX, optionY, optionWidth, optionHeight;
548 PRInt32 listX, listY, listWidth, listHeight;
549 GetBounds(&optionX, &optionY, &optionWidth, &optionHeight);
550 listAcc->GetBounds(&listX, &listY, &listWidth, &listHeight);
551 if (optionY < listY || optionY + optionHeight > listY + listHeight) {
552 *aState |= nsIAccessibleStates::STATE_OFFSCREEN;
557 return NS_OK;
560 PRInt32
561 nsHTMLSelectOptionAccessible::GetLevelInternal()
563 nsIContent *parentContent = mContent->GetParent();
565 PRInt32 level =
566 parentContent->NodeInfo()->Equals(nsAccessibilityAtoms::optgroup) ? 2 : 1;
568 if (level == 1 &&
569 nsAccUtils::Role(this) != nsIAccessibleRole::ROLE_HEADING) {
570 level = 0; // In a single level list, the level is irrelevant
573 return level;
576 void
577 nsHTMLSelectOptionAccessible::GetPositionAndSizeInternal(PRInt32 *aPosInSet,
578 PRInt32 *aSetSize)
580 nsIContent *parentContent = mContent->GetParent();
582 PRInt32 posInSet = 0, setSize = 0;
583 PRBool isContentFound = PR_FALSE;
585 PRUint32 childCount = parentContent->GetChildCount();
586 for (PRUint32 childIdx = 0; childIdx < childCount; childIdx++) {
587 nsIContent *childContent = parentContent->GetChildAt(childIdx);
588 if (childContent->NodeInfo()->Equals(mContent->NodeInfo())) {
589 if (!isContentFound) {
590 if (childContent == mContent)
591 isContentFound = PR_TRUE;
593 posInSet++;
595 setSize++;
599 *aSetSize = setSize;
600 *aPosInSet = posInSet;
603 ////////////////////////////////////////////////////////////////////////////////
604 // nsHTMLSelectOptionAccessible: nsIAccessible
606 /** select us! close combo box if necessary*/
607 NS_IMETHODIMP nsHTMLSelectOptionAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
609 if (aIndex == eAction_Select) {
610 aName.AssignLiteral("select");
611 return NS_OK;
613 return NS_ERROR_INVALID_ARG;
616 NS_IMETHODIMP nsHTMLSelectOptionAccessible::GetNumActions(PRUint8 *_retval)
618 *_retval = 1;
619 return NS_OK;
622 NS_IMETHODIMP nsHTMLSelectOptionAccessible::DoAction(PRUint8 index)
624 if (index == eAction_Select) { // default action
625 nsCOMPtr<nsIDOMHTMLOptionElement> newHTMLOption(do_QueryInterface(mContent));
626 if (!newHTMLOption)
627 return NS_ERROR_FAILURE;
628 // Clear old selection
629 nsAccessible* parent = GetParent();
630 NS_ASSERTION(parent, "No parent!");
632 nsCOMPtr<nsIContent> oldHTMLOptionContent =
633 GetFocusedOption(parent->GetContent());
634 nsCOMPtr<nsIDOMHTMLOptionElement> oldHTMLOption =
635 do_QueryInterface(oldHTMLOptionContent);
636 if (oldHTMLOption)
637 oldHTMLOption->SetSelected(PR_FALSE);
638 // Set new selection
639 newHTMLOption->SetSelected(PR_TRUE);
641 // If combo box, and open, close it
642 // First, get the <select> widgets list control frame
643 nsIContent *selectContent = mContent;
644 do {
645 selectContent = selectContent->GetParent();
646 nsCOMPtr<nsIDOMHTMLSelectElement> selectControl =
647 do_QueryInterface(selectContent);
648 if (selectControl)
649 break;
651 } while (selectContent);
653 nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
654 nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(mContent));
656 if (!selectContent || !presShell || !option)
657 return NS_ERROR_FAILURE;
659 nsIFrame *selectFrame = selectContent->GetPrimaryFrame();
660 nsIComboboxControlFrame *comboBoxFrame = do_QueryFrame(selectFrame);
661 if (comboBoxFrame) {
662 nsIFrame *listFrame = comboBoxFrame->GetDropDown();
663 if (comboBoxFrame->IsDroppedDown() && listFrame) {
664 // use this list control frame to roll up the list
665 nsIListControlFrame *listControlFrame = do_QueryFrame(listFrame);
666 if (listControlFrame) {
667 PRInt32 newIndex = 0;
668 option->GetIndex(&newIndex);
669 listControlFrame->ComboboxFinish(newIndex);
673 return NS_OK;
676 return NS_ERROR_INVALID_ARG;
679 ////////////////////////////////////////////////////////////////////////////////
680 // nsHTMLSelectOptionAccessible: static methods
683 * Helper method for getting the focused DOM Node from our parent(list) node. We
684 * need to use the frame to get the focused option because for some reason we
685 * weren't getting the proper notification when the focus changed using the DOM
687 already_AddRefed<nsIContent>
688 nsHTMLSelectOptionAccessible::GetFocusedOption(nsIContent *aListNode)
690 NS_ASSERTION(aListNode, "Called GetFocusedOptionNode without a valid list node");
692 nsIFrame *frame = aListNode->GetPrimaryFrame();
693 if (!frame)
694 return nsnull;
696 PRInt32 focusedOptionIndex = 0;
698 // Get options
699 nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(aListNode));
700 NS_ASSERTION(selectElement, "No select element where it should be");
702 nsCOMPtr<nsIDOMHTMLOptionsCollection> options;
703 nsresult rv = selectElement->GetOptions(getter_AddRefs(options));
705 if (NS_SUCCEEDED(rv)) {
706 nsIListControlFrame *listFrame = do_QueryFrame(frame);
707 if (listFrame) {
708 // Get what's focused in listbox by asking frame for "selected item".
709 // Can't use dom interface for this, because it will always return the first selected item
710 // when there is more than 1 item selected. We need the focused item, not
711 // the first selected item.
712 focusedOptionIndex = listFrame->GetSelectedIndex();
713 if (focusedOptionIndex == -1) {
714 nsCOMPtr<nsIDOMNode> nextOption;
715 while (PR_TRUE) {
716 ++ focusedOptionIndex;
717 options->Item(focusedOptionIndex, getter_AddRefs(nextOption));
718 nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = do_QueryInterface(nextOption);
719 if (!optionElement) {
720 break;
722 PRBool disabled;
723 optionElement->GetDisabled(&disabled);
724 if (!disabled) {
725 break;
730 else // Combo boxes can only have 1 selected option, so they can use the dom interface for this
731 rv = selectElement->GetSelectedIndex(&focusedOptionIndex);
734 // Either use options and focused index, or default return null
735 if (NS_SUCCEEDED(rv) && options && focusedOptionIndex >= 0) { // Something is focused
736 nsCOMPtr<nsIDOMNode> focusedOptionNode;
737 options->Item(focusedOptionIndex, getter_AddRefs(focusedOptionNode));
738 nsIContent *focusedOption = nsnull;
739 if (focusedOptionNode)
740 CallQueryInterface(focusedOptionNode, &focusedOption);
741 return focusedOption;
744 return nsnull;
747 void
748 nsHTMLSelectOptionAccessible::SelectionChangedIfOption(nsIContent *aPossibleOptionNode)
750 if (!aPossibleOptionNode ||
751 aPossibleOptionNode->Tag() != nsAccessibilityAtoms::option ||
752 !aPossibleOptionNode->IsHTML()) {
753 return;
756 nsAccessible *multiSelect =
757 nsAccUtils::GetMultiSelectableContainer(aPossibleOptionNode);
758 if (!multiSelect)
759 return;
761 nsAccessible *option = GetAccService()->GetAccessible(aPossibleOptionNode);
762 if (!option)
763 return;
766 nsRefPtr<AccEvent> selWithinEvent =
767 new AccEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, multiSelect);
769 if (!selWithinEvent)
770 return;
772 option->GetDocAccessible()->FireDelayedAccessibleEvent(selWithinEvent);
774 PRUint32 state = nsAccUtils::State(option);
775 PRUint32 eventType;
776 if (state & nsIAccessibleStates::STATE_SELECTED) {
777 eventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
779 else {
780 eventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
783 nsRefPtr<AccEvent> selAddRemoveEvent = new AccEvent(eventType, option);
785 if (selAddRemoveEvent)
786 option->GetDocAccessible()->FireDelayedAccessibleEvent(selAddRemoveEvent);
789 ////////////////////////////////////////////////////////////////////////////////
790 // nsHTMLSelectOptionAccessible: private methods
792 nsIContent* nsHTMLSelectOptionAccessible::GetSelectState(PRUint32* aState,
793 PRUint32* aExtraState)
795 *aState = 0;
797 if (aExtraState)
798 *aExtraState = 0;
800 nsIContent *content = mContent;
801 while (content && content->Tag() != nsAccessibilityAtoms::select) {
802 content = content->GetParent();
805 if (content) {
806 nsAccessible* selAcc = GetAccService()->GetAccessible(content);
807 if (selAcc) {
808 selAcc->GetState(aState, aExtraState);
809 return content;
812 return nsnull;
816 ////////////////////////////////////////////////////////////////////////////////
817 // nsHTMLSelectOptGroupAccessible
818 ////////////////////////////////////////////////////////////////////////////////
820 nsHTMLSelectOptGroupAccessible::
821 nsHTMLSelectOptGroupAccessible(nsIContent *aContent,
822 nsIWeakReference *aShell) :
823 nsHTMLSelectOptionAccessible(aContent, aShell)
827 nsresult
828 nsHTMLSelectOptGroupAccessible::GetRoleInternal(PRUint32 *aRole)
830 *aRole = nsIAccessibleRole::ROLE_HEADING;
831 return NS_OK;
834 nsresult
835 nsHTMLSelectOptGroupAccessible::GetStateInternal(PRUint32 *aState,
836 PRUint32 *aExtraState)
838 nsresult rv = nsHTMLSelectOptionAccessible::GetStateInternal(aState,
839 aExtraState);
840 NS_ENSURE_A11Y_SUCCESS(rv, rv);
842 *aState &= ~(nsIAccessibleStates::STATE_FOCUSABLE |
843 nsIAccessibleStates::STATE_SELECTABLE);
845 return NS_OK;
848 NS_IMETHODIMP nsHTMLSelectOptGroupAccessible::DoAction(PRUint8 index)
850 return NS_ERROR_NOT_IMPLEMENTED;
853 NS_IMETHODIMP nsHTMLSelectOptGroupAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
855 return NS_ERROR_NOT_IMPLEMENTED;
858 NS_IMETHODIMP nsHTMLSelectOptGroupAccessible::GetNumActions(PRUint8 *_retval)
860 return NS_ERROR_NOT_IMPLEMENTED;
863 ////////////////////////////////////////////////////////////////////////////////
864 // nsHTMLSelectOptGroupAccessible: nsAccessible protected
866 void
867 nsHTMLSelectOptGroupAccessible::CacheChildren()
869 // XXX To do (bug 378612) - create text child for the anonymous attribute
870 // content, so that nsIAccessibleText is supported for the <optgroup> as it is
871 // for an <option>. Attribute content is what layout creates for
872 // the label="foo" on the <optgroup>. See eStyleContentType_Attr and
873 // CreateAttributeContent() in nsCSSFrameConstructor
877 ////////////////////////////////////////////////////////////////////////////////
878 // nsHTMLComboboxAccessible
879 ////////////////////////////////////////////////////////////////////////////////
881 nsHTMLComboboxAccessible::
882 nsHTMLComboboxAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
883 nsAccessibleWrap(aContent, aShell)
887 nsresult
888 nsHTMLComboboxAccessible::GetRoleInternal(PRUint32 *aRole)
890 *aRole = nsIAccessibleRole::ROLE_COMBOBOX;
891 return NS_OK;
894 void
895 nsHTMLComboboxAccessible::CacheChildren()
897 nsIFrame* frame = GetFrame();
898 if (!frame)
899 return;
901 nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
902 if (!comboFrame)
903 return;
905 nsIFrame *listFrame = comboFrame->GetDropDown();
906 if (!listFrame)
907 return;
909 if (!mListAccessible) {
910 mListAccessible =
911 new nsHTMLComboboxListAccessible(mParent, mContent, mWeakShell);
912 if (!mListAccessible)
913 return;
915 // Initialize and put into cache.
916 if (!mListAccessible->Init()) {
917 mListAccessible->Shutdown();
918 return;
922 AppendChild(mListAccessible);
924 // Cache combobox option accessibles so that we build complete accessible tree
925 // for combobox.
926 mListAccessible->EnsureChildren();
929 void
930 nsHTMLComboboxAccessible::Shutdown()
932 nsAccessibleWrap::Shutdown();
934 if (mListAccessible) {
935 mListAccessible->Shutdown();
936 mListAccessible = nsnull;
941 * As a nsHTMLComboboxAccessible we can have the following states:
942 * STATE_FOCUSED
943 * STATE_FOCUSABLE
944 * STATE_HASPOPUP
945 * STATE_EXPANDED
946 * STATE_COLLAPSED
948 nsresult
949 nsHTMLComboboxAccessible::GetStateInternal(PRUint32 *aState,
950 PRUint32 *aExtraState)
952 // Get focus status from base class
953 nsresult rv = nsAccessible::GetStateInternal(aState, aExtraState);
954 NS_ENSURE_A11Y_SUCCESS(rv, rv);
956 nsIFrame *frame = GetBoundsFrame();
957 nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
958 if (comboFrame && comboFrame->IsDroppedDown()) {
959 *aState |= nsIAccessibleStates::STATE_EXPANDED;
961 else {
962 *aState &= ~nsIAccessibleStates::STATE_FOCUSED; // Focus is on an option
963 *aState |= nsIAccessibleStates::STATE_COLLAPSED;
966 *aState |= nsIAccessibleStates::STATE_HASPOPUP |
967 nsIAccessibleStates::STATE_FOCUSABLE;
969 return NS_OK;
972 NS_IMETHODIMP nsHTMLComboboxAccessible::GetDescription(nsAString& aDescription)
974 aDescription.Truncate();
975 // First check to see if combo box itself has a description, perhaps through
976 // tooltip (title attribute) or via aria-describedby
977 nsAccessible::GetDescription(aDescription);
978 if (!aDescription.IsEmpty()) {
979 return NS_OK;
981 // Use description of currently focused option
982 nsAccessible *option = GetFocusedOptionAccessible();
983 return option ? option->GetDescription(aDescription) : NS_OK;
986 nsAccessible *
987 nsHTMLComboboxAccessible::GetFocusedOptionAccessible()
989 if (IsDefunct())
990 return nsnull;
992 nsCOMPtr<nsIContent> focusedOption =
993 nsHTMLSelectOptionAccessible::GetFocusedOption(mContent);
994 if (!focusedOption) {
995 return nsnull;
998 return GetAccService()->GetAccessibleInWeakShell(focusedOption,
999 mWeakShell);
1003 * MSAA/ATK accessible value != HTML value, especially not in combo boxes.
1004 * Our accessible value is the text label for of our ( first ) selected child.
1005 * The easiest way to get this is from the first child which is the readonly textfield.
1007 NS_IMETHODIMP nsHTMLComboboxAccessible::GetValue(nsAString& aValue)
1009 // Use accessible name of currently focused option.
1010 nsAccessible *option = GetFocusedOptionAccessible();
1011 return option ? option->GetName(aValue) : NS_OK;
1014 /** Just one action ( click ). */
1015 NS_IMETHODIMP nsHTMLComboboxAccessible::GetNumActions(PRUint8 *aNumActions)
1017 *aNumActions = 1;
1018 return NS_OK;
1022 * Programmaticaly toggle the combo box
1024 NS_IMETHODIMP nsHTMLComboboxAccessible::DoAction(PRUint8 aIndex)
1026 if (aIndex != nsHTMLComboboxAccessible::eAction_Click) {
1027 return NS_ERROR_INVALID_ARG;
1029 nsIFrame *frame = GetFrame();
1030 if (!frame) {
1031 return NS_ERROR_FAILURE;
1033 nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
1034 if (!comboFrame) {
1035 return NS_ERROR_FAILURE;
1037 // Reverse whether it's dropped down or not
1038 comboFrame->ShowDropDown(!comboFrame->IsDroppedDown());
1040 return NS_OK;
1044 * Our action name is the reverse of our state:
1045 * if we are closed -> open is our name.
1046 * if we are open -> closed is our name.
1047 * Uses the frame to get the state, updated on every click
1049 NS_IMETHODIMP nsHTMLComboboxAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
1051 if (aIndex != nsHTMLComboboxAccessible::eAction_Click) {
1052 return NS_ERROR_INVALID_ARG;
1054 nsIFrame *frame = GetFrame();
1055 if (!frame) {
1056 return NS_ERROR_FAILURE;
1058 nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
1059 if (!comboFrame) {
1060 return NS_ERROR_FAILURE;
1062 if (comboFrame->IsDroppedDown())
1063 aName.AssignLiteral("close");
1064 else
1065 aName.AssignLiteral("open");
1067 return NS_OK;
1071 ////////////////////////////////////////////////////////////////////////////////
1072 // nsHTMLComboboxListAccessible
1073 ////////////////////////////////////////////////////////////////////////////////
1075 nsHTMLComboboxListAccessible::
1076 nsHTMLComboboxListAccessible(nsIAccessible *aParent, nsIContent *aContent,
1077 nsIWeakReference *aShell) :
1078 nsHTMLSelectListAccessible(aContent, aShell)
1082 nsIFrame*
1083 nsHTMLComboboxListAccessible::GetFrame()
1085 nsIFrame* frame = nsHTMLSelectListAccessible::GetFrame();
1087 if (frame) {
1088 nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
1089 if (comboBox) {
1090 return comboBox->GetDropDown();
1094 return nsnull;
1098 * As a nsHTMLComboboxListAccessible we can have the following states:
1099 * STATE_FOCUSED
1100 * STATE_FOCUSABLE
1101 * STATE_INVISIBLE
1102 * STATE_FLOATING
1104 nsresult
1105 nsHTMLComboboxListAccessible::GetStateInternal(PRUint32 *aState,
1106 PRUint32 *aExtraState)
1108 // Get focus status from base class
1109 nsresult rv = nsAccessible::GetStateInternal(aState, aExtraState);
1110 NS_ENSURE_A11Y_SUCCESS(rv, rv);
1112 nsIFrame *boundsFrame = GetBoundsFrame();
1113 nsIComboboxControlFrame* comboFrame = do_QueryFrame(boundsFrame);
1114 if (comboFrame && comboFrame->IsDroppedDown())
1115 *aState |= nsIAccessibleStates::STATE_FLOATING;
1116 else
1117 *aState |= nsIAccessibleStates::STATE_INVISIBLE;
1119 return NS_OK;
1122 NS_IMETHODIMP nsHTMLComboboxListAccessible::GetUniqueID(void **aUniqueID)
1124 // Since mContent is same for all tree item, use |this| pointer as the unique
1125 // Id.
1126 *aUniqueID = static_cast<void*>(this);
1127 return NS_OK;
1131 * Gets the bounds for the areaFrame.
1132 * Walks the Frame tree and checks for proper frames.
1134 void nsHTMLComboboxListAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame)
1136 *aBoundingFrame = nsnull;
1138 nsAccessible* comboAcc = GetParent();
1139 if (!comboAcc)
1140 return;
1142 if (0 == (nsAccUtils::State(comboAcc) & nsIAccessibleStates::STATE_COLLAPSED)) {
1143 nsHTMLSelectListAccessible::GetBoundsRect(aBounds, aBoundingFrame);
1144 return;
1147 // Get the first option.
1148 nsIContent* content = mContent->GetChildAt(0);
1149 if (!content) {
1150 return;
1152 nsIFrame* frame = content->GetPrimaryFrame();
1153 if (!frame) {
1154 *aBoundingFrame = nsnull;
1155 return;
1158 *aBoundingFrame = frame->GetParent();
1159 aBounds = (*aBoundingFrame)->GetRect();