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 "LocalAccessible.h"
9 #include "AccIterator.h"
10 #include "nsAccessibilityService.h"
11 #include "DocAccessible.h"
13 #include "mozilla/dom/ChildIterator.h"
14 #include "mozilla/dom/Element.h"
16 using namespace mozilla
;
17 using namespace mozilla::a11y
;
19 ////////////////////////////////////////////////////////////////////////////////
21 ////////////////////////////////////////////////////////////////////////////////
23 TreeWalker::TreeWalker(LocalAccessible
* aContext
)
24 : mDoc(aContext
->Document()),
28 mChildFilter(nsIContent::eSkipPlaceholderContent
),
31 mChildFilter
|= nsIContent::eAllChildren
;
33 mAnchorNode
= mContext
->IsDoc() ? mDoc
->DocumentNode()->GetRootElement()
34 : mContext
->GetContent();
36 MOZ_COUNT_CTOR(TreeWalker
);
39 TreeWalker::TreeWalker(LocalAccessible
* aContext
, nsIContent
* aAnchorNode
,
41 : mDoc(aContext
->Document()),
43 mAnchorNode(aAnchorNode
),
45 mChildFilter(nsIContent::eSkipPlaceholderContent
),
48 MOZ_ASSERT(mFlags
& eWalkCache
,
49 "This constructor cannot be used for tree creation");
50 MOZ_ASSERT(aAnchorNode
, "No anchor node for the accessible tree walker");
52 mChildFilter
|= nsIContent::eAllChildren
;
54 MOZ_COUNT_CTOR(TreeWalker
);
57 TreeWalker::TreeWalker(DocAccessible
* aDocument
, nsIContent
* aAnchorNode
)
60 mAnchorNode(aAnchorNode
),
62 mChildFilter(nsIContent::eSkipPlaceholderContent
|
63 nsIContent::eAllChildren
),
66 MOZ_ASSERT(aAnchorNode
, "No anchor node for the accessible tree walker");
67 MOZ_COUNT_CTOR(TreeWalker
);
70 TreeWalker::~TreeWalker() { MOZ_COUNT_DTOR(TreeWalker
); }
72 LocalAccessible
* TreeWalker::Scope(nsIContent
* aAnchorNode
) {
75 mAnchorNode
= aAnchorNode
;
79 bool skipSubtree
= false;
80 LocalAccessible
* acc
= AccessibleFor(aAnchorNode
, 0, &skipSubtree
);
86 return skipSubtree
? nullptr : Next();
89 bool TreeWalker::Seek(nsIContent
* aChildNode
) {
90 MOZ_ASSERT(aChildNode
, "Child cannot be null");
94 if (mAnchorNode
== aChildNode
) {
98 nsIContent
* childNode
= nullptr;
99 nsINode
* parentNode
= aChildNode
;
101 childNode
= parentNode
->AsContent();
102 parentNode
= childNode
->GetFlattenedTreeParent();
104 // Handle the special case of XBL binding child under a shadow root.
105 if (parentNode
&& parentNode
->IsShadowRoot()) {
106 parentNode
= childNode
->GetFlattenedTreeParent();
107 if (parentNode
== mAnchorNode
) {
113 if (!parentNode
|| !parentNode
->IsElement()) {
117 // If ARIA owned child.
118 LocalAccessible
* child
= mDoc
->GetAccessible(childNode
);
119 if (child
&& child
->IsRelocated()) {
122 "Walker should not be scoped when seeking into relocated children");
123 if (child
->LocalParent() != mContext
) {
127 LocalAccessible
* ownedChild
= nullptr;
128 while ((ownedChild
= mDoc
->ARIAOwnedAt(mContext
, mARIAOwnsIdx
++)) &&
129 ownedChild
!= child
) {
133 MOZ_ASSERT(ownedChild
, "A child has to be in ARIA owned elements");
134 mPhase
= eAtARIAOwns
;
139 dom::AllChildrenIterator
* iter
=
140 PrependState(parentNode
->AsElement(), true);
141 if (!iter
->Seek(childNode
)) {
145 if (parentNode
== mAnchorNode
) {
151 MOZ_ASSERT_UNREACHABLE("because the do-while loop never breaks");
154 LocalAccessible
* TreeWalker::Next() {
155 if (mStateStack
.IsEmpty()) {
156 if (mPhase
== eAtEnd
) {
160 if (mPhase
== eAtDOM
|| mPhase
== eAtARIAOwns
) {
161 if (!(mFlags
& eScoped
)) {
162 mPhase
= eAtARIAOwns
;
163 LocalAccessible
* child
= mDoc
->ARIAOwnedAt(mContext
, mARIAOwnsIdx
);
169 MOZ_ASSERT(!(mFlags
& eScoped
) || mPhase
!= eAtARIAOwns
,
170 "Don't walk relocated children in scoped mode");
181 PushState(mAnchorNode
, true);
184 dom::AllChildrenIterator
* top
= &mStateStack
[mStateStack
.Length() - 1];
186 while (nsIContent
* childNode
= top
->GetNextChild()) {
187 bool skipSubtree
= false;
188 LocalAccessible
* child
= AccessibleFor(childNode
, mFlags
, &skipSubtree
);
193 // Walk down the subtree if allowed.
194 if (!skipSubtree
&& childNode
->IsElement()) {
195 top
= PushState(childNode
, true);
201 // If we traversed the whole subtree of the anchor node. Move to next node
202 // relative anchor node within the context subtree if asked.
203 if (mFlags
!= eWalkContextTree
) {
204 // eWalkCache flag presence indicates that the search is scoped to the
205 // anchor (no ARIA owns stuff).
206 if (mFlags
& eWalkCache
) {
213 nsINode
* contextNode
= mContext
->GetNode();
214 while (mAnchorNode
!= contextNode
) {
215 nsINode
* parentNode
= mAnchorNode
->GetFlattenedTreeParent();
216 if (!parentNode
|| !parentNode
->IsElement()) return nullptr;
218 nsIContent
* parent
= parentNode
->AsElement();
219 top
= PushState(parent
, true);
220 if (top
->Seek(mAnchorNode
)) {
221 mAnchorNode
= parent
;
225 // XXX We really should never get here, it means we're trying to find an
226 // accessible for a dom node where iterating over its parent's children
227 // doesn't return it. However this sometimes happens when we're asked for
228 // the nearest accessible to place holder content which we ignore.
229 mAnchorNode
= parent
;
235 LocalAccessible
* TreeWalker::Prev() {
236 if (mStateStack
.IsEmpty()) {
237 if (mPhase
== eAtStart
|| mPhase
== eAtDOM
) {
242 if (mPhase
== eAtEnd
) {
243 if (mFlags
& eScoped
) {
246 mPhase
= eAtARIAOwns
;
247 mARIAOwnsIdx
= mDoc
->ARIAOwnedCount(mContext
);
251 if (mPhase
== eAtARIAOwns
) {
252 MOZ_ASSERT(!(mFlags
& eScoped
),
253 "Should not walk relocated children in scoped mode");
254 if (mARIAOwnsIdx
> 0) {
255 return mDoc
->ARIAOwnedAt(mContext
, --mARIAOwnsIdx
);
264 PushState(mAnchorNode
, false);
268 dom::AllChildrenIterator
* top
= &mStateStack
[mStateStack
.Length() - 1];
270 while (nsIContent
* childNode
= top
->GetPreviousChild()) {
271 // No accessible creation on the way back.
272 bool skipSubtree
= false;
273 LocalAccessible
* child
=
274 AccessibleFor(childNode
, eWalkCache
, &skipSubtree
);
279 // Walk down into subtree to find accessibles.
280 if (!skipSubtree
&& childNode
->IsElement()) {
281 top
= PushState(childNode
, false);
287 // Move to a previous node relative the anchor node within the context
289 if (mFlags
!= eWalkContextTree
) {
294 nsINode
* contextNode
= mContext
->GetNode();
295 while (mAnchorNode
!= contextNode
) {
296 nsINode
* parentNode
= mAnchorNode
->GetFlattenedTreeParent();
297 if (!parentNode
|| !parentNode
->IsElement()) {
301 nsIContent
* parent
= parentNode
->AsElement();
302 top
= PushState(parent
, true);
303 if (top
->Seek(mAnchorNode
)) {
304 mAnchorNode
= parent
;
308 mAnchorNode
= parent
;
315 LocalAccessible
* TreeWalker::AccessibleFor(nsIContent
* aNode
, uint32_t aFlags
,
316 bool* aSkipSubtree
) {
317 // Ignore the accessible and its subtree if it was repositioned by means
319 LocalAccessible
* child
= mDoc
->GetAccessible(aNode
);
321 if (child
->IsRelocated()) {
322 *aSkipSubtree
= true;
328 // Create an accessible if allowed.
329 if (!(aFlags
& eWalkCache
) && mContext
->IsAcceptableChild(aNode
)) {
330 // We may have ARIA owned element in the dependent attributes map, but the
331 // element may be not allowed for this ARIA owns relation, if the relation
332 // crosses out XBL anonymous content boundaries. In this case we won't
333 // create an accessible object for it, when aria-owns is processed, which
334 // may make the element subtree inaccessible. To avoid that let's create
335 // an accessible object now, and later, if allowed, move it in the tree,
336 // when aria-owns relation is processed.
337 if (mDoc
->RelocateARIAOwnedIfNeeded(aNode
) && !aNode
->IsXULElement()) {
338 *aSkipSubtree
= true;
341 return GetAccService()->CreateAccessible(aNode
, mContext
, aSkipSubtree
);
347 dom::AllChildrenIterator
* TreeWalker::PopState() {
348 mStateStack
.RemoveLastElement();
349 return mStateStack
.IsEmpty() ? nullptr : &mStateStack
.LastElement();