Bug 1832284 - Fix rooting hazard in JSObject::swap r=sfink
[gecko.git] / accessible / html / HTMLSelectAccessible.cpp
blob0366ce19edb60273a00eeadadb97d134f89fa211
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 "nsEventShell.h"
13 #include "nsTextEquivUtils.h"
14 #include "Role.h"
15 #include "States.h"
17 #include "nsCOMPtr.h"
18 #include "mozilla/dom/HTMLOptionElement.h"
19 #include "mozilla/dom/HTMLOptGroupElement.h"
20 #include "mozilla/dom/HTMLSelectElement.h"
21 #include "nsComboboxControlFrame.h"
22 #include "nsContainerFrame.h"
23 #include "nsListControlFrame.h"
25 using namespace mozilla::a11y;
26 using namespace mozilla::dom;
28 ////////////////////////////////////////////////////////////////////////////////
29 // HTMLSelectListAccessible
30 ////////////////////////////////////////////////////////////////////////////////
32 HTMLSelectListAccessible::HTMLSelectListAccessible(nsIContent* aContent,
33 DocAccessible* aDoc)
34 : AccessibleWrap(aContent, aDoc) {
35 mGenericTypes |= eListControl | eSelect;
38 ////////////////////////////////////////////////////////////////////////////////
39 // HTMLSelectListAccessible: LocalAccessible public
41 uint64_t HTMLSelectListAccessible::NativeState() const {
42 uint64_t state = AccessibleWrap::NativeState();
43 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
44 state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
47 return state;
50 role HTMLSelectListAccessible::NativeRole() const { return roles::LISTBOX; }
52 ////////////////////////////////////////////////////////////////////////////////
53 // HTMLSelectListAccessible: SelectAccessible
55 bool HTMLSelectListAccessible::SelectAll() {
56 return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
57 ? AccessibleWrap::SelectAll()
58 : false;
61 bool HTMLSelectListAccessible::UnselectAll() {
62 return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
63 ? AccessibleWrap::UnselectAll()
64 : false;
67 ////////////////////////////////////////////////////////////////////////////////
68 // HTMLSelectListAccessible: Widgets
70 bool HTMLSelectListAccessible::IsWidget() const { return true; }
72 bool HTMLSelectListAccessible::IsActiveWidget() const {
73 return FocusMgr()->HasDOMFocus(mContent);
76 bool HTMLSelectListAccessible::AreItemsOperable() const { return true; }
78 LocalAccessible* HTMLSelectListAccessible::CurrentItem() const {
79 nsListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
80 if (listControlFrame) {
81 nsCOMPtr<nsIContent> activeOptionNode =
82 listControlFrame->GetCurrentOption();
83 if (activeOptionNode) {
84 DocAccessible* document = Document();
85 if (document) return document->GetAccessible(activeOptionNode);
88 return nullptr;
91 void HTMLSelectListAccessible::SetCurrentItem(const LocalAccessible* aItem) {
92 if (!aItem->GetContent()->IsElement()) return;
94 aItem->GetContent()->AsElement()->SetAttr(
95 kNameSpaceID_None, nsGkAtoms::selected, u"true"_ns, true);
98 bool HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const {
99 return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
102 bool HTMLSelectListAccessible::AttributeChangesState(nsAtom* aAttribute) {
103 return aAttribute == nsGkAtoms::multiple ||
104 LocalAccessible::AttributeChangesState(aAttribute);
107 ////////////////////////////////////////////////////////////////////////////////
108 // HTMLSelectOptionAccessible
109 ////////////////////////////////////////////////////////////////////////////////
111 HTMLSelectOptionAccessible::HTMLSelectOptionAccessible(nsIContent* aContent,
112 DocAccessible* aDoc)
113 : HyperTextAccessibleWrap(aContent, aDoc) {}
115 ////////////////////////////////////////////////////////////////////////////////
116 // HTMLSelectOptionAccessible: LocalAccessible public
118 role HTMLSelectOptionAccessible::NativeRole() const {
119 if (GetCombobox()) return roles::COMBOBOX_OPTION;
121 return roles::OPTION;
124 ENameValueFlag HTMLSelectOptionAccessible::NativeName(nsString& aName) const {
125 if (auto* option = dom::HTMLOptionElement::FromNode(mContent)) {
126 option->GetAttr(nsGkAtoms::label, aName);
127 if (!aName.IsEmpty()) {
128 return eNameOK;
130 option->GetText(aName);
131 return eNameFromSubtree;
133 if (auto* group = dom::HTMLOptGroupElement::FromNode(mContent)) {
134 group->GetLabel(aName);
135 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
137 MOZ_ASSERT_UNREACHABLE("What content do we have?");
138 return eNameFromSubtree;
141 void HTMLSelectOptionAccessible::DOMAttributeChanged(
142 int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType,
143 const nsAttrValue* aOldValue, uint64_t aOldState) {
144 HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute,
145 aModType, aOldValue, aOldState);
147 if (aAttribute == nsGkAtoms::label) {
148 dom::Element* elm = Elm();
149 if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) &&
150 !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) {
151 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
156 uint64_t HTMLSelectOptionAccessible::NativeState() const {
157 // As a HTMLSelectOptionAccessible we can have the following states:
158 // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
159 // Upcall to LocalAccessible, but skip HyperTextAccessible impl
160 // because we don't want EDITABLE or SELECTABLE_TEXT
161 uint64_t state = LocalAccessible::NativeState();
163 LocalAccessible* select = GetSelect();
164 if (!select) return state;
166 uint64_t selectState = select->State();
167 if (selectState & states::INVISIBLE) return state;
169 // Are we selected?
170 HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
171 bool selected = option && option->Selected();
172 if (selected) state |= states::SELECTED;
174 if (selectState & states::OFFSCREEN) {
175 state |= states::OFFSCREEN;
176 } else if (selectState & states::COLLAPSED) {
177 // <select> is COLLAPSED: add OFFSCREEN, if not the currently
178 // visible option
179 if (!selected) {
180 state |= states::OFFSCREEN;
181 // Ensure the invisible state is removed. Otherwise, group info will skip
182 // this option. Furthermore, this gets cached and this doesn't get
183 // invalidated even once the select is expanded.
184 state &= ~states::INVISIBLE;
185 } else {
186 // Clear offscreen and invisible for currently showing option
187 state &= ~(states::OFFSCREEN | states::INVISIBLE);
188 state |= selectState & states::OPAQUE1;
190 } else {
191 // XXX list frames are weird, don't rely on LocalAccessible's general
192 // visibility implementation unless they get reimplemented in layout
193 state &= ~states::OFFSCREEN;
194 // <select> is not collapsed: compare bounds to calculate OFFSCREEN
195 LocalAccessible* listAcc = LocalParent();
196 if (listAcc) {
197 LayoutDeviceIntRect optionRect = Bounds();
198 LayoutDeviceIntRect listRect = listAcc->Bounds();
199 if (optionRect.Y() < listRect.Y() ||
200 optionRect.YMost() > listRect.YMost()) {
201 state |= states::OFFSCREEN;
206 return state;
209 uint64_t HTMLSelectOptionAccessible::NativeInteractiveState() const {
210 return NativelyUnavailable() ? states::UNAVAILABLE
211 : states::FOCUSABLE | states::SELECTABLE;
214 nsRect HTMLSelectOptionAccessible::RelativeBounds(
215 nsIFrame** aBoundingFrame) const {
216 LocalAccessible* combobox = GetCombobox();
217 if (combobox && (combobox->State() & states::COLLAPSED)) {
218 return combobox->RelativeBounds(aBoundingFrame);
221 return HyperTextAccessibleWrap::RelativeBounds(aBoundingFrame);
224 void HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex,
225 nsAString& aName) {
226 if (aIndex == eAction_Select) aName.AssignLiteral("select");
229 bool HTMLSelectOptionAccessible::HasPrimaryAction() const { return true; }
231 void HTMLSelectOptionAccessible::SetSelected(bool aSelect) {
232 HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
233 if (option) option->SetSelected(aSelect);
236 ////////////////////////////////////////////////////////////////////////////////
237 // HTMLSelectOptionAccessible: Widgets
239 LocalAccessible* HTMLSelectOptionAccessible::ContainerWidget() const {
240 LocalAccessible* parent = LocalParent();
241 if (parent && parent->IsHTMLOptGroup()) {
242 parent = parent->LocalParent();
245 return parent && parent->IsListControl() ? parent : nullptr;
248 ////////////////////////////////////////////////////////////////////////////////
249 // HTMLSelectOptGroupAccessible
250 ////////////////////////////////////////////////////////////////////////////////
252 role HTMLSelectOptGroupAccessible::NativeRole() const {
253 return roles::GROUPING;
256 uint64_t HTMLSelectOptGroupAccessible::NativeInteractiveState() const {
257 return NativelyUnavailable() ? states::UNAVAILABLE : 0;
260 bool HTMLSelectOptGroupAccessible::IsAcceptableChild(nsIContent* aEl) const {
261 return aEl->IsCharacterData() || aEl->IsHTMLElement(nsGkAtoms::option);
264 bool HTMLSelectOptGroupAccessible::HasPrimaryAction() const { return false; }
266 ////////////////////////////////////////////////////////////////////////////////
267 // HTMLComboboxAccessible
268 ////////////////////////////////////////////////////////////////////////////////
270 HTMLComboboxAccessible::HTMLComboboxAccessible(nsIContent* aContent,
271 DocAccessible* aDoc)
272 : AccessibleWrap(aContent, aDoc) {
273 mType = eHTMLComboboxType;
274 mGenericTypes |= eCombobox;
275 mStateFlags |= eNoKidsFromDOM;
277 if ((nsComboboxControlFrame*)do_QueryFrame(GetFrame())) {
278 mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
279 Document()->BindToDocument(mListAccessible, nullptr);
280 AppendChild(mListAccessible);
284 ////////////////////////////////////////////////////////////////////////////////
285 // HTMLComboboxAccessible: LocalAccessible
287 role HTMLComboboxAccessible::NativeRole() const { return roles::COMBOBOX; }
289 bool HTMLComboboxAccessible::RemoveChild(LocalAccessible* aChild) {
290 MOZ_ASSERT(aChild == mListAccessible);
291 if (AccessibleWrap::RemoveChild(aChild)) {
292 mListAccessible = nullptr;
293 return true;
295 return false;
298 void HTMLComboboxAccessible::Shutdown() {
299 MOZ_ASSERT(!mDoc || mDoc->IsDefunct() || !mListAccessible);
300 if (mListAccessible) {
301 mListAccessible->Shutdown();
302 mListAccessible = nullptr;
305 AccessibleWrap::Shutdown();
308 uint64_t HTMLComboboxAccessible::NativeState() const {
309 // As a HTMLComboboxAccessible we can have the following states:
310 // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
311 // Get focus status from base class
312 uint64_t state = LocalAccessible::NativeState();
314 nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
315 if (comboFrame && comboFrame->IsDroppedDown()) {
316 state |= states::EXPANDED;
317 } else {
318 state |= states::COLLAPSED;
321 state |= states::HASPOPUP;
322 return state;
325 void HTMLComboboxAccessible::Description(nsString& aDescription) const {
326 aDescription.Truncate();
327 // First check to see if combo box itself has a description, perhaps through
328 // tooltip (title attribute) or via aria-describedby
329 LocalAccessible::Description(aDescription);
330 if (!aDescription.IsEmpty()) return;
332 // Otherwise use description of selected option.
333 LocalAccessible* option = SelectedOption();
334 if (option) option->Description(aDescription);
337 void HTMLComboboxAccessible::Value(nsString& aValue) const {
338 // Use accessible name of selected option.
339 LocalAccessible* option = SelectedOption();
340 if (option) option->Name(aValue);
343 bool HTMLComboboxAccessible::HasPrimaryAction() const { return true; }
345 void HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
346 if (aIndex != HTMLComboboxAccessible::eAction_Click) return;
348 nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
349 if (!comboFrame) return;
351 if (comboFrame->IsDroppedDown()) {
352 aName.AssignLiteral("close");
353 } else {
354 aName.AssignLiteral("open");
358 bool HTMLComboboxAccessible::IsAcceptableChild(nsIContent* aEl) const {
359 return false;
362 ////////////////////////////////////////////////////////////////////////////////
363 // HTMLComboboxAccessible: Widgets
365 bool HTMLComboboxAccessible::IsWidget() const { return true; }
367 bool HTMLComboboxAccessible::IsActiveWidget() const {
368 return FocusMgr()->HasDOMFocus(mContent);
371 bool HTMLComboboxAccessible::AreItemsOperable() const {
372 nsComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
373 return comboboxFrame && comboboxFrame->IsDroppedDown();
376 LocalAccessible* HTMLComboboxAccessible::CurrentItem() const {
377 return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr;
380 void HTMLComboboxAccessible::SetCurrentItem(const LocalAccessible* aItem) {
381 if (AreItemsOperable()) mListAccessible->SetCurrentItem(aItem);
384 ////////////////////////////////////////////////////////////////////////////////
385 // HTMLComboboxAccessible: protected
387 LocalAccessible* HTMLComboboxAccessible::SelectedOption() const {
388 HTMLSelectElement* select = HTMLSelectElement::FromNode(mContent);
389 int32_t selectedIndex = select->SelectedIndex();
391 if (selectedIndex >= 0) {
392 HTMLOptionElement* option = select->Item(selectedIndex);
393 if (option) {
394 DocAccessible* document = Document();
395 if (document) return document->GetAccessible(option);
399 return nullptr;
402 ////////////////////////////////////////////////////////////////////////////////
403 // HTMLComboboxListAccessible
404 ////////////////////////////////////////////////////////////////////////////////
406 HTMLComboboxListAccessible::HTMLComboboxListAccessible(LocalAccessible* aParent,
407 nsIContent* aContent,
408 DocAccessible* aDoc)
409 : HTMLSelectListAccessible(aContent, aDoc) {
410 mStateFlags |= eSharedNode;
413 ////////////////////////////////////////////////////////////////////////////////
414 // HTMLComboboxAccessible: LocalAccessible
416 role HTMLComboboxListAccessible::NativeRole() const {
417 return roles::COMBOBOX_LIST;
420 uint64_t HTMLComboboxListAccessible::NativeState() const {
421 // As a HTMLComboboxListAccessible we can have the following states:
422 // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
423 // Get focus status from base class
424 uint64_t state = LocalAccessible::NativeState();
426 nsComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame());
427 if (comboFrame && comboFrame->IsDroppedDown()) {
428 state |= states::FLOATING;
429 } else {
430 state |= states::INVISIBLE;
433 return state;
436 nsRect HTMLComboboxListAccessible::RelativeBounds(
437 nsIFrame** aBoundingFrame) const {
438 *aBoundingFrame = nullptr;
440 LocalAccessible* comboAcc = LocalParent();
441 if (!comboAcc) return nsRect();
443 if (0 == (comboAcc->State() & states::COLLAPSED)) {
444 return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame);
447 // Get the first option.
448 nsIContent* content = mContent->GetFirstChild();
449 if (!content) return nsRect();
451 nsIFrame* frame = content->GetPrimaryFrame();
452 if (!frame) {
453 *aBoundingFrame = nullptr;
454 return nsRect();
457 *aBoundingFrame = frame->GetParent();
458 return (*aBoundingFrame)->GetRect();
461 bool HTMLComboboxListAccessible::IsAcceptableChild(nsIContent* aEl) const {
462 return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
465 ////////////////////////////////////////////////////////////////////////////////
466 // HTMLComboboxListAccessible: Widgets
468 bool HTMLComboboxListAccessible::IsActiveWidget() const {
469 return mParent && mParent->IsActiveWidget();
472 bool HTMLComboboxListAccessible::AreItemsOperable() const {
473 return mParent && mParent->AreItemsOperable();