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 "Accessible-inl.h"
9 #include "nsAccUtils.h"
10 #include "nsEventShell.h"
11 #include "nsTextEquivUtils.h"
16 #include "nsContentList.h"
17 #include "mozilla/dom/HTMLInputElement.h"
18 #include "nsIDOMNSEditableElement.h"
19 #include "nsIDOMHTMLTextAreaElement.h"
20 #include "nsIEditor.h"
21 #include "nsIFormControl.h"
22 #include "nsIPersistentProperties2.h"
23 #include "nsISelectionController.h"
24 #include "nsIServiceManager.h"
25 #include "nsITextControlFrame.h"
26 #include "nsNameSpaceManager.h"
27 #include "mozilla/dom/ScriptSettings.h"
29 #include "mozilla/EventStates.h"
30 #include "mozilla/FloatingPoint.h"
31 #include "mozilla/Preferences.h"
33 using namespace mozilla
;
34 using namespace mozilla::dom
;
35 using namespace mozilla::a11y
;
37 ////////////////////////////////////////////////////////////////////////////////
38 // HTMLCheckboxAccessible
39 ////////////////////////////////////////////////////////////////////////////////
42 HTMLCheckboxAccessible::NativeRole()
44 return roles::CHECKBUTTON
;
48 HTMLCheckboxAccessible::ActionCount()
54 HTMLCheckboxAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
)
56 if (aIndex
== eAction_Click
) { // 0 is the magic value for default action
57 uint64_t state
= NativeState();
58 if (state
& states::CHECKED
)
59 aName
.AssignLiteral("uncheck");
60 else if (state
& states::MIXED
)
61 aName
.AssignLiteral("cycle");
63 aName
.AssignLiteral("check");
68 HTMLCheckboxAccessible::DoAction(uint8_t aIndex
)
78 HTMLCheckboxAccessible::NativeState()
80 uint64_t state
= LeafAccessible::NativeState();
82 state
|= states::CHECKABLE
;
83 HTMLInputElement
* input
= HTMLInputElement::FromContent(mContent
);
87 if (input
->Indeterminate())
88 return state
| states::MIXED
;
91 return state
| states::CHECKED
;
96 ////////////////////////////////////////////////////////////////////////////////
97 // HTMLCheckboxAccessible: Widgets
100 HTMLCheckboxAccessible::IsWidget() const
106 ////////////////////////////////////////////////////////////////////////////////
107 // HTMLRadioButtonAccessible
108 ////////////////////////////////////////////////////////////////////////////////
111 HTMLRadioButtonAccessible::NativeState()
113 uint64_t state
= AccessibleWrap::NativeState();
115 state
|= states::CHECKABLE
;
117 HTMLInputElement
* input
= HTMLInputElement::FromContent(mContent
);
118 if (input
&& input
->Checked())
119 state
|= states::CHECKED
;
125 HTMLRadioButtonAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet
,
128 int32_t namespaceId
= mContent
->NodeInfo()->NamespaceID();
129 nsAutoString tagName
;
130 mContent
->NodeInfo()->GetName(tagName
);
133 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::type
, type
);
135 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::name
, name
);
137 nsRefPtr
<nsContentList
> inputElms
;
139 nsCOMPtr
<nsIFormControl
> formControlNode(do_QueryInterface(mContent
));
140 dom::Element
* formElm
= formControlNode
->GetFormElement();
142 inputElms
= NS_GetContentList(formElm
, namespaceId
, tagName
);
144 inputElms
= NS_GetContentList(mContent
->OwnerDoc(), namespaceId
, tagName
);
145 NS_ENSURE_TRUE_VOID(inputElms
);
147 uint32_t inputCount
= inputElms
->Length(false);
149 // Compute posinset and setsize.
153 for (uint32_t index
= 0; index
< inputCount
; index
++) {
154 nsIContent
* inputElm
= inputElms
->Item(index
, false);
155 if (inputElm
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
156 type
, eCaseMatters
) &&
157 inputElm
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::name
,
158 name
, eCaseMatters
) && mDoc
->HasAccessible(inputElm
)) {
160 if (inputElm
== mContent
)
165 *aPosInSet
= indexOf
;
169 ////////////////////////////////////////////////////////////////////////////////
170 // HTMLButtonAccessible
171 ////////////////////////////////////////////////////////////////////////////////
173 HTMLButtonAccessible::
174 HTMLButtonAccessible(nsIContent
* aContent
, DocAccessible
* aDoc
) :
175 HyperTextAccessibleWrap(aContent
, aDoc
)
177 mGenericTypes
|= eButton
;
181 HTMLButtonAccessible::ActionCount()
187 HTMLButtonAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
)
189 if (aIndex
== eAction_Click
)
190 aName
.AssignLiteral("press");
194 HTMLButtonAccessible::DoAction(uint8_t aIndex
)
196 if (aIndex
!= eAction_Click
)
204 HTMLButtonAccessible::State()
206 uint64_t state
= HyperTextAccessibleWrap::State();
207 if (state
== states::DEFUNCT
)
210 // Inherit states from input@type="file" suitable for the button. Note,
211 // no special processing for unavailable state since inheritance is supplied
213 if (mParent
&& mParent
->IsHTMLFileInput()) {
214 uint64_t parentState
= mParent
->State();
215 state
|= parentState
& (states::BUSY
| states::REQUIRED
|
216 states::HASPOPUP
| states::INVALID
);
223 HTMLButtonAccessible::NativeState()
225 uint64_t state
= HyperTextAccessibleWrap::NativeState();
227 EventStates elmState
= mContent
->AsElement()->State();
228 if (elmState
.HasState(NS_EVENT_STATE_DEFAULT
))
229 state
|= states::DEFAULT
;
235 HTMLButtonAccessible::NativeRole()
237 return roles::PUSHBUTTON
;
241 HTMLButtonAccessible::NativeName(nsString
& aName
)
243 // No need to check @value attribute for buttons since this attribute results
244 // in native anonymous text node and the name is calculated from subtree.
245 // The same magic works for @alt and @value attributes in case of type="image"
246 // element that has no valid @src (note if input@type="image" has an image
247 // then neither @alt nor @value attributes are used to generate a visual label
248 // and thus we need to obtain the accessible name directly from attribute
249 // value). Also the same algorithm works in case of default labels for
250 // type="submit"/"reset"/"image" elements.
252 ENameValueFlag nameFlag
= Accessible::NativeName(aName
);
253 if (!aName
.IsEmpty() || mContent
->Tag() != nsGkAtoms::input
||
254 !mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
255 nsGkAtoms::image
, eCaseMatters
))
258 if (!mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::alt
, aName
))
259 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::value
, aName
);
261 aName
.CompressWhitespace();
265 ////////////////////////////////////////////////////////////////////////////////
266 // HTMLButtonAccessible: Widgets
269 HTMLButtonAccessible::IsWidget() const
275 ////////////////////////////////////////////////////////////////////////////////
276 // HTMLTextFieldAccessible
277 ////////////////////////////////////////////////////////////////////////////////
279 HTMLTextFieldAccessible::
280 HTMLTextFieldAccessible(nsIContent
* aContent
, DocAccessible
* aDoc
) :
281 HyperTextAccessibleWrap(aContent
, aDoc
)
283 mType
= eHTMLTextFieldType
;
286 NS_IMPL_ISUPPORTS_INHERITED0(HTMLTextFieldAccessible
,
290 HTMLTextFieldAccessible::NativeRole()
292 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
293 nsGkAtoms::password
, eIgnoreCase
)) {
294 return roles::PASSWORD_TEXT
;
300 already_AddRefed
<nsIPersistentProperties
>
301 HTMLTextFieldAccessible::NativeAttributes()
303 nsCOMPtr
<nsIPersistentProperties
> attributes
=
304 HyperTextAccessibleWrap::NativeAttributes();
306 // Expose type for text input elements as it gives some useful context,
307 // especially for mobile.
309 if (mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::type
, type
))
310 nsAccUtils::SetAccAttr(attributes
, nsGkAtoms::textInputType
, type
);
312 return attributes
.forget();
316 HTMLTextFieldAccessible::NativeName(nsString
& aName
)
318 ENameValueFlag nameFlag
= Accessible::NativeName(aName
);
319 if (!aName
.IsEmpty())
322 // If part of compound of XUL widget then grab a name from XUL widget element.
323 nsIContent
* widgetElm
= XULWidgetElm();
325 XULElmName(mDoc
, widgetElm
, aName
);
327 if (!aName
.IsEmpty())
330 // text inputs and textareas might have useful placeholder text
331 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::placeholder
, aName
);
336 HTMLTextFieldAccessible::Value(nsString
& aValue
)
339 if (NativeState() & states::PROTECTED
) // Don't return password text!
342 nsCOMPtr
<nsIDOMHTMLTextAreaElement
> textArea(do_QueryInterface(mContent
));
344 textArea
->GetValue(aValue
);
348 HTMLInputElement
* input
= HTMLInputElement::FromContent(mContent
);
350 input
->GetValue(aValue
);
354 HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState
) const
356 HyperTextAccessibleWrap::ApplyARIAState(aState
);
357 aria::MapToState(aria::eARIAAutoComplete
, mContent
->AsElement(), aState
);
359 // If part of compound of XUL widget then pick up ARIA stuff from XUL widget
361 nsIContent
* widgetElm
= XULWidgetElm();
363 aria::MapToState(aria::eARIAAutoComplete
, widgetElm
->AsElement(), aState
);
367 HTMLTextFieldAccessible::NativeState()
369 uint64_t state
= HyperTextAccessibleWrap::NativeState();
371 // Text fields are always editable, even if they are also read only or
373 state
|= states::EDITABLE
;
375 // can be focusable, focused, protected. readonly, unavailable, selected
376 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
377 nsGkAtoms::password
, eIgnoreCase
)) {
378 state
|= states::PROTECTED
;
381 if (mContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::readonly
)) {
382 state
|= states::READONLY
;
385 // Is it an <input> or a <textarea> ?
386 HTMLInputElement
* input
= HTMLInputElement::FromContent(mContent
);
387 state
|= input
&& input
->IsSingleLineTextControl() ?
388 states::SINGLE_LINE
: states::MULTI_LINE
;
390 if (state
& (states::PROTECTED
| states::MULTI_LINE
| states::READONLY
|
391 states::UNAVAILABLE
))
394 // Expose autocomplete states if this input is part of autocomplete widget.
395 Accessible
* widget
= ContainerWidget();
396 if (widget
&& widget
-IsAutoComplete()) {
397 state
|= states::HASPOPUP
| states::SUPPORTS_AUTOCOMPLETION
;
401 // Expose autocomplete state if it has associated autocomplete list.
402 if (mContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::list
))
403 return state
| states::SUPPORTS_AUTOCOMPLETION
| states::HASPOPUP
;
405 // Ordinal XUL textboxes don't support autocomplete.
406 if (!XULWidgetElm() && Preferences::GetBool("browser.formfill.enable")) {
407 // Check to see if autocompletion is allowed on this input. We don't expose
408 // it for password fields even though the entire password can be remembered
409 // for a page if the user asks it to be. However, the kind of autocomplete
410 // we're talking here is based on what the user types, where a popup of
411 // possible choices comes up.
412 nsAutoString autocomplete
;
413 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::autocomplete
,
416 if (!autocomplete
.LowerCaseEqualsLiteral("off")) {
417 nsIContent
* formContent
= input
->GetFormElement();
419 formContent
->GetAttr(kNameSpaceID_None
,
420 nsGkAtoms::autocomplete
, autocomplete
);
423 if (!formContent
|| !autocomplete
.LowerCaseEqualsLiteral("off"))
424 state
|= states::SUPPORTS_AUTOCOMPLETION
;
432 HTMLTextFieldAccessible::ActionCount()
438 HTMLTextFieldAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
)
440 if (aIndex
== eAction_Click
)
441 aName
.AssignLiteral("activate");
445 HTMLTextFieldAccessible::DoAction(uint8_t aIndex
)
454 already_AddRefed
<nsIEditor
>
455 HTMLTextFieldAccessible::GetEditor() const
457 nsCOMPtr
<nsIDOMNSEditableElement
> editableElt(do_QueryInterface(mContent
));
461 // nsGenericHTMLElement::GetEditor has a security check.
462 // Make sure we're not restricted by the permissions of
463 // whatever script is currently running.
464 mozilla::dom::AutoNoJSAPI nojsapi
;
466 nsCOMPtr
<nsIEditor
> editor
;
467 editableElt
->GetEditor(getter_AddRefs(editor
));
469 return editor
.forget();
472 ////////////////////////////////////////////////////////////////////////////////
473 // HTMLTextFieldAccessible: Widgets
476 HTMLTextFieldAccessible::IsWidget() const
482 HTMLTextFieldAccessible::ContainerWidget() const
484 return mParent
&& mParent
->Role() == roles::AUTOCOMPLETE
? mParent
: nullptr;
488 ////////////////////////////////////////////////////////////////////////////////
489 // HTMLFileInputAccessible
490 ////////////////////////////////////////////////////////////////////////////////
492 HTMLFileInputAccessible::
493 HTMLFileInputAccessible(nsIContent
* aContent
, DocAccessible
* aDoc
) :
494 HyperTextAccessibleWrap(aContent
, aDoc
)
496 mType
= eHTMLFileInputType
;
500 HTMLFileInputAccessible::NativeRole()
502 // JAWS wants a text container, others don't mind. No specific role in
504 return roles::TEXT_CONTAINER
;
508 HTMLFileInputAccessible::HandleAccEvent(AccEvent
* aEvent
)
510 nsresult rv
= HyperTextAccessibleWrap::HandleAccEvent(aEvent
);
511 NS_ENSURE_SUCCESS(rv
, rv
);
513 // Redirect state change events for inherited states to child controls. Note,
514 // unavailable state is not redirected. That's a standard for unavailable
516 AccStateChangeEvent
* event
= downcast_accEvent(aEvent
);
518 (event
->GetState() == states::BUSY
||
519 event
->GetState() == states::REQUIRED
||
520 event
->GetState() == states::HASPOPUP
||
521 event
->GetState() == states::INVALID
)) {
522 Accessible
* button
= GetChildAt(0);
523 if (button
&& button
->Role() == roles::PUSHBUTTON
) {
524 nsRefPtr
<AccStateChangeEvent
> childEvent
=
525 new AccStateChangeEvent(button
, event
->GetState(),
526 event
->IsStateEnabled(), event
->FromUserInput());
527 nsEventShell::FireEvent(childEvent
);
535 ////////////////////////////////////////////////////////////////////////////////
536 // HTMLSpinnerAccessible
537 ////////////////////////////////////////////////////////////////////////////////
540 HTMLSpinnerAccessible::NativeRole()
542 return roles::SPINBUTTON
;
546 HTMLSpinnerAccessible::Value(nsString
& aValue
)
548 AccessibleWrap::Value(aValue
);
549 if (!aValue
.IsEmpty())
552 HTMLInputElement::FromContent(mContent
)->GetValue(aValue
);
556 HTMLSpinnerAccessible::MaxValue() const
558 double value
= AccessibleWrap::MaxValue();
562 return HTMLInputElement::FromContent(mContent
)->GetMaximum().toDouble();
567 HTMLSpinnerAccessible::MinValue() const
569 double value
= AccessibleWrap::MinValue();
573 return HTMLInputElement::FromContent(mContent
)->GetMinimum().toDouble();
577 HTMLSpinnerAccessible::Step() const
579 double value
= AccessibleWrap::Step();
583 return HTMLInputElement::FromContent(mContent
)->GetStep().toDouble();
587 HTMLSpinnerAccessible::CurValue() const
589 double value
= AccessibleWrap::CurValue();
593 return HTMLInputElement::FromContent(mContent
)->GetValueAsDecimal().toDouble();
597 HTMLSpinnerAccessible::SetCurValue(double aValue
)
600 HTMLInputElement::FromContent(mContent
)->SetValueAsNumber(aValue
, er
);
605 ////////////////////////////////////////////////////////////////////////////////
606 // HTMLRangeAccessible
607 ////////////////////////////////////////////////////////////////////////////////
610 HTMLRangeAccessible::NativeRole()
612 return roles::SLIDER
;
616 HTMLRangeAccessible::IsWidget() const
622 HTMLRangeAccessible::Value(nsString
& aValue
)
624 LeafAccessible::Value(aValue
);
625 if (!aValue
.IsEmpty())
628 HTMLInputElement::FromContent(mContent
)->GetValue(aValue
);
632 HTMLRangeAccessible::MaxValue() const
634 double value
= LeafAccessible::MaxValue();
638 return HTMLInputElement::FromContent(mContent
)->GetMaximum().toDouble();
642 HTMLRangeAccessible::MinValue() const
644 double value
= LeafAccessible::MinValue();
648 return HTMLInputElement::FromContent(mContent
)->GetMinimum().toDouble();
652 HTMLRangeAccessible::Step() const
654 double value
= LeafAccessible::Step();
658 return HTMLInputElement::FromContent(mContent
)->GetStep().toDouble();
662 HTMLRangeAccessible::CurValue() const
664 double value
= LeafAccessible::CurValue();
668 return HTMLInputElement::FromContent(mContent
)->GetValueAsDecimal().toDouble();
672 HTMLRangeAccessible::SetCurValue(double aValue
)
675 HTMLInputElement::FromContent(mContent
)->SetValueAsNumber(aValue
, er
);
680 ////////////////////////////////////////////////////////////////////////////////
681 // HTMLGroupboxAccessible
682 ////////////////////////////////////////////////////////////////////////////////
684 HTMLGroupboxAccessible::
685 HTMLGroupboxAccessible(nsIContent
* aContent
, DocAccessible
* aDoc
) :
686 HyperTextAccessibleWrap(aContent
, aDoc
)
691 HTMLGroupboxAccessible::NativeRole()
693 return roles::GROUPING
;
697 HTMLGroupboxAccessible::GetLegend() const
699 for (nsIContent
* legendContent
= mContent
->GetFirstChild(); legendContent
;
700 legendContent
= legendContent
->GetNextSibling()) {
701 if (legendContent
->NodeInfo()->Equals(nsGkAtoms::legend
,
702 mContent
->GetNameSpaceID())) {
703 // Either XHTML namespace or no namespace
704 return legendContent
;
712 HTMLGroupboxAccessible::NativeName(nsString
& aName
)
714 ENameValueFlag nameFlag
= Accessible::NativeName(aName
);
715 if (!aName
.IsEmpty())
718 nsIContent
* legendContent
= GetLegend();
720 nsTextEquivUtils::AppendTextEquivFromContent(this, legendContent
, &aName
);
726 HTMLGroupboxAccessible::RelationByType(RelationType aType
)
728 Relation rel
= HyperTextAccessibleWrap::RelationByType(aType
);
729 // No override for label, so use <legend> for this <fieldset>
730 if (aType
== RelationType::LABELLED_BY
)
731 rel
.AppendTarget(mDoc
, GetLegend());
736 ////////////////////////////////////////////////////////////////////////////////
737 // HTMLLegendAccessible
738 ////////////////////////////////////////////////////////////////////////////////
740 HTMLLegendAccessible::
741 HTMLLegendAccessible(nsIContent
* aContent
, DocAccessible
* aDoc
) :
742 HyperTextAccessibleWrap(aContent
, aDoc
)
747 HTMLLegendAccessible::RelationByType(RelationType aType
)
749 Relation rel
= HyperTextAccessibleWrap::RelationByType(aType
);
750 if (aType
!= RelationType::LABEL_FOR
)
753 Accessible
* groupbox
= Parent();
754 if (groupbox
&& groupbox
->Role() == roles::GROUPING
)
755 rel
.AppendTarget(groupbox
);
761 HTMLLegendAccessible::NativeRole()
766 ////////////////////////////////////////////////////////////////////////////////
767 // HTMLFigureAccessible
768 ////////////////////////////////////////////////////////////////////////////////
770 HTMLFigureAccessible::
771 HTMLFigureAccessible(nsIContent
* aContent
, DocAccessible
* aDoc
) :
772 HyperTextAccessibleWrap(aContent
, aDoc
)
776 already_AddRefed
<nsIPersistentProperties
>
777 HTMLFigureAccessible::NativeAttributes()
779 nsCOMPtr
<nsIPersistentProperties
> attributes
=
780 HyperTextAccessibleWrap::NativeAttributes();
782 // Expose figure xml-role.
783 nsAccUtils::SetAccAttr(attributes
, nsGkAtoms::xmlroles
,
784 NS_LITERAL_STRING("figure"));
785 return attributes
.forget();
789 HTMLFigureAccessible::NativeRole()
791 return roles::FIGURE
;
795 HTMLFigureAccessible::NativeName(nsString
& aName
)
797 ENameValueFlag nameFlag
= HyperTextAccessibleWrap::NativeName(aName
);
798 if (!aName
.IsEmpty())
801 nsIContent
* captionContent
= Caption();
803 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent
, &aName
);
809 HTMLFigureAccessible::RelationByType(RelationType aType
)
811 Relation rel
= HyperTextAccessibleWrap::RelationByType(aType
);
812 if (aType
== RelationType::LABELLED_BY
)
813 rel
.AppendTarget(mDoc
, Caption());
819 HTMLFigureAccessible::Caption() const
821 for (nsIContent
* childContent
= mContent
->GetFirstChild(); childContent
;
822 childContent
= childContent
->GetNextSibling()) {
823 if (childContent
->NodeInfo()->Equals(nsGkAtoms::figcaption
,
824 mContent
->GetNameSpaceID())) {
832 ////////////////////////////////////////////////////////////////////////////////
833 // HTMLFigcaptionAccessible
834 ////////////////////////////////////////////////////////////////////////////////
836 HTMLFigcaptionAccessible::
837 HTMLFigcaptionAccessible(nsIContent
* aContent
, DocAccessible
* aDoc
) :
838 HyperTextAccessibleWrap(aContent
, aDoc
)
843 HTMLFigcaptionAccessible::NativeRole()
845 return roles::CAPTION
;
849 HTMLFigcaptionAccessible::RelationByType(RelationType aType
)
851 Relation rel
= HyperTextAccessibleWrap::RelationByType(aType
);
852 if (aType
!= RelationType::LABEL_FOR
)
855 Accessible
* figure
= Parent();
857 figure
->GetContent()->NodeInfo()->Equals(nsGkAtoms::figure
,
858 mContent
->GetNameSpaceID())) {
859 rel
.AppendTarget(figure
);