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