tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }