Backed out changeset 0f0edda611cf (bug 1874454) for causing bp-nu bustages in CocoaHe...
[gecko.git] / accessible / html / HTMLFormControlAccessible.cpp
blob05caf8d9a1ac0eb579ffac831f8ec1db683c41ad
1 /* -*- Mode: C++; tab-width: 2; 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 "HTMLFormControlAccessible.h"
8 #include "CacheConstants.h"
9 #include "DocAccessible-inl.h"
10 #include "LocalAccessible-inl.h"
11 #include "nsAccUtils.h"
12 #include "nsEventShell.h"
13 #include "nsTextEquivUtils.h"
14 #include "Relation.h"
15 #include "mozilla/a11y/Role.h"
16 #include "States.h"
17 #include "TextLeafAccessible.h"
19 #include "nsContentList.h"
20 #include "mozilla/dom/HTMLInputElement.h"
21 #include "mozilla/dom/HTMLTextAreaElement.h"
22 #include "mozilla/dom/HTMLFormControlsCollection.h"
23 #include "nsIFormControl.h"
25 #include "mozilla/FloatingPoint.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/TextEditor.h"
29 using namespace mozilla;
30 using namespace mozilla::dom;
31 using namespace mozilla::a11y;
33 ////////////////////////////////////////////////////////////////////////////////
34 // HTMLFormAccessible
35 ////////////////////////////////////////////////////////////////////////////////
37 role HTMLFormAccessible::NativeRole() const {
38 nsAutoString name;
39 const_cast<HTMLFormAccessible*>(this)->Name(name);
40 return name.IsEmpty() ? roles::FORM : roles::FORM_LANDMARK;
43 void HTMLFormAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
44 nsAtom* aAttribute,
45 int32_t aModType,
46 const nsAttrValue* aOldValue,
47 uint64_t aOldState) {
48 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
49 aOldValue, aOldState);
50 if (aAttribute == nsGkAtoms::autocomplete) {
51 dom::HTMLFormElement* formEl = dom::HTMLFormElement::FromNode(mContent);
53 HTMLFormControlsCollection* controls = formEl->Elements();
54 uint32_t length = controls->Length();
55 for (uint32_t i = 0; i < length; i++) {
56 if (LocalAccessible* acc = mDoc->GetAccessible(controls->Item(i))) {
57 if (acc->IsTextField() && !acc->IsPassword()) {
58 if (!acc->Elm()->HasAttr(nsGkAtoms::list_) &&
59 !acc->Elm()->AttrValueIs(kNameSpaceID_None,
60 nsGkAtoms::autocomplete, nsGkAtoms::OFF,
61 eIgnoreCase)) {
62 RefPtr<AccEvent> stateChangeEvent =
63 new AccStateChangeEvent(acc, states::SUPPORTS_AUTOCOMPLETION);
64 mDoc->FireDelayedEvent(stateChangeEvent);
72 ////////////////////////////////////////////////////////////////////////////////
73 // HTMLRadioButtonAccessible
74 ////////////////////////////////////////////////////////////////////////////////
76 uint64_t HTMLRadioButtonAccessible::NativeState() const {
77 uint64_t state = AccessibleWrap::NativeState();
79 state |= states::CHECKABLE;
81 HTMLInputElement* input = HTMLInputElement::FromNode(mContent);
82 if (input && input->Checked()) state |= states::CHECKED;
84 return state;
87 void HTMLRadioButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet,
88 int32_t* aSetSize) {
89 Unused << ComputeGroupAttributes(aPosInSet, aSetSize);
92 void HTMLRadioButtonAccessible::DOMAttributeChanged(
93 int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType,
94 const nsAttrValue* aOldValue, uint64_t aOldState) {
95 if (aAttribute == nsGkAtoms::name) {
96 // If our name changed, it's possible our MEMBER_OF relation
97 // also changed. Push a cache update for Relations.
98 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
99 } else {
100 // Otherwise, handle this attribute change the way our parent
101 // class wants us to handle it.
102 RadioButtonAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute,
103 aModType, aOldValue, aOldState);
107 Relation HTMLRadioButtonAccessible::ComputeGroupAttributes(
108 int32_t* aPosInSet, int32_t* aSetSize) const {
109 Relation rel = Relation();
110 int32_t namespaceId = mContent->NodeInfo()->NamespaceID();
111 nsAutoString tagName;
112 mContent->NodeInfo()->GetName(tagName);
114 nsAutoString type;
115 mContent->AsElement()->GetAttr(nsGkAtoms::type, type);
116 nsAutoString name;
117 mContent->AsElement()->GetAttr(nsGkAtoms::name, name);
119 RefPtr<nsContentList> inputElms;
121 nsCOMPtr<nsIFormControl> formControlNode(do_QueryInterface(mContent));
122 if (dom::Element* formElm = formControlNode->GetForm()) {
123 inputElms = NS_GetContentList(formElm, namespaceId, tagName);
124 } else {
125 inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName);
127 NS_ENSURE_TRUE(inputElms, rel);
129 uint32_t inputCount = inputElms->Length(false);
131 // Compute posinset and setsize.
132 int32_t indexOf = 0;
133 int32_t count = 0;
135 for (uint32_t index = 0; index < inputCount; index++) {
136 nsIContent* inputElm = inputElms->Item(index, false);
137 if (inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
138 type, eCaseMatters) &&
139 inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
140 name, eCaseMatters) &&
141 mDoc->HasAccessible(inputElm)) {
142 count++;
143 rel.AppendTarget(mDoc->GetAccessible(inputElm));
144 if (inputElm == mContent) indexOf = count;
148 *aPosInSet = indexOf;
149 *aSetSize = count;
150 return rel;
153 Relation HTMLRadioButtonAccessible::RelationByType(RelationType aType) const {
154 if (aType == RelationType::MEMBER_OF) {
155 int32_t unusedPos, unusedSetSize;
156 return ComputeGroupAttributes(&unusedPos, &unusedSetSize);
159 return LocalAccessible::RelationByType(aType);
162 ////////////////////////////////////////////////////////////////////////////////
163 // HTMLButtonAccessible
164 ////////////////////////////////////////////////////////////////////////////////
166 HTMLButtonAccessible::HTMLButtonAccessible(nsIContent* aContent,
167 DocAccessible* aDoc)
168 : HyperTextAccessible(aContent, aDoc) {
169 mGenericTypes |= eButton;
172 bool HTMLButtonAccessible::HasPrimaryAction() const { return true; }
174 void HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
175 if (aIndex == eAction_Click) aName.AssignLiteral("press");
178 uint64_t HTMLButtonAccessible::NativeState() const {
179 uint64_t state = HyperTextAccessible::NativeState();
181 dom::Element* elm = Elm();
182 if (auto* popover = elm->GetEffectivePopoverTargetElement()) {
183 if (popover->IsPopoverOpen()) {
184 state |= states::EXPANDED;
185 } else {
186 state |= states::COLLAPSED;
190 ElementState elmState = mContent->AsElement()->State();
191 if (elmState.HasState(ElementState::DEFAULT)) state |= states::DEFAULT;
193 return state;
196 role HTMLButtonAccessible::NativeRole() const { return roles::PUSHBUTTON; }
198 ENameValueFlag HTMLButtonAccessible::NativeName(nsString& aName) const {
199 // No need to check @value attribute for buttons since this attribute results
200 // in native anonymous text node and the name is calculated from subtree.
201 // The same magic works for @alt and @value attributes in case of type="image"
202 // element that has no valid @src (note if input@type="image" has an image
203 // then neither @alt nor @value attributes are used to generate a visual label
204 // and thus we need to obtain the accessible name directly from attribute
205 // value). Also the same algorithm works in case of default labels for
206 // type="submit"/"reset"/"image" elements.
208 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
209 if (!aName.IsEmpty() || !mContent->IsHTMLElement(nsGkAtoms::input) ||
210 !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
211 nsGkAtoms::image, eCaseMatters)) {
212 return nameFlag;
215 if (!mContent->AsElement()->GetAttr(nsGkAtoms::alt, aName)) {
216 mContent->AsElement()->GetAttr(nsGkAtoms::value, aName);
219 aName.CompressWhitespace();
220 return eNameOK;
223 void HTMLButtonAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
224 nsAtom* aAttribute,
225 int32_t aModType,
226 const nsAttrValue* aOldValue,
227 uint64_t aOldState) {
228 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
229 aOldValue, aOldState);
231 if (aAttribute == nsGkAtoms::value) {
232 dom::Element* elm = Elm();
233 if (elm->IsHTMLElement(nsGkAtoms::input) ||
234 (elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::image,
235 eCaseMatters) &&
236 !elm->HasAttr(nsGkAtoms::alt))) {
237 if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) &&
238 !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) {
239 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
245 ////////////////////////////////////////////////////////////////////////////////
246 // HTMLButtonAccessible: Widgets
248 bool HTMLButtonAccessible::IsWidget() const { return true; }
250 ////////////////////////////////////////////////////////////////////////////////
251 // HTMLTextFieldAccessible
252 ////////////////////////////////////////////////////////////////////////////////
254 HTMLTextFieldAccessible::HTMLTextFieldAccessible(nsIContent* aContent,
255 DocAccessible* aDoc)
256 : HyperTextAccessible(aContent, aDoc) {
257 mType = mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
258 nsGkAtoms::password, eIgnoreCase)
259 ? eHTMLTextPasswordFieldType
260 : eHTMLTextFieldType;
263 role HTMLTextFieldAccessible::NativeRole() const {
264 if (mType == eHTMLTextPasswordFieldType) {
265 return roles::PASSWORD_TEXT;
267 if (mContent->AsElement()->HasAttr(nsGkAtoms::list_)) {
268 return roles::EDITCOMBOBOX;
270 return roles::ENTRY;
273 already_AddRefed<AccAttributes> HTMLTextFieldAccessible::NativeAttributes() {
274 RefPtr<AccAttributes> attributes = HyperTextAccessible::NativeAttributes();
276 // Expose type for text input elements as it gives some useful context,
277 // especially for mobile.
278 if (const nsAttrValue* attr =
279 mContent->AsElement()->GetParsedAttr(nsGkAtoms::type)) {
280 RefPtr<nsAtom> inputType = attr->GetAsAtom();
281 if (inputType) {
282 if (!ARIARoleMap() && inputType == nsGkAtoms::search) {
283 attributes->SetAttribute(nsGkAtoms::xmlroles, nsGkAtoms::searchbox);
285 attributes->SetAttribute(nsGkAtoms::textInputType, inputType);
288 // If this element has the placeholder attribute set,
289 // and if that is not identical to the name, expose it as an object attribute.
290 nsString placeholderText;
291 if (mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, placeholderText)) {
292 nsAutoString name;
293 const_cast<HTMLTextFieldAccessible*>(this)->Name(name);
294 if (!name.Equals(placeholderText)) {
295 attributes->SetAttribute(nsGkAtoms::placeholder,
296 std::move(placeholderText));
300 return attributes.forget();
303 ENameValueFlag HTMLTextFieldAccessible::NativeName(nsString& aName) const {
304 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
305 if (!aName.IsEmpty()) return nameFlag;
307 if (!aName.IsEmpty()) return eNameOK;
309 // text inputs and textareas might have useful placeholder text
310 mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, aName);
311 return eNameOK;
314 void HTMLTextFieldAccessible::Value(nsString& aValue) const {
315 aValue.Truncate();
316 if (NativeState() & states::PROTECTED) { // Don't return password text!
317 return;
320 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(mContent);
321 if (textArea) {
322 textArea->GetValue(aValue);
323 return;
326 HTMLInputElement* input = HTMLInputElement::FromNode(mContent);
327 if (input) {
328 // Pass NonSystem as the caller type, to be safe. We don't expect to have a
329 // file input here.
330 input->GetValue(aValue, CallerType::NonSystem);
334 bool HTMLTextFieldAccessible::AttributeChangesState(nsAtom* aAttribute) {
335 if (aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::list_ ||
336 aAttribute == nsGkAtoms::autocomplete) {
337 return true;
340 return LocalAccessible::AttributeChangesState(aAttribute);
343 void HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const {
344 HyperTextAccessible::ApplyARIAState(aState);
345 aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
348 uint64_t HTMLTextFieldAccessible::NativeState() const {
349 uint64_t state = HyperTextAccessible::NativeState();
351 // Text fields are always editable, even if they are also read only or
352 // disabled.
353 state |= states::EDITABLE;
355 // can be focusable, focused, protected. readonly, unavailable, selected
356 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
357 nsGkAtoms::password, eIgnoreCase)) {
358 state |= states::PROTECTED;
361 if (mContent->AsElement()->HasAttr(nsGkAtoms::readonly)) {
362 state |= states::READONLY;
365 // Is it an <input> or a <textarea> ?
366 HTMLInputElement* input = HTMLInputElement::FromNode(mContent);
367 state |= input && input->IsSingleLineTextControl() ? states::SINGLE_LINE
368 : states::MULTI_LINE;
370 if (state & (states::PROTECTED | states::MULTI_LINE | states::READONLY |
371 states::UNAVAILABLE)) {
372 return state;
375 // Expose autocomplete state if it has associated autocomplete list.
376 if (mContent->AsElement()->HasAttr(nsGkAtoms::list_)) {
377 return state | states::SUPPORTS_AUTOCOMPLETION | states::HASPOPUP;
380 if (Preferences::GetBool("browser.formfill.enable")) {
381 // Check to see if autocompletion is allowed on this input. We don't expose
382 // it for password fields even though the entire password can be remembered
383 // for a page if the user asks it to be. However, the kind of autocomplete
384 // we're talking here is based on what the user types, where a popup of
385 // possible choices comes up.
386 nsAutoString autocomplete;
387 mContent->AsElement()->GetAttr(nsGkAtoms::autocomplete, autocomplete);
389 if (!autocomplete.LowerCaseEqualsLiteral("off")) {
390 Element* formElement = input->GetForm();
391 if (formElement) {
392 formElement->GetAttr(nsGkAtoms::autocomplete, autocomplete);
395 if (!formElement || !autocomplete.LowerCaseEqualsLiteral("off")) {
396 state |= states::SUPPORTS_AUTOCOMPLETION;
401 return state;
404 bool HTMLTextFieldAccessible::HasPrimaryAction() const { return true; }
406 void HTMLTextFieldAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
407 if (aIndex == eAction_Click) aName.AssignLiteral("activate");
410 bool HTMLTextFieldAccessible::DoAction(uint8_t aIndex) const {
411 if (aIndex != 0) return false;
413 if (FocusMgr()->IsFocused(this)) {
414 // This already has focus, so TakeFocus()will do nothing. However, the user
415 // might be activating this element because they dismissed a touch keyboard
416 // and want to bring it back.
417 DoCommand();
418 } else {
419 TakeFocus();
421 return true;
424 already_AddRefed<EditorBase> HTMLTextFieldAccessible::GetEditor() const {
425 RefPtr<TextControlElement> textControlElement =
426 TextControlElement::FromNodeOrNull(mContent);
427 if (!textControlElement) {
428 return nullptr;
430 RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor();
431 return textEditor.forget();
434 void HTMLTextFieldAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
435 nsAtom* aAttribute,
436 int32_t aModType,
437 const nsAttrValue* aOldValue,
438 uint64_t aOldState) {
439 if (aAttribute == nsGkAtoms::placeholder) {
440 mDoc->QueueCacheUpdate(this, CacheDomain::NameAndDescription);
441 return;
443 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
444 aOldValue, aOldState);
447 ////////////////////////////////////////////////////////////////////////////////
448 // HTMLTextFieldAccessible: Widgets
450 bool HTMLTextFieldAccessible::IsWidget() const { return true; }
452 LocalAccessible* HTMLTextFieldAccessible::ContainerWidget() const {
453 return nullptr;
456 ////////////////////////////////////////////////////////////////////////////////
457 // HTMLFileInputAccessible
458 ////////////////////////////////////////////////////////////////////////////////
460 HTMLFileInputAccessible::HTMLFileInputAccessible(nsIContent* aContent,
461 DocAccessible* aDoc)
462 : HyperTextAccessible(aContent, aDoc) {
463 mType = eHTMLFileInputType;
464 mGenericTypes |= eButton;
467 role HTMLFileInputAccessible::NativeRole() const { return roles::PUSHBUTTON; }
469 bool HTMLFileInputAccessible::IsAcceptableChild(nsIContent* aEl) const {
470 // File inputs are rendered using native anonymous children. However, we
471 // want to expose this as a button Accessible so that clients can pick up the
472 // name and description from the button they activate, rather than a
473 // container. We still expose the text leaf descendants so we can get the
474 // name of the Browse button and the file name.
475 return aEl->IsText();
478 ENameValueFlag HTMLFileInputAccessible::Name(nsString& aName) const {
479 ENameValueFlag flag = HyperTextAccessible::Name(aName);
480 if (flag == eNameFromSubtree) {
481 // The author didn't provide a name. We'll compute the name from our subtree
482 // below.
483 aName.Truncate();
484 } else {
485 // The author provided a name. We do use that, but we also append our
486 // subtree text so the user knows this is a file chooser button and what
487 // file has been chosen.
488 if (aName.IsEmpty()) {
489 // Name computation is recursing, perhaps due to a wrapping <label>. Don't
490 // append the subtree text. Return " " to prevent
491 // nsTextEquivUtils::AppendFromAccessible walking the subtree itself.
492 aName += ' ';
493 return flag;
496 // Unfortunately, GetNameFromSubtree doesn't separate the button text from the
497 // file name text. Compute the text ourselves.
498 uint32_t count = ChildCount();
499 for (uint32_t c = 0; c < count; ++c) {
500 TextLeafAccessible* leaf = LocalChildAt(c)->AsTextLeaf();
501 MOZ_ASSERT(leaf);
502 if (!aName.IsEmpty()) {
503 aName += ' ';
505 aName += leaf->Text();
507 return flag;
510 bool HTMLFileInputAccessible::HasPrimaryAction() const { return true; }
512 void HTMLFileInputAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
513 if (aIndex == 0) {
514 aName.AssignLiteral("press");
518 bool HTMLFileInputAccessible::IsWidget() const { return true; }
520 ////////////////////////////////////////////////////////////////////////////////
521 // HTMLSpinnerAccessible
522 ////////////////////////////////////////////////////////////////////////////////
524 role HTMLSpinnerAccessible::NativeRole() const { return roles::SPINBUTTON; }
526 void HTMLSpinnerAccessible::Value(nsString& aValue) const {
527 HTMLTextFieldAccessible::Value(aValue);
528 if (!aValue.IsEmpty()) return;
530 // Pass NonSystem as the caller type, to be safe. We don't expect to have a
531 // file input here.
532 HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem);
535 double HTMLSpinnerAccessible::MaxValue() const {
536 double value = HTMLTextFieldAccessible::MaxValue();
537 if (!std::isnan(value)) return value;
539 return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble();
542 double HTMLSpinnerAccessible::MinValue() const {
543 double value = HTMLTextFieldAccessible::MinValue();
544 if (!std::isnan(value)) return value;
546 return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble();
549 double HTMLSpinnerAccessible::Step() const {
550 double value = HTMLTextFieldAccessible::Step();
551 if (!std::isnan(value)) return value;
553 return HTMLInputElement::FromNode(mContent)->GetStep().toDouble();
556 double HTMLSpinnerAccessible::CurValue() const {
557 double value = HTMLTextFieldAccessible::CurValue();
558 if (!std::isnan(value)) return value;
560 return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble();
563 bool HTMLSpinnerAccessible::SetCurValue(double aValue) {
564 ErrorResult er;
565 HTMLInputElement::FromNode(mContent)->SetValueAsNumber(aValue, er);
566 return !er.Failed();
569 ////////////////////////////////////////////////////////////////////////////////
570 // HTMLRangeAccessible
571 ////////////////////////////////////////////////////////////////////////////////
573 role HTMLRangeAccessible::NativeRole() const { return roles::SLIDER; }
575 bool HTMLRangeAccessible::IsWidget() const { return true; }
577 void HTMLRangeAccessible::Value(nsString& aValue) const {
578 LeafAccessible::Value(aValue);
579 if (!aValue.IsEmpty()) return;
581 // Pass NonSystem as the caller type, to be safe. We don't expect to have a
582 // file input here.
583 HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem);
586 double HTMLRangeAccessible::MaxValue() const {
587 double value = LeafAccessible::MaxValue();
588 if (!std::isnan(value)) return value;
590 return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble();
593 double HTMLRangeAccessible::MinValue() const {
594 double value = LeafAccessible::MinValue();
595 if (!std::isnan(value)) return value;
597 return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble();
600 double HTMLRangeAccessible::Step() const {
601 double value = LeafAccessible::Step();
602 if (!std::isnan(value)) return value;
604 return HTMLInputElement::FromNode(mContent)->GetStep().toDouble();
607 double HTMLRangeAccessible::CurValue() const {
608 double value = LeafAccessible::CurValue();
609 if (!std::isnan(value)) return value;
611 return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble();
614 bool HTMLRangeAccessible::SetCurValue(double aValue) {
615 nsAutoString strValue;
616 strValue.AppendFloat(aValue);
617 HTMLInputElement::FromNode(mContent)->SetUserInput(
618 strValue, *nsContentUtils::GetSystemPrincipal());
619 return true;
622 ////////////////////////////////////////////////////////////////////////////////
623 // HTMLGroupboxAccessible
624 ////////////////////////////////////////////////////////////////////////////////
626 HTMLGroupboxAccessible::HTMLGroupboxAccessible(nsIContent* aContent,
627 DocAccessible* aDoc)
628 : HyperTextAccessible(aContent, aDoc) {}
630 role HTMLGroupboxAccessible::NativeRole() const { return roles::GROUPING; }
632 nsIContent* HTMLGroupboxAccessible::GetLegend() const {
633 for (nsIContent* legendContent = mContent->GetFirstChild(); legendContent;
634 legendContent = legendContent->GetNextSibling()) {
635 if (legendContent->NodeInfo()->Equals(nsGkAtoms::legend,
636 mContent->GetNameSpaceID())) {
637 // Either XHTML namespace or no namespace
638 return legendContent;
642 return nullptr;
645 ENameValueFlag HTMLGroupboxAccessible::NativeName(nsString& aName) const {
646 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
647 if (!aName.IsEmpty()) return nameFlag;
649 nsIContent* legendContent = GetLegend();
650 if (legendContent) {
651 nsTextEquivUtils::AppendTextEquivFromContent(this, legendContent, &aName);
654 aName.CompressWhitespace();
655 return eNameOK;
658 Relation HTMLGroupboxAccessible::RelationByType(RelationType aType) const {
659 Relation rel = HyperTextAccessible::RelationByType(aType);
660 // No override for label, so use <legend> for this <fieldset>
661 if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, GetLegend());
663 return rel;
666 ////////////////////////////////////////////////////////////////////////////////
667 // HTMLLegendAccessible
668 ////////////////////////////////////////////////////////////////////////////////
670 HTMLLegendAccessible::HTMLLegendAccessible(nsIContent* aContent,
671 DocAccessible* aDoc)
672 : HyperTextAccessible(aContent, aDoc) {}
674 Relation HTMLLegendAccessible::RelationByType(RelationType aType) const {
675 Relation rel = HyperTextAccessible::RelationByType(aType);
676 if (aType != RelationType::LABEL_FOR) return rel;
678 LocalAccessible* groupbox = LocalParent();
679 if (groupbox && groupbox->Role() == roles::GROUPING) {
680 rel.AppendTarget(groupbox);
683 return rel;
686 ////////////////////////////////////////////////////////////////////////////////
687 // HTMLFigureAccessible
688 ////////////////////////////////////////////////////////////////////////////////
690 HTMLFigureAccessible::HTMLFigureAccessible(nsIContent* aContent,
691 DocAccessible* aDoc)
692 : HyperTextAccessible(aContent, aDoc) {}
694 ENameValueFlag HTMLFigureAccessible::NativeName(nsString& aName) const {
695 ENameValueFlag nameFlag = HyperTextAccessible::NativeName(aName);
696 if (!aName.IsEmpty()) return nameFlag;
698 nsIContent* captionContent = Caption();
699 if (captionContent) {
700 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName);
703 aName.CompressWhitespace();
704 return eNameOK;
707 Relation HTMLFigureAccessible::RelationByType(RelationType aType) const {
708 Relation rel = HyperTextAccessible::RelationByType(aType);
709 if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, Caption());
711 return rel;
714 nsIContent* HTMLFigureAccessible::Caption() const {
715 for (nsIContent* childContent = mContent->GetFirstChild(); childContent;
716 childContent = childContent->GetNextSibling()) {
717 if (childContent->NodeInfo()->Equals(nsGkAtoms::figcaption,
718 mContent->GetNameSpaceID())) {
719 return childContent;
723 return nullptr;
726 ////////////////////////////////////////////////////////////////////////////////
727 // HTMLFigcaptionAccessible
728 ////////////////////////////////////////////////////////////////////////////////
730 HTMLFigcaptionAccessible::HTMLFigcaptionAccessible(nsIContent* aContent,
731 DocAccessible* aDoc)
732 : HyperTextAccessible(aContent, aDoc) {}
734 Relation HTMLFigcaptionAccessible::RelationByType(RelationType aType) const {
735 Relation rel = HyperTextAccessible::RelationByType(aType);
736 if (aType != RelationType::LABEL_FOR) return rel;
738 LocalAccessible* figure = LocalParent();
739 if (figure && figure->GetContent()->NodeInfo()->Equals(
740 nsGkAtoms::figure, mContent->GetNameSpaceID())) {
741 rel.AppendTarget(figure);
744 return rel;
747 ////////////////////////////////////////////////////////////////////////////////
748 // HTMLProgressAccessible
749 ////////////////////////////////////////////////////////////////////////////////
751 role HTMLProgressAccessible::NativeRole() const { return roles::PROGRESSBAR; }
753 uint64_t HTMLProgressAccessible::NativeState() const {
754 uint64_t state = LeafAccessible::NativeState();
756 // An undetermined progressbar (i.e. without a value) has a mixed state.
757 nsAutoString attrValue;
758 mContent->AsElement()->GetAttr(nsGkAtoms::value, attrValue);
759 if (attrValue.IsEmpty()) {
760 state |= states::MIXED;
763 return state;
766 bool HTMLProgressAccessible::IsWidget() const { return true; }
768 void HTMLProgressAccessible::Value(nsString& aValue) const {
769 LeafAccessible::Value(aValue);
770 if (!aValue.IsEmpty()) {
771 return;
774 double maxValue = MaxValue();
775 if (std::isnan(maxValue) || maxValue == 0) {
776 return;
779 double curValue = CurValue();
780 if (std::isnan(curValue)) {
781 return;
784 // Treat the current value bigger than maximum as 100%.
785 double percentValue =
786 (curValue < maxValue) ? (curValue / maxValue) * 100 : 100;
788 aValue.AppendFloat(percentValue);
789 aValue.Append('%');
792 double HTMLProgressAccessible::MaxValue() const {
793 double value = LeafAccessible::MaxValue();
794 if (!std::isnan(value)) {
795 return value;
798 nsAutoString strValue;
799 if (mContent->AsElement()->GetAttr(nsGkAtoms::max, strValue)) {
800 nsresult result = NS_OK;
801 value = strValue.ToDouble(&result);
802 if (NS_SUCCEEDED(result)) {
803 return value;
807 return 1;
810 double HTMLProgressAccessible::MinValue() const {
811 double value = LeafAccessible::MinValue();
812 return std::isnan(value) ? 0 : value;
815 double HTMLProgressAccessible::Step() const {
816 double value = LeafAccessible::Step();
817 return std::isnan(value) ? 0 : value;
820 double HTMLProgressAccessible::CurValue() const {
821 double value = LeafAccessible::CurValue();
822 if (!std::isnan(value)) {
823 return value;
826 nsAutoString attrValue;
827 if (!mContent->AsElement()->GetAttr(nsGkAtoms::value, attrValue)) {
828 return UnspecifiedNaN<double>();
831 nsresult error = NS_OK;
832 value = attrValue.ToDouble(&error);
833 return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
836 bool HTMLProgressAccessible::SetCurValue(double aValue) {
837 return false; // progress meters are readonly.
840 void HTMLProgressAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
841 nsAtom* aAttribute,
842 int32_t aModType,
843 const nsAttrValue* aOldValue,
844 uint64_t aOldState) {
845 LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
846 aOldValue, aOldState);
848 if (aAttribute == nsGkAtoms::value) {
849 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
851 uint64_t currState = NativeState();
852 if ((aOldState ^ currState) & states::MIXED) {
853 RefPtr<AccEvent> stateChangeEvent = new AccStateChangeEvent(
854 this, states::MIXED, (currState & states::MIXED));
855 mDoc->FireDelayedEvent(stateChangeEvent);
860 ////////////////////////////////////////////////////////////////////////////////
861 // HTMLMeterAccessible
862 ////////////////////////////////////////////////////////////////////////////////
864 role HTMLMeterAccessible::NativeRole() const { return roles::METER; }
866 bool HTMLMeterAccessible::IsWidget() const { return true; }
868 void HTMLMeterAccessible::Value(nsString& aValue) const {
869 LeafAccessible::Value(aValue);
870 if (!aValue.IsEmpty()) {
871 return;
874 // If we did not get a value from the above LeafAccessible call,
875 // we should check to see if the meter has inner text.
876 // If it does, we'll use that as our value.
877 nsTextEquivUtils::AppendFromDOMChildren(mContent, &aValue);
878 aValue.CompressWhitespace();
879 if (!aValue.IsEmpty()) {
880 return;
883 // If no inner text is found, use curValue
884 double curValue = CurValue();
885 if (std::isnan(curValue)) {
886 return;
889 aValue.AppendFloat(curValue);
892 double HTMLMeterAccessible::MaxValue() const {
893 double max = LeafAccessible::MaxValue();
894 double min = MinValue();
896 if (!std::isnan(max)) {
897 return max > min ? max : min;
900 // If we didn't find a max value, check for the max attribute
901 nsAutoString strValue;
902 if (mContent->AsElement()->GetAttr(nsGkAtoms::max, strValue)) {
903 nsresult result = NS_OK;
904 max = strValue.ToDouble(&result);
905 if (NS_SUCCEEDED(result)) {
906 return max > min ? max : min;
910 return 1 > min ? 1 : min;
913 double HTMLMeterAccessible::MinValue() const {
914 double min = LeafAccessible::MinValue();
915 if (!std::isnan(min)) {
916 return min;
919 nsAutoString strValue;
920 if (mContent->AsElement()->GetAttr(nsGkAtoms::min, strValue)) {
921 nsresult result = NS_OK;
922 min = strValue.ToDouble(&result);
923 if (NS_SUCCEEDED(result)) {
924 return min;
928 return 0;
931 double HTMLMeterAccessible::CurValue() const {
932 double value = LeafAccessible::CurValue();
933 double minValue = MinValue();
935 if (std::isnan(value)) {
936 /* If we didn't find a value from the LeafAccessible call above, check
937 * for a value attribute */
938 nsAutoString attrValue;
939 if (!mContent->AsElement()->GetAttr(nsGkAtoms::value, attrValue)) {
940 return minValue;
943 // If we find a value attribute, attempt to convert it to a double
944 nsresult error = NS_OK;
945 value = attrValue.ToDouble(&error);
946 if (NS_FAILED(error)) {
947 return minValue;
951 /* If we end up with a defined value, verify it falls between
952 * our established min/max. Otherwise, snap it to the nearest boundary. */
953 double maxValue = MaxValue();
954 if (value > maxValue) {
955 value = maxValue;
956 } else if (value < minValue) {
957 value = minValue;
960 return value;
963 bool HTMLMeterAccessible::SetCurValue(double aValue) {
964 return false; // meters are readonly.
967 void HTMLMeterAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
968 nsAtom* aAttribute,
969 int32_t aModType,
970 const nsAttrValue* aOldValue,
971 uint64_t aOldState) {
972 LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
973 aOldValue, aOldState);
975 if (aAttribute == nsGkAtoms::value) {
976 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);