Bug 1731994: part 3) Extend documentation of `ContentPermissionRequestBase`'s constru...
[gecko.git] / accessible / html / HTMLFormControlAccessible.cpp
blob090070448bc26f5e43c0f5f3ceea0ad40f539ee2
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 "DocAccessible-inl.h"
9 #include "LocalAccessible-inl.h"
10 #include "nsAccUtils.h"
11 #include "nsEventShell.h"
12 #include "nsTextEquivUtils.h"
13 #include "Relation.h"
14 #include "Role.h"
15 #include "States.h"
17 #include "nsContentList.h"
18 #include "mozilla/dom/HTMLInputElement.h"
19 #include "mozilla/dom/HTMLTextAreaElement.h"
20 #include "nsIFormControl.h"
21 #include "nsITextControlFrame.h"
22 #include "nsNameSpaceManager.h"
23 #include "mozilla/dom/ScriptSettings.h"
25 #include "mozilla/EditorBase.h"
26 #include "mozilla/EventStates.h"
27 #include "mozilla/FloatingPoint.h"
28 #include "mozilla/Preferences.h"
29 #include "mozilla/TextEditor.h"
31 using namespace mozilla;
32 using namespace mozilla::dom;
33 using namespace mozilla::a11y;
35 ////////////////////////////////////////////////////////////////////////////////
36 // HTMLFormAccessible
37 ////////////////////////////////////////////////////////////////////////////////
39 role HTMLFormAccessible::NativeRole() const {
40 nsAutoString name;
41 const_cast<HTMLFormAccessible*>(this)->Name(name);
42 return name.IsEmpty() ? roles::FORM : roles::FORM_LANDMARK;
45 nsAtom* HTMLFormAccessible::LandmarkRole() const {
46 if (!HasOwnContent()) {
47 return nullptr;
50 // Only return xml-roles "form" if the form has an accessible name.
51 nsAutoString name;
52 const_cast<HTMLFormAccessible*>(this)->Name(name);
53 return name.IsEmpty() ? HyperTextAccessibleWrap::LandmarkRole()
54 : nsGkAtoms::form;
57 ////////////////////////////////////////////////////////////////////////////////
58 // HTMLRadioButtonAccessible
59 ////////////////////////////////////////////////////////////////////////////////
61 uint64_t HTMLRadioButtonAccessible::NativeState() const {
62 uint64_t state = AccessibleWrap::NativeState();
64 state |= states::CHECKABLE;
66 HTMLInputElement* input = HTMLInputElement::FromNode(mContent);
67 if (input && input->Checked()) state |= states::CHECKED;
69 return state;
72 void HTMLRadioButtonAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet,
73 int32_t* aSetSize) {
74 Unused << ComputeGroupAttributes(aPosInSet, aSetSize);
77 Relation HTMLRadioButtonAccessible::ComputeGroupAttributes(
78 int32_t* aPosInSet, int32_t* aSetSize) const {
79 Relation rel = Relation();
80 int32_t namespaceId = mContent->NodeInfo()->NamespaceID();
81 nsAutoString tagName;
82 mContent->NodeInfo()->GetName(tagName);
84 nsAutoString type;
85 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type);
86 nsAutoString name;
87 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
89 RefPtr<nsContentList> inputElms;
91 nsCOMPtr<nsIFormControl> formControlNode(do_QueryInterface(mContent));
92 if (dom::Element* formElm = formControlNode->GetForm()) {
93 inputElms = NS_GetContentList(formElm, namespaceId, tagName);
94 } else {
95 inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName);
97 NS_ENSURE_TRUE(inputElms, rel);
99 uint32_t inputCount = inputElms->Length(false);
101 // Compute posinset and setsize.
102 int32_t indexOf = 0;
103 int32_t count = 0;
105 for (uint32_t index = 0; index < inputCount; index++) {
106 nsIContent* inputElm = inputElms->Item(index, false);
107 if (inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
108 type, eCaseMatters) &&
109 inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
110 name, eCaseMatters) &&
111 mDoc->HasAccessible(inputElm)) {
112 count++;
113 rel.AppendTarget(mDoc->GetAccessible(inputElm));
114 if (inputElm == mContent) indexOf = count;
118 *aPosInSet = indexOf;
119 *aSetSize = count;
120 return rel;
123 Relation HTMLRadioButtonAccessible::RelationByType(RelationType aType) const {
124 if (aType == RelationType::MEMBER_OF) {
125 int32_t unusedPos, unusedSetSize;
126 return ComputeGroupAttributes(&unusedPos, &unusedSetSize);
129 return LocalAccessible::RelationByType(aType);
132 ////////////////////////////////////////////////////////////////////////////////
133 // HTMLButtonAccessible
134 ////////////////////////////////////////////////////////////////////////////////
136 HTMLButtonAccessible::HTMLButtonAccessible(nsIContent* aContent,
137 DocAccessible* aDoc)
138 : HyperTextAccessibleWrap(aContent, aDoc) {
139 mGenericTypes |= eButton;
142 uint8_t HTMLButtonAccessible::ActionCount() const { return 1; }
144 void HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
145 if (aIndex == eAction_Click) aName.AssignLiteral("press");
148 bool HTMLButtonAccessible::DoAction(uint8_t aIndex) const {
149 if (aIndex != eAction_Click) return false;
151 DoCommand();
152 return true;
155 uint64_t HTMLButtonAccessible::State() {
156 uint64_t state = HyperTextAccessibleWrap::State();
157 if (state == states::DEFUNCT) return state;
159 // Inherit states from input@type="file" suitable for the button. Note,
160 // no special processing for unavailable state since inheritance is supplied
161 // other code paths.
162 if (mParent && mParent->IsHTMLFileInput()) {
163 uint64_t parentState = mParent->State();
164 state |= parentState & (states::BUSY | states::REQUIRED | states::HASPOPUP |
165 states::INVALID);
168 return state;
171 uint64_t HTMLButtonAccessible::NativeState() const {
172 uint64_t state = HyperTextAccessibleWrap::NativeState();
174 EventStates elmState = mContent->AsElement()->State();
175 if (elmState.HasState(NS_EVENT_STATE_DEFAULT)) state |= states::DEFAULT;
177 return state;
180 role HTMLButtonAccessible::NativeRole() const { return roles::PUSHBUTTON; }
182 ENameValueFlag HTMLButtonAccessible::NativeName(nsString& aName) const {
183 // No need to check @value attribute for buttons since this attribute results
184 // in native anonymous text node and the name is calculated from subtree.
185 // The same magic works for @alt and @value attributes in case of type="image"
186 // element that has no valid @src (note if input@type="image" has an image
187 // then neither @alt nor @value attributes are used to generate a visual label
188 // and thus we need to obtain the accessible name directly from attribute
189 // value). Also the same algorithm works in case of default labels for
190 // type="submit"/"reset"/"image" elements.
192 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
193 if (!aName.IsEmpty() || !mContent->IsHTMLElement(nsGkAtoms::input) ||
194 !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
195 nsGkAtoms::image, eCaseMatters)) {
196 return nameFlag;
199 if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::alt,
200 aName)) {
201 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName);
204 aName.CompressWhitespace();
205 return eNameOK;
208 void HTMLButtonAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
209 nsAtom* aAttribute,
210 int32_t aModType,
211 const nsAttrValue* aOldValue,
212 uint64_t aOldState) {
213 HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute,
214 aModType, aOldValue, aOldState);
216 if (aAttribute == nsGkAtoms::value) {
217 dom::Element* elm = Elm();
218 if (elm->IsHTMLElement(nsGkAtoms::input) ||
219 (elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::image,
220 eCaseMatters) &&
221 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt))) {
222 if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
223 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
224 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
230 ////////////////////////////////////////////////////////////////////////////////
231 // HTMLButtonAccessible: Widgets
233 bool HTMLButtonAccessible::IsWidget() const { return true; }
235 ////////////////////////////////////////////////////////////////////////////////
236 // HTMLTextFieldAccessible
237 ////////////////////////////////////////////////////////////////////////////////
239 HTMLTextFieldAccessible::HTMLTextFieldAccessible(nsIContent* aContent,
240 DocAccessible* aDoc)
241 : HyperTextAccessibleWrap(aContent, aDoc) {
242 mType = mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
243 nsGkAtoms::password, eIgnoreCase)
244 ? eHTMLTextPasswordFieldType
245 : eHTMLTextFieldType;
248 role HTMLTextFieldAccessible::NativeRole() const {
249 if (mType == eHTMLTextPasswordFieldType) {
250 return roles::PASSWORD_TEXT;
252 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::list_)) {
253 return roles::EDITCOMBOBOX;
255 return roles::ENTRY;
258 already_AddRefed<AccAttributes> HTMLTextFieldAccessible::NativeAttributes() {
259 RefPtr<AccAttributes> attributes =
260 HyperTextAccessibleWrap::NativeAttributes();
262 // Expose type for text input elements as it gives some useful context,
263 // especially for mobile.
264 nsAutoString type;
265 // In the case of this element being part of a binding, the binding's
266 // parent's type should have precedence. For example an input[type=number]
267 // has an embedded anonymous input[type=text] (along with spinner buttons).
268 // In that case, we would want to take the input type from the parent
269 // and not the anonymous content.
270 nsIContent* widgetElm = BindingOrWidgetParent();
271 if ((widgetElm && widgetElm->AsElement()->GetAttr(kNameSpaceID_None,
272 nsGkAtoms::type, type)) ||
273 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type,
274 type)) {
275 attributes->SetAttribute(nsGkAtoms::textInputType, type);
276 if (!ARIARoleMap() && type.EqualsLiteral("search")) {
277 attributes->SetAttribute(nsGkAtoms::xmlroles, u"searchbox"_ns);
281 // If this element has the placeholder attribute set,
282 // and if that is not identical to the name, expose it as an object attribute.
283 nsAutoString placeholderText;
284 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
285 placeholderText)) {
286 nsAutoString name;
287 const_cast<HTMLTextFieldAccessible*>(this)->Name(name);
288 if (!name.Equals(placeholderText)) {
289 attributes->SetAttribute(nsGkAtoms::placeholder, placeholderText);
293 return attributes.forget();
296 ENameValueFlag HTMLTextFieldAccessible::NativeName(nsString& aName) const {
297 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
298 if (!aName.IsEmpty()) return nameFlag;
300 // If part of compound of XUL widget then grab a name from XUL widget element.
301 nsIContent* widgetElm = BindingOrWidgetParent();
302 if (widgetElm) XULElmName(mDoc, widgetElm, aName);
304 if (!aName.IsEmpty()) return eNameOK;
306 // text inputs and textareas might have useful placeholder text
307 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
308 aName);
309 return eNameOK;
312 void HTMLTextFieldAccessible::Value(nsString& aValue) const {
313 aValue.Truncate();
314 if (NativeState() & states::PROTECTED) { // Don't return password text!
315 return;
318 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(mContent);
319 if (textArea) {
320 textArea->GetValue(aValue);
321 return;
324 HTMLInputElement* input = HTMLInputElement::FromNode(mContent);
325 if (input) {
326 // Pass NonSystem as the caller type, to be safe. We don't expect to have a
327 // file input here.
328 input->GetValue(aValue, CallerType::NonSystem);
332 void HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const {
333 HyperTextAccessibleWrap::ApplyARIAState(aState);
334 aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
336 // If part of compound of XUL widget then pick up ARIA stuff from XUL widget
337 // element.
338 nsIContent* widgetElm = BindingOrWidgetParent();
339 if (widgetElm) {
340 aria::MapToState(aria::eARIAAutoComplete, widgetElm->AsElement(), aState);
344 uint64_t HTMLTextFieldAccessible::NativeState() const {
345 uint64_t state = HyperTextAccessibleWrap::NativeState();
347 // Text fields are always editable, even if they are also read only or
348 // disabled.
349 state |= states::EDITABLE;
351 // can be focusable, focused, protected. readonly, unavailable, selected
352 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
353 nsGkAtoms::password, eIgnoreCase)) {
354 state |= states::PROTECTED;
357 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) {
358 state |= states::READONLY;
361 // Is it an <input> or a <textarea> ?
362 HTMLInputElement* input = HTMLInputElement::FromNode(mContent);
363 state |= input && input->IsSingleLineTextControl() ? states::SINGLE_LINE
364 : states::MULTI_LINE;
366 if (state & (states::PROTECTED | states::MULTI_LINE | states::READONLY |
367 states::UNAVAILABLE)) {
368 return state;
371 // Expose autocomplete states if this input is part of autocomplete widget.
372 LocalAccessible* widget = ContainerWidget();
373 if (widget && widget - IsAutoComplete()) {
374 state |= states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION;
375 return state;
378 // Expose autocomplete state if it has associated autocomplete list.
379 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::list_)) {
380 return state | states::SUPPORTS_AUTOCOMPLETION | states::HASPOPUP;
383 // Ordinal XUL textboxes don't support autocomplete.
384 if (!BindingOrWidgetParent() &&
385 Preferences::GetBool("browser.formfill.enable")) {
386 // Check to see if autocompletion is allowed on this input. We don't expose
387 // it for password fields even though the entire password can be remembered
388 // for a page if the user asks it to be. However, the kind of autocomplete
389 // we're talking here is based on what the user types, where a popup of
390 // possible choices comes up.
391 nsAutoString autocomplete;
392 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete,
393 autocomplete);
395 if (!autocomplete.LowerCaseEqualsLiteral("off")) {
396 Element* formElement = input->GetForm();
397 if (formElement) {
398 formElement->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete,
399 autocomplete);
402 if (!formElement || !autocomplete.LowerCaseEqualsLiteral("off")) {
403 state |= states::SUPPORTS_AUTOCOMPLETION;
408 return state;
411 uint8_t HTMLTextFieldAccessible::ActionCount() const { return 1; }
413 void HTMLTextFieldAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
414 if (aIndex == eAction_Click) aName.AssignLiteral("activate");
417 bool HTMLTextFieldAccessible::DoAction(uint8_t aIndex) const {
418 if (aIndex != 0) return false;
420 if (FocusMgr()->IsFocused(this)) {
421 // This already has focus, so TakeFocus()will do nothing. However, the user
422 // might be activating this element because they dismissed a touch keyboard
423 // and want to bring it back.
424 DoCommand();
425 } else {
426 TakeFocus();
428 return true;
431 already_AddRefed<EditorBase> HTMLTextFieldAccessible::GetEditor() const {
432 RefPtr<TextControlElement> textControlElement =
433 TextControlElement::FromNodeOrNull(mContent);
434 if (!textControlElement) {
435 return nullptr;
437 RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor();
438 return textEditor.forget();
441 ////////////////////////////////////////////////////////////////////////////////
442 // HTMLTextFieldAccessible: Widgets
444 bool HTMLTextFieldAccessible::IsWidget() const { return true; }
446 LocalAccessible* HTMLTextFieldAccessible::ContainerWidget() const {
447 if (!mParent || mParent->Role() != roles::AUTOCOMPLETE) {
448 return nullptr;
450 return mParent;
453 ////////////////////////////////////////////////////////////////////////////////
454 // HTMLFileInputAccessible
455 ////////////////////////////////////////////////////////////////////////////////
457 HTMLFileInputAccessible::HTMLFileInputAccessible(nsIContent* aContent,
458 DocAccessible* aDoc)
459 : HyperTextAccessibleWrap(aContent, aDoc) {
460 mType = eHTMLFileInputType;
463 role HTMLFileInputAccessible::NativeRole() const {
464 // No specific role in AT APIs. We use GROUPING so that the label will be
465 // reported by screen readers when focus enters this control .
466 return roles::GROUPING;
469 nsresult HTMLFileInputAccessible::HandleAccEvent(AccEvent* aEvent) {
470 nsresult rv = HyperTextAccessibleWrap::HandleAccEvent(aEvent);
471 NS_ENSURE_SUCCESS(rv, rv);
473 // Redirect state change events for inherited states to child controls. Note,
474 // unavailable state is not redirected. That's a standard for unavailable
475 // state handling.
476 AccStateChangeEvent* event = downcast_accEvent(aEvent);
477 if (event && (event->GetState() == states::BUSY ||
478 event->GetState() == states::REQUIRED ||
479 event->GetState() == states::HASPOPUP ||
480 event->GetState() == states::INVALID)) {
481 LocalAccessible* button = LocalChildAt(0);
482 if (button && button->Role() == roles::PUSHBUTTON) {
483 RefPtr<AccStateChangeEvent> childEvent = new AccStateChangeEvent(
484 button, event->GetState(), event->IsStateEnabled(),
485 event->FromUserInput());
486 nsEventShell::FireEvent(childEvent);
490 return NS_OK;
493 LocalAccessible* HTMLFileInputAccessible::CurrentItem() const {
494 // Allow aria-activedescendant to override.
495 if (LocalAccessible* item = HyperTextAccessibleWrap::CurrentItem()) {
496 return item;
499 // The HTML file input itself gets DOM focus, not the button inside it.
500 // For a11y, we want the button to get focus.
501 LocalAccessible* button = LocalFirstChild();
502 if (!button) {
503 MOZ_ASSERT_UNREACHABLE("File input doesn't contain a button");
504 return nullptr;
506 MOZ_ASSERT(button->IsButton());
507 return button;
510 ////////////////////////////////////////////////////////////////////////////////
511 // HTMLSpinnerAccessible
512 ////////////////////////////////////////////////////////////////////////////////
514 role HTMLSpinnerAccessible::NativeRole() const { return roles::SPINBUTTON; }
516 void HTMLSpinnerAccessible::Value(nsString& aValue) const {
517 HTMLTextFieldAccessible::Value(aValue);
518 if (!aValue.IsEmpty()) return;
520 // Pass NonSystem as the caller type, to be safe. We don't expect to have a
521 // file input here.
522 HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem);
525 double HTMLSpinnerAccessible::MaxValue() const {
526 double value = HTMLTextFieldAccessible::MaxValue();
527 if (!IsNaN(value)) return value;
529 return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble();
532 double HTMLSpinnerAccessible::MinValue() const {
533 double value = HTMLTextFieldAccessible::MinValue();
534 if (!IsNaN(value)) return value;
536 return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble();
539 double HTMLSpinnerAccessible::Step() const {
540 double value = HTMLTextFieldAccessible::Step();
541 if (!IsNaN(value)) return value;
543 return HTMLInputElement::FromNode(mContent)->GetStep().toDouble();
546 double HTMLSpinnerAccessible::CurValue() const {
547 double value = HTMLTextFieldAccessible::CurValue();
548 if (!IsNaN(value)) return value;
550 return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble();
553 bool HTMLSpinnerAccessible::SetCurValue(double aValue) {
554 ErrorResult er;
555 HTMLInputElement::FromNode(mContent)->SetValueAsNumber(aValue, er);
556 return !er.Failed();
559 ////////////////////////////////////////////////////////////////////////////////
560 // HTMLRangeAccessible
561 ////////////////////////////////////////////////////////////////////////////////
563 role HTMLRangeAccessible::NativeRole() const { return roles::SLIDER; }
565 bool HTMLRangeAccessible::IsWidget() const { return true; }
567 void HTMLRangeAccessible::Value(nsString& aValue) const {
568 LeafAccessible::Value(aValue);
569 if (!aValue.IsEmpty()) return;
571 // Pass NonSystem as the caller type, to be safe. We don't expect to have a
572 // file input here.
573 HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem);
576 double HTMLRangeAccessible::MaxValue() const {
577 double value = LeafAccessible::MaxValue();
578 if (!IsNaN(value)) return value;
580 return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble();
583 double HTMLRangeAccessible::MinValue() const {
584 double value = LeafAccessible::MinValue();
585 if (!IsNaN(value)) return value;
587 return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble();
590 double HTMLRangeAccessible::Step() const {
591 double value = LeafAccessible::Step();
592 if (!IsNaN(value)) return value;
594 return HTMLInputElement::FromNode(mContent)->GetStep().toDouble();
597 double HTMLRangeAccessible::CurValue() const {
598 double value = LeafAccessible::CurValue();
599 if (!IsNaN(value)) return value;
601 return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble();
604 bool HTMLRangeAccessible::SetCurValue(double aValue) {
605 ErrorResult er;
606 HTMLInputElement::FromNode(mContent)->SetValueAsNumber(aValue, er);
607 return !er.Failed();
610 ////////////////////////////////////////////////////////////////////////////////
611 // HTMLGroupboxAccessible
612 ////////////////////////////////////////////////////////////////////////////////
614 HTMLGroupboxAccessible::HTMLGroupboxAccessible(nsIContent* aContent,
615 DocAccessible* aDoc)
616 : HyperTextAccessibleWrap(aContent, aDoc) {}
618 role HTMLGroupboxAccessible::NativeRole() const { return roles::GROUPING; }
620 nsIContent* HTMLGroupboxAccessible::GetLegend() const {
621 for (nsIContent* legendContent = mContent->GetFirstChild(); legendContent;
622 legendContent = legendContent->GetNextSibling()) {
623 if (legendContent->NodeInfo()->Equals(nsGkAtoms::legend,
624 mContent->GetNameSpaceID())) {
625 // Either XHTML namespace or no namespace
626 return legendContent;
630 return nullptr;
633 ENameValueFlag HTMLGroupboxAccessible::NativeName(nsString& aName) const {
634 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
635 if (!aName.IsEmpty()) return nameFlag;
637 nsIContent* legendContent = GetLegend();
638 if (legendContent) {
639 nsTextEquivUtils::AppendTextEquivFromContent(this, legendContent, &aName);
642 aName.CompressWhitespace();
643 return eNameOK;
646 Relation HTMLGroupboxAccessible::RelationByType(RelationType aType) const {
647 Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
648 // No override for label, so use <legend> for this <fieldset>
649 if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, GetLegend());
651 return rel;
654 ////////////////////////////////////////////////////////////////////////////////
655 // HTMLLegendAccessible
656 ////////////////////////////////////////////////////////////////////////////////
658 HTMLLegendAccessible::HTMLLegendAccessible(nsIContent* aContent,
659 DocAccessible* aDoc)
660 : HyperTextAccessibleWrap(aContent, aDoc) {}
662 Relation HTMLLegendAccessible::RelationByType(RelationType aType) const {
663 Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
664 if (aType != RelationType::LABEL_FOR) return rel;
666 LocalAccessible* groupbox = LocalParent();
667 if (groupbox && groupbox->Role() == roles::GROUPING) {
668 rel.AppendTarget(groupbox);
671 return rel;
674 ////////////////////////////////////////////////////////////////////////////////
675 // HTMLFigureAccessible
676 ////////////////////////////////////////////////////////////////////////////////
678 HTMLFigureAccessible::HTMLFigureAccessible(nsIContent* aContent,
679 DocAccessible* aDoc)
680 : HyperTextAccessibleWrap(aContent, aDoc) {}
682 ENameValueFlag HTMLFigureAccessible::NativeName(nsString& aName) const {
683 ENameValueFlag nameFlag = HyperTextAccessibleWrap::NativeName(aName);
684 if (!aName.IsEmpty()) return nameFlag;
686 nsIContent* captionContent = Caption();
687 if (captionContent) {
688 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName);
691 aName.CompressWhitespace();
692 return eNameOK;
695 Relation HTMLFigureAccessible::RelationByType(RelationType aType) const {
696 Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
697 if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, Caption());
699 return rel;
702 nsIContent* HTMLFigureAccessible::Caption() const {
703 for (nsIContent* childContent = mContent->GetFirstChild(); childContent;
704 childContent = childContent->GetNextSibling()) {
705 if (childContent->NodeInfo()->Equals(nsGkAtoms::figcaption,
706 mContent->GetNameSpaceID())) {
707 return childContent;
711 return nullptr;
714 ////////////////////////////////////////////////////////////////////////////////
715 // HTMLFigcaptionAccessible
716 ////////////////////////////////////////////////////////////////////////////////
718 HTMLFigcaptionAccessible::HTMLFigcaptionAccessible(nsIContent* aContent,
719 DocAccessible* aDoc)
720 : HyperTextAccessibleWrap(aContent, aDoc) {}
722 Relation HTMLFigcaptionAccessible::RelationByType(RelationType aType) const {
723 Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
724 if (aType != RelationType::LABEL_FOR) return rel;
726 LocalAccessible* figure = LocalParent();
727 if (figure && figure->GetContent()->NodeInfo()->Equals(
728 nsGkAtoms::figure, mContent->GetNameSpaceID())) {
729 rel.AppendTarget(figure);
732 return rel;
735 ////////////////////////////////////////////////////////////////////////////////
736 // HTMLProgressAccessible
737 ////////////////////////////////////////////////////////////////////////////////
739 role HTMLProgressAccessible::NativeRole() const { return roles::PROGRESSBAR; }
741 uint64_t HTMLProgressAccessible::NativeState() const {
742 uint64_t state = LeafAccessible::NativeState();
744 // An undetermined progressbar (i.e. without a value) has a mixed state.
745 nsAutoString attrValue;
746 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
747 attrValue);
748 if (attrValue.IsEmpty()) {
749 state |= states::MIXED;
752 return state;
755 bool HTMLProgressAccessible::IsWidget() const { return true; }
757 void HTMLProgressAccessible::Value(nsString& aValue) const {
758 LeafAccessible::Value(aValue);
759 if (!aValue.IsEmpty()) {
760 return;
763 double maxValue = MaxValue();
764 if (IsNaN(maxValue) || maxValue == 0) {
765 return;
768 double curValue = CurValue();
769 if (IsNaN(curValue)) {
770 return;
773 // Treat the current value bigger than maximum as 100%.
774 double percentValue =
775 (curValue < maxValue) ? (curValue / maxValue) * 100 : 100;
777 aValue.AppendFloat(percentValue);
778 aValue.Append('%');
781 double HTMLProgressAccessible::MaxValue() const {
782 double value = LeafAccessible::MaxValue();
783 if (!IsNaN(value)) {
784 return value;
787 nsAutoString strValue;
788 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::max,
789 strValue)) {
790 nsresult result = NS_OK;
791 value = strValue.ToDouble(&result);
792 if (NS_SUCCEEDED(result)) {
793 return value;
797 return 1;
800 double HTMLProgressAccessible::MinValue() const {
801 double value = LeafAccessible::MinValue();
802 return IsNaN(value) ? 0 : value;
805 double HTMLProgressAccessible::Step() const {
806 double value = LeafAccessible::Step();
807 return IsNaN(value) ? 0 : value;
810 double HTMLProgressAccessible::CurValue() const {
811 double value = LeafAccessible::CurValue();
812 if (!IsNaN(value)) {
813 return value;
816 nsAutoString attrValue;
817 if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
818 attrValue)) {
819 return UnspecifiedNaN<double>();
822 nsresult error = NS_OK;
823 value = attrValue.ToDouble(&error);
824 return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
827 bool HTMLProgressAccessible::SetCurValue(double aValue) {
828 return false; // progress meters are readonly.
831 void HTMLProgressAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
832 nsAtom* aAttribute,
833 int32_t aModType,
834 const nsAttrValue* aOldValue,
835 uint64_t aOldState) {
836 LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
837 aOldValue, aOldState);
839 if (aAttribute == nsGkAtoms::value) {
840 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
842 uint64_t currState = NativeState();
843 if ((aOldState ^ currState) & states::MIXED) {
844 RefPtr<AccEvent> stateChangeEvent = new AccStateChangeEvent(
845 this, states::MIXED, (currState & states::MIXED));
846 mDoc->FireDelayedEvent(stateChangeEvent);
851 ////////////////////////////////////////////////////////////////////////////////
852 // HTMLMeterAccessible
853 ////////////////////////////////////////////////////////////////////////////////
855 role HTMLMeterAccessible::NativeRole() const { return roles::METER; }
857 bool HTMLMeterAccessible::IsWidget() const { return true; }
859 void HTMLMeterAccessible::Value(nsString& aValue) const {
860 LeafAccessible::Value(aValue);
861 if (!aValue.IsEmpty()) {
862 return;
865 // If we did not get a value from the above LeafAccessible call,
866 // we should check to see if the meter has inner text.
867 // If it does, we'll use that as our value.
868 nsTextEquivUtils::AppendFromDOMChildren(mContent, &aValue);
869 aValue.CompressWhitespace();
870 if (!aValue.IsEmpty()) {
871 return;
874 // If no inner text is found, use curValue
875 double curValue = CurValue();
876 if (IsNaN(curValue)) {
877 return;
880 aValue.AppendFloat(curValue);
883 double HTMLMeterAccessible::MaxValue() const {
884 double max = LeafAccessible::MaxValue();
885 double min = MinValue();
887 if (!IsNaN(max)) {
888 return max > min ? max : min;
891 // If we didn't find a max value, check for the max attribute
892 nsAutoString strValue;
893 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::max,
894 strValue)) {
895 nsresult result = NS_OK;
896 max = strValue.ToDouble(&result);
897 if (NS_SUCCEEDED(result)) {
898 return max > min ? max : min;
902 return 1 > min ? 1 : min;
905 double HTMLMeterAccessible::MinValue() const {
906 double min = LeafAccessible::MinValue();
907 if (!IsNaN(min)) {
908 return min;
911 nsAutoString strValue;
912 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::min,
913 strValue)) {
914 nsresult result = NS_OK;
915 min = strValue.ToDouble(&result);
916 if (NS_SUCCEEDED(result)) {
917 return min;
921 return 0;
924 double HTMLMeterAccessible::CurValue() const {
925 double value = LeafAccessible::CurValue();
926 double minValue = MinValue();
928 if (IsNaN(value)) {
929 /* If we didn't find a value from the LeafAccessible call above, check
930 * for a value attribute */
931 nsAutoString attrValue;
932 if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
933 attrValue)) {
934 return minValue;
937 // If we find a value attribute, attempt to convert it to a double
938 nsresult error = NS_OK;
939 value = attrValue.ToDouble(&error);
940 if (NS_FAILED(error)) {
941 return minValue;
945 /* If we end up with a defined value, verify it falls between
946 * our established min/max. Otherwise, snap it to the nearest boundary. */
947 double maxValue = MaxValue();
948 if (value > maxValue) {
949 value = maxValue;
950 } else if (value < minValue) {
951 value = minValue;
954 return value;
957 bool HTMLMeterAccessible::SetCurValue(double aValue) {
958 return false; // meters are readonly.
961 void HTMLMeterAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
962 nsAtom* aAttribute,
963 int32_t aModType,
964 const nsAttrValue* aOldValue,
965 uint64_t aOldState) {
966 LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
967 aOldValue, aOldState);
969 if (aAttribute == nsGkAtoms::value) {
970 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);