Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/base/TreeWalker.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "TreeWalker.h"
7
8
#include "Accessible.h"
9
#include "AccIterator.h"
10
#include "nsAccessibilityService.h"
11
#include "DocAccessible.h"
12
13
#include "mozilla/dom/ChildIterator.h"
14
#include "mozilla/dom/Element.h"
15
16
using namespace mozilla;
17
using namespace mozilla::a11y;
18
19
////////////////////////////////////////////////////////////////////////////////
20
// TreeWalker
21
////////////////////////////////////////////////////////////////////////////////
22
23
TreeWalker::
24
  TreeWalker(Accessible* aContext) :
25
  mDoc(aContext->Document()), mContext(aContext), mAnchorNode(nullptr),
26
  mARIAOwnsIdx(0),
27
  mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(0),
28
  mPhase(eAtStart)
29
0
{
30
0
  mChildFilter |= mContext->NoXBLKids() ?
31
0
    nsIContent::eAllButXBL : nsIContent::eAllChildren;
32
0
33
0
  mAnchorNode = mContext->IsDoc() ?
34
0
    mDoc->DocumentNode()->GetRootElement() : mContext->GetContent();
35
0
36
0
  MOZ_COUNT_CTOR(TreeWalker);
37
0
}
38
39
TreeWalker::
40
  TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags) :
41
  mDoc(aContext->Document()), mContext(aContext), mAnchorNode(aAnchorNode),
42
  mARIAOwnsIdx(0),
43
  mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(aFlags),
44
  mPhase(eAtStart)
45
0
{
46
0
  MOZ_ASSERT(mFlags & eWalkCache, "This constructor cannot be used for tree creation");
47
0
  MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
48
0
49
0
  mChildFilter |= mContext->NoXBLKids() ?
50
0
    nsIContent::eAllButXBL : nsIContent::eAllChildren;
51
0
52
0
  MOZ_COUNT_CTOR(TreeWalker);
53
0
}
54
55
TreeWalker::
56
  TreeWalker(DocAccessible* aDocument, nsIContent* aAnchorNode) :
57
  mDoc(aDocument), mContext(nullptr), mAnchorNode(aAnchorNode),
58
  mARIAOwnsIdx(0),
59
  mChildFilter(nsIContent::eSkipPlaceholderContent | nsIContent::eAllChildren),
60
  mFlags(eWalkCache),
61
  mPhase(eAtStart)
62
0
{
63
0
  MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
64
0
  MOZ_COUNT_CTOR(TreeWalker);
65
0
}
66
67
TreeWalker::~TreeWalker()
68
0
{
69
0
  MOZ_COUNT_DTOR(TreeWalker);
70
0
}
71
72
Accessible*
73
TreeWalker::Scope(nsIContent* aAnchorNode)
74
0
{
75
0
  Reset();
76
0
77
0
  mAnchorNode = aAnchorNode;
78
0
79
0
  mFlags |= eScoped;
80
0
81
0
  bool skipSubtree = false;
82
0
  Accessible* acc = AccessibleFor(aAnchorNode, 0, &skipSubtree);
83
0
  if (acc) {
84
0
    mPhase = eAtEnd;
85
0
    return acc;
86
0
  }
87
0
88
0
  return skipSubtree ? nullptr : Next();
89
0
}
90
91
bool
92
TreeWalker::Seek(nsIContent* aChildNode)
93
0
{
94
0
  MOZ_ASSERT(aChildNode, "Child cannot be null");
95
0
96
0
  Reset();
97
0
98
0
  if (mAnchorNode == aChildNode) {
99
0
    return true;
100
0
  }
101
0
102
0
  nsIContent* childNode = nullptr;
103
0
  nsINode* parentNode = aChildNode;
104
0
  do {
105
0
    childNode = parentNode->AsContent();
106
0
    parentNode = childNode->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) &&
107
0
      (mChildFilter & nsIContent::eAllButXBL) ?
108
0
      childNode->GetParentNode() : childNode->GetFlattenedTreeParent();
109
0
110
0
    if (!parentNode || !parentNode->IsElement()) {
111
0
      return false;
112
0
    }
113
0
114
0
    // If ARIA owned child.
115
0
    Accessible* child = mDoc->GetAccessible(childNode);
116
0
    if (child && child->IsRelocated()) {
117
0
      MOZ_ASSERT(!(mFlags & eScoped),
118
0
        "Walker should not be scoped when seeking into relocated children");
119
0
      if (child->Parent() != mContext) {
120
0
        return false;
121
0
      }
122
0
123
0
      Accessible* ownedChild = nullptr;
124
0
      while ((ownedChild = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx++)) &&
125
0
             ownedChild != child);
126
0
127
0
      MOZ_ASSERT(ownedChild, "A child has to be in ARIA owned elements");
128
0
      mPhase = eAtARIAOwns;
129
0
      return true;
130
0
    }
131
0
132
0
    // Look in DOM.
133
0
    dom::AllChildrenIterator* iter = PrependState(parentNode->AsElement(), true);
134
0
    if (!iter->Seek(childNode)) {
135
0
      return false;
136
0
    }
137
0
138
0
    if (parentNode == mAnchorNode) {
139
0
      mPhase = eAtDOM;
140
0
      return true;
141
0
    }
142
0
  } while (true);
143
0
144
0
  return false;
145
0
}
146
147
Accessible*
148
TreeWalker::Next()
149
0
{
150
0
  if (mStateStack.IsEmpty()) {
151
0
    if (mPhase == eAtEnd) {
152
0
      return nullptr;
153
0
    }
154
0
155
0
    if (mPhase == eAtDOM || mPhase == eAtARIAOwns) {
156
0
      if (!(mFlags & eScoped)) {
157
0
        mPhase = eAtARIAOwns;
158
0
        Accessible* child = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx);
159
0
        if (child) {
160
0
          mARIAOwnsIdx++;
161
0
          return child;
162
0
        }
163
0
      }
164
0
      MOZ_ASSERT(!(mFlags & eScoped) || mPhase != eAtARIAOwns,
165
0
        "Don't walk relocated children in scoped mode");
166
0
      mPhase = eAtEnd;
167
0
      return nullptr;
168
0
    }
169
0
170
0
    if (!mAnchorNode) {
171
0
      mPhase = eAtEnd;
172
0
      return nullptr;
173
0
    }
174
0
175
0
    mPhase = eAtDOM;
176
0
    PushState(mAnchorNode, true);
177
0
  }
178
0
179
0
  dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
180
0
  while (top) {
181
0
    while (nsIContent* childNode = top->GetNextChild()) {
182
0
      bool skipSubtree = false;
183
0
      Accessible* child = AccessibleFor(childNode, mFlags, &skipSubtree);
184
0
      if (child) {
185
0
        return child;
186
0
      }
187
0
188
0
      // Walk down the subtree if allowed.
189
0
      if (!skipSubtree && childNode->IsElement()) {
190
0
        top = PushState(childNode, true);
191
0
      }
192
0
    }
193
0
    top = PopState();
194
0
  }
195
0
196
0
  // If we traversed the whole subtree of the anchor node. Move to next node
197
0
  // relative anchor node within the context subtree if asked.
198
0
  if (mFlags != eWalkContextTree) {
199
0
    // eWalkCache flag presence indicates that the search is scoped to the
200
0
    // anchor (no ARIA owns stuff).
201
0
    if (mFlags & eWalkCache) {
202
0
      mPhase = eAtEnd;
203
0
      return nullptr;
204
0
    }
205
0
    return Next();
206
0
  }
207
0
208
0
  nsINode* contextNode = mContext->GetNode();
209
0
  while (mAnchorNode != contextNode) {
210
0
    nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
211
0
    if (!parentNode || !parentNode->IsElement())
212
0
      return nullptr;
213
0
214
0
    nsIContent* parent = parentNode->AsElement();
215
0
    top = PushState(parent, true);
216
0
    if (top->Seek(mAnchorNode)) {
217
0
      mAnchorNode = parent;
218
0
      return Next();
219
0
    }
220
0
221
0
    // XXX We really should never get here, it means we're trying to find an
222
0
    // accessible for a dom node where iterating over its parent's children
223
0
    // doesn't return it. However this sometimes happens when we're asked for
224
0
    // the nearest accessible to place holder content which we ignore.
225
0
    mAnchorNode = parent;
226
0
  }
227
0
228
0
  return Next();
229
0
}
230
231
Accessible*
232
TreeWalker::Prev()
233
0
{
234
0
  if (mStateStack.IsEmpty()) {
235
0
    if (mPhase == eAtStart || mPhase == eAtDOM) {
236
0
      mPhase = eAtStart;
237
0
      return nullptr;
238
0
    }
239
0
240
0
    if (mPhase == eAtEnd) {
241
0
      if (mFlags & eScoped) {
242
0
        mPhase = eAtDOM;
243
0
      } else {
244
0
        mPhase = eAtARIAOwns;
245
0
        mARIAOwnsIdx = mDoc->ARIAOwnedCount(mContext);
246
0
      }
247
0
    }
248
0
249
0
    if (mPhase == eAtARIAOwns) {
250
0
      MOZ_ASSERT(!(mFlags & eScoped),
251
0
        "Should not walk relocated children in scoped mode");
252
0
      if (mARIAOwnsIdx > 0) {
253
0
        return mDoc->ARIAOwnedAt(mContext, --mARIAOwnsIdx);
254
0
      }
255
0
256
0
      if (!mAnchorNode) {
257
0
        mPhase = eAtStart;
258
0
        return nullptr;
259
0
      }
260
0
261
0
      mPhase = eAtDOM;
262
0
      PushState(mAnchorNode, false);
263
0
    }
264
0
  }
265
0
266
0
  dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
267
0
  while (top) {
268
0
    while (nsIContent* childNode = top->GetPreviousChild()) {
269
0
      // No accessible creation on the way back.
270
0
      bool skipSubtree = false;
271
0
      Accessible* child = AccessibleFor(childNode, eWalkCache, &skipSubtree);
272
0
      if (child) {
273
0
        return child;
274
0
      }
275
0
276
0
      // Walk down into subtree to find accessibles.
277
0
      if (!skipSubtree && childNode->IsElement()) {
278
0
        top = PushState(childNode, false);
279
0
      }
280
0
    }
281
0
    top = PopState();
282
0
  }
283
0
284
0
  // Move to a previous node relative the anchor node within the context
285
0
  // subtree if asked.
286
0
  if (mFlags != eWalkContextTree) {
287
0
    mPhase = eAtStart;
288
0
    return nullptr;
289
0
  }
290
0
291
0
  nsINode* contextNode = mContext->GetNode();
292
0
  while (mAnchorNode != contextNode) {
293
0
    nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
294
0
    if (!parentNode || !parentNode->IsElement()) {
295
0
      return nullptr;
296
0
    }
297
0
298
0
    nsIContent* parent = parentNode->AsElement();
299
0
    top = PushState(parent, true);
300
0
    if (top->Seek(mAnchorNode)) {
301
0
      mAnchorNode = parent;
302
0
      return Prev();
303
0
    }
304
0
305
0
    mAnchorNode = parent;
306
0
  }
307
0
308
0
  mPhase = eAtStart;
309
0
  return nullptr;
310
0
}
311
312
Accessible*
313
TreeWalker::AccessibleFor(nsIContent* aNode, uint32_t aFlags, bool* aSkipSubtree)
314
0
{
315
0
  // Ignore the accessible and its subtree if it was repositioned by means
316
0
  // of aria-owns.
317
0
  Accessible* child = mDoc->GetAccessible(aNode);
318
0
  if (child) {
319
0
    if (child->IsRelocated()) {
320
0
      *aSkipSubtree = true;
321
0
      return nullptr;
322
0
    }
323
0
    return child;
324
0
  }
325
0
326
0
  // Create an accessible if allowed.
327
0
  if (!(aFlags & eWalkCache) && mContext->IsAcceptableChild(aNode)) {
328
0
    // We may have ARIA owned element in the dependent attributes map, but the
329
0
    // element may be not allowed for this ARIA owns relation, if the relation
330
0
    // crosses out XBL anonymous content boundaries. In this case we won't
331
0
    // create an accessible object for it, when aria-owns is processed, which
332
0
    // may make the element subtree inaccessible. To avoid that let's create
333
0
    // an accessible object now, and later, if allowed, move it in the tree,
334
0
    // when aria-owns relation is processed.
335
0
    if (mDoc->RelocateARIAOwnedIfNeeded(aNode) && !aNode->IsXULElement()) {
336
0
      *aSkipSubtree = true;
337
0
      return nullptr;
338
0
    }
339
0
    return GetAccService()->CreateAccessible(aNode, mContext, aSkipSubtree);
340
0
  }
341
0
342
0
  return nullptr;
343
0
}
344
345
dom::AllChildrenIterator*
346
TreeWalker::PopState()
347
0
{
348
0
  mStateStack.RemoveLastElement();
349
0
  return mStateStack.IsEmpty() ? nullptr : &mStateStack.LastElement();
350
0
}