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
) {
91 rel
.AppendIter(new IDRefsIterator(mDoc
, mContent
, nsGkAtoms::_for
));
97 void HTMLOutputAccessible::DOMAttributeChanged(int32_t aNameSpaceID
,
100 const nsAttrValue
* aOldValue
,
101 uint64_t aOldState
) {
102 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID
, aAttribute
, aModType
,
103 aOldValue
, aOldState
);
105 if (aAttribute
== nsGkAtoms::_for
) {
106 mDoc
->QueueCacheUpdate(this, CacheDomain::Relations
);
110 ////////////////////////////////////////////////////////////////////////////////
111 // HTMLSummaryAccessible
112 ////////////////////////////////////////////////////////////////////////////////
114 HTMLSummaryAccessible::HTMLSummaryAccessible(nsIContent
* aContent
,
116 : HyperTextAccessible(aContent
, aDoc
) {
117 mGenericTypes
|= eButton
;
120 bool HTMLSummaryAccessible::HasPrimaryAction() const { return true; }
122 void HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
123 if (aIndex
!= eAction_Click
) {
127 dom::HTMLSummaryElement
* summary
=
128 dom::HTMLSummaryElement::FromNode(mContent
);
133 dom::HTMLDetailsElement
* details
= summary
->GetDetails();
138 if (details
->Open()) {
139 aName
.AssignLiteral("collapse");
141 aName
.AssignLiteral("expand");
145 uint64_t HTMLSummaryAccessible::NativeState() const {
146 uint64_t state
= HyperTextAccessible::NativeState();
148 dom::HTMLSummaryElement
* summary
=
149 dom::HTMLSummaryElement::FromNode(mContent
);
154 dom::HTMLDetailsElement
* details
= summary
->GetDetails();
159 if (details
->Open()) {
160 state
|= states::EXPANDED
;
162 state
|= states::COLLAPSED
;
168 HTMLSummaryAccessible
* HTMLSummaryAccessible::FromDetails(
169 LocalAccessible
* details
) {
170 if (!dom::HTMLDetailsElement::FromNodeOrNull(details
->GetContent())) {
174 HTMLSummaryAccessible
* summaryAccessible
= nullptr;
175 for (uint32_t i
= 0; i
< details
->ChildCount(); i
++) {
176 // Iterate through the children of our details accessible to locate main
177 // summary. This iteration includes the anonymous summary if the details
178 // element was not explicitly created with one.
179 LocalAccessible
* child
= details
->LocalChildAt(i
);
181 mozilla::dom::HTMLSummaryElement::FromNodeOrNull(child
->GetContent());
182 if (summary
&& summary
->IsMainSummary()) {
183 summaryAccessible
= static_cast<HTMLSummaryAccessible
*>(child
);
188 return summaryAccessible
;
191 ////////////////////////////////////////////////////////////////////////////////
192 // HTMLSummaryAccessible: Widgets
194 bool HTMLSummaryAccessible::IsWidget() const { return true; }
196 ////////////////////////////////////////////////////////////////////////////////
197 // HTMLHeaderOrFooterAccessible
198 ////////////////////////////////////////////////////////////////////////////////
200 role
HTMLHeaderOrFooterAccessible::NativeRole() const {
201 // Only map header and footer if they are direct descendants of the body tag.
202 // If other sectioning or sectioning root elements, they become sections.
203 nsIContent
* parent
= mContent
->GetParent();
205 if (parent
->IsAnyOfHTMLElements(
206 nsGkAtoms::article
, nsGkAtoms::aside
, nsGkAtoms::nav
,
207 nsGkAtoms::section
, nsGkAtoms::main
, nsGkAtoms::blockquote
,
208 nsGkAtoms::details
, nsGkAtoms::dialog
, nsGkAtoms::fieldset
,
209 nsGkAtoms::figure
, nsGkAtoms::td
)) {
212 parent
= parent
->GetParent();
215 // No sectioning or sectioning root elements found.
217 return roles::LANDMARK
;
220 return roles::SECTION
;
223 ////////////////////////////////////////////////////////////////////////////////
224 // HTMLAsideAccessible
225 ////////////////////////////////////////////////////////////////////////////////
227 role
HTMLAsideAccessible::NativeRole() const {
228 // Per the HTML-AAM spec, there are two cases for aside elements:
229 // 1. scoped to body or main elements -> 'complementary' role
230 // 2. scoped to sectioning content elements
231 // -> if the element has an accessible name, 'complementary' role
232 // -> otherwise, 'generic' role
233 // To implement this, walk ancestors until we find a sectioning content
234 // element, or a body/main element, then take actions based on the rules
236 nsIContent
* parent
= mContent
->GetParent();
238 if (parent
->IsAnyOfHTMLElements(nsGkAtoms::article
, nsGkAtoms::aside
,
239 nsGkAtoms::nav
, nsGkAtoms::section
)) {
240 return !NameIsEmpty() ? roles::LANDMARK
: roles::SECTION
;
242 if (parent
->IsAnyOfHTMLElements(nsGkAtoms::main
, nsGkAtoms::body
)) {
243 return roles::LANDMARK
;
245 parent
= parent
->GetParent();
248 // Fall back to landmark, though we always expect to find a body element.
249 return roles::LANDMARK
;
252 ////////////////////////////////////////////////////////////////////////////////
253 // HTMLSectionAccessible
254 ////////////////////////////////////////////////////////////////////////////////
256 role
HTMLSectionAccessible::NativeRole() const {
257 return NameIsEmpty() ? roles::SECTION
: roles::REGION
;