no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / accessible / html / HTMLSelectAccessible.cpp
blobf8fb4180c7607ca5e346e1fcb6963d15f871d91e
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"
13 #include "States.h"
15 #include "nsCOMPtr.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,
31 DocAccessible* aDoc)
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;
45 return state;
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()
56 : false;
59 bool HTMLSelectListAccessible::UnselectAll() {
60 return mContent->AsElement()->HasAttr(nsGkAtoms::multiple)
61 ? AccessibleWrap::UnselectAll()
62 : false;
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);
86 return nullptr;
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,
110 DocAccessible* aDoc)
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()) {
126 return eNameOK;
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;
167 // Are we selected?
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
176 // visible option
177 if (!selected) {
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;
183 } else {
184 // Clear offscreen and invisible for currently showing option
185 state &= ~(states::OFFSCREEN | states::INVISIBLE);
186 state |= selectState & states::OPAQUE1;
188 } else {
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();
194 if (listAcc) {
195 LayoutDeviceIntRect optionRect = Bounds();
196 LayoutDeviceIntRect listRect = listAcc->Bounds();
197 if (optionRect.Y() < listRect.Y() ||
198 optionRect.YMost() > listRect.YMost()) {
199 state |= states::OFFSCREEN;
204 return state;
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,
223 nsAString& aName) {
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,
269 DocAccessible* aDoc)
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;
291 return true;
293 return false;
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;
315 } else {
316 state |= states::COLLAPSED;
319 state |= states::HASPOPUP;
320 return state;
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");
351 } else {
352 aName.AssignLiteral("open");
356 bool HTMLComboboxAccessible::IsAcceptableChild(nsIContent* aEl) const {
357 return false;
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);
391 if (option) {
392 DocAccessible* document = Document();
393 if (document) return document->GetAccessible(option);
397 return nullptr;
400 ////////////////////////////////////////////////////////////////////////////////
401 // HTMLComboboxListAccessible
402 ////////////////////////////////////////////////////////////////////////////////
404 HTMLComboboxListAccessible::HTMLComboboxListAccessible(LocalAccessible* aParent,
405 nsIContent* aContent,
406 DocAccessible* aDoc)
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;
427 } else {
428 state |= states::INVISIBLE;
431 return state;
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();
450 if (!frame) {
451 *aBoundingFrame = nullptr;
452 return nsRect();
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();