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 "HTMLElementAccessibles.h"
8 #include "CacheConstants.h"
9 #include "nsCoreUtils.h"
10 #include "nsTextEquivUtils.h"
12 #include "mozilla/a11y/Role.h"
15 #include "mozilla/dom/HTMLLabelElement.h"
16 #include "mozilla/dom/HTMLDetailsElement.h"
17 #include "mozilla/dom/HTMLSummaryElement.h"
19 using namespace mozilla::a11y
;
21 ////////////////////////////////////////////////////////////////////////////////
23 ////////////////////////////////////////////////////////////////////////////////
25 role
HTMLHRAccessible::NativeRole() const { return roles::SEPARATOR
; }
27 ////////////////////////////////////////////////////////////////////////////////
29 ////////////////////////////////////////////////////////////////////////////////
31 role
HTMLBRAccessible::NativeRole() const { return roles::WHITESPACE
; }
33 uint64_t HTMLBRAccessible::NativeState() const { return states::READONLY
; }
35 ENameValueFlag
HTMLBRAccessible::NativeName(nsString
& aName
) const {
36 aName
= static_cast<char16_t
>('\n'); // Newline char
40 ////////////////////////////////////////////////////////////////////////////////
41 // HTMLLabelAccessible
42 ////////////////////////////////////////////////////////////////////////////////
44 ENameValueFlag
HTMLLabelAccessible::NativeName(nsString
& aName
) const {
45 nsTextEquivUtils::GetNameFromSubtree(this, aName
);
46 return aName
.IsEmpty() ? eNameOK
: eNameFromSubtree
;
49 Relation
HTMLLabelAccessible::RelationByType(RelationType aType
) const {
50 Relation rel
= AccessibleWrap::RelationByType(aType
);
51 if (aType
== RelationType::LABEL_FOR
) {
52 dom::HTMLLabelElement
* label
= dom::HTMLLabelElement::FromNode(mContent
);
53 rel
.AppendTarget(mDoc
, label
->GetControl());
59 void HTMLLabelAccessible::DOMAttributeChanged(int32_t aNameSpaceID
,
62 const nsAttrValue
* aOldValue
,
64 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID
, aAttribute
, aModType
,
65 aOldValue
, aOldState
);
67 if (aAttribute
== nsGkAtoms::_for
) {
68 mDoc
->QueueCacheUpdate(this, CacheDomain::Relations
| CacheDomain::Actions
);
72 bool HTMLLabelAccessible::HasPrimaryAction() const {
73 return nsCoreUtils::IsLabelWithControl(mContent
);
76 void HTMLLabelAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
78 if (HasPrimaryAction()) {
79 aName
.AssignLiteral("click");
84 ////////////////////////////////////////////////////////////////////////////////
85 // nsHTMLOuputAccessible
86 ////////////////////////////////////////////////////////////////////////////////
88 Relation
HTMLOutputAccessible::RelationByType(RelationType aType
) const {
89 Relation rel
= AccessibleWrap::RelationByType(aType
);
90 if (aType
== RelationType::CONTROLLED_BY
) {
92 new AssociatedElementsIterator(mDoc
, mContent
, nsGkAtoms::_for
));
98 void HTMLOutputAccessible::DOMAttributeChanged(int32_t aNameSpaceID
,
101 const nsAttrValue
* aOldValue
,
102 uint64_t aOldState
) {
103 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID
, aAttribute
, aModType
,
104 aOldValue
, aOldState
);
106 if (aAttribute
== nsGkAtoms::_for
) {
107 mDoc
->QueueCacheUpdate(this, CacheDomain::Relations
);
111 ////////////////////////////////////////////////////////////////////////////////
112 // HTMLSummaryAccessible
113 ////////////////////////////////////////////////////////////////////////////////
115 HTMLSummaryAccessible::HTMLSummaryAccessible(nsIContent
* aContent
,
117 : HyperTextAccessible(aContent
, aDoc
) {
118 mGenericTypes
|= eButton
;
121 bool HTMLSummaryAccessible::HasPrimaryAction() const { return true; }
123 void HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
124 if (aIndex
!= eAction_Click
) {
128 dom::HTMLSummaryElement
* summary
=
129 dom::HTMLSummaryElement::FromNode(mContent
);
134 dom::HTMLDetailsElement
* details
= summary
->GetDetails();
139 if (details
->Open()) {
140 aName
.AssignLiteral("collapse");
142 aName
.AssignLiteral("expand");
146 uint64_t HTMLSummaryAccessible::NativeState() const {
147 uint64_t state
= HyperTextAccessible::NativeState();
149 dom::HTMLSummaryElement
* summary
=
150 dom::HTMLSummaryElement::FromNode(mContent
);
155 dom::HTMLDetailsElement
* details
= summary
->GetDetails();
160 if (details
->Open()) {
161 state
|= states::EXPANDED
;
163 state
|= states::COLLAPSED
;
169 HTMLSummaryAccessible
* HTMLSummaryAccessible::FromDetails(
170 LocalAccessible
* details
) {
171 if (!dom::HTMLDetailsElement::FromNodeOrNull(details
->GetContent())) {
175 HTMLSummaryAccessible
* summaryAccessible
= nullptr;
176 for (uint32_t i
= 0; i
< details
->ChildCount(); i
++) {
177 // Iterate through the children of our details accessible to locate main
178 // summary. This iteration includes the anonymous summary if the details
179 // element was not explicitly created with one.
180 LocalAccessible
* child
= details
->LocalChildAt(i
);
182 mozilla::dom::HTMLSummaryElement::FromNodeOrNull(child
->GetContent());
183 if (summary
&& summary
->IsMainSummary()) {
184 summaryAccessible
= static_cast<HTMLSummaryAccessible
*>(child
);
189 return summaryAccessible
;
192 ////////////////////////////////////////////////////////////////////////////////
193 // HTMLSummaryAccessible: Widgets
195 bool HTMLSummaryAccessible::IsWidget() const { return true; }
197 ////////////////////////////////////////////////////////////////////////////////
198 // HTMLHeaderOrFooterAccessible
199 ////////////////////////////////////////////////////////////////////////////////
201 role
HTMLHeaderOrFooterAccessible::NativeRole() const {
202 // Only map header and footer if they are direct descendants of the body tag.
203 // If other sectioning or sectioning root elements, they become sections.
204 nsIContent
* parent
= mContent
->GetParent();
206 if (parent
->IsAnyOfHTMLElements(
207 nsGkAtoms::article
, nsGkAtoms::aside
, nsGkAtoms::nav
,
208 nsGkAtoms::section
, nsGkAtoms::main
, nsGkAtoms::blockquote
,
209 nsGkAtoms::details
, nsGkAtoms::dialog
, nsGkAtoms::fieldset
,
210 nsGkAtoms::figure
, nsGkAtoms::td
)) {
213 parent
= parent
->GetParent();
216 // No sectioning or sectioning root elements found.
218 return roles::LANDMARK
;
221 return roles::SECTION
;
224 ////////////////////////////////////////////////////////////////////////////////
225 // HTMLAsideAccessible
226 ////////////////////////////////////////////////////////////////////////////////
228 role
HTMLAsideAccessible::NativeRole() const {
229 // Per the HTML-AAM spec, there are two cases for aside elements:
230 // 1. scoped to body or main elements -> 'complementary' role
231 // 2. scoped to sectioning content elements
232 // -> if the element has an accessible name, 'complementary' role
233 // -> otherwise, 'generic' role
234 // To implement this, walk ancestors until we find a sectioning content
235 // element, or a body/main element, then take actions based on the rules
237 nsIContent
* parent
= mContent
->GetParent();
239 if (parent
->IsAnyOfHTMLElements(nsGkAtoms::article
, nsGkAtoms::aside
,
240 nsGkAtoms::nav
, nsGkAtoms::section
)) {
241 return !NameIsEmpty() ? roles::LANDMARK
: roles::SECTION
;
243 if (parent
->IsAnyOfHTMLElements(nsGkAtoms::main
, nsGkAtoms::body
)) {
244 return roles::LANDMARK
;
246 parent
= parent
->GetParent();
249 // Fall back to landmark, though we always expect to find a body element.
250 return roles::LANDMARK
;
253 ////////////////////////////////////////////////////////////////////////////////
254 // HTMLSectionAccessible
255 ////////////////////////////////////////////////////////////////////////////////
257 role
HTMLSectionAccessible::NativeRole() const {
258 return NameIsEmpty() ? roles::SECTION
: roles::REGION
;