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/. */
6 #include "TreeWalker.h"
8 #include "nsAccessibilityService.h"
9 #include "DocAccessible.h"
11 #include "mozilla/dom/ChildIterator.h"
12 #include "mozilla/dom/Element.h"
14 using namespace mozilla
;
15 using namespace mozilla::a11y
;
17 ////////////////////////////////////////////////////////////////////////////////
19 ////////////////////////////////////////////////////////////////////////////////
21 TreeWalker::TreeWalker(LocalAccessible
* aContext
)
22 : mDoc(aContext
->Document()),
26 mChildFilter(nsIContent::eSkipPlaceholderContent
),
29 mChildFilter
|= nsIContent::eAllChildren
;
31 mAnchorNode
= mContext
->IsDoc() ? mDoc
->DocumentNode()->GetRootElement()
32 : mContext
->GetContent();
34 MOZ_COUNT_CTOR(TreeWalker
);
37 TreeWalker::TreeWalker(LocalAccessible
* aContext
, nsIContent
* aAnchorNode
,
39 : mDoc(aContext
->Document()),
41 mAnchorNode(aAnchorNode
),
43 mChildFilter(nsIContent::eSkipPlaceholderContent
),
46 MOZ_ASSERT(mFlags
& eWalkCache
,
47 "This constructor cannot be used for tree creation");
48 MOZ_ASSERT(aAnchorNode
, "No anchor node for the accessible tree walker");
50 mChildFilter
|= nsIContent::eAllChildren
;
52 MOZ_COUNT_CTOR(TreeWalker
);
55 TreeWalker::TreeWalker(DocAccessible
* aDocument
, nsIContent
* aAnchorNode
)
58 mAnchorNode(aAnchorNode
),
60 mChildFilter(nsIContent::eSkipPlaceholderContent
|
61 nsIContent::eAllChildren
),
64 MOZ_ASSERT(aAnchorNode
, "No anchor node for the accessible tree walker");
65 MOZ_COUNT_CTOR(TreeWalker
);
68 TreeWalker::~TreeWalker() { MOZ_COUNT_DTOR(TreeWalker
); }
70 LocalAccessible
* TreeWalker::Scope(nsIContent
* aAnchorNode
) {
73 mAnchorNode
= aAnchorNode
;
77 bool skipSubtree
= false;
78 LocalAccessible
* acc
= AccessibleFor(aAnchorNode
, 0, &skipSubtree
);
84 return skipSubtree
? nullptr : Next();
87 bool TreeWalker::Seek(nsIContent
* aChildNode
) {
88 MOZ_ASSERT(aChildNode
, "Child cannot be null");
92 if (mAnchorNode
== aChildNode
) {
96 nsIContent
* childNode
= nullptr;
97 nsINode
* parentNode
= aChildNode
;
99 childNode
= parentNode
->AsContent();
100 parentNode
= childNode
->GetFlattenedTreeParent();
102 // Handle the special case of XBL binding child under a shadow root.
103 if (parentNode
&& parentNode
->IsShadowRoot()) {
104 parentNode
= childNode
->GetFlattenedTreeParent();
105 if (parentNode
== mAnchorNode
) {
111 if (!parentNode
|| !parentNode
->IsElement()) {
115 // If ARIA owned child.
116 LocalAccessible
* child
= mDoc
->GetAccessible(childNode
);
117 if (child
&& child
->IsRelocated()) {
120 "Walker should not be scoped when seeking into relocated children");
121 if (child
->LocalParent() != mContext
) {
125 LocalAccessible
* ownedChild
= nullptr;
126 while ((ownedChild
= mDoc
->ARIAOwnedAt(mContext
, mARIAOwnsIdx
++)) &&
127 ownedChild
!= child
) {
131 MOZ_ASSERT(ownedChild
, "A child has to be in ARIA owned elements");
132 mPhase
= eAtARIAOwns
;
137 dom::AllChildrenIterator
* iter
=
138 PrependState(parentNode
->AsElement(), true);
139 if (!iter
->Seek(childNode
)) {
143 if (parentNode
== mAnchorNode
) {
149 MOZ_ASSERT_UNREACHABLE("because the do-while loop never breaks");
152 LocalAccessible
* TreeWalker::Next() {
153 if (mStateStack
.IsEmpty()) {
154 if (mPhase
== eAtEnd
) {
158 if (mPhase
== eAtDOM
|| mPhase
== eAtARIAOwns
) {
159 if (!(mFlags
& eScoped
)) {
160 mPhase
= eAtARIAOwns
;
161 LocalAccessible
* child
= mDoc
->ARIAOwnedAt(mContext
, mARIAOwnsIdx
);
167 MOZ_ASSERT(!(mFlags
& eScoped
) || mPhase
!= eAtARIAOwns
,
168 "Don't walk relocated children in scoped mode");
179 PushState(mAnchorNode
, true);
182 dom::AllChildrenIterator
* top
= &mStateStack
[mStateStack
.Length() - 1];
184 while (nsIContent
* childNode
= top
->GetNextChild()) {
185 bool skipSubtree
= false;
186 LocalAccessible
* child
= AccessibleFor(childNode
, mFlags
, &skipSubtree
);
191 // Walk down the subtree if allowed.
192 if (!skipSubtree
&& childNode
->IsElement()) {
193 top
= PushState(childNode
, true);
199 // If we traversed the whole subtree of the anchor node. Move to next node
200 // relative anchor node within the context subtree if asked.
201 if (mFlags
!= eWalkContextTree
) {
202 // eWalkCache flag presence indicates that the search is scoped to the
203 // anchor (no ARIA owns stuff).
204 if (mFlags
& eWalkCache
) {
211 nsINode
* contextNode
= mContext
->GetNode();
212 while (mAnchorNode
!= contextNode
) {
213 nsINode
* parentNode
= mAnchorNode
->GetFlattenedTreeParent();
214 if (!parentNode
|| !parentNode
->IsElement()) return nullptr;
216 nsIContent
* parent
= parentNode
->AsElement();
217 top
= PushState(parent
, true);
218 if (top
->Seek(mAnchorNode
)) {
219 mAnchorNode
= parent
;
223 // XXX We really should never get here, it means we're trying to find an
224 // accessible for a dom node where iterating over its parent's children
225 // doesn't return it. However this sometimes happens when we're asked for
226 // the nearest accessible to place holder content which we ignore.
227 mAnchorNode
= parent
;
233 LocalAccessible
* TreeWalker::Prev() {
234 if (mStateStack
.IsEmpty()) {
235 if (mPhase
== eAtStart
|| mPhase
== eAtDOM
) {
240 if (mPhase
== eAtEnd
) {
241 if (mFlags
& eScoped
) {
244 mPhase
= eAtARIAOwns
;
245 mARIAOwnsIdx
= mDoc
->ARIAOwnedCount(mContext
);
249 if (mPhase
== eAtARIAOwns
) {
250 MOZ_ASSERT(!(mFlags
& eScoped
),
251 "Should not walk relocated children in scoped mode");
252 if (mARIAOwnsIdx
> 0) {
253 return mDoc
->ARIAOwnedAt(mContext
, --mARIAOwnsIdx
);
262 PushState(mAnchorNode
, false);
266 dom::AllChildrenIterator
* top
= &mStateStack
[mStateStack
.Length() - 1];
268 while (nsIContent
* childNode
= top
->GetPreviousChild()) {
269 // No accessible creation on the way back.
270 bool skipSubtree
= false;
271 LocalAccessible
* child
=
272 AccessibleFor(childNode
, eWalkCache
, &skipSubtree
);
277 // Walk down into subtree to find accessibles.
278 if (!skipSubtree
&& childNode
->IsElement()) {
279 top
= PushState(childNode
, false);
285 // Move to a previous node relative the anchor node within the context
287 if (mFlags
!= eWalkContextTree
) {
292 nsINode
* contextNode
= mContext
->GetNode();
293 while (mAnchorNode
!= contextNode
) {
294 nsINode
* parentNode
= mAnchorNode
->GetFlattenedTreeParent();
295 if (!parentNode
|| !parentNode
->IsElement()) {
299 nsIContent
* parent
= parentNode
->AsElement();
300 top
= PushState(parent
, true);
301 if (top
->Seek(mAnchorNode
)) {
302 mAnchorNode
= parent
;
306 mAnchorNode
= parent
;
313 LocalAccessible
* TreeWalker::AccessibleFor(nsIContent
* aNode
, uint32_t aFlags
,
314 bool* aSkipSubtree
) {
315 // Ignore the accessible and its subtree if it was repositioned by means
317 LocalAccessible
* child
= mDoc
->GetAccessible(aNode
);
319 if (child
->IsRelocated()) {
320 *aSkipSubtree
= true;
326 // Create an accessible if allowed.
327 if (!(aFlags
& eWalkCache
) && mContext
->IsAcceptableChild(aNode
)) {
328 // We may have ARIA owned element in the dependent attributes map, but the
329 // element may be not allowed for this ARIA owns relation, if the relation
330 // crosses out XBL anonymous content boundaries. In this case we won't
331 // create an accessible object for it, when aria-owns is processed, which
332 // may make the element subtree inaccessible. To avoid that let's create
333 // an accessible object now, and later, if allowed, move it in the tree,
334 // when aria-owns relation is processed.
335 if (mDoc
->RelocateARIAOwnedIfNeeded(aNode
) && !aNode
->IsXULElement()) {
336 *aSkipSubtree
= true;
339 return GetAccService()->CreateAccessible(aNode
, mContext
, aSkipSubtree
);
345 dom::AllChildrenIterator
* TreeWalker::PopState() {
346 mStateStack
.RemoveLastElement();
347 return mStateStack
.IsEmpty() ? nullptr : &mStateStack
.LastElement();