Coverage Report

Created: 2018-09-25 14:53

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