Bug 1921551 - React to sync sign in flow correctly r=android-reviewers,matt-tighe
[gecko.git] / accessible / html / HTMLSelectAccessible.cpp
blob02d449df36dccd92fca2308706bef805e17fb6c5
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 "DocAccessible-inl.h"
10 #include "nsAccessibilityService.h"
11 #include "nsAccUtils.h"
12 #include "DocAccessible.h"
13 #include "mozilla/a11y/Role.h"
14 #include "States.h"
16 #include "nsCOMPtr.h"
17 #include "mozilla/dom/HTMLOptionElement.h"
18 #include "mozilla/dom/HTMLOptGroupElement.h"
19 #include "mozilla/dom/HTMLSelectElement.h"
20 #include "nsComboboxControlFrame.h"
21 #include "nsContainerFrame.h"
22 #include "nsListControlFrame.h"
24 using namespace mozilla::a11y;
25 using namespace mozilla::dom;
27 ////////////////////////////////////////////////////////////////////////////////
28 // HTMLSelectListAccessible
29 ////////////////////////////////////////////////////////////////////////////////
31 HTMLSelectListAccessible::HTMLSelectListAccessible(nsIContent* aContent,
32 DocAccessible* aDoc)
33 : AccessibleWrap(aContent, aDoc) {
34 mGenericTypes |= eListControl | eSelect;
37 ////////////////////////////////////////////////////////////////////////////////
38 // HTMLSelectListAccessible: LocalAccessible public
40 uint64_t HTMLSelectListAccessible::NativeState() const {
41 uint64_t state = AccessibleWrap::NativeState();
42 if (mContent->AsElement()->HasAttr(nsGkAtoms::multiple)) {
43 state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
46 return state;
49 role HTMLSelectListAccessible::NativeRole() const { return roles::LISTBOX; }
51 ////////////////////////////////////////////////////////////////////////////////
52 // HTMLSelectListAccessible: SelectAccessible
54 bool HTMLSelectListAccessible::SelectAll() {
55 return mContent->AsElement()->HasAttr(nsGkAtoms::multiple)
56 ? AccessibleWrap::SelectAll()
57 : false;
60 bool HTMLSelectListAccessible::UnselectAll() {
61 return mContent->AsElement()->HasAttr(nsGkAtoms::multiple)
62 ? AccessibleWrap::UnselectAll()
63 : false;
66 ////////////////////////////////////////////////////////////////////////////////
67 // HTMLSelectListAccessible: Widgets
69 bool HTMLSelectListAccessible::IsWidget() const { return true; }
71 bool HTMLSelectListAccessible::IsActiveWidget() const {
72 return FocusMgr()->HasDOMFocus(mContent);
75 bool HTMLSelectListAccessible::AreItemsOperable() const { return true; }
77 LocalAccessible* HTMLSelectListAccessible::CurrentItem() const {
78 nsListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
79 if (listControlFrame) {
80 nsCOMPtr<nsIContent> activeOptionNode =
81 listControlFrame->GetCurrentOption();
82 if (activeOptionNode) {
83 DocAccessible* document = Document();
84 if (document) return document->GetAccessible(activeOptionNode);
87 return nullptr;
90 void HTMLSelectListAccessible::SetCurrentItem(const LocalAccessible* aItem) {
91 if (!aItem->GetContent()->IsElement()) return;
93 aItem->GetContent()->AsElement()->SetAttr(
94 kNameSpaceID_None, nsGkAtoms::selected, u"true"_ns, true);
97 bool HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const {
98 return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
101 bool HTMLSelectListAccessible::AttributeChangesState(nsAtom* aAttribute) {
102 return aAttribute == nsGkAtoms::multiple ||
103 LocalAccessible::AttributeChangesState(aAttribute);
106 ////////////////////////////////////////////////////////////////////////////////
107 // HTMLSelectOptionAccessible
108 ////////////////////////////////////////////////////////////////////////////////
110 HTMLSelectOptionAccessible::HTMLSelectOptionAccessible(nsIContent* aContent,
111 DocAccessible* aDoc)
112 : HyperTextAccessible(aContent, aDoc) {}
114 ////////////////////////////////////////////////////////////////////////////////
115 // HTMLSelectOptionAccessible: LocalAccessible public
117 role HTMLSelectOptionAccessible::NativeRole() const {
118 if (GetCombobox()) return roles::COMBOBOX_OPTION;
120 return roles::OPTION;
123 ENameValueFlag HTMLSelectOptionAccessible::NativeName(nsString& aName) const {
124 if (auto* option = dom::HTMLOptionElement::FromNode(mContent)) {
125 option->GetAttr(nsGkAtoms::label, aName);
126 if (!aName.IsEmpty()) {
127 return eNameOK;
129 option->GetText(aName);
130 return eNameFromSubtree;
132 if (auto* group = dom::HTMLOptGroupElement::FromNode(mContent)) {
133 group->GetLabel(aName);
134 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
136 MOZ_ASSERT_UNREACHABLE("What content do we have?");
137 return eNameFromSubtree;
140 void HTMLSelectOptionAccessible::DOMAttributeChanged(
141 int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType,
142 const nsAttrValue* aOldValue, uint64_t aOldState) {
143 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
144 aOldValue, aOldState);
146 if (aAttribute == nsGkAtoms::label) {
147 dom::Element* elm = Elm();
148 if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) &&
149 !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) {
150 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
155 uint64_t HTMLSelectOptionAccessible::NativeState() const {
156 // As a HTMLSelectOptionAccessible we can have the following states:
157 // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
158 // Upcall to LocalAccessible, but skip HyperTextAccessible impl
159 // because we don't want EDITABLE or SELECTABLE_TEXT
160 uint64_t state = LocalAccessible::NativeState();
162 LocalAccessible* select = GetSelect();
163 if (!select) return state;
165 uint64_t selectState = select->State();
166 if (selectState & states::INVISIBLE) return state;
168 // Are we selected?
169 HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
170 bool selected = option && option->Selected();
171 if (selected) state |= states::SELECTED;
173 if (selectState & states::OFFSCREEN) {
174 state |= states::OFFSCREEN;
175 } else if (selectState & states::COLLAPSED) {
176 // <select> is COLLAPSED: add OFFSCREEN, if not the currently
177 // visible option
178 if (!selected) {
179 state |= states::OFFSCREEN;
180 // Ensure the invisible state is removed. Otherwise, group info will skip
181 // this option. Furthermore, this gets cached and this doesn't get
182 // invalidated even once the select is expanded.
183 state &= ~states::INVISIBLE;
184 } else {
185 // Clear offscreen and invisible for currently showing option
186 state &= ~(states::OFFSCREEN | states::INVISIBLE);
187 state |= selectState & states::OPAQUE1;
189 } else {
190 // XXX list frames are weird, don't rely on LocalAccessible's general
191 // visibility implementation unless they get reimplemented in layout
192 state &= ~states::OFFSCREEN;
193 // <select> is not collapsed: compare bounds to calculate OFFSCREEN
194 LocalAccessible* listAcc = LocalParent();
195 if (listAcc) {
196 LayoutDeviceIntRect optionRect = Bounds();
197 LayoutDeviceIntRect listRect = listAcc->Bounds();
198 if (optionRect.Y() < listRect.Y() ||
199 optionRect.YMost() > listRect.YMost()) {
200 state |= states::OFFSCREEN;
205 return state;
208 uint64_t HTMLSelectOptionAccessible::NativeInteractiveState() const {
209 return NativelyUnavailable() ? states::UNAVAILABLE
210 : states::FOCUSABLE | states::SELECTABLE;
213 nsRect HTMLSelectOptionAccessible::RelativeBounds(
214 nsIFrame** aBoundingFrame) const {
215 LocalAccessible* combobox = GetCombobox();
216 if (combobox && (combobox->State() & states::COLLAPSED)) {
217 return combobox->RelativeBounds(aBoundingFrame);
220 return HyperTextAccessible::RelativeBounds(aBoundingFrame);
223 void HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex,
224 nsAString& aName) {
225 if (aIndex == eAction_Select) aName.AssignLiteral("select");
228 bool HTMLSelectOptionAccessible::HasPrimaryAction() const { return true; }
230 void HTMLSelectOptionAccessible::SetSelected(bool aSelect) {
231 HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
232 if (option) option->SetSelected(aSelect);
235 ////////////////////////////////////////////////////////////////////////////////
236 // HTMLSelectOptionAccessible: Widgets
238 LocalAccessible* HTMLSelectOptionAccessible::ContainerWidget() const {
239 LocalAccessible* parent = LocalParent();
240 if (parent && parent->IsHTMLOptGroup()) {
241 parent = parent->LocalParent();
244 return parent && parent->IsListControl() ? parent : nullptr;
247 ////////////////////////////////////////////////////////////////////////////////
248 // HTMLSelectOptGroupAccessible
249 ////////////////////////////////////////////////////////////////////////////////
251 role HTMLSelectOptGroupAccessible::NativeRole() const {
252 return roles::GROUPING;
255 uint64_t HTMLSelectOptGroupAccessible::NativeInteractiveState() const {
256 return NativelyUnavailable() ? states::UNAVAILABLE : 0;
259 bool HTMLSelectOptGroupAccessible::IsAcceptableChild(nsIContent* aEl) const {
260 return aEl->IsCharacterData() || aEl->IsHTMLElement(nsGkAtoms::option);
263 bool HTMLSelectOptGroupAccessible::HasPrimaryAction() const { return false; }
265 ////////////////////////////////////////////////////////////////////////////////
266 // HTMLComboboxAccessible
267 ////////////////////////////////////////////////////////////////////////////////
269 HTMLComboboxAccessible::HTMLComboboxAccessible(nsIContent* aContent,
270 DocAccessible* aDoc)
271 : AccessibleWrap(aContent, aDoc) {
272 mType = eHTMLComboboxType;
273 mGenericTypes |= eCombobox;
274 mStateFlags |= eNoKidsFromDOM;
276 if ((nsComboboxControlFrame*)do_QueryFrame(GetFrame())) {
277 mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
278 Document()->BindToDocument(mListAccessible, nullptr);
279 AppendChild(mListAccessible);
283 ////////////////////////////////////////////////////////////////////////////////
284 // HTMLComboboxAccessible: LocalAccessible
286 role HTMLComboboxAccessible::NativeRole() const { return roles::COMBOBOX; }
288 bool HTMLComboboxAccessible::RemoveChild(LocalAccessible* aChild) {
289 MOZ_ASSERT(aChild == mListAccessible);
290 if (AccessibleWrap::RemoveChild(aChild)) {
291 mListAccessible = nullptr;
292 return true;
294 return false;
297 void HTMLComboboxAccessible::Shutdown() {
298 MOZ_ASSERT(!mDoc || mDoc->IsDefunct() || !mListAccessible);
299 if (mListAccessible) {
300 mListAccessible->Shutdown();
301 mListAccessible = nullptr;
304 AccessibleWrap::Shutdown();
307 uint64_t HTMLComboboxAccessible::NativeState() const {
308 // As a HTMLComboboxAccessible we can have the following states:
309 // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
310 // Get focus status from base class
311 uint64_t state = LocalAccessible::NativeState();
313 nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
314 if (comboFrame && comboFrame->IsDroppedDown()) {
315 state |= states::EXPANDED;
316 } else {
317 state |= states::COLLAPSED;
320 state |= states::HASPOPUP;
321 return state;
324 void HTMLComboboxAccessible::Description(nsString& aDescription) const {
325 aDescription.Truncate();
326 // First check to see if combo box itself has a description, perhaps through
327 // tooltip (title attribute) or via aria-describedby
328 LocalAccessible::Description(aDescription);
329 if (!aDescription.IsEmpty()) return;
331 // Otherwise use description of selected option.
332 LocalAccessible* option = SelectedOption();
333 if (option) option->Description(aDescription);
336 void HTMLComboboxAccessible::Value(nsString& aValue) const {
337 // Use accessible name of selected option.
338 LocalAccessible* option = SelectedOption();
339 if (option) option->Name(aValue);
342 bool HTMLComboboxAccessible::HasPrimaryAction() const { return true; }
344 void HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
345 if (aIndex != HTMLComboboxAccessible::eAction_Click) return;
347 nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
348 if (!comboFrame) return;
350 if (comboFrame->IsDroppedDown()) {
351 aName.AssignLiteral("close");
352 } else {
353 aName.AssignLiteral("open");
357 bool HTMLComboboxAccessible::IsAcceptableChild(nsIContent* aEl) const {
358 return false;
361 ////////////////////////////////////////////////////////////////////////////////
362 // HTMLComboboxAccessible: Widgets
364 bool HTMLComboboxAccessible::IsWidget() const { return true; }
366 bool HTMLComboboxAccessible::IsActiveWidget() const {
367 return FocusMgr()->HasDOMFocus(mContent);
370 bool HTMLComboboxAccessible::AreItemsOperable() const {
371 nsComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
372 return comboboxFrame && comboboxFrame->IsDroppedDown();
375 LocalAccessible* HTMLComboboxAccessible::CurrentItem() const {
376 return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr;
379 void HTMLComboboxAccessible::SetCurrentItem(const LocalAccessible* aItem) {
380 if (AreItemsOperable()) mListAccessible->SetCurrentItem(aItem);
383 ////////////////////////////////////////////////////////////////////////////////
384 // HTMLComboboxAccessible: protected
386 LocalAccessible* HTMLComboboxAccessible::SelectedOption() const {
387 HTMLSelectElement* select = HTMLSelectElement::FromNode(mContent);
388 int32_t selectedIndex = select->SelectedIndex();
390 if (selectedIndex >= 0) {
391 HTMLOptionElement* option = select->Item(selectedIndex);
392 if (option) {
393 DocAccessible* document = Document();
394 if (document) return document->GetAccessible(option);
398 return nullptr;
401 ////////////////////////////////////////////////////////////////////////////////
402 // HTMLComboboxListAccessible
403 ////////////////////////////////////////////////////////////////////////////////
405 HTMLComboboxListAccessible::HTMLComboboxListAccessible(LocalAccessible* aParent,
406 nsIContent* aContent,
407 DocAccessible* aDoc)
408 : HTMLSelectListAccessible(aContent, aDoc) {
409 mStateFlags |= eSharedNode;
412 ////////////////////////////////////////////////////////////////////////////////
413 // HTMLComboboxAccessible: LocalAccessible
415 role HTMLComboboxListAccessible::NativeRole() const {
416 return roles::COMBOBOX_LIST;
419 uint64_t HTMLComboboxListAccessible::NativeState() const {
420 // As a HTMLComboboxListAccessible we can have the following states:
421 // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
422 // Get focus status from base class
423 uint64_t state = LocalAccessible::NativeState();
425 nsComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame());
426 if (comboFrame && comboFrame->IsDroppedDown()) {
427 state |= states::FLOATING;
428 } else {
429 state |= states::INVISIBLE;
432 return state;
435 nsRect HTMLComboboxListAccessible::RelativeBounds(
436 nsIFrame** aBoundingFrame) const {
437 *aBoundingFrame = nullptr;
439 LocalAccessible* comboAcc = LocalParent();
440 if (!comboAcc) return nsRect();
442 if (0 == (comboAcc->State() & states::COLLAPSED)) {
443 return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame);
446 // Get the first option.
447 nsIContent* content = mContent->GetFirstChild();
448 if (!content) return nsRect();
450 nsIFrame* frame = content->GetPrimaryFrame();
451 if (!frame) {
452 *aBoundingFrame = nullptr;
453 return nsRect();
456 *aBoundingFrame = frame->GetParent();
457 return (*aBoundingFrame)->GetRect();
460 bool HTMLComboboxListAccessible::IsAcceptableChild(nsIContent* aEl) const {
461 return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
464 ////////////////////////////////////////////////////////////////////////////////
465 // HTMLComboboxListAccessible: Widgets
467 bool HTMLComboboxListAccessible::IsActiveWidget() const {
468 return mParent && mParent->IsActiveWidget();
471 bool HTMLComboboxListAccessible::AreItemsOperable() const {
472 return mParent && mParent->AreItemsOperable();