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
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.
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"
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
)
72 nsCOMPtr
<nsIDOMHTMLSelectElement
> htmlSelect
=
73 do_QueryInterface(mParentSelect
->mContent
);
75 htmlSelect
->GetOptions(getter_AddRefs(mOptions
));
77 mOptions
->GetLength(&mLength
);
81 PRBool
nsHTMLSelectableAccessible::iterator::Advance()
83 if (mIndex
< mLength
) {
84 nsCOMPtr
<nsIDOMNode
> tempNode
;
86 mOptions
->Item(mIndex
, getter_AddRefs(tempNode
));
87 mOption
= do_QueryInterface(tempNode
);
95 void nsHTMLSelectableAccessible::iterator::CalcSelectionCount(PRInt32
*aSelectionCount
)
97 PRBool isSelected
= PR_FALSE
;
100 mOption
->GetSelected(&isSelected
);
103 (*aSelectionCount
)++;
107 nsHTMLSelectableAccessible::iterator::AddAccessibleIfSelected(nsIMutableArray
*aSelectedAccessibles
,
108 nsPresContext
*aContext
)
110 PRBool isSelected
= PR_FALSE
;
111 nsAccessible
*optionAcc
= nsnull
;
114 mOption
->GetSelected(&isSelected
);
116 nsCOMPtr
<nsIContent
> optionContent(do_QueryInterface(mOption
));
117 optionAcc
= GetAccService()->GetAccessibleInWeakShell(optionContent
,
123 aSelectedAccessibles
->AppendElement(static_cast<nsIAccessible
*>(optionAcc
),
128 nsHTMLSelectableAccessible::iterator::GetAccessibleIfSelected(PRInt32 aIndex
,
129 nsPresContext
*aContext
,
130 nsIAccessible
**aAccessible
)
132 PRBool isSelected
= PR_FALSE
;
134 *aAccessible
= nsnull
;
137 mOption
->GetSelected(&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
);
154 void nsHTMLSelectableAccessible::iterator::Select(PRBool aSelect
)
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
)
172 NS_IMETHODIMP
nsHTMLSelectableAccessible::ChangeSelection(PRInt32 aIndex
, PRUint8 aMethod
, PRBool
*aSelState
)
174 *aSelState
= PR_FALSE
;
176 nsCOMPtr
<nsIDOMHTMLSelectElement
> htmlSelect(do_QueryInterface(mContent
));
178 return NS_ERROR_FAILURE
;
180 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
;
181 htmlSelect
->GetOptions(getter_AddRefs(options
));
183 return NS_ERROR_FAILURE
;
185 nsCOMPtr
<nsIDOMNode
> tempNode
;
186 options
->Item(aIndex
, getter_AddRefs(tempNode
));
187 nsCOMPtr
<nsIDOMHTMLOptionElement
> tempOption(do_QueryInterface(tempNode
));
189 return NS_ERROR_FAILURE
;
191 tempOption
->GetSelected(aSelState
);
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
);
201 NS_IMETHODIMP
nsHTMLSelectableAccessible::GetSelectedChildren(nsIArray
**_retval
)
205 nsCOMPtr
<nsIMutableArray
> selectedAccessibles
=
206 do_CreateInstance(NS_ARRAY_CONTRACTID
);
207 NS_ENSURE_STATE(selectedAccessibles
);
209 nsPresContext
*context
= GetPresContext();
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
;
226 // return the nth selected child's nsIAccessible object
227 NS_IMETHODIMP
nsHTMLSelectableAccessible::RefSelection(PRInt32 aIndex
, nsIAccessible
**_retval
)
231 nsPresContext
*context
= GetPresContext();
233 return NS_ERROR_FAILURE
;
235 nsHTMLSelectableAccessible::iterator
iter(this, mWeakShell
);
236 while (iter
.Advance())
237 if (iter
.GetAccessibleIfSelected(aIndex
, context
, _retval
))
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
);
254 NS_IMETHODIMP
nsHTMLSelectableAccessible::AddChildToSelection(PRInt32 aIndex
)
257 return ChangeSelection(aIndex
, eSelection_Add
, &isSelected
);
260 NS_IMETHODIMP
nsHTMLSelectableAccessible::RemoveChildFromSelection(PRInt32 aIndex
)
263 return ChangeSelection(aIndex
, eSelection_Remove
, &isSelected
);
266 NS_IMETHODIMP
nsHTMLSelectableAccessible::IsChildSelected(PRInt32 aIndex
, PRBool
*_retval
)
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
);
280 NS_IMETHODIMP
nsHTMLSelectableAccessible::SelectAllSelection(PRBool
*_retval
)
284 nsCOMPtr
<nsIDOMHTMLSelectElement
> htmlSelect(do_QueryInterface(mContent
));
286 return NS_ERROR_FAILURE
;
288 htmlSelect
->GetMultiple(_retval
);
290 nsHTMLSelectableAccessible::iterator
iter(this, mWeakShell
);
291 while (iter
.Advance())
292 iter
.Select(PR_TRUE
);
298 ////////////////////////////////////////////////////////////////////////////////
299 // nsHTMLSelectListAccessible
300 ////////////////////////////////////////////////////////////////////////////////
302 nsHTMLSelectListAccessible::
303 nsHTMLSelectListAccessible(nsIContent
*aContent
, nsIWeakReference
*aShell
) :
304 nsHTMLSelectableAccessible(aContent
, aShell
)
308 ////////////////////////////////////////////////////////////////////////////////
309 // nsHTMLSelectListAccessible: nsAccessible public
312 nsHTMLSelectListAccessible::GetStateInternal(PRUint32
*aState
,
313 PRUint32
*aExtraState
)
315 nsresult rv
= nsHTMLSelectableAccessible::GetStateInternal(aState
,
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
));
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
;
335 select
->GetMultiple(&multiple
);
337 *aState
|= nsIAccessibleStates::STATE_MULTISELECTABLE
|
338 nsIAccessibleStates::STATE_EXTSELECTABLE
;
345 nsHTMLSelectListAccessible::GetRoleInternal(PRUint32
*aRole
)
347 if (nsAccUtils::Role(mParent
) == nsIAccessibleRole::ROLE_COMBOBOX
)
348 *aRole
= nsIAccessibleRole::ROLE_COMBOBOX_LIST
;
350 *aRole
= nsIAccessibleRole::ROLE_LISTBOX
;
355 ////////////////////////////////////////////////////////////////////////////////
356 // nsHTMLSelectListAccessible: nsAccessible protected
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
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()) {
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
,
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
415 nsHTMLSelectOptionAccessible::GetRoleInternal(PRUint32
*aRole
)
417 if (nsAccUtils::Role(mParent
) == nsIAccessibleRole::ROLE_COMBOBOX_LIST
)
418 *aRole
= nsIAccessibleRole::ROLE_COMBOBOX_OPTION
;
420 *aRole
= nsIAccessibleRole::ROLE_OPTION
;
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())
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);
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
);
455 // nsAccessible protected
456 nsIFrame
* nsHTMLSelectOptionAccessible::GetBoundsFrame()
459 nsCOMPtr
<nsIContent
> content
= GetSelectState(&state
);
460 if (state
& nsIAccessibleStates::STATE_COLLAPSED
) {
462 return content
->GetPrimaryFrame();
468 return nsAccessible::GetBoundsFrame();
472 * As a nsHTMLSelectOptionAccessible we can have the following states:
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
,
491 if (selectState
& nsIAccessibleStates::STATE_INVISIBLE
) {
495 NS_ENSURE_TRUE(selectContent
, NS_ERROR_FAILURE
);
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
508 nsCOMPtr
<nsIContent
> focusedOption
= GetFocusedOption(selectContent
);
509 if (focusedOption
== mContent
)
510 *aState
|= nsIAccessibleStates::STATE_FOCUSED
;
514 PRBool isSelected
= PR_FALSE
;
515 nsCOMPtr
<nsIDOMHTMLOptionElement
> option(do_QueryInterface(mContent
));
517 option
->GetSelected(&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
529 *aState
|= nsIAccessibleStates::STATE_OFFSCREEN
;
532 // Clear offscreen and invisible for currently showing option
533 *aState
&= ~nsIAccessibleStates::STATE_OFFSCREEN
;
534 *aState
&= ~nsIAccessibleStates::STATE_INVISIBLE
;
536 *aExtraState
|= selectExtState
& nsIAccessibleStates::EXT_STATE_OPAQUE
;
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();
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
;
561 nsHTMLSelectOptionAccessible::GetLevelInternal()
563 nsIContent
*parentContent
= mContent
->GetParent();
566 parentContent
->NodeInfo()->Equals(nsAccessibilityAtoms::optgroup
) ? 2 : 1;
569 nsAccUtils::Role(this) != nsIAccessibleRole::ROLE_HEADING
) {
570 level
= 0; // In a single level list, the level is irrelevant
577 nsHTMLSelectOptionAccessible::GetPositionAndSizeInternal(PRInt32
*aPosInSet
,
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
;
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");
613 return NS_ERROR_INVALID_ARG
;
616 NS_IMETHODIMP
nsHTMLSelectOptionAccessible::GetNumActions(PRUint8
*_retval
)
622 NS_IMETHODIMP
nsHTMLSelectOptionAccessible::DoAction(PRUint8 index
)
624 if (index
== eAction_Select
) { // default action
625 nsCOMPtr
<nsIDOMHTMLOptionElement
> newHTMLOption(do_QueryInterface(mContent
));
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
);
637 oldHTMLOption
->SetSelected(PR_FALSE
);
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
;
645 selectContent
= selectContent
->GetParent();
646 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectControl
=
647 do_QueryInterface(selectContent
);
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
);
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
);
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();
696 PRInt32 focusedOptionIndex
= 0;
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
);
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
;
716 ++ focusedOptionIndex
;
717 options
->Item(focusedOptionIndex
, getter_AddRefs(nextOption
));
718 nsCOMPtr
<nsIDOMHTMLOptionElement
> optionElement
= do_QueryInterface(nextOption
);
719 if (!optionElement
) {
723 optionElement
->GetDisabled(&disabled
);
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
;
748 nsHTMLSelectOptionAccessible::SelectionChangedIfOption(nsIContent
*aPossibleOptionNode
)
750 if (!aPossibleOptionNode
||
751 aPossibleOptionNode
->Tag() != nsAccessibilityAtoms::option
||
752 !aPossibleOptionNode
->IsHTML()) {
756 nsAccessible
*multiSelect
=
757 nsAccUtils::GetMultiSelectableContainer(aPossibleOptionNode
);
761 nsAccessible
*option
= GetAccService()->GetAccessible(aPossibleOptionNode
);
766 nsRefPtr
<AccEvent
> selWithinEvent
=
767 new AccEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN
, multiSelect
);
772 option
->GetDocAccessible()->FireDelayedAccessibleEvent(selWithinEvent
);
774 PRUint32 state
= nsAccUtils::State(option
);
776 if (state
& nsIAccessibleStates::STATE_SELECTED
) {
777 eventType
= nsIAccessibleEvent::EVENT_SELECTION_ADD
;
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
)
800 nsIContent
*content
= mContent
;
801 while (content
&& content
->Tag() != nsAccessibilityAtoms::select
) {
802 content
= content
->GetParent();
806 nsAccessible
* selAcc
= GetAccService()->GetAccessible(content
);
808 selAcc
->GetState(aState
, aExtraState
);
816 ////////////////////////////////////////////////////////////////////////////////
817 // nsHTMLSelectOptGroupAccessible
818 ////////////////////////////////////////////////////////////////////////////////
820 nsHTMLSelectOptGroupAccessible::
821 nsHTMLSelectOptGroupAccessible(nsIContent
*aContent
,
822 nsIWeakReference
*aShell
) :
823 nsHTMLSelectOptionAccessible(aContent
, aShell
)
828 nsHTMLSelectOptGroupAccessible::GetRoleInternal(PRUint32
*aRole
)
830 *aRole
= nsIAccessibleRole::ROLE_HEADING
;
835 nsHTMLSelectOptGroupAccessible::GetStateInternal(PRUint32
*aState
,
836 PRUint32
*aExtraState
)
838 nsresult rv
= nsHTMLSelectOptionAccessible::GetStateInternal(aState
,
840 NS_ENSURE_A11Y_SUCCESS(rv
, rv
);
842 *aState
&= ~(nsIAccessibleStates::STATE_FOCUSABLE
|
843 nsIAccessibleStates::STATE_SELECTABLE
);
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
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
)
888 nsHTMLComboboxAccessible::GetRoleInternal(PRUint32
*aRole
)
890 *aRole
= nsIAccessibleRole::ROLE_COMBOBOX
;
895 nsHTMLComboboxAccessible::CacheChildren()
897 nsIFrame
* frame
= GetFrame();
901 nsIComboboxControlFrame
*comboFrame
= do_QueryFrame(frame
);
905 nsIFrame
*listFrame
= comboFrame
->GetDropDown();
909 if (!mListAccessible
) {
911 new nsHTMLComboboxListAccessible(mParent
, mContent
, mWeakShell
);
912 if (!mListAccessible
)
915 // Initialize and put into cache.
916 if (!mListAccessible
->Init()) {
917 mListAccessible
->Shutdown();
922 AppendChild(mListAccessible
);
924 // Cache combobox option accessibles so that we build complete accessible tree
926 mListAccessible
->EnsureChildren();
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:
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
;
962 *aState
&= ~nsIAccessibleStates::STATE_FOCUSED
; // Focus is on an option
963 *aState
|= nsIAccessibleStates::STATE_COLLAPSED
;
966 *aState
|= nsIAccessibleStates::STATE_HASPOPUP
|
967 nsIAccessibleStates::STATE_FOCUSABLE
;
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()) {
981 // Use description of currently focused option
982 nsAccessible
*option
= GetFocusedOptionAccessible();
983 return option
? option
->GetDescription(aDescription
) : NS_OK
;
987 nsHTMLComboboxAccessible::GetFocusedOptionAccessible()
992 nsCOMPtr
<nsIContent
> focusedOption
=
993 nsHTMLSelectOptionAccessible::GetFocusedOption(mContent
);
994 if (!focusedOption
) {
998 return GetAccService()->GetAccessibleInWeakShell(focusedOption
,
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
)
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();
1031 return NS_ERROR_FAILURE
;
1033 nsIComboboxControlFrame
*comboFrame
= do_QueryFrame(frame
);
1035 return NS_ERROR_FAILURE
;
1037 // Reverse whether it's dropped down or not
1038 comboFrame
->ShowDropDown(!comboFrame
->IsDroppedDown());
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();
1056 return NS_ERROR_FAILURE
;
1058 nsIComboboxControlFrame
*comboFrame
= do_QueryFrame(frame
);
1060 return NS_ERROR_FAILURE
;
1062 if (comboFrame
->IsDroppedDown())
1063 aName
.AssignLiteral("close");
1065 aName
.AssignLiteral("open");
1071 ////////////////////////////////////////////////////////////////////////////////
1072 // nsHTMLComboboxListAccessible
1073 ////////////////////////////////////////////////////////////////////////////////
1075 nsHTMLComboboxListAccessible::
1076 nsHTMLComboboxListAccessible(nsIAccessible
*aParent
, nsIContent
*aContent
,
1077 nsIWeakReference
*aShell
) :
1078 nsHTMLSelectListAccessible(aContent
, aShell
)
1083 nsHTMLComboboxListAccessible::GetFrame()
1085 nsIFrame
* frame
= nsHTMLSelectListAccessible::GetFrame();
1088 nsIComboboxControlFrame
* comboBox
= do_QueryFrame(frame
);
1090 return comboBox
->GetDropDown();
1098 * As a nsHTMLComboboxListAccessible we can have the following states:
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
;
1117 *aState
|= nsIAccessibleStates::STATE_INVISIBLE
;
1122 NS_IMETHODIMP
nsHTMLComboboxListAccessible::GetUniqueID(void **aUniqueID
)
1124 // Since mContent is same for all tree item, use |this| pointer as the unique
1126 *aUniqueID
= static_cast<void*>(this);
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();
1142 if (0 == (nsAccUtils::State(comboAcc
) & nsIAccessibleStates::STATE_COLLAPSED
)) {
1143 nsHTMLSelectListAccessible::GetBoundsRect(aBounds
, aBoundingFrame
);
1147 // Get the first option.
1148 nsIContent
* content
= mContent
->GetChildAt(0);
1152 nsIFrame
* frame
= content
->GetPrimaryFrame();
1154 *aBoundingFrame
= nsnull
;
1158 *aBoundingFrame
= frame
->GetParent();
1159 aBounds
= (*aBoundingFrame
)->GetRect();