2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
10 #include "nsCocoaUtils.h"
11 #include "DocAccessibleParent.h"
12 #include "nsIAccessiblePivot.h"
13 #include "nsAccUtils.h"
15 #include "nsAccessibilityService.h"
17 using namespace mozilla;
18 using namespace mozilla::a11y;
22 RotorRule::RotorRule(Accessible* aDirectDescendantsFrom,
23 const nsString& aSearchText)
24 : mDirectDescendantsFrom(aDirectDescendantsFrom),
25 mSearchText(aSearchText) {}
27 RotorRule::RotorRule(const nsString& aSearchText)
28 : mDirectDescendantsFrom(nullptr), mSearchText(aSearchText) {}
30 uint16_t RotorRule::Match(Accessible* aAcc) {
31 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
33 if (nsAccUtils::MustPrune(aAcc)) {
34 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
37 if (mDirectDescendantsFrom && (aAcc != mDirectDescendantsFrom)) {
38 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
41 if ([GetNativeFromGeckoAccessible(aAcc) isAccessibilityElement]) {
42 result |= nsIAccessibleTraversalRule::FILTER_MATCH;
45 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH) &&
46 !mSearchText.IsEmpty()) {
47 // If we have a non-empty search text, there are some roles
48 // we can safely ignore.
49 switch (aAcc->Role()) {
53 case roles::COMBOBOX_LIST:
55 case roles::MENUPOPUP:
57 case roles::APPLICATION:
58 // XXX: These roles either have AXTitle/AXDescription overridden as
59 // empty, or should never be returned in search text results. This
60 // should be better mapped somewhere.
61 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
66 if (!CaseInsensitiveFindInReadable(mSearchText, name)) {
67 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
78 RotorRoleRule::RotorRoleRule(role aRole, Accessible* aDirectDescendantsFrom,
79 const nsString& aSearchText)
80 : RotorRule(aDirectDescendantsFrom, aSearchText), mRole(aRole){};
82 RotorRoleRule::RotorRoleRule(role aRole, const nsString& aSearchText)
83 : RotorRule(aSearchText), mRole(aRole){};
85 uint16_t RotorRoleRule::Match(Accessible* aAcc) {
86 uint16_t result = RotorRule::Match(aAcc);
88 // if a match was found in the base-class's Match function,
89 // it is valid to consider that match again here. if it is
90 // not of the desired role, we flip the match bit to "unmatch"
91 // otherwise, the match persists.
92 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH) &&
93 aAcc->Role() != mRole) {
94 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
100 // Rotor Mac Role Rule
102 RotorMacRoleRule::RotorMacRoleRule(NSString* aMacRole,
103 Accessible* aDirectDescendantsFrom,
104 const nsString& aSearchText)
105 : RotorRule(aDirectDescendantsFrom, aSearchText), mMacRole(aMacRole) {
109 RotorMacRoleRule::RotorMacRoleRule(NSString* aMacRole,
110 const nsString& aSearchText)
111 : RotorRule(aSearchText), mMacRole(aMacRole) {
115 RotorMacRoleRule::~RotorMacRoleRule() { [mMacRole release]; }
117 uint16_t RotorMacRoleRule::Match(Accessible* aAcc) {
118 uint16_t result = RotorRule::Match(aAcc);
120 // if a match was found in the base-class's Match function,
121 // it is valid to consider that match again here. if it is
122 // not of the desired role, we flip the match bit to "unmatch"
123 // otherwise, the match persists.
124 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
125 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc);
126 if (![[nativeMatch moxRole] isEqualToString:mMacRole]) {
127 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
134 // Rotor Control Rule
136 RotorControlRule::RotorControlRule(Accessible* aDirectDescendantsFrom,
137 const nsString& aSearchText)
138 : RotorRule(aDirectDescendantsFrom, aSearchText){};
140 RotorControlRule::RotorControlRule(const nsString& aSearchText)
141 : RotorRule(aSearchText){};
143 uint16_t RotorControlRule::Match(Accessible* aAcc) {
144 uint16_t result = RotorRule::Match(aAcc);
146 // if a match was found in the base-class's Match function,
147 // it is valid to consider that match again here. if it is
148 // not of the desired role, we flip the match bit to "unmatch"
149 // otherwise, the match persists.
150 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
151 switch (aAcc->Role()) {
152 case roles::PUSHBUTTON:
153 case roles::SPINBUTTON:
155 case roles::CHECKBUTTON:
157 case roles::COMBOBOX:
158 case roles::EDITCOMBOBOX:
159 case roles::RADIOBUTTON:
160 case roles::RADIO_GROUP:
166 case roles::PASSWORD_TEXT:
167 case roles::BUTTONMENU:
170 case roles::DATE_EDITOR:
171 case roles::TIME_EDITOR:
172 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
175 case roles::GROUPING: {
176 // Groupings are sometimes used (like radio groups) to denote
177 // sets of controls. If that's the case, we want to surface
178 // them. We also want to surface grouped time and date controls.
179 for (unsigned int i = 0; i < aAcc->ChildCount(); i++) {
180 Accessible* currChild = aAcc->ChildAt(i);
181 if (currChild->Role() == roles::CHECKBUTTON ||
182 currChild->Role() == roles::SWITCH ||
183 currChild->Role() == roles::SPINBUTTON ||
184 currChild->Role() == roles::RADIOBUTTON) {
189 // if we iterated through the groups children and didn't
190 // find a control with one of the roles above, we should
191 // ignore this grouping
192 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
197 // if we did not match on any above role, we should
198 // ignore this accessible.
199 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
206 // Rotor TextEntry Rule
208 RotorTextEntryRule::RotorTextEntryRule(Accessible* aDirectDescendantsFrom,
209 const nsString& aSearchText)
210 : RotorRule(aDirectDescendantsFrom, aSearchText){};
212 RotorTextEntryRule::RotorTextEntryRule(const nsString& aSearchText)
213 : RotorRule(aSearchText){};
215 uint16_t RotorTextEntryRule::Match(Accessible* aAcc) {
216 uint16_t result = RotorRule::Match(aAcc);
218 // if a match was found in the base-class's Match function,
219 // it is valid to consider that match again here. if it is
220 // not of the desired role, we flip the match bit to "unmatch"
221 // otherwise, the match persists.
222 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
223 if (aAcc->Role() != roles::PASSWORD_TEXT && aAcc->Role() != roles::ENTRY) {
224 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
233 RotorLinkRule::RotorLinkRule(Accessible* aDirectDescendantsFrom,
234 const nsString& aSearchText)
235 : RotorRule(aDirectDescendantsFrom, aSearchText){};
237 RotorLinkRule::RotorLinkRule(const nsString& aSearchText)
238 : RotorRule(aSearchText){};
240 uint16_t RotorLinkRule::Match(Accessible* aAcc) {
241 uint16_t result = RotorRule::Match(aAcc);
243 // if a match was found in the base-class's Match function,
244 // it is valid to consider that match again here. if it is
245 // not of the desired role, we flip the match bit to "unmatch"
246 // otherwise, the match persists.
247 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
248 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc);
249 if (![[nativeMatch moxRole] isEqualToString:@"AXLink"]) {
250 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
257 RotorVisitedLinkRule::RotorVisitedLinkRule(const nsString& aSearchText)
258 : RotorLinkRule(aSearchText) {}
260 RotorVisitedLinkRule::RotorVisitedLinkRule(Accessible* aDirectDescendantsFrom,
261 const nsString& aSearchText)
262 : RotorLinkRule(aDirectDescendantsFrom, aSearchText) {}
264 uint16_t RotorVisitedLinkRule::Match(Accessible* aAcc) {
265 uint16_t result = RotorLinkRule::Match(aAcc);
267 if (result & nsIAccessibleTraversalRule::FILTER_MATCH) {
268 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc);
269 if (![[nativeMatch moxVisited] boolValue]) {
270 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
277 RotorUnvisitedLinkRule::RotorUnvisitedLinkRule(const nsString& aSearchText)
278 : RotorLinkRule(aSearchText) {}
280 RotorUnvisitedLinkRule::RotorUnvisitedLinkRule(
281 Accessible* aDirectDescendantsFrom, const nsString& aSearchText)
282 : RotorLinkRule(aDirectDescendantsFrom, aSearchText) {}
284 uint16_t RotorUnvisitedLinkRule::Match(Accessible* aAcc) {
285 uint16_t result = RotorLinkRule::Match(aAcc);
287 if (result & nsIAccessibleTraversalRule::FILTER_MATCH) {
288 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc);
289 if ([[nativeMatch moxVisited] boolValue]) {
290 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
299 RotorNotMacRoleRule::RotorNotMacRoleRule(NSString* aMacRole,
300 Accessible* aDirectDescendantsFrom,
301 const nsString& aSearchText)
302 : RotorMacRoleRule(aMacRole, aDirectDescendantsFrom, aSearchText) {}
304 RotorNotMacRoleRule::RotorNotMacRoleRule(NSString* aMacRole,
305 const nsString& aSearchText)
306 : RotorMacRoleRule(aMacRole, aSearchText) {}
308 uint16_t RotorNotMacRoleRule::Match(Accessible* aAcc) {
309 uint16_t result = RotorRule::Match(aAcc);
311 // if a match was found in the base-class's Match function,
312 // it is valid to consider that match again here. if it is
313 // not different from the desired role, we flip the
314 // match bit to "unmatch" otherwise, the match persists.
315 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
316 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc);
317 if ([[nativeMatch moxRole] isEqualToString:mMacRole]) {
318 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
324 // Rotor Static Text Rule
326 RotorStaticTextRule::RotorStaticTextRule(Accessible* aDirectDescendantsFrom,
327 const nsString& aSearchText)
328 : RotorRule(aDirectDescendantsFrom, aSearchText){};
330 RotorStaticTextRule::RotorStaticTextRule(const nsString& aSearchText)
331 : RotorRule(aSearchText){};
333 uint16_t RotorStaticTextRule::Match(Accessible* aAcc) {
334 uint16_t result = RotorRule::Match(aAcc);
336 // if a match was found in the base-class's Match function,
337 // it is valid to consider that match again here. if it is
338 // not of the desired role, we flip the match bit to "unmatch"
339 // otherwise, the match persists.
340 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
341 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc);
342 if (![[nativeMatch moxRole] isEqualToString:@"AXStaticText"]) {
343 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
350 // Rotor Heading Level Rule
352 RotorHeadingLevelRule::RotorHeadingLevelRule(int32_t aLevel,
353 Accessible* aDirectDescendantsFrom,
354 const nsString& aSearchText)
355 : RotorRoleRule(roles::HEADING, aDirectDescendantsFrom, aSearchText),
358 RotorHeadingLevelRule::RotorHeadingLevelRule(int32_t aLevel,
359 const nsString& aSearchText)
360 : RotorRoleRule(roles::HEADING, aSearchText), mLevel(aLevel){};
362 uint16_t RotorHeadingLevelRule::Match(Accessible* aAcc) {
363 uint16_t result = RotorRoleRule::Match(aAcc);
365 // if a match was found in the base-class's Match function,
366 // it is valid to consider that match again here. if it is
367 // not of the desired heading level, we flip the match bit to
368 // "unmatch" otherwise, the match persists.
369 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
370 int32_t currLevel = aAcc->GroupPosition().level;
372 if (currLevel != mLevel) {
373 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
380 uint16_t RotorLiveRegionRule::Match(Accessible* aAcc) {
381 uint16_t result = RotorRule::Match(aAcc);
383 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
384 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc);
385 if (![nativeMatch moxIsLiveRegion]) {
386 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;