1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8 #include "nsTextEquivUtils.h"
9
10 #include "Accessible-inl.h"
11 #include "AccIterator.h"
12 #include "nsCoreUtils.h"
13 #include "mozilla/dom/Text.h"
14
15 using namespace mozilla;
16 using namespace mozilla::a11y;
17
18 /**
19 * The accessible for which we are computing a text equivalent. It is useful
20 * for bailing out during recursive text computation, or for special cases
21 * like step f. of the ARIA implementation guide.
22 */
23 static const Accessible* sInitiatorAcc = nullptr;
24
25 ////////////////////////////////////////////////////////////////////////////////
26 // nsTextEquivUtils. Public.
27
GetNameFromSubtree(const Accessible * aAccessible,nsAString & aName)28 nsresult nsTextEquivUtils::GetNameFromSubtree(const Accessible* aAccessible,
29 nsAString& aName) {
30 aName.Truncate();
31
32 if (sInitiatorAcc) return NS_OK;
33
34 sInitiatorAcc = aAccessible;
35 if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
36 // XXX: is it necessary to care the accessible is not a document?
37 if (aAccessible->IsContent()) {
38 nsAutoString name;
39 AppendFromAccessibleChildren(aAccessible, &name);
40 name.CompressWhitespace();
41 if (!nsCoreUtils::IsWhitespaceString(name)) aName = name;
42 }
43 }
44
45 sInitiatorAcc = nullptr;
46
47 return NS_OK;
48 }
49
GetTextEquivFromIDRefs(const Accessible * aAccessible,nsAtom * aIDRefsAttr,nsAString & aTextEquiv)50 nsresult nsTextEquivUtils::GetTextEquivFromIDRefs(const Accessible* aAccessible,
51 nsAtom* aIDRefsAttr,
52 nsAString& aTextEquiv) {
53 aTextEquiv.Truncate();
54
55 nsIContent* content = aAccessible->GetContent();
56 if (!content) return NS_OK;
57
58 nsIContent* refContent = nullptr;
59 IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
60 while ((refContent = iter.NextElem())) {
61 if (!aTextEquiv.IsEmpty()) aTextEquiv += ' ';
62
63 nsresult rv =
64 AppendTextEquivFromContent(aAccessible, refContent, &aTextEquiv);
65 NS_ENSURE_SUCCESS(rv, rv);
66 }
67
68 return NS_OK;
69 }
70
AppendTextEquivFromContent(const Accessible * aInitiatorAcc,nsIContent * aContent,nsAString * aString)71 nsresult nsTextEquivUtils::AppendTextEquivFromContent(
72 const Accessible* aInitiatorAcc, nsIContent* aContent, nsAString* aString) {
73 // Prevent recursion which can cause infinite loops.
74 if (sInitiatorAcc) return NS_OK;
75
76 sInitiatorAcc = aInitiatorAcc;
77
78 // If the given content is not visible or isn't accessible then go down
79 // through the DOM subtree otherwise go down through accessible subtree and
80 // calculate the flat string.
81 nsIFrame* frame = aContent->GetPrimaryFrame();
82 bool isVisible = frame && frame->StyleVisibility()->IsVisible();
83
84 nsresult rv = NS_ERROR_FAILURE;
85 bool goThroughDOMSubtree = true;
86
87 if (isVisible) {
88 Accessible* accessible = sInitiatorAcc->Document()->GetAccessible(aContent);
89 if (accessible) {
90 rv = AppendFromAccessible(accessible, aString);
91 goThroughDOMSubtree = false;
92 }
93 }
94
95 if (goThroughDOMSubtree) rv = AppendFromDOMNode(aContent, aString);
96
97 sInitiatorAcc = nullptr;
98 return rv;
99 }
100
AppendTextEquivFromTextContent(nsIContent * aContent,nsAString * aString)101 nsresult nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent* aContent,
102 nsAString* aString) {
103 if (aContent->IsText()) {
104 bool isHTMLBlock = false;
105
106 nsIContent* parentContent = aContent->GetFlattenedTreeParent();
107 if (parentContent) {
108 nsIFrame* frame = parentContent->GetPrimaryFrame();
109 if (frame) {
110 // If this text is inside a block level frame (as opposed to span
111 // level), we need to add spaces around that block's text, so we don't
112 // get words jammed together in final name.
113 const nsStyleDisplay* display = frame->StyleDisplay();
114 if (display->IsBlockOutsideStyle() ||
115 display->mDisplay == StyleDisplay::TableCell) {
116 isHTMLBlock = true;
117 if (!aString->IsEmpty()) {
118 aString->Append(char16_t(' '));
119 }
120 }
121 }
122 }
123
124 if (aContent->TextLength() > 0) {
125 nsIFrame* frame = aContent->GetPrimaryFrame();
126 if (frame) {
127 nsIFrame::RenderedText text = frame->GetRenderedText(
128 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
129 nsIFrame::TrailingWhitespace::DontTrim);
130 aString->Append(text.mString);
131 } else {
132 // If aContent is an object that is display: none, we have no a frame.
133 aContent->GetAsText()->AppendTextTo(*aString);
134 }
135 if (isHTMLBlock && !aString->IsEmpty()) {
136 aString->Append(char16_t(' '));
137 }
138 }
139
140 return NS_OK;
141 }
142
143 if (aContent->IsHTMLElement() &&
144 aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
145 aString->AppendLiteral("\r\n");
146 return NS_OK;
147 }
148
149 return NS_OK_NO_NAME_CLAUSE_HANDLED;
150 }
151
152 ////////////////////////////////////////////////////////////////////////////////
153 // nsTextEquivUtils. Private.
154
AppendFromAccessibleChildren(const Accessible * aAccessible,nsAString * aString)155 nsresult nsTextEquivUtils::AppendFromAccessibleChildren(
156 const Accessible* aAccessible, nsAString* aString) {
157 nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
158
159 uint32_t childCount = aAccessible->ChildCount();
160 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
161 Accessible* child = aAccessible->GetChildAt(childIdx);
162 rv = AppendFromAccessible(child, aString);
163 NS_ENSURE_SUCCESS(rv, rv);
164 }
165
166 return rv;
167 }
168
AppendFromAccessible(Accessible * aAccessible,nsAString * aString)169 nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
170 nsAString* aString) {
171 // XXX: is it necessary to care the accessible is not a document?
172 if (aAccessible->IsContent()) {
173 nsresult rv =
174 AppendTextEquivFromTextContent(aAccessible->GetContent(), aString);
175 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return rv;
176 }
177
178 bool isEmptyTextEquiv = true;
179
180 // If the name is from tooltip then append it to result string in the end
181 // (see h. step of name computation guide).
182 nsAutoString text;
183 if (aAccessible->Name(text) != eNameFromTooltip)
184 isEmptyTextEquiv = !AppendString(aString, text);
185
186 // Implementation of f. step.
187 nsresult rv = AppendFromValue(aAccessible, aString);
188 NS_ENSURE_SUCCESS(rv, rv);
189
190 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
191
192 // Implementation of g) step of text equivalent computation guide. Go down
193 // into subtree if accessible allows "text equivalent from subtree rule" or
194 // it's not root and not control.
195 if (isEmptyTextEquiv) {
196 if (ShouldIncludeInSubtreeCalculation(aAccessible)) {
197 rv = AppendFromAccessibleChildren(aAccessible, aString);
198 NS_ENSURE_SUCCESS(rv, rv);
199
200 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
201 }
202 }
203
204 // Implementation of h. step
205 if (isEmptyTextEquiv && !text.IsEmpty()) {
206 AppendString(aString, text);
207 return NS_OK;
208 }
209
210 return rv;
211 }
212
AppendFromValue(Accessible * aAccessible,nsAString * aString)213 nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
214 nsAString* aString) {
215 if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
216 return NS_OK_NO_NAME_CLAUSE_HANDLED;
217
218 // Implementation of step f. of text equivalent computation. If the given
219 // accessible is not root accessible (the accessible the text equivalent is
220 // computed for in the end) then append accessible value. Otherwise append
221 // value if and only if the given accessible is in the middle of its parent.
222
223 nsAutoString text;
224 if (aAccessible != sInitiatorAcc) {
225 aAccessible->Value(text);
226
227 return AppendString(aString, text) ? NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
228 }
229
230 // XXX: is it necessary to care the accessible is not a document?
231 if (aAccessible->IsDoc()) return NS_ERROR_UNEXPECTED;
232
233 nsIContent* content = aAccessible->GetContent();
234
235 for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
236 childContent = childContent->GetPreviousSibling()) {
237 // check for preceding text...
238 if (!childContent->TextIsOnlyWhitespace()) {
239 for (nsIContent* siblingContent = content->GetNextSibling();
240 siblingContent; siblingContent = siblingContent->GetNextSibling()) {
241 // .. and subsequent text
242 if (!siblingContent->TextIsOnlyWhitespace()) {
243 aAccessible->Value(text);
244
245 return AppendString(aString, text) ? NS_OK
246 : NS_OK_NO_NAME_CLAUSE_HANDLED;
247 break;
248 }
249 }
250 break;
251 }
252 }
253
254 return NS_OK_NO_NAME_CLAUSE_HANDLED;
255 }
256
AppendFromDOMChildren(nsIContent * aContent,nsAString * aString)257 nsresult nsTextEquivUtils::AppendFromDOMChildren(nsIContent* aContent,
258 nsAString* aString) {
259 for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
260 childContent = childContent->GetNextSibling()) {
261 nsresult rv = AppendFromDOMNode(childContent, aString);
262 NS_ENSURE_SUCCESS(rv, rv);
263 }
264
265 return NS_OK;
266 }
267
AppendFromDOMNode(nsIContent * aContent,nsAString * aString)268 nsresult nsTextEquivUtils::AppendFromDOMNode(nsIContent* aContent,
269 nsAString* aString) {
270 nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
271 NS_ENSURE_SUCCESS(rv, rv);
272
273 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return NS_OK;
274
275 if (aContent->IsXULElement()) {
276 nsAutoString textEquivalent;
277 if (aContent->NodeInfo()->Equals(nsGkAtoms::label, kNameSpaceID_XUL)) {
278 aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
279 textEquivalent);
280 } else {
281 aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label,
282 textEquivalent);
283 }
284
285 if (textEquivalent.IsEmpty()) {
286 aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext,
287 textEquivalent);
288 }
289
290 AppendString(aString, textEquivalent);
291 }
292
293 return AppendFromDOMChildren(aContent, aString);
294 }
295
AppendString(nsAString * aString,const nsAString & aTextEquivalent)296 bool nsTextEquivUtils::AppendString(nsAString* aString,
297 const nsAString& aTextEquivalent) {
298 if (aTextEquivalent.IsEmpty()) return false;
299
300 // Insert spaces to insure that words from controls aren't jammed together.
301 if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
302 aString->Append(char16_t(' '));
303
304 aString->Append(aTextEquivalent);
305
306 if (!nsCoreUtils::IsWhitespace(aString->Last()))
307 aString->Append(char16_t(' '));
308
309 return true;
310 }
311
GetRoleRule(role aRole)312 uint32_t nsTextEquivUtils::GetRoleRule(role aRole) {
313 #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, \
314 androidClass, nameRule) \
315 case roles::geckoRole: \
316 return nameRule;
317
318 switch (aRole) {
319 #include "RoleMap.h"
320 default:
321 MOZ_CRASH("Unknown role.");
322 }
323
324 #undef ROLE
325 }
326
ShouldIncludeInSubtreeCalculation(Accessible * aAccessible)327 bool nsTextEquivUtils::ShouldIncludeInSubtreeCalculation(
328 Accessible* aAccessible) {
329 uint32_t nameRule = GetRoleRule(aAccessible->Role());
330 if (nameRule == eNameFromSubtreeRule) {
331 return true;
332 }
333 if (!(nameRule & eNameFromSubtreeIfReqRule)) {
334 return false;
335 }
336
337 if (aAccessible == sInitiatorAcc) {
338 // We're calculating the text equivalent for this accessible, but this
339 // accessible should only be included when calculating the text equivalent
340 // for something else.
341 return false;
342 }
343
344 // sInitiatorAcc can be null when, for example, Accessible::Value calls
345 // GetTextEquivFromSubtree.
346 role initiatorRole = sInitiatorAcc ? sInitiatorAcc->Role() : roles::NOTHING;
347 if (initiatorRole == roles::OUTLINEITEM &&
348 aAccessible->Role() == roles::GROUPING) {
349 // Child treeitems are contained in a group. We don't want to include those
350 // in the parent treeitem's text equivalent.
351 return false;
352 }
353
354 return true;
355 }
356