no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / accessible / android / TraversalRule.cpp
blob4203fd48c6d754d3ff59cbb50a116fe11c251de8
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,
23 true) {}
25 TraversalRule::TraversalRule(int32_t aGranularity, bool aIsLocal)
26 : mGranularity(aGranularity), mIsLocal(aIsLocal) {}
28 uint16_t TraversalRule::Match(Accessible* aAcc) {
29 MOZ_ASSERT(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
34 // in it.
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) {
53 return result;
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);
63 break;
64 case java::SessionAccessibility::HTML_GRANULARITY_CONTROL:
65 result |= ControlMatch(aAcc);
66 break;
67 case java::SessionAccessibility::HTML_GRANULARITY_SECTION:
68 result |= SectionMatch(aAcc);
69 break;
70 case java::SessionAccessibility::HTML_GRANULARITY_HEADING:
71 result |= HeadingMatch(aAcc);
72 break;
73 case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK:
74 result |= LandmarkMatch(aAcc);
75 break;
76 default:
77 result |= DefaultMatch(aAcc);
78 break;
81 return result;
84 bool TraversalRule::IsSingleLineage(Accessible* aAccessible) {
85 Accessible* child = aAccessible;
86 while (child) {
87 switch (child->ChildCount()) {
88 case 0:
89 return true;
90 case 1:
91 child = child->FirstChild();
92 break;
93 case 2:
94 if (IsListItemBullet(child->FirstChild())) {
95 child = child->LastChild();
96 } else {
97 return false;
99 break;
100 default:
101 return false;
105 return true;
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) {
117 continue;
120 if (child->ChildCount() > 0 || child->ActionCount() > 0) {
121 return false;
125 return true;
128 bool TraversalRule::HasName(const Accessible* aAccessible) {
129 nsAutoString name;
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:
178 case roles::LISTBOX:
179 case roles::ENTRY:
180 case roles::PASSWORD_TEXT:
181 case roles::PAGETAB:
182 case roles::RADIOBUTTON:
183 case roles::RADIO_MENU_ITEM:
184 case roles::SLIDER:
185 case roles::CHECKBUTTON:
186 case roles::CHECK_MENU_ITEM:
187 case roles::SWITCH:
188 case roles::MENUITEM:
189 return nsIAccessibleTraversalRule::FILTER_MATCH |
190 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
191 case roles::LINK:
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;
201 break;
202 default:
203 break;
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;
222 break;
223 case roles::TEXT_LEAF:
224 case roles::GRAPHIC:
225 // Nameless text leaves are boring, skip them.
226 if (HasName(aAccessible)) {
227 return nsIAccessibleTraversalRule::FILTER_MATCH;
229 break;
230 case roles::STATICTEXT:
231 // Ignore list bullets
232 if (!IsListItemBullet(aAccessible)) {
233 return nsIAccessibleTraversalRule::FILTER_MATCH;
235 break;
236 case roles::HEADING:
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;
245 break;
246 case roles::GRID_CELL:
247 if (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible)) {
248 return nsIAccessibleTraversalRule::FILTER_MATCH |
249 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
251 break;
252 case roles::LABEL:
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;
258 break;
259 case roles::MENUITEM:
260 case roles::LINK:
261 case roles::PAGETAB:
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:
272 case roles::ENTRY:
273 case roles::KEY:
274 case roles::SLIDER:
275 case roles::SPINBUTTON:
276 case roles::OPTION:
277 case roles::SWITCH:
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;
283 default:
284 break;
287 return nsIAccessibleTraversalRule::FILTER_IGNORE;