Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
8 #include "nsTextEquivUtils.h"
10 #include "Accessible-inl.h"
11 #include "AccIterator.h"
12 #include "nsCoreUtils.h"
13 #include "nsIDOMXULLabeledControlEl.h"
15 using namespace mozilla::a11y;
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;
24 ////////////////////////////////////////////////////////////////////////////////
25 // nsTextEquivUtils. Public.
27 nsresult
28 nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible,
29 nsAString& aName)
30 {
31 aName.Truncate();
33 if (sInitiatorAcc)
34 return NS_OK;
36 sInitiatorAcc = aAccessible;
37 if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
38 //XXX: is it necessary to care the accessible is not a document?
39 if (aAccessible->IsContent()) {
40 nsAutoString name;
41 AppendFromAccessibleChildren(aAccessible, &name);
42 name.CompressWhitespace();
43 if (!nsCoreUtils::IsWhitespaceString(name))
44 aName = name;
45 }
46 }
48 sInitiatorAcc = nullptr;
50 return NS_OK;
51 }
53 nsresult
54 nsTextEquivUtils::GetTextEquivFromIDRefs(Accessible* aAccessible,
55 nsIAtom *aIDRefsAttr,
56 nsAString& aTextEquiv)
57 {
58 aTextEquiv.Truncate();
60 nsIContent* content = aAccessible->GetContent();
61 if (!content)
62 return NS_OK;
64 nsIContent* refContent = nullptr;
65 IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
66 while ((refContent = iter.NextElem())) {
67 if (!aTextEquiv.IsEmpty())
68 aTextEquiv += ' ';
70 nsresult rv = AppendTextEquivFromContent(aAccessible, refContent,
71 &aTextEquiv);
72 NS_ENSURE_SUCCESS(rv, rv);
73 }
75 return NS_OK;
76 }
78 nsresult
79 nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc,
80 nsIContent *aContent,
81 nsAString *aString)
82 {
83 // Prevent recursion which can cause infinite loops.
84 if (sInitiatorAcc)
85 return NS_OK;
87 sInitiatorAcc = aInitiatorAcc;
89 // If the given content is not visible or isn't accessible then go down
90 // through the DOM subtree otherwise go down through accessible subtree and
91 // calculate the flat string.
92 nsIFrame *frame = aContent->GetPrimaryFrame();
93 bool isVisible = frame && frame->StyleVisibility()->IsVisible();
95 nsresult rv = NS_ERROR_FAILURE;
96 bool goThroughDOMSubtree = true;
98 if (isVisible) {
99 Accessible* accessible =
100 sInitiatorAcc->Document()->GetAccessible(aContent);
101 if (accessible) {
102 rv = AppendFromAccessible(accessible, aString);
103 goThroughDOMSubtree = false;
104 }
105 }
107 if (goThroughDOMSubtree)
108 rv = AppendFromDOMNode(aContent, aString);
110 sInitiatorAcc = nullptr;
111 return rv;
112 }
114 nsresult
115 nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
116 nsAString *aString)
117 {
118 if (aContent->IsNodeOfType(nsINode::eTEXT)) {
119 bool isHTMLBlock = false;
121 nsIContent *parentContent = aContent->GetFlattenedTreeParent();
122 if (parentContent) {
123 nsIFrame *frame = parentContent->GetPrimaryFrame();
124 if (frame) {
125 // If this text is inside a block level frame (as opposed to span
126 // level), we need to add spaces around that block's text, so we don't
127 // get words jammed together in final name.
128 const nsStyleDisplay* display = frame->StyleDisplay();
129 if (display->IsBlockOutsideStyle() ||
130 display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) {
131 isHTMLBlock = true;
132 if (!aString->IsEmpty()) {
133 aString->Append(char16_t(' '));
134 }
135 }
136 }
137 }
139 if (aContent->TextLength() > 0) {
140 nsIFrame *frame = aContent->GetPrimaryFrame();
141 if (frame) {
142 nsresult rv = frame->GetRenderedText(aString);
143 NS_ENSURE_SUCCESS(rv, rv);
144 } else {
145 // If aContent is an object that is display: none, we have no a frame.
146 aContent->AppendTextTo(*aString);
147 }
148 if (isHTMLBlock && !aString->IsEmpty()) {
149 aString->Append(char16_t(' '));
150 }
151 }
153 return NS_OK;
154 }
156 if (aContent->IsHTML() &&
157 aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
158 aString->AppendLiteral("\r\n");
159 return NS_OK;
160 }
162 return NS_OK_NO_NAME_CLAUSE_HANDLED;
163 }
165 ////////////////////////////////////////////////////////////////////////////////
166 // nsTextEquivUtils. Private.
168 nsresult
169 nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible,
170 nsAString *aString)
171 {
172 nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
174 uint32_t childCount = aAccessible->ChildCount();
175 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
176 Accessible* child = aAccessible->GetChildAt(childIdx);
177 rv = AppendFromAccessible(child, aString);
178 NS_ENSURE_SUCCESS(rv, rv);
179 }
181 return rv;
182 }
184 nsresult
185 nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
186 nsAString *aString)
187 {
188 //XXX: is it necessary to care the accessible is not a document?
189 if (aAccessible->IsContent()) {
190 nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(),
191 aString);
192 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
193 return rv;
194 }
196 bool isEmptyTextEquiv = true;
198 // If the name is from tooltip then append it to result string in the end
199 // (see h. step of name computation guide).
200 nsAutoString text;
201 if (aAccessible->Name(text) != eNameFromTooltip)
202 isEmptyTextEquiv = !AppendString(aString, text);
204 // Implementation of f. step.
205 nsresult rv = AppendFromValue(aAccessible, aString);
206 NS_ENSURE_SUCCESS(rv, rv);
208 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
209 isEmptyTextEquiv = false;
211 // Implementation of g) step of text equivalent computation guide. Go down
212 // into subtree if accessible allows "text equivalent from subtree rule" or
213 // it's not root and not control.
214 if (isEmptyTextEquiv) {
215 uint32_t nameRule = GetRoleRule(aAccessible->Role());
216 if (nameRule & eNameFromSubtreeIfReqRule) {
217 rv = AppendFromAccessibleChildren(aAccessible, aString);
218 NS_ENSURE_SUCCESS(rv, rv);
220 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
221 isEmptyTextEquiv = false;
222 }
223 }
225 // Implementation of h. step
226 if (isEmptyTextEquiv && !text.IsEmpty()) {
227 AppendString(aString, text);
228 return NS_OK;
229 }
231 return rv;
232 }
234 nsresult
235 nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
236 nsAString *aString)
237 {
238 if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
239 return NS_OK_NO_NAME_CLAUSE_HANDLED;
241 // Implementation of step f. of text equivalent computation. If the given
242 // accessible is not root accessible (the accessible the text equivalent is
243 // computed for in the end) then append accessible value. Otherwise append
244 // value if and only if the given accessible is in the middle of its parent.
246 nsAutoString text;
247 if (aAccessible != sInitiatorAcc) {
248 aAccessible->Value(text);
250 return AppendString(aString, text) ?
251 NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
252 }
254 //XXX: is it necessary to care the accessible is not a document?
255 if (aAccessible->IsDoc())
256 return NS_ERROR_UNEXPECTED;
258 nsIContent *content = aAccessible->GetContent();
260 for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
261 childContent = childContent->GetPreviousSibling()) {
262 // check for preceding text...
263 if (!childContent->TextIsOnlyWhitespace()) {
264 for (nsIContent* siblingContent = content->GetNextSibling(); siblingContent;
265 siblingContent = siblingContent->GetNextSibling()) {
266 // .. and subsequent text
267 if (!siblingContent->TextIsOnlyWhitespace()) {
268 aAccessible->Value(text);
270 return AppendString(aString, text) ?
271 NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
272 break;
273 }
274 }
275 break;
276 }
277 }
279 return NS_OK_NO_NAME_CLAUSE_HANDLED;
280 }
282 nsresult
283 nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent,
284 nsAString *aString)
285 {
286 for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
287 childContent = childContent->GetNextSibling()) {
288 nsresult rv = AppendFromDOMNode(childContent, aString);
289 NS_ENSURE_SUCCESS(rv, rv);
290 }
292 return NS_OK;
293 }
295 nsresult
296 nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString)
297 {
298 nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
299 NS_ENSURE_SUCCESS(rv, rv);
301 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
302 return NS_OK;
304 if (aContent->IsXUL()) {
305 nsAutoString textEquivalent;
306 nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
307 do_QueryInterface(aContent);
309 if (labeledEl) {
310 labeledEl->GetLabel(textEquivalent);
311 } else {
312 if (aContent->NodeInfo()->Equals(nsGkAtoms::label,
313 kNameSpaceID_XUL))
314 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
315 textEquivalent);
317 if (textEquivalent.IsEmpty())
318 aContent->GetAttr(kNameSpaceID_None,
319 nsGkAtoms::tooltiptext, textEquivalent);
320 }
322 AppendString(aString, textEquivalent);
323 }
325 return AppendFromDOMChildren(aContent, aString);
326 }
328 bool
329 nsTextEquivUtils::AppendString(nsAString *aString,
330 const nsAString& aTextEquivalent)
331 {
332 if (aTextEquivalent.IsEmpty())
333 return false;
335 // Insert spaces to insure that words from controls aren't jammed together.
336 if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
337 aString->Append(char16_t(' '));
339 aString->Append(aTextEquivalent);
341 if (!nsCoreUtils::IsWhitespace(aString->Last()))
342 aString->Append(char16_t(' '));
344 return true;
345 }
347 uint32_t
348 nsTextEquivUtils::GetRoleRule(role aRole)
349 {
350 #define ROLE(geckoRole, stringRole, atkRole, \
351 macRole, msaaRole, ia2Role, nameRule) \
352 case roles::geckoRole: \
353 return nameRule;
355 switch (aRole) {
356 #include "RoleMap.h"
357 default:
358 MOZ_CRASH("Unknown role.");
359 }
361 #undef ROLE
362 }