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/. */
5 
6 #include "HTMLElementAccessibles.h"
7 
8 #include "DocAccessible.h"
9 #include "nsAccUtils.h"
10 #include "nsTextEquivUtils.h"
11 #include "Relation.h"
12 #include "Role.h"
13 #include "States.h"
14 
15 #include "mozilla/dom/HTMLLabelElement.h"
16 #include "mozilla/dom/HTMLDetailsElement.h"
17 #include "mozilla/dom/HTMLSummaryElement.h"
18 
19 using namespace mozilla::a11y;
20 
21 ////////////////////////////////////////////////////////////////////////////////
22 // HTMLHRAccessible
23 ////////////////////////////////////////////////////////////////////////////////
24 
NativeRole() const25 role HTMLHRAccessible::NativeRole() const { return roles::SEPARATOR; }
26 
27 ////////////////////////////////////////////////////////////////////////////////
28 // HTMLBRAccessible
29 ////////////////////////////////////////////////////////////////////////////////
30 
NativeRole() const31 role HTMLBRAccessible::NativeRole() const { return roles::WHITESPACE; }
32 
NativeState() const33 uint64_t HTMLBRAccessible::NativeState() const { return states::READONLY; }
34 
NativeName(nsString & aName) const35 ENameValueFlag HTMLBRAccessible::NativeName(nsString& aName) const {
36   aName = static_cast<char16_t>('\n');  // Newline char
37   return eNameOK;
38 }
39 
40 ////////////////////////////////////////////////////////////////////////////////
41 // HTMLLabelAccessible
42 ////////////////////////////////////////////////////////////////////////////////
43 
NativeName(nsString & aName) const44 ENameValueFlag HTMLLabelAccessible::NativeName(nsString& aName) const {
45   nsTextEquivUtils::GetNameFromSubtree(this, aName);
46   return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
47 }
48 
RelationByType(RelationType aType) const49 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());
54   }
55 
56   return rel;
57 }
58 
ActionCount() const59 uint8_t HTMLLabelAccessible::ActionCount() const {
60   return nsCoreUtils::IsLabelWithControl(mContent) ? 1 : 0;
61 }
62 
ActionNameAt(uint8_t aIndex,nsAString & aName)63 void HTMLLabelAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
64   if (aIndex == 0) {
65     if (nsCoreUtils::IsLabelWithControl(mContent)) aName.AssignLiteral("click");
66   }
67 }
68 
DoAction(uint8_t aIndex) const69 bool HTMLLabelAccessible::DoAction(uint8_t aIndex) const {
70   if (aIndex != 0) return false;
71 
72   DoCommand();
73   return true;
74 }
75 
76 ////////////////////////////////////////////////////////////////////////////////
77 // nsHTMLOuputAccessible
78 ////////////////////////////////////////////////////////////////////////////////
79 
RelationByType(RelationType aType) const80 Relation HTMLOutputAccessible::RelationByType(RelationType aType) const {
81   Relation rel = AccessibleWrap::RelationByType(aType);
82   if (aType == RelationType::CONTROLLED_BY) {
83     rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::_for));
84   }
85 
86   return rel;
87 }
88 
89 ////////////////////////////////////////////////////////////////////////////////
90 // HTMLSummaryAccessible
91 ////////////////////////////////////////////////////////////////////////////////
92 
HTMLSummaryAccessible(nsIContent * aContent,DocAccessible * aDoc)93 HTMLSummaryAccessible::HTMLSummaryAccessible(nsIContent* aContent,
94                                              DocAccessible* aDoc)
95     : HyperTextAccessibleWrap(aContent, aDoc) {
96   mGenericTypes |= eButton;
97 }
98 
ActionCount() const99 uint8_t HTMLSummaryAccessible::ActionCount() const { return 1; }
100 
ActionNameAt(uint8_t aIndex,nsAString & aName)101 void HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
102   if (aIndex != eAction_Click) {
103     return;
104   }
105 
106   dom::HTMLSummaryElement* summary =
107       dom::HTMLSummaryElement::FromNode(mContent);
108   if (!summary) {
109     return;
110   }
111 
112   dom::HTMLDetailsElement* details = summary->GetDetails();
113   if (!details) {
114     return;
115   }
116 
117   if (details->Open()) {
118     aName.AssignLiteral("collapse");
119   } else {
120     aName.AssignLiteral("expand");
121   }
122 }
123 
DoAction(uint8_t aIndex) const124 bool HTMLSummaryAccessible::DoAction(uint8_t aIndex) const {
125   if (aIndex != eAction_Click) return false;
126 
127   DoCommand();
128   return true;
129 }
130 
NativeState() const131 uint64_t HTMLSummaryAccessible::NativeState() const {
132   uint64_t state = HyperTextAccessibleWrap::NativeState();
133 
134   dom::HTMLSummaryElement* summary =
135       dom::HTMLSummaryElement::FromNode(mContent);
136   if (!summary) {
137     return state;
138   }
139 
140   dom::HTMLDetailsElement* details = summary->GetDetails();
141   if (!details) {
142     return state;
143   }
144 
145   if (details->Open()) {
146     state |= states::EXPANDED;
147   } else {
148     state |= states::COLLAPSED;
149   }
150 
151   return state;
152 }
153 
FromDetails(LocalAccessible * details)154 HTMLSummaryAccessible* HTMLSummaryAccessible::FromDetails(
155     LocalAccessible* details) {
156   if (!dom::HTMLDetailsElement::FromNodeOrNull(details->GetContent())) {
157     return nullptr;
158   }
159 
160   HTMLSummaryAccessible* summaryAccessible = nullptr;
161   for (uint32_t i = 0; i < details->ChildCount(); i++) {
162     // Iterate through the children of our details accessible to locate main
163     // summary. This iteration includes the anonymous summary if the details
164     // element was not explicitly created with one.
165     LocalAccessible* child = details->LocalChildAt(i);
166     auto* summary =
167         mozilla::dom::HTMLSummaryElement::FromNodeOrNull(child->GetContent());
168     if (summary && summary->IsMainSummary()) {
169       summaryAccessible = static_cast<HTMLSummaryAccessible*>(child);
170       break;
171     }
172   }
173 
174   return summaryAccessible;
175 }
176 
177 ////////////////////////////////////////////////////////////////////////////////
178 // HTMLSummaryAccessible: Widgets
179 
IsWidget() const180 bool HTMLSummaryAccessible::IsWidget() const { return true; }
181 
182 ////////////////////////////////////////////////////////////////////////////////
183 // HTMLHeaderOrFooterAccessible
184 ////////////////////////////////////////////////////////////////////////////////
185 
NativeRole() const186 role HTMLHeaderOrFooterAccessible::NativeRole() const {
187   // Only map header and footer if they are direct descendants of the body tag.
188   // If other sectioning or sectioning root elements, they become sections.
189   nsIContent* parent = mContent->GetParent();
190   while (parent) {
191     if (parent->IsAnyOfHTMLElements(
192             nsGkAtoms::article, nsGkAtoms::aside, nsGkAtoms::nav,
193             nsGkAtoms::section, nsGkAtoms::main, nsGkAtoms::blockquote,
194             nsGkAtoms::details, nsGkAtoms::dialog, nsGkAtoms::fieldset,
195             nsGkAtoms::figure, nsGkAtoms::td)) {
196       break;
197     }
198     parent = parent->GetParent();
199   }
200 
201   // No sectioning or sectioning root elements found.
202   if (!parent) {
203     return roles::LANDMARK;
204   }
205 
206   return roles::SECTION;
207 }
208 
LandmarkRole() const209 nsAtom* HTMLHeaderOrFooterAccessible::LandmarkRole() const {
210   if (!HasOwnContent()) return nullptr;
211 
212   a11y::role r = const_cast<HTMLHeaderOrFooterAccessible*>(this)->Role();
213   if (r == roles::LANDMARK) {
214     if (mContent->IsHTMLElement(nsGkAtoms::header)) {
215       return nsGkAtoms::banner;
216     }
217 
218     if (mContent->IsHTMLElement(nsGkAtoms::footer)) {
219       return nsGkAtoms::contentinfo;
220     }
221   }
222 
223   return HyperTextAccessibleWrap::LandmarkRole();
224 }
225 
226 ////////////////////////////////////////////////////////////////////////////////
227 // HTMLSectionAccessible
228 ////////////////////////////////////////////////////////////////////////////////
229 
NativeRole() const230 role HTMLSectionAccessible::NativeRole() const {
231   nsAutoString name;
232   const_cast<HTMLSectionAccessible*>(this)->Name(name);
233   return name.IsEmpty() ? roles::SECTION : roles::REGION;
234 }
235 
LandmarkRole() const236 nsAtom* HTMLSectionAccessible::LandmarkRole() const {
237   if (!HasOwnContent()) {
238     return nullptr;
239   }
240 
241   // Only return xml-roles "region" if the section has an accessible name.
242   nsAutoString name;
243   const_cast<HTMLSectionAccessible*>(this)->Name(name);
244   return name.IsEmpty() ? HyperTextAccessibleWrap::LandmarkRole()
245                         : nsGkAtoms::region;
246 }
247