1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TraversalRule.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/a11y/Accessible.h"
12 #include "mozilla/a11y/Role.h"
13 #include "HTMLListAccessible.h"
14 #include "SessionAccessibility.h"
15 #include "nsAccUtils.h"
16 #include "nsIAccessiblePivot.h"
18 using namespace mozilla
;
19 using namespace mozilla::a11y
;
21 TraversalRule::TraversalRule()
22 : TraversalRule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT
,
25 TraversalRule::TraversalRule(int32_t aGranularity
, bool aIsLocal
)
26 : mGranularity(aGranularity
), mIsLocal(aIsLocal
) {}
28 uint16_t TraversalRule::Match(Accessible
* aAcc
) {
31 if (mIsLocal
&& aAcc
->IsRemote()) {
32 // If we encounter a remote accessible in a local rule, we should
33 // ignore the subtree because we won't encounter anymore local accessibles
35 return nsIAccessibleTraversalRule::FILTER_IGNORE
|
36 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
37 } else if (!mIsLocal
&& aAcc
->IsLocal()) {
38 // If we encounter a local accessible in a remote rule we are likely
39 // traversing backwards/upwards, we don't ignore its subtree because it is
40 // likely the outer doc root of the remote tree.
41 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
44 uint16_t result
= nsIAccessibleTraversalRule::FILTER_IGNORE
;
46 if (nsAccUtils::MustPrune(aAcc
)) {
47 result
|= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
50 uint64_t state
= aAcc
->State();
52 if ((state
& states::INVISIBLE
) != 0) {
56 if (aAcc
->Opacity() == 0.0f
) {
57 return result
| nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
60 switch (mGranularity
) {
61 case java::SessionAccessibility::HTML_GRANULARITY_LINK
:
62 result
|= LinkMatch(aAcc
);
64 case java::SessionAccessibility::HTML_GRANULARITY_CONTROL
:
65 result
|= ControlMatch(aAcc
);
67 case java::SessionAccessibility::HTML_GRANULARITY_SECTION
:
68 result
|= SectionMatch(aAcc
);
70 case java::SessionAccessibility::HTML_GRANULARITY_HEADING
:
71 result
|= HeadingMatch(aAcc
);
73 case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK
:
74 result
|= LandmarkMatch(aAcc
);
77 result
|= DefaultMatch(aAcc
);
84 bool TraversalRule::IsSingleLineage(Accessible
* aAccessible
) {
85 Accessible
* child
= aAccessible
;
87 switch (child
->ChildCount()) {
91 child
= child
->FirstChild();
94 if (IsListItemBullet(child
->FirstChild())) {
95 child
= child
->LastChild();
108 bool TraversalRule::IsListItemBullet(const Accessible
* aAccessible
) {
109 return aAccessible
->Role() == roles::LISTITEM_MARKER
;
112 bool TraversalRule::IsFlatSubtree(const Accessible
* aAccessible
) {
113 for (auto child
= aAccessible
->FirstChild(); child
;
114 child
= child
->NextSibling()) {
115 roles::Role role
= child
->Role();
116 if (role
== roles::TEXT_LEAF
|| role
== roles::STATICTEXT
) {
120 if (child
->ChildCount() > 0 || child
->ActionCount() > 0) {
128 bool TraversalRule::HasName(const Accessible
* aAccessible
) {
130 aAccessible
->Name(name
);
131 name
.CompressWhitespace();
132 return !name
.IsEmpty();
135 uint16_t TraversalRule::LinkMatch(Accessible
* aAccessible
) {
136 if (aAccessible
->Role() == roles::LINK
&&
137 (aAccessible
->State() & states::LINKED
) != 0) {
138 return nsIAccessibleTraversalRule::FILTER_MATCH
|
139 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
142 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
145 uint16_t TraversalRule::HeadingMatch(Accessible
* aAccessible
) {
146 if (aAccessible
->Role() == roles::HEADING
&& aAccessible
->ChildCount()) {
147 return nsIAccessibleTraversalRule::FILTER_MATCH
;
150 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
153 uint16_t TraversalRule::SectionMatch(Accessible
* aAccessible
) {
154 roles::Role role
= aAccessible
->Role();
155 if (role
== roles::HEADING
|| role
== roles::LANDMARK
||
156 aAccessible
->LandmarkRole()) {
157 return nsIAccessibleTraversalRule::FILTER_MATCH
;
160 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
163 uint16_t TraversalRule::LandmarkMatch(Accessible
* aAccessible
) {
164 if (aAccessible
->LandmarkRole()) {
165 return nsIAccessibleTraversalRule::FILTER_MATCH
;
168 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
171 uint16_t TraversalRule::ControlMatch(Accessible
* aAccessible
) {
172 switch (aAccessible
->Role()) {
173 case roles::PUSHBUTTON
:
174 case roles::SPINBUTTON
:
175 case roles::TOGGLE_BUTTON
:
176 case roles::BUTTONDROPDOWN
:
177 case roles::COMBOBOX
:
180 case roles::PASSWORD_TEXT
:
182 case roles::RADIOBUTTON
:
183 case roles::RADIO_MENU_ITEM
:
185 case roles::CHECKBUTTON
:
186 case roles::CHECK_MENU_ITEM
:
188 case roles::MENUITEM
:
189 return nsIAccessibleTraversalRule::FILTER_MATCH
|
190 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
192 return LinkMatch(aAccessible
);
193 case roles::EDITCOMBOBOX
:
194 if (aAccessible
->State() & states::EDITABLE
) {
195 // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is
196 // editable. If it's a 1.1 combobox, the combobox is just a container;
197 // we want to stop on the textbox inside it, not the container.
198 return nsIAccessibleTraversalRule::FILTER_MATCH
|
199 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
206 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
209 uint16_t TraversalRule::DefaultMatch(Accessible
* aAccessible
) {
210 switch (aAccessible
->Role()) {
211 case roles::COMBOBOX
:
212 // We don't want to ignore the subtree because this is often
213 // where the list box hangs out.
214 return nsIAccessibleTraversalRule::FILTER_MATCH
;
215 case roles::EDITCOMBOBOX
:
216 if (aAccessible
->State() & states::EDITABLE
) {
217 // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is
218 // editable. If it's a 1.1 combobox, the combobox is just a container;
219 // we want to stop on the textbox inside it.
220 return nsIAccessibleTraversalRule::FILTER_MATCH
;
223 case roles::TEXT_LEAF
:
225 // Nameless text leaves are boring, skip them.
226 if (HasName(aAccessible
)) {
227 return nsIAccessibleTraversalRule::FILTER_MATCH
;
230 case roles::STATICTEXT
:
231 // Ignore list bullets
232 if (!IsListItemBullet(aAccessible
)) {
233 return nsIAccessibleTraversalRule::FILTER_MATCH
;
237 case roles::COLUMNHEADER
:
238 case roles::ROWHEADER
:
239 case roles::STATUSBAR
:
240 if ((aAccessible
->ChildCount() > 0 || HasName(aAccessible
)) &&
241 (IsSingleLineage(aAccessible
) || IsFlatSubtree(aAccessible
))) {
242 return nsIAccessibleTraversalRule::FILTER_MATCH
|
243 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
246 case roles::GRID_CELL
:
247 if (IsSingleLineage(aAccessible
) || IsFlatSubtree(aAccessible
)) {
248 return nsIAccessibleTraversalRule::FILTER_MATCH
|
249 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
253 if (IsFlatSubtree(aAccessible
)) {
254 // Match if this is a label with text but no nested controls.
255 return nsIAccessibleTraversalRule::FILTER_MATCH
|
256 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
259 case roles::MENUITEM
:
262 case roles::PUSHBUTTON
:
263 case roles::CHECKBUTTON
:
264 case roles::RADIOBUTTON
:
265 case roles::PROGRESSBAR
:
266 case roles::BUTTONDROPDOWN
:
267 case roles::BUTTONMENU
:
268 case roles::CHECK_MENU_ITEM
:
269 case roles::PASSWORD_TEXT
:
270 case roles::RADIO_MENU_ITEM
:
271 case roles::TOGGLE_BUTTON
:
275 case roles::SPINBUTTON
:
278 case roles::MATHML_MATH
:
279 // Ignore the subtree, if there is one. So that we don't land on
280 // the same content that was already presented by its parent.
281 return nsIAccessibleTraversalRule::FILTER_MATCH
|
282 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
287 return nsIAccessibleTraversalRule::FILTER_IGNORE
;