RotorRules.mm (14534B)
1 /* clang-format off */ 2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 3 /* clang-format on */ 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/. */ 7 8 #import "RotorRules.h" 9 10 #include "nsCocoaUtils.h" 11 #include "DocAccessibleParent.h" 12 #include "nsIAccessiblePivot.h" 13 #include "nsAccUtils.h" 14 15 #include "nsAccessibilityService.h" 16 17 using namespace mozilla; 18 using namespace mozilla::a11y; 19 20 // Generic Rotor Rule 21 22 RotorRule::RotorRule(Accessible* aDirectDescendantsFrom, 23 const nsString& aSearchText) 24 : mDirectDescendantsFrom(aDirectDescendantsFrom), 25 mSearchText(aSearchText) {} 26 27 RotorRule::RotorRule(const nsString& aSearchText) 28 : mDirectDescendantsFrom(nullptr), mSearchText(aSearchText) {} 29 30 uint16_t RotorRule::Match(Accessible* aAcc) { 31 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; 32 33 if (nsAccUtils::MustPrune(aAcc)) { 34 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 35 } 36 37 if (mDirectDescendantsFrom && (aAcc != mDirectDescendantsFrom)) { 38 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 39 } 40 41 if ([GetNativeFromGeckoAccessible(aAcc) isAccessibilityElement]) { 42 result |= nsIAccessibleTraversalRule::FILTER_MATCH; 43 } 44 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()) { 50 case roles::LANDMARK: 51 case roles::COMBOBOX: 52 case roles::LISTITEM: 53 case roles::COMBOBOX_LIST: 54 case roles::MENUBAR: 55 case roles::MENUPOPUP: 56 case roles::DOCUMENT: 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; 62 break; 63 default: 64 nsAutoString name; 65 aAcc->Name(name); 66 if (!CaseInsensitiveFindInReadable(mSearchText, name)) { 67 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH; 68 } 69 break; 70 } 71 } 72 73 return result; 74 } 75 76 // Rotor Role Rule 77 78 RotorRoleRule::RotorRoleRule(role aRole, Accessible* aDirectDescendantsFrom, 79 const nsString& aSearchText) 80 : RotorRule(aDirectDescendantsFrom, aSearchText), mRole(aRole) {}; 81 82 RotorRoleRule::RotorRoleRule(role aRole, const nsString& aSearchText) 83 : RotorRule(aSearchText), mRole(aRole) {}; 84 85 uint16_t RotorRoleRule::Match(Accessible* aAcc) { 86 uint16_t result = RotorRule::Match(aAcc); 87 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; 95 } 96 97 return result; 98 } 99 100 // Rotor Mac Role Rule 101 102 RotorMacRoleRule::RotorMacRoleRule(NSString* aMacRole, 103 Accessible* aDirectDescendantsFrom, 104 const nsString& aSearchText) 105 : RotorRule(aDirectDescendantsFrom, aSearchText), mMacRole(aMacRole) { 106 [mMacRole retain]; 107 }; 108 109 RotorMacRoleRule::RotorMacRoleRule(NSString* aMacRole, 110 const nsString& aSearchText) 111 : RotorRule(aSearchText), mMacRole(aMacRole) { 112 [mMacRole retain]; 113 }; 114 115 RotorMacRoleRule::~RotorMacRoleRule() { [mMacRole release]; } 116 117 uint16_t RotorMacRoleRule::Match(Accessible* aAcc) { 118 uint16_t result = RotorRule::Match(aAcc); 119 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; 128 } 129 } 130 131 return result; 132 } 133 134 // Rotor Control Rule 135 136 RotorControlRule::RotorControlRule(Accessible* aDirectDescendantsFrom, 137 const nsString& aSearchText) 138 : RotorRule(aDirectDescendantsFrom, aSearchText) {}; 139 140 RotorControlRule::RotorControlRule(const nsString& aSearchText) 141 : RotorRule(aSearchText) {}; 142 143 uint16_t RotorControlRule::Match(Accessible* aAcc) { 144 uint16_t result = RotorRule::Match(aAcc); 145 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: 154 case roles::DETAILS: 155 case roles::CHECKBUTTON: 156 case roles::LISTBOX: 157 case roles::COMBOBOX: 158 case roles::EDITCOMBOBOX: 159 case roles::RADIOBUTTON: 160 case roles::RADIO_GROUP: 161 case roles::PAGETAB: 162 case roles::SLIDER: 163 case roles::SWITCH: 164 case roles::ENTRY: 165 case roles::OUTLINE: 166 case roles::PASSWORD_TEXT: 167 case roles::BUTTONMENU: 168 return result; 169 170 case roles::DATE_EDITOR: 171 case roles::TIME_EDITOR: 172 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 173 return result; 174 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) { 185 return result; 186 } 187 } 188 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; 193 return result; 194 } 195 196 default: 197 // if we did not match on any above role, we should 198 // ignore this accessible. 199 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH; 200 } 201 } 202 203 return result; 204 } 205 206 // Rotor TextEntry Rule 207 208 RotorTextEntryRule::RotorTextEntryRule(Accessible* aDirectDescendantsFrom, 209 const nsString& aSearchText) 210 : RotorRule(aDirectDescendantsFrom, aSearchText) {}; 211 212 RotorTextEntryRule::RotorTextEntryRule(const nsString& aSearchText) 213 : RotorRule(aSearchText) {}; 214 215 uint16_t RotorTextEntryRule::Match(Accessible* aAcc) { 216 uint16_t result = RotorRule::Match(aAcc); 217 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; 225 } 226 } 227 228 return result; 229 } 230 231 // Rotor Link Rule 232 233 RotorLinkRule::RotorLinkRule(Accessible* aDirectDescendantsFrom, 234 const nsString& aSearchText) 235 : RotorRule(aDirectDescendantsFrom, aSearchText) {}; 236 237 RotorLinkRule::RotorLinkRule(const nsString& aSearchText) 238 : RotorRule(aSearchText) {}; 239 240 uint16_t RotorLinkRule::Match(Accessible* aAcc) { 241 uint16_t result = RotorRule::Match(aAcc); 242 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; 251 } 252 } 253 254 return result; 255 } 256 257 RotorVisitedLinkRule::RotorVisitedLinkRule(const nsString& aSearchText) 258 : RotorLinkRule(aSearchText) {} 259 260 RotorVisitedLinkRule::RotorVisitedLinkRule(Accessible* aDirectDescendantsFrom, 261 const nsString& aSearchText) 262 : RotorLinkRule(aDirectDescendantsFrom, aSearchText) {} 263 264 uint16_t RotorVisitedLinkRule::Match(Accessible* aAcc) { 265 uint16_t result = RotorLinkRule::Match(aAcc); 266 267 if (result & nsIAccessibleTraversalRule::FILTER_MATCH) { 268 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc); 269 if (![[nativeMatch moxVisited] boolValue]) { 270 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH; 271 } 272 } 273 274 return result; 275 } 276 277 RotorUnvisitedLinkRule::RotorUnvisitedLinkRule(const nsString& aSearchText) 278 : RotorLinkRule(aSearchText) {} 279 280 RotorUnvisitedLinkRule::RotorUnvisitedLinkRule( 281 Accessible* aDirectDescendantsFrom, const nsString& aSearchText) 282 : RotorLinkRule(aDirectDescendantsFrom, aSearchText) {} 283 284 uint16_t RotorUnvisitedLinkRule::Match(Accessible* aAcc) { 285 uint16_t result = RotorLinkRule::Match(aAcc); 286 287 if (result & nsIAccessibleTraversalRule::FILTER_MATCH) { 288 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc); 289 if ([[nativeMatch moxVisited] boolValue]) { 290 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH; 291 } 292 } 293 294 return result; 295 } 296 297 // Match Not Rule 298 299 RotorNotMacRoleRule::RotorNotMacRoleRule(NSString* aMacRole, 300 Accessible* aDirectDescendantsFrom, 301 const nsString& aSearchText) 302 : RotorMacRoleRule(aMacRole, aDirectDescendantsFrom, aSearchText) {} 303 304 RotorNotMacRoleRule::RotorNotMacRoleRule(NSString* aMacRole, 305 const nsString& aSearchText) 306 : RotorMacRoleRule(aMacRole, aSearchText) {} 307 308 uint16_t RotorNotMacRoleRule::Match(Accessible* aAcc) { 309 uint16_t result = RotorRule::Match(aAcc); 310 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; 319 } 320 } 321 return result; 322 } 323 324 // Rotor Static Text Rule 325 326 RotorStaticTextRule::RotorStaticTextRule(Accessible* aDirectDescendantsFrom, 327 const nsString& aSearchText) 328 : RotorRule(aDirectDescendantsFrom, aSearchText) {}; 329 330 RotorStaticTextRule::RotorStaticTextRule(const nsString& aSearchText) 331 : RotorRule(aSearchText) {}; 332 333 uint16_t RotorStaticTextRule::Match(Accessible* aAcc) { 334 uint16_t result = RotorRule::Match(aAcc); 335 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; 344 } 345 } 346 347 return result; 348 } 349 350 // Rotor Heading Level Rule 351 352 RotorHeadingLevelRule::RotorHeadingLevelRule(int32_t aLevel, 353 Accessible* aDirectDescendantsFrom, 354 const nsString& aSearchText) 355 : RotorRoleRule(roles::HEADING, aDirectDescendantsFrom, aSearchText), 356 mLevel(aLevel) {}; 357 358 RotorHeadingLevelRule::RotorHeadingLevelRule(int32_t aLevel, 359 const nsString& aSearchText) 360 : RotorRoleRule(roles::HEADING, aSearchText), mLevel(aLevel) {}; 361 362 uint16_t RotorHeadingLevelRule::Match(Accessible* aAcc) { 363 uint16_t result = RotorRoleRule::Match(aAcc); 364 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; 371 372 if (currLevel != mLevel) { 373 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH; 374 } 375 } 376 377 return result; 378 } 379 380 uint16_t RotorLiveRegionRule::Match(Accessible* aAcc) { 381 uint16_t result = RotorRule::Match(aAcc); 382 383 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) { 384 mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAcc); 385 if (![nativeMatch moxIsLiveRegion]) { 386 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH; 387 } 388 } 389 return result; 390 } 391 392 // Rotor Focusable Rule 393 394 RotorFocusableRule::RotorFocusableRule(Accessible* aDirectDescendantsFrom, 395 const nsString& aSearchText) 396 : RotorRule(aDirectDescendantsFrom, aSearchText) {}; 397 398 RotorFocusableRule::RotorFocusableRule(const nsString& aSearchText) 399 : RotorRule(aSearchText) {}; 400 401 uint16_t RotorFocusableRule::Match(Accessible* aAcc) { 402 uint16_t result = RotorRule::Match(aAcc); 403 // if a match was found in the base-class's Match function, 404 // it is valid to consider that match again here. if it is 405 // not of the desired role, we flip the match bit to "unmatch" 406 // otherwise, the match persists. 407 if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) { 408 if ((aAcc->State() & states::FOCUSABLE) == 0 || aAcc->ActionCount() == 0) { 409 // If the accessible was not focusable, or it is focusable but does not 410 // have an action (eg. an overflow: auto container), don't match. 411 result &= ~nsIAccessibleTraversalRule::FILTER_MATCH; 412 } 413 } 414 415 return result; 416 }