1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "HTMLSelectAccessible.h"
8 #include "LocalAccessible-inl.h"
9 #include "nsAccessibilityService.h"
10 #include "nsAccUtils.h"
11 #include "DocAccessible.h"
12 #include "mozilla/a11y/Role.h"
16 #include "mozilla/dom/HTMLOptionElement.h"
17 #include "mozilla/dom/HTMLOptGroupElement.h"
18 #include "mozilla/dom/HTMLSelectElement.h"
19 #include "nsComboboxControlFrame.h"
20 #include "nsContainerFrame.h"
21 #include "nsListControlFrame.h"
23 using namespace mozilla::a11y
;
24 using namespace mozilla::dom
;
26 ////////////////////////////////////////////////////////////////////////////////
27 // HTMLSelectListAccessible
28 ////////////////////////////////////////////////////////////////////////////////
30 HTMLSelectListAccessible::HTMLSelectListAccessible(nsIContent
* aContent
,
32 : AccessibleWrap(aContent
, aDoc
) {
33 mGenericTypes
|= eListControl
| eSelect
;
36 ////////////////////////////////////////////////////////////////////////////////
37 // HTMLSelectListAccessible: LocalAccessible public
39 uint64_t HTMLSelectListAccessible::NativeState() const {
40 uint64_t state
= AccessibleWrap::NativeState();
41 if (mContent
->AsElement()->HasAttr(nsGkAtoms::multiple
)) {
42 state
|= states::MULTISELECTABLE
| states::EXTSELECTABLE
;
48 role
HTMLSelectListAccessible::NativeRole() const { return roles::LISTBOX
; }
50 ////////////////////////////////////////////////////////////////////////////////
51 // HTMLSelectListAccessible: SelectAccessible
53 bool HTMLSelectListAccessible::SelectAll() {
54 return mContent
->AsElement()->HasAttr(nsGkAtoms::multiple
)
55 ? AccessibleWrap::SelectAll()
59 bool HTMLSelectListAccessible::UnselectAll() {
60 return mContent
->AsElement()->HasAttr(nsGkAtoms::multiple
)
61 ? AccessibleWrap::UnselectAll()
65 ////////////////////////////////////////////////////////////////////////////////
66 // HTMLSelectListAccessible: Widgets
68 bool HTMLSelectListAccessible::IsWidget() const { return true; }
70 bool HTMLSelectListAccessible::IsActiveWidget() const {
71 return FocusMgr()->HasDOMFocus(mContent
);
74 bool HTMLSelectListAccessible::AreItemsOperable() const { return true; }
76 LocalAccessible
* HTMLSelectListAccessible::CurrentItem() const {
77 nsListControlFrame
* listControlFrame
= do_QueryFrame(GetFrame());
78 if (listControlFrame
) {
79 nsCOMPtr
<nsIContent
> activeOptionNode
=
80 listControlFrame
->GetCurrentOption();
81 if (activeOptionNode
) {
82 DocAccessible
* document
= Document();
83 if (document
) return document
->GetAccessible(activeOptionNode
);
89 void HTMLSelectListAccessible::SetCurrentItem(const LocalAccessible
* aItem
) {
90 if (!aItem
->GetContent()->IsElement()) return;
92 aItem
->GetContent()->AsElement()->SetAttr(
93 kNameSpaceID_None
, nsGkAtoms::selected
, u
"true"_ns
, true);
96 bool HTMLSelectListAccessible::IsAcceptableChild(nsIContent
* aEl
) const {
97 return aEl
->IsAnyOfHTMLElements(nsGkAtoms::option
, nsGkAtoms::optgroup
);
100 bool HTMLSelectListAccessible::AttributeChangesState(nsAtom
* aAttribute
) {
101 return aAttribute
== nsGkAtoms::multiple
||
102 LocalAccessible::AttributeChangesState(aAttribute
);
105 ////////////////////////////////////////////////////////////////////////////////
106 // HTMLSelectOptionAccessible
107 ////////////////////////////////////////////////////////////////////////////////
109 HTMLSelectOptionAccessible::HTMLSelectOptionAccessible(nsIContent
* aContent
,
111 : HyperTextAccessible(aContent
, aDoc
) {}
113 ////////////////////////////////////////////////////////////////////////////////
114 // HTMLSelectOptionAccessible: LocalAccessible public
116 role
HTMLSelectOptionAccessible::NativeRole() const {
117 if (GetCombobox()) return roles::COMBOBOX_OPTION
;
119 return roles::OPTION
;
122 ENameValueFlag
HTMLSelectOptionAccessible::NativeName(nsString
& aName
) const {
123 if (auto* option
= dom::HTMLOptionElement::FromNode(mContent
)) {
124 option
->GetAttr(nsGkAtoms::label
, aName
);
125 if (!aName
.IsEmpty()) {
128 option
->GetText(aName
);
129 return eNameFromSubtree
;
131 if (auto* group
= dom::HTMLOptGroupElement::FromNode(mContent
)) {
132 group
->GetLabel(aName
);
133 return aName
.IsEmpty() ? eNameOK
: eNameFromSubtree
;
135 MOZ_ASSERT_UNREACHABLE("What content do we have?");
136 return eNameFromSubtree
;
139 void HTMLSelectOptionAccessible::DOMAttributeChanged(
140 int32_t aNameSpaceID
, nsAtom
* aAttribute
, int32_t aModType
,
141 const nsAttrValue
* aOldValue
, uint64_t aOldState
) {
142 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID
, aAttribute
, aModType
,
143 aOldValue
, aOldState
);
145 if (aAttribute
== nsGkAtoms::label
) {
146 dom::Element
* elm
= Elm();
147 if (!nsAccUtils::HasARIAAttr(elm
, nsGkAtoms::aria_labelledby
) &&
148 !nsAccUtils::HasARIAAttr(elm
, nsGkAtoms::aria_label
)) {
149 mDoc
->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
, this);
154 uint64_t HTMLSelectOptionAccessible::NativeState() const {
155 // As a HTMLSelectOptionAccessible we can have the following states:
156 // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
157 // Upcall to LocalAccessible, but skip HyperTextAccessible impl
158 // because we don't want EDITABLE or SELECTABLE_TEXT
159 uint64_t state
= LocalAccessible::NativeState();
161 LocalAccessible
* select
= GetSelect();
162 if (!select
) return state
;
164 uint64_t selectState
= select
->State();
165 if (selectState
& states::INVISIBLE
) return state
;
168 HTMLOptionElement
* option
= HTMLOptionElement::FromNode(mContent
);
169 bool selected
= option
&& option
->Selected();
170 if (selected
) state
|= states::SELECTED
;
172 if (selectState
& states::OFFSCREEN
) {
173 state
|= states::OFFSCREEN
;
174 } else if (selectState
& states::COLLAPSED
) {
175 // <select> is COLLAPSED: add OFFSCREEN, if not the currently
178 state
|= states::OFFSCREEN
;
179 // Ensure the invisible state is removed. Otherwise, group info will skip
180 // this option. Furthermore, this gets cached and this doesn't get
181 // invalidated even once the select is expanded.
182 state
&= ~states::INVISIBLE
;
184 // Clear offscreen and invisible for currently showing option
185 state
&= ~(states::OFFSCREEN
| states::INVISIBLE
);
186 state
|= selectState
& states::OPAQUE1
;
189 // XXX list frames are weird, don't rely on LocalAccessible's general
190 // visibility implementation unless they get reimplemented in layout
191 state
&= ~states::OFFSCREEN
;
192 // <select> is not collapsed: compare bounds to calculate OFFSCREEN
193 LocalAccessible
* listAcc
= LocalParent();
195 LayoutDeviceIntRect optionRect
= Bounds();
196 LayoutDeviceIntRect listRect
= listAcc
->Bounds();
197 if (optionRect
.Y() < listRect
.Y() ||
198 optionRect
.YMost() > listRect
.YMost()) {
199 state
|= states::OFFSCREEN
;
207 uint64_t HTMLSelectOptionAccessible::NativeInteractiveState() const {
208 return NativelyUnavailable() ? states::UNAVAILABLE
209 : states::FOCUSABLE
| states::SELECTABLE
;
212 nsRect
HTMLSelectOptionAccessible::RelativeBounds(
213 nsIFrame
** aBoundingFrame
) const {
214 LocalAccessible
* combobox
= GetCombobox();
215 if (combobox
&& (combobox
->State() & states::COLLAPSED
)) {
216 return combobox
->RelativeBounds(aBoundingFrame
);
219 return HyperTextAccessible::RelativeBounds(aBoundingFrame
);
222 void HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex
,
224 if (aIndex
== eAction_Select
) aName
.AssignLiteral("select");
227 bool HTMLSelectOptionAccessible::HasPrimaryAction() const { return true; }
229 void HTMLSelectOptionAccessible::SetSelected(bool aSelect
) {
230 HTMLOptionElement
* option
= HTMLOptionElement::FromNode(mContent
);
231 if (option
) option
->SetSelected(aSelect
);
234 ////////////////////////////////////////////////////////////////////////////////
235 // HTMLSelectOptionAccessible: Widgets
237 LocalAccessible
* HTMLSelectOptionAccessible::ContainerWidget() const {
238 LocalAccessible
* parent
= LocalParent();
239 if (parent
&& parent
->IsHTMLOptGroup()) {
240 parent
= parent
->LocalParent();
243 return parent
&& parent
->IsListControl() ? parent
: nullptr;
246 ////////////////////////////////////////////////////////////////////////////////
247 // HTMLSelectOptGroupAccessible
248 ////////////////////////////////////////////////////////////////////////////////
250 role
HTMLSelectOptGroupAccessible::NativeRole() const {
251 return roles::GROUPING
;
254 uint64_t HTMLSelectOptGroupAccessible::NativeInteractiveState() const {
255 return NativelyUnavailable() ? states::UNAVAILABLE
: 0;
258 bool HTMLSelectOptGroupAccessible::IsAcceptableChild(nsIContent
* aEl
) const {
259 return aEl
->IsCharacterData() || aEl
->IsHTMLElement(nsGkAtoms::option
);
262 bool HTMLSelectOptGroupAccessible::HasPrimaryAction() const { return false; }
264 ////////////////////////////////////////////////////////////////////////////////
265 // HTMLComboboxAccessible
266 ////////////////////////////////////////////////////////////////////////////////
268 HTMLComboboxAccessible::HTMLComboboxAccessible(nsIContent
* aContent
,
270 : AccessibleWrap(aContent
, aDoc
) {
271 mType
= eHTMLComboboxType
;
272 mGenericTypes
|= eCombobox
;
273 mStateFlags
|= eNoKidsFromDOM
;
275 if ((nsComboboxControlFrame
*)do_QueryFrame(GetFrame())) {
276 mListAccessible
= new HTMLComboboxListAccessible(mParent
, mContent
, mDoc
);
277 Document()->BindToDocument(mListAccessible
, nullptr);
278 AppendChild(mListAccessible
);
282 ////////////////////////////////////////////////////////////////////////////////
283 // HTMLComboboxAccessible: LocalAccessible
285 role
HTMLComboboxAccessible::NativeRole() const { return roles::COMBOBOX
; }
287 bool HTMLComboboxAccessible::RemoveChild(LocalAccessible
* aChild
) {
288 MOZ_ASSERT(aChild
== mListAccessible
);
289 if (AccessibleWrap::RemoveChild(aChild
)) {
290 mListAccessible
= nullptr;
296 void HTMLComboboxAccessible::Shutdown() {
297 MOZ_ASSERT(!mDoc
|| mDoc
->IsDefunct() || !mListAccessible
);
298 if (mListAccessible
) {
299 mListAccessible
->Shutdown();
300 mListAccessible
= nullptr;
303 AccessibleWrap::Shutdown();
306 uint64_t HTMLComboboxAccessible::NativeState() const {
307 // As a HTMLComboboxAccessible we can have the following states:
308 // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
309 // Get focus status from base class
310 uint64_t state
= LocalAccessible::NativeState();
312 nsComboboxControlFrame
* comboFrame
= do_QueryFrame(GetFrame());
313 if (comboFrame
&& comboFrame
->IsDroppedDown()) {
314 state
|= states::EXPANDED
;
316 state
|= states::COLLAPSED
;
319 state
|= states::HASPOPUP
;
323 void HTMLComboboxAccessible::Description(nsString
& aDescription
) const {
324 aDescription
.Truncate();
325 // First check to see if combo box itself has a description, perhaps through
326 // tooltip (title attribute) or via aria-describedby
327 LocalAccessible::Description(aDescription
);
328 if (!aDescription
.IsEmpty()) return;
330 // Otherwise use description of selected option.
331 LocalAccessible
* option
= SelectedOption();
332 if (option
) option
->Description(aDescription
);
335 void HTMLComboboxAccessible::Value(nsString
& aValue
) const {
336 // Use accessible name of selected option.
337 LocalAccessible
* option
= SelectedOption();
338 if (option
) option
->Name(aValue
);
341 bool HTMLComboboxAccessible::HasPrimaryAction() const { return true; }
343 void HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
344 if (aIndex
!= HTMLComboboxAccessible::eAction_Click
) return;
346 nsComboboxControlFrame
* comboFrame
= do_QueryFrame(GetFrame());
347 if (!comboFrame
) return;
349 if (comboFrame
->IsDroppedDown()) {
350 aName
.AssignLiteral("close");
352 aName
.AssignLiteral("open");
356 bool HTMLComboboxAccessible::IsAcceptableChild(nsIContent
* aEl
) const {
360 ////////////////////////////////////////////////////////////////////////////////
361 // HTMLComboboxAccessible: Widgets
363 bool HTMLComboboxAccessible::IsWidget() const { return true; }
365 bool HTMLComboboxAccessible::IsActiveWidget() const {
366 return FocusMgr()->HasDOMFocus(mContent
);
369 bool HTMLComboboxAccessible::AreItemsOperable() const {
370 nsComboboxControlFrame
* comboboxFrame
= do_QueryFrame(GetFrame());
371 return comboboxFrame
&& comboboxFrame
->IsDroppedDown();
374 LocalAccessible
* HTMLComboboxAccessible::CurrentItem() const {
375 return AreItemsOperable() ? mListAccessible
->CurrentItem() : nullptr;
378 void HTMLComboboxAccessible::SetCurrentItem(const LocalAccessible
* aItem
) {
379 if (AreItemsOperable()) mListAccessible
->SetCurrentItem(aItem
);
382 ////////////////////////////////////////////////////////////////////////////////
383 // HTMLComboboxAccessible: protected
385 LocalAccessible
* HTMLComboboxAccessible::SelectedOption() const {
386 HTMLSelectElement
* select
= HTMLSelectElement::FromNode(mContent
);
387 int32_t selectedIndex
= select
->SelectedIndex();
389 if (selectedIndex
>= 0) {
390 HTMLOptionElement
* option
= select
->Item(selectedIndex
);
392 DocAccessible
* document
= Document();
393 if (document
) return document
->GetAccessible(option
);
400 ////////////////////////////////////////////////////////////////////////////////
401 // HTMLComboboxListAccessible
402 ////////////////////////////////////////////////////////////////////////////////
404 HTMLComboboxListAccessible::HTMLComboboxListAccessible(LocalAccessible
* aParent
,
405 nsIContent
* aContent
,
407 : HTMLSelectListAccessible(aContent
, aDoc
) {
408 mStateFlags
|= eSharedNode
;
411 ////////////////////////////////////////////////////////////////////////////////
412 // HTMLComboboxAccessible: LocalAccessible
414 role
HTMLComboboxListAccessible::NativeRole() const {
415 return roles::COMBOBOX_LIST
;
418 uint64_t HTMLComboboxListAccessible::NativeState() const {
419 // As a HTMLComboboxListAccessible we can have the following states:
420 // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
421 // Get focus status from base class
422 uint64_t state
= LocalAccessible::NativeState();
424 nsComboboxControlFrame
* comboFrame
= do_QueryFrame(mParent
->GetFrame());
425 if (comboFrame
&& comboFrame
->IsDroppedDown()) {
426 state
|= states::FLOATING
;
428 state
|= states::INVISIBLE
;
434 nsRect
HTMLComboboxListAccessible::RelativeBounds(
435 nsIFrame
** aBoundingFrame
) const {
436 *aBoundingFrame
= nullptr;
438 LocalAccessible
* comboAcc
= LocalParent();
439 if (!comboAcc
) return nsRect();
441 if (0 == (comboAcc
->State() & states::COLLAPSED
)) {
442 return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame
);
445 // Get the first option.
446 nsIContent
* content
= mContent
->GetFirstChild();
447 if (!content
) return nsRect();
449 nsIFrame
* frame
= content
->GetPrimaryFrame();
451 *aBoundingFrame
= nullptr;
455 *aBoundingFrame
= frame
->GetParent();
456 return (*aBoundingFrame
)->GetRect();
459 bool HTMLComboboxListAccessible::IsAcceptableChild(nsIContent
* aEl
) const {
460 return aEl
->IsAnyOfHTMLElements(nsGkAtoms::option
, nsGkAtoms::optgroup
);
463 ////////////////////////////////////////////////////////////////////////////////
464 // HTMLComboboxListAccessible: Widgets
466 bool HTMLComboboxListAccessible::IsActiveWidget() const {
467 return mParent
&& mParent
->IsActiveWidget();
470 bool HTMLComboboxListAccessible::AreItemsOperable() const {
471 return mParent
&& mParent
->AreItemsOperable();