1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:expandtab:shiftwidth=2:tabstop=2:
3 : */
4 : /* ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is mozilla.org code.
18 : *
19 : * The Initial Developer of the Original Code is Mozilla Foundation
20 : * Portions created by the Initial Developer are Copyright (C) 2008
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Alexander Surkov <surkov.alexander@gmail.com> (original author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsTextEquivUtils.h"
41 :
42 : #include "AccIterator.h"
43 : #include "nsAccessibilityService.h"
44 : #include "nsAccessible.h"
45 : #include "nsAccUtils.h"
46 :
47 : #include "nsIDOMXULLabeledControlEl.h"
48 :
49 : #include "nsArrayUtils.h"
50 :
51 : #define NS_OK_NO_NAME_CLAUSE_HANDLED \
52 : NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x24)
53 :
54 : ////////////////////////////////////////////////////////////////////////////////
55 : // nsTextEquivUtils. Public.
56 :
57 : nsresult
58 0 : nsTextEquivUtils::GetNameFromSubtree(nsAccessible *aAccessible,
59 : nsAString& aName)
60 : {
61 0 : aName.Truncate();
62 :
63 0 : if (gInitiatorAcc)
64 0 : return NS_OK;
65 :
66 0 : gInitiatorAcc = aAccessible;
67 :
68 0 : PRUint32 nameRule = gRoleToNameRulesMap[aAccessible->Role()];
69 0 : if (nameRule == eFromSubtree) {
70 : //XXX: is it necessary to care the accessible is not a document?
71 0 : if (aAccessible->IsContent()) {
72 0 : nsAutoString name;
73 0 : AppendFromAccessibleChildren(aAccessible, &name);
74 0 : name.CompressWhitespace();
75 0 : if (!IsWhitespaceString(name))
76 0 : aName = name;
77 : }
78 : }
79 :
80 0 : gInitiatorAcc = nsnull;
81 :
82 0 : return NS_OK;
83 : }
84 :
85 : nsresult
86 0 : nsTextEquivUtils::GetTextEquivFromIDRefs(nsAccessible *aAccessible,
87 : nsIAtom *aIDRefsAttr,
88 : nsAString& aTextEquiv)
89 : {
90 0 : aTextEquiv.Truncate();
91 :
92 0 : nsIContent* content = aAccessible->GetContent();
93 0 : if (!content)
94 0 : return NS_OK;
95 :
96 0 : nsIContent* refContent = nsnull;
97 0 : IDRefsIterator iter(content, aIDRefsAttr);
98 0 : while ((refContent = iter.NextElem())) {
99 0 : if (!aTextEquiv.IsEmpty())
100 0 : aTextEquiv += ' ';
101 :
102 : nsresult rv = AppendTextEquivFromContent(aAccessible, refContent,
103 0 : &aTextEquiv);
104 0 : NS_ENSURE_SUCCESS(rv, rv);
105 : }
106 :
107 0 : return NS_OK;
108 : }
109 :
110 : nsresult
111 0 : nsTextEquivUtils::AppendTextEquivFromContent(nsAccessible *aInitiatorAcc,
112 : nsIContent *aContent,
113 : nsAString *aString)
114 : {
115 : // Prevent recursion which can cause infinite loops.
116 0 : if (gInitiatorAcc)
117 0 : return NS_OK;
118 :
119 0 : gInitiatorAcc = aInitiatorAcc;
120 :
121 0 : nsIPresShell* shell = nsCoreUtils::GetPresShellFor(aContent);
122 0 : if (!shell) {
123 : NS_ASSERTION(true, "There is no presshell!");
124 0 : gInitiatorAcc = nsnull;
125 0 : return NS_ERROR_UNEXPECTED;
126 : }
127 :
128 : // If the given content is not visible or isn't accessible then go down
129 : // through the DOM subtree otherwise go down through accessible subtree and
130 : // calculate the flat string.
131 0 : nsIFrame *frame = aContent->GetPrimaryFrame();
132 0 : bool isVisible = frame && frame->GetStyleVisibility()->IsVisible();
133 :
134 0 : nsresult rv = NS_ERROR_FAILURE;
135 0 : bool goThroughDOMSubtree = true;
136 :
137 0 : if (isVisible) {
138 : nsAccessible* accessible =
139 0 : GetAccService()->GetAccessible(aContent, shell);
140 0 : if (accessible) {
141 0 : rv = AppendFromAccessible(accessible, aString);
142 0 : goThroughDOMSubtree = false;
143 : }
144 : }
145 :
146 0 : if (goThroughDOMSubtree)
147 0 : rv = AppendFromDOMNode(aContent, aString);
148 :
149 0 : gInitiatorAcc = nsnull;
150 0 : return rv;
151 : }
152 :
153 : nsresult
154 0 : nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
155 : nsAString *aString)
156 : {
157 0 : if (aContent->IsNodeOfType(nsINode::eTEXT)) {
158 0 : bool isHTMLBlock = false;
159 :
160 0 : nsIContent *parentContent = aContent->GetParent();
161 0 : if (parentContent) {
162 0 : nsIFrame *frame = parentContent->GetPrimaryFrame();
163 0 : if (frame) {
164 : // If this text is inside a block level frame (as opposed to span
165 : // level), we need to add spaces around that block's text, so we don't
166 : // get words jammed together in final name.
167 0 : const nsStyleDisplay* display = frame->GetStyleDisplay();
168 0 : if (display->IsBlockOutside() ||
169 : display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) {
170 0 : isHTMLBlock = true;
171 0 : if (!aString->IsEmpty()) {
172 0 : aString->Append(PRUnichar(' '));
173 : }
174 : }
175 : }
176 : }
177 :
178 0 : if (aContent->TextLength() > 0) {
179 0 : nsIFrame *frame = aContent->GetPrimaryFrame();
180 0 : if (frame) {
181 0 : nsresult rv = frame->GetRenderedText(aString);
182 0 : NS_ENSURE_SUCCESS(rv, rv);
183 : } else {
184 : // If aContent is an object that is display: none, we have no a frame.
185 0 : aContent->AppendTextTo(*aString);
186 : }
187 0 : if (isHTMLBlock && !aString->IsEmpty()) {
188 0 : aString->Append(PRUnichar(' '));
189 : }
190 : }
191 :
192 0 : return NS_OK;
193 : }
194 :
195 0 : if (aContent->IsHTML() &&
196 0 : aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
197 0 : aString->AppendLiteral("\r\n");
198 0 : return NS_OK;
199 : }
200 :
201 0 : return NS_OK_NO_NAME_CLAUSE_HANDLED;
202 : }
203 :
204 : ////////////////////////////////////////////////////////////////////////////////
205 : // nsTextEquivUtils. Private.
206 :
207 1464 : nsRefPtr<nsAccessible> nsTextEquivUtils::gInitiatorAcc;
208 :
209 : nsresult
210 0 : nsTextEquivUtils::AppendFromAccessibleChildren(nsAccessible *aAccessible,
211 : nsAString *aString)
212 : {
213 0 : nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
214 :
215 0 : PRInt32 childCount = aAccessible->GetChildCount();
216 0 : for (PRInt32 childIdx = 0; childIdx < childCount; childIdx++) {
217 0 : nsAccessible *child = aAccessible->GetChildAt(childIdx);
218 0 : rv = AppendFromAccessible(child, aString);
219 0 : NS_ENSURE_SUCCESS(rv, rv);
220 : }
221 :
222 0 : return rv;
223 : }
224 :
225 : nsresult
226 0 : nsTextEquivUtils::AppendFromAccessible(nsAccessible *aAccessible,
227 : nsAString *aString)
228 : {
229 : //XXX: is it necessary to care the accessible is not a document?
230 0 : if (aAccessible->IsContent()) {
231 : nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(),
232 0 : aString);
233 0 : if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
234 0 : return rv;
235 : }
236 :
237 0 : nsAutoString text;
238 0 : nsresult rv = aAccessible->GetName(text);
239 0 : NS_ENSURE_SUCCESS(rv, rv);
240 :
241 0 : bool isEmptyTextEquiv = true;
242 :
243 : // If the name is from tooltip then append it to result string in the end
244 : // (see h. step of name computation guide).
245 0 : if (rv != NS_OK_NAME_FROM_TOOLTIP)
246 0 : isEmptyTextEquiv = !AppendString(aString, text);
247 :
248 : // Implementation of f. step.
249 0 : rv = AppendFromValue(aAccessible, aString);
250 0 : NS_ENSURE_SUCCESS(rv, rv);
251 :
252 0 : if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
253 0 : isEmptyTextEquiv = false;
254 :
255 : // Implementation of g) step of text equivalent computation guide. Go down
256 : // into subtree if accessible allows "text equivalent from subtree rule" or
257 : // it's not root and not control.
258 0 : if (isEmptyTextEquiv) {
259 0 : PRUint32 nameRule = gRoleToNameRulesMap[aAccessible->Role()];
260 0 : if (nameRule & eFromSubtreeIfRec) {
261 0 : rv = AppendFromAccessibleChildren(aAccessible, aString);
262 0 : NS_ENSURE_SUCCESS(rv, rv);
263 :
264 0 : if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
265 0 : isEmptyTextEquiv = false;
266 : }
267 : }
268 :
269 : // Implementation of h. step
270 0 : if (isEmptyTextEquiv && !text.IsEmpty()) {
271 0 : AppendString(aString, text);
272 0 : return NS_OK;
273 : }
274 :
275 0 : return rv;
276 : }
277 :
278 : nsresult
279 0 : nsTextEquivUtils::AppendFromValue(nsAccessible *aAccessible,
280 : nsAString *aString)
281 : {
282 0 : PRUint32 nameRule = gRoleToNameRulesMap[aAccessible->Role()];
283 0 : if (nameRule != eFromValue)
284 0 : return NS_OK_NO_NAME_CLAUSE_HANDLED;
285 :
286 : // Implementation of step f. of text equivalent computation. If the given
287 : // accessible is not root accessible (the accessible the text equivalent is
288 : // computed for in the end) then append accessible value. Otherwise append
289 : // value if and only if the given accessible is in the middle of its parent.
290 :
291 0 : nsAutoString text;
292 0 : if (aAccessible != gInitiatorAcc) {
293 0 : nsresult rv = aAccessible->GetValue(text);
294 0 : NS_ENSURE_SUCCESS(rv, rv);
295 :
296 0 : return AppendString(aString, text) ?
297 0 : NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
298 : }
299 :
300 : //XXX: is it necessary to care the accessible is not a document?
301 0 : if (aAccessible->IsDocumentNode())
302 0 : return NS_ERROR_UNEXPECTED;
303 :
304 0 : nsIContent *content = aAccessible->GetContent();
305 :
306 0 : for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
307 0 : childContent = childContent->GetPreviousSibling()) {
308 : // check for preceding text...
309 0 : if (!childContent->TextIsOnlyWhitespace()) {
310 0 : for (nsIContent* siblingContent = content->GetNextSibling(); siblingContent;
311 0 : siblingContent = siblingContent->GetNextSibling()) {
312 : // .. and subsequent text
313 0 : if (!siblingContent->TextIsOnlyWhitespace()) {
314 0 : nsresult rv = aAccessible->GetValue(text);
315 0 : NS_ENSURE_SUCCESS(rv, rv);
316 :
317 0 : return AppendString(aString, text) ?
318 0 : NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
319 : break;
320 : }
321 : }
322 0 : break;
323 : }
324 : }
325 :
326 0 : return NS_OK_NO_NAME_CLAUSE_HANDLED;
327 : }
328 :
329 : nsresult
330 0 : nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent,
331 : nsAString *aString)
332 : {
333 0 : for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
334 0 : childContent = childContent->GetNextSibling()) {
335 0 : nsresult rv = AppendFromDOMNode(childContent, aString);
336 0 : NS_ENSURE_SUCCESS(rv, rv);
337 : }
338 :
339 0 : return NS_OK;
340 : }
341 :
342 : nsresult
343 0 : nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString)
344 : {
345 0 : nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
346 0 : NS_ENSURE_SUCCESS(rv, rv);
347 :
348 0 : if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
349 0 : return NS_OK;
350 :
351 0 : if (aContent->IsXUL()) {
352 0 : nsAutoString textEquivalent;
353 : nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
354 0 : do_QueryInterface(aContent);
355 :
356 0 : if (labeledEl) {
357 0 : labeledEl->GetLabel(textEquivalent);
358 : } else {
359 0 : if (aContent->NodeInfo()->Equals(nsGkAtoms::label,
360 0 : kNameSpaceID_XUL))
361 : aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
362 0 : textEquivalent);
363 :
364 0 : if (textEquivalent.IsEmpty())
365 : aContent->GetAttr(kNameSpaceID_None,
366 0 : nsGkAtoms::tooltiptext, textEquivalent);
367 : }
368 :
369 0 : AppendString(aString, textEquivalent);
370 : }
371 :
372 0 : return AppendFromDOMChildren(aContent, aString);
373 : }
374 :
375 : bool
376 0 : nsTextEquivUtils::AppendString(nsAString *aString,
377 : const nsAString& aTextEquivalent)
378 : {
379 : // Insert spaces to insure that words from controls aren't jammed together.
380 0 : if (aTextEquivalent.IsEmpty())
381 0 : return false;
382 :
383 0 : if (!aString->IsEmpty())
384 0 : aString->Append(PRUnichar(' '));
385 :
386 0 : aString->Append(aTextEquivalent);
387 0 : return true;
388 : }
389 :
390 : bool
391 0 : nsTextEquivUtils::IsWhitespaceString(const nsSubstring& aString)
392 : {
393 : nsSubstring::const_char_iterator iterBegin, iterEnd;
394 :
395 0 : aString.BeginReading(iterBegin);
396 0 : aString.EndReading(iterEnd);
397 :
398 0 : while (iterBegin != iterEnd && IsWhitespace(*iterBegin))
399 0 : ++iterBegin;
400 :
401 0 : return iterBegin == iterEnd;
402 : }
403 :
404 : bool
405 0 : nsTextEquivUtils::IsWhitespace(PRUnichar aChar)
406 : {
407 : return aChar == ' ' || aChar == '\n' ||
408 0 : aChar == '\r' || aChar == '\t' || aChar == 0xa0;
409 : }
410 :
411 : ////////////////////////////////////////////////////////////////////////////////
412 : // Name rules to role map.
413 :
414 : PRUint32 nsTextEquivUtils::gRoleToNameRulesMap[] =
415 : {
416 : eFromSubtreeIfRec, // ROLE_NOTHING
417 : eNoRule, // ROLE_TITLEBAR
418 : eNoRule, // ROLE_MENUBAR
419 : eNoRule, // ROLE_SCROLLBAR
420 : eNoRule, // ROLE_GRIP
421 : eNoRule, // ROLE_SOUND
422 : eNoRule, // ROLE_CURSOR
423 : eNoRule, // ROLE_CARET
424 : eNoRule, // ROLE_ALERT
425 : eNoRule, // ROLE_WINDOW
426 : eNoRule, // ROLE_INTERNAL_FRAME
427 : eNoRule, // ROLE_MENUPOPUP
428 : eFromSubtree, // ROLE_MENUITEM
429 : eFromSubtree, // ROLE_TOOLTIP
430 : eNoRule, // ROLE_APPLICATION
431 : eNoRule, // ROLE_DOCUMENT
432 : eNoRule, // ROLE_PANE
433 : eNoRule, // ROLE_CHART
434 : eNoRule, // ROLE_DIALOG
435 : eNoRule, // ROLE_BORDER
436 : eNoRule, // ROLE_GROUPING
437 : eNoRule, // ROLE_SEPARATOR
438 : eNoRule, // ROLE_TOOLBAR
439 : eNoRule, // ROLE_STATUSBAR
440 : eNoRule, // ROLE_TABLE
441 : eFromSubtree, // ROLE_COLUMNHEADER
442 : eFromSubtree, // ROLE_ROWHEADER
443 : eFromSubtree, // ROLE_COLUMN
444 : eFromSubtree, // ROLE_ROW
445 : eFromSubtreeIfRec, // ROLE_CELL
446 : eFromSubtree, // ROLE_LINK
447 : eFromSubtree, // ROLE_HELPBALLOON
448 : eNoRule, // ROLE_CHARACTER
449 : eFromSubtreeIfRec, // ROLE_LIST
450 : eFromSubtree, // ROLE_LISTITEM
451 : eNoRule, // ROLE_OUTLINE
452 : eFromSubtree, // ROLE_OUTLINEITEM
453 : eFromSubtree, // ROLE_PAGETAB
454 : eNoRule, // ROLE_PROPERTYPAGE
455 : eNoRule, // ROLE_INDICATOR
456 : eNoRule, // ROLE_GRAPHIC
457 : eNoRule, // ROLE_STATICTEXT
458 : eNoRule, // ROLE_TEXT_LEAF
459 : eFromSubtree, // ROLE_PUSHBUTTON
460 : eFromSubtree, // ROLE_CHECKBUTTON
461 : eFromSubtree, // ROLE_RADIOBUTTON
462 : eFromValue, // ROLE_COMBOBOX
463 : eNoRule, // ROLE_DROPLIST
464 : eFromValue, // ROLE_PROGRESSBAR
465 : eNoRule, // ROLE_DIAL
466 : eNoRule, // ROLE_HOTKEYFIELD
467 : eNoRule, // ROLE_SLIDER
468 : eNoRule, // ROLE_SPINBUTTON
469 : eNoRule, // ROLE_DIAGRAM
470 : eNoRule, // ROLE_ANIMATION
471 : eNoRule, // ROLE_EQUATION
472 : eFromSubtree, // ROLE_BUTTONDROPDOWN
473 : eFromSubtree, // ROLE_BUTTONMENU
474 : eFromSubtree, // ROLE_BUTTONDROPDOWNGRID
475 : eNoRule, // ROLE_WHITESPACE
476 : eNoRule, // ROLE_PAGETABLIST
477 : eNoRule, // ROLE_CLOCK
478 : eNoRule, // ROLE_SPLITBUTTON
479 : eNoRule, // ROLE_IPADDRESS
480 : eNoRule, // ROLE_ACCEL_LABEL
481 : eNoRule, // ROLE_ARROW
482 : eNoRule, // ROLE_CANVAS
483 : eFromSubtree, // ROLE_CHECK_MENU_ITEM
484 : eNoRule, // ROLE_COLOR_CHOOSER
485 : eNoRule, // ROLE_DATE_EDITOR
486 : eNoRule, // ROLE_DESKTOP_ICON
487 : eNoRule, // ROLE_DESKTOP_FRAME
488 : eNoRule, // ROLE_DIRECTORY_PANE
489 : eNoRule, // ROLE_FILE_CHOOSER
490 : eNoRule, // ROLE_FONT_CHOOSER
491 : eNoRule, // ROLE_CHROME_WINDOW
492 : eNoRule, // ROLE_GLASS_PANE
493 : eFromSubtreeIfRec, // ROLE_HTML_CONTAINER
494 : eNoRule, // ROLE_ICON
495 : eFromSubtree, // ROLE_LABEL
496 : eNoRule, // ROLE_LAYERED_PANE
497 : eNoRule, // ROLE_OPTION_PANE
498 : eNoRule, // ROLE_PASSWORD_TEXT
499 : eNoRule, // ROLE_POPUP_MENU
500 : eFromSubtree, // ROLE_RADIO_MENU_ITEM
501 : eNoRule, // ROLE_ROOT_PANE
502 : eNoRule, // ROLE_SCROLL_PANE
503 : eNoRule, // ROLE_SPLIT_PANE
504 : eFromSubtree, // ROLE_TABLE_COLUMN_HEADER
505 : eFromSubtree, // ROLE_TABLE_ROW_HEADER
506 : eFromSubtree, // ROLE_TEAR_OFF_MENU_ITEM
507 : eNoRule, // ROLE_TERMINAL
508 : eFromSubtreeIfRec, // ROLE_TEXT_CONTAINER
509 : eFromSubtree, // ROLE_TOGGLE_BUTTON
510 : eNoRule, // ROLE_TREE_TABLE
511 : eNoRule, // ROLE_VIEWPORT
512 : eNoRule, // ROLE_HEADER
513 : eNoRule, // ROLE_FOOTER
514 : eFromSubtreeIfRec, // ROLE_PARAGRAPH
515 : eNoRule, // ROLE_RULER
516 : eNoRule, // ROLE_AUTOCOMPLETE
517 : eNoRule, // ROLE_EDITBAR
518 : eFromValue, // ROLE_ENTRY
519 : eFromSubtreeIfRec, // ROLE_CAPTION
520 : eNoRule, // ROLE_DOCUMENT_FRAME
521 : eFromSubtreeIfRec, // ROLE_HEADING
522 : eNoRule, // ROLE_PAGE
523 : eFromSubtreeIfRec, // ROLE_SECTION
524 : eNoRule, // ROLE_REDUNDANT_OBJECT
525 : eNoRule, // ROLE_FORM
526 : eNoRule, // ROLE_IME
527 : eNoRule, // ROLE_APP_ROOT
528 : eFromSubtree, // ROLE_PARENT_MENUITEM
529 : eNoRule, // ROLE_CALENDAR
530 : eNoRule, // ROLE_COMBOBOX_LIST
531 : eFromSubtree, // ROLE_COMBOBOX_OPTION
532 : eNoRule, // ROLE_IMAGE_MAP
533 : eFromSubtree, // ROLE_OPTION
534 : eFromSubtree, // ROLE_RICH_OPTION
535 : eNoRule, // ROLE_LISTBOX
536 : eNoRule, // ROLE_FLAT_EQUATION
537 : eFromSubtree // ROLE_GRID_CELL
538 4392 : };
|