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 "nsIDOMXULLabeledControlEl.h"
14
15 using namespace mozilla::a11y;
16
17 /**
18 * The accessible for which we are computing a text equivalent. It is useful
19 * for bailing out during recursive text computation, or for special cases
20 * like step f. of the ARIA implementation guide.
21 */
22 static Accessible* sInitiatorAcc = nullptr;
23
24 ////////////////////////////////////////////////////////////////////////////////
25 // nsTextEquivUtils. Public.
26
GetNameFromSubtree(Accessible * aAccessible,nsAString & aName)27 nsresult nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible,
28 nsAString& aName) {
29 aName.Truncate();
30
31 if (sInitiatorAcc) return NS_OK;
32
33 sInitiatorAcc = aAccessible;
34 if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
35 // XXX: is it necessary to care the accessible is not a document?
36 if (aAccessible->IsContent()) {
37 nsAutoString name;
38 AppendFromAccessibleChildren(aAccessible, &name);
39 name.CompressWhitespace();
40 if (!nsCoreUtils::IsWhitespaceString(name)) aName = name;
41 }
42 }
43
44 sInitiatorAcc = nullptr;
45
46 return NS_OK;
47 }
48
GetTextEquivFromIDRefs(Accessible * aAccessible,nsAtom * aIDRefsAttr,nsAString & aTextEquiv)49 nsresult nsTextEquivUtils::GetTextEquivFromIDRefs(Accessible* aAccessible,
50 nsAtom* aIDRefsAttr,
51 nsAString& aTextEquiv) {
52 aTextEquiv.Truncate();
53
54 nsIContent* content = aAccessible->GetContent();
55 if (!content) return NS_OK;
56
57 nsIContent* refContent = nullptr;
58 IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
59 while ((refContent = iter.NextElem())) {
60 if (!aTextEquiv.IsEmpty()) aTextEquiv += ' ';
61
62 nsresult rv =
63 AppendTextEquivFromContent(aAccessible, refContent, &aTextEquiv);
64 NS_ENSURE_SUCCESS(rv, rv);
65 }
66
67 return NS_OK;
68 }
69
AppendTextEquivFromContent(Accessible * aInitiatorAcc,nsIContent * aContent,nsAString * aString)70 nsresult nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc,
71 nsIContent* aContent,
72 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->IsNodeOfType(nsINode::eTEXT)) {
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::OFFSETS_IN_CONTENT_TEXT,
129 nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
130 aString->Append(text.mString);
131 } else {
132 // If aContent is an object that is display: none, we have no a frame.
133 aContent->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(Accessible * aAccessible,nsAString * aString)155 nsresult nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible,
156 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 uint32_t nameRule = GetRoleRule(aAccessible->Role());
197 if (nameRule & eNameFromSubtreeIfReqRule) {
198 rv = AppendFromAccessibleChildren(aAccessible, aString);
199 NS_ENSURE_SUCCESS(rv, rv);
200
201 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
202 }
203 }
204
205 // Implementation of h. step
206 if (isEmptyTextEquiv && !text.IsEmpty()) {
207 AppendString(aString, text);
208 return NS_OK;
209 }
210
211 return rv;
212 }
213
AppendFromValue(Accessible * aAccessible,nsAString * aString)214 nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
215 nsAString* aString) {
216 if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
217 return NS_OK_NO_NAME_CLAUSE_HANDLED;
218
219 // Implementation of step f. of text equivalent computation. If the given
220 // accessible is not root accessible (the accessible the text equivalent is
221 // computed for in the end) then append accessible value. Otherwise append
222 // value if and only if the given accessible is in the middle of its parent.
223
224 nsAutoString text;
225 if (aAccessible != sInitiatorAcc) {
226 aAccessible->Value(text);
227
228 return AppendString(aString, text) ? NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
229 }
230
231 // XXX: is it necessary to care the accessible is not a document?
232 if (aAccessible->IsDoc()) return NS_ERROR_UNEXPECTED;
233
234 nsIContent* content = aAccessible->GetContent();
235
236 for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
237 childContent = childContent->GetPreviousSibling()) {
238 // check for preceding text...
239 if (!childContent->TextIsOnlyWhitespace()) {
240 for (nsIContent* siblingContent = content->GetNextSibling();
241 siblingContent; siblingContent = siblingContent->GetNextSibling()) {
242 // .. and subsequent text
243 if (!siblingContent->TextIsOnlyWhitespace()) {
244 aAccessible->Value(text);
245
246 return AppendString(aString, text) ? NS_OK
247 : NS_OK_NO_NAME_CLAUSE_HANDLED;
248 break;
249 }
250 }
251 break;
252 }
253 }
254
255 return NS_OK_NO_NAME_CLAUSE_HANDLED;
256 }
257
AppendFromDOMChildren(nsIContent * aContent,nsAString * aString)258 nsresult nsTextEquivUtils::AppendFromDOMChildren(nsIContent* aContent,
259 nsAString* aString) {
260 for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
261 childContent = childContent->GetNextSibling()) {
262 nsresult rv = AppendFromDOMNode(childContent, aString);
263 NS_ENSURE_SUCCESS(rv, rv);
264 }
265
266 return NS_OK;
267 }
268
AppendFromDOMNode(nsIContent * aContent,nsAString * aString)269 nsresult nsTextEquivUtils::AppendFromDOMNode(nsIContent* aContent,
270 nsAString* aString) {
271 nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
272 NS_ENSURE_SUCCESS(rv, rv);
273
274 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return NS_OK;
275
276 if (aContent->IsXULElement()) {
277 nsAutoString textEquivalent;
278 nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
279 do_QueryInterface(aContent);
280
281 if (labeledEl) {
282 labeledEl->GetLabel(textEquivalent);
283 } else {
284 if (aContent->NodeInfo()->Equals(nsGkAtoms::label, kNameSpaceID_XUL))
285 aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
286 textEquivalent);
287
288 if (textEquivalent.IsEmpty())
289 aContent->AsElement()->GetAttr(kNameSpaceID_None,
290 nsGkAtoms::tooltiptext, textEquivalent);
291 }
292
293 AppendString(aString, textEquivalent);
294 }
295
296 return AppendFromDOMChildren(aContent, aString);
297 }
298
AppendString(nsAString * aString,const nsAString & aTextEquivalent)299 bool nsTextEquivUtils::AppendString(nsAString* aString,
300 const nsAString& aTextEquivalent) {
301 if (aTextEquivalent.IsEmpty()) return false;
302
303 // Insert spaces to insure that words from controls aren't jammed together.
304 if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
305 aString->Append(char16_t(' '));
306
307 aString->Append(aTextEquivalent);
308
309 if (!nsCoreUtils::IsWhitespace(aString->Last()))
310 aString->Append(char16_t(' '));
311
312 return true;
313 }
314
GetRoleRule(role aRole)315 uint32_t nsTextEquivUtils::GetRoleRule(role aRole) {
316 #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, \
317 nameRule) \
318 case roles::geckoRole: \
319 return nameRule;
320
321 switch (aRole) {
322 #include "RoleMap.h"
323 default:
324 MOZ_CRASH("Unknown role.");
325 }
326
327 #undef ROLE
328 }
329