tor-browser

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

TraversalRule.cpp (9032B)


      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/. */
      6 
      7 #include "TraversalRule.h"
      8 
      9 #include "mozilla/a11y/Accessible.h"
     10 
     11 #include "mozilla/a11y/Role.h"
     12 #include "HTMLListAccessible.h"
     13 #include "SessionAccessibility.h"
     14 #include "nsAccUtils.h"
     15 #include "nsIAccessiblePivot.h"
     16 
     17 using namespace mozilla;
     18 using namespace mozilla::a11y;
     19 
     20 TraversalRule::TraversalRule()
     21    : TraversalRule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT,
     22                    true) {}
     23 
     24 TraversalRule::TraversalRule(int32_t aGranularity, bool aIsLocal)
     25    : mGranularity(aGranularity), mIsLocal(aIsLocal) {}
     26 
     27 uint16_t TraversalRule::Match(Accessible* aAcc) {
     28  MOZ_ASSERT(aAcc);
     29 
     30  if (mIsLocal && aAcc->IsRemote()) {
     31    // If we encounter a remote accessible in a local rule, we should
     32    // ignore the subtree because we won't encounter anymore local accessibles
     33    // in it.
     34    return nsIAccessibleTraversalRule::FILTER_IGNORE |
     35           nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
     36  } else if (!mIsLocal && aAcc->IsLocal()) {
     37    // If we encounter a local accessible in a remote rule we are likely
     38    // traversing backwards/upwards, we don't ignore its subtree because it is
     39    // likely the outer doc root of the remote tree.
     40    return nsIAccessibleTraversalRule::FILTER_IGNORE;
     41  }
     42 
     43  uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
     44 
     45  if (nsAccUtils::MustPrune(aAcc)) {
     46    result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
     47  }
     48 
     49  uint64_t state = aAcc->State();
     50 
     51  if ((state & states::INVISIBLE) != 0) {
     52    return result;
     53  }
     54 
     55  if (aAcc->Opacity() == 0.0f) {
     56    return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
     57  }
     58 
     59  switch (mGranularity) {
     60    case java::SessionAccessibility::HTML_GRANULARITY_LINK:
     61      result |= LinkMatch(aAcc);
     62      break;
     63    case java::SessionAccessibility::HTML_GRANULARITY_CONTROL:
     64      result |= ControlMatch(aAcc);
     65      break;
     66    case java::SessionAccessibility::HTML_GRANULARITY_SECTION:
     67      result |= SectionMatch(aAcc);
     68      break;
     69    case java::SessionAccessibility::HTML_GRANULARITY_HEADING:
     70      result |= HeadingMatch(aAcc);
     71      break;
     72    case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK:
     73      result |= LandmarkMatch(aAcc);
     74      break;
     75    default:
     76      result |= DefaultMatch(aAcc);
     77      break;
     78  }
     79 
     80  return result;
     81 }
     82 
     83 bool TraversalRule::IsSingleLineage(Accessible* aAccessible) {
     84  Accessible* child = aAccessible;
     85  while (child) {
     86    switch (child->ChildCount()) {
     87      case 0:
     88        return true;
     89      case 1:
     90        child = child->FirstChild();
     91        break;
     92      case 2:
     93        if (IsListItemBullet(child->FirstChild())) {
     94          child = child->LastChild();
     95        } else {
     96          return false;
     97        }
     98        break;
     99      default:
    100        return false;
    101    }
    102  }
    103 
    104  return true;
    105 }
    106 
    107 bool TraversalRule::IsListItemBullet(const Accessible* aAccessible) {
    108  return aAccessible->Role() == roles::LISTITEM_MARKER;
    109 }
    110 
    111 bool TraversalRule::IsFlatSubtree(const Accessible* aAccessible) {
    112  for (auto child = aAccessible->FirstChild(); child;
    113       child = child->NextSibling()) {
    114    roles::Role role = child->Role();
    115    if (role == roles::TEXT_LEAF || role == roles::STATICTEXT) {
    116      continue;
    117    }
    118 
    119    if (child->ChildCount() > 0 || child->ActionCount() > 0) {
    120      return false;
    121    }
    122  }
    123 
    124  return true;
    125 }
    126 
    127 bool TraversalRule::HasName(const Accessible* aAccessible) {
    128  nsAutoString name;
    129  aAccessible->Name(name);
    130  name.CompressWhitespace();
    131  return !name.IsEmpty();
    132 }
    133 
    134 uint16_t TraversalRule::LinkMatch(Accessible* aAccessible) {
    135  if (aAccessible->Role() == roles::LINK &&
    136      (aAccessible->State() & states::LINKED) != 0) {
    137    return nsIAccessibleTraversalRule::FILTER_MATCH |
    138           nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    139  }
    140 
    141  return nsIAccessibleTraversalRule::FILTER_IGNORE;
    142 }
    143 
    144 uint16_t TraversalRule::HeadingMatch(Accessible* aAccessible) {
    145  if (aAccessible->Role() == roles::HEADING && aAccessible->ChildCount()) {
    146    return nsIAccessibleTraversalRule::FILTER_MATCH;
    147  }
    148 
    149  return nsIAccessibleTraversalRule::FILTER_IGNORE;
    150 }
    151 
    152 uint16_t TraversalRule::SectionMatch(Accessible* aAccessible) {
    153  roles::Role role = aAccessible->Role();
    154  if (role == roles::HEADING || role == roles::LANDMARK ||
    155      aAccessible->LandmarkRole()) {
    156    return nsIAccessibleTraversalRule::FILTER_MATCH;
    157  }
    158 
    159  return nsIAccessibleTraversalRule::FILTER_IGNORE;
    160 }
    161 
    162 uint16_t TraversalRule::LandmarkMatch(Accessible* aAccessible) {
    163  if (aAccessible->LandmarkRole()) {
    164    return nsIAccessibleTraversalRule::FILTER_MATCH;
    165  }
    166 
    167  return nsIAccessibleTraversalRule::FILTER_IGNORE;
    168 }
    169 
    170 uint16_t TraversalRule::ControlMatch(Accessible* aAccessible) {
    171  switch (aAccessible->Role()) {
    172    case roles::PUSHBUTTON:
    173    case roles::SPINBUTTON:
    174    case roles::TOGGLE_BUTTON:
    175    case roles::BUTTONDROPDOWN:
    176    case roles::COMBOBOX:
    177    case roles::LISTBOX:
    178    case roles::ENTRY:
    179    case roles::PASSWORD_TEXT:
    180    case roles::PAGETAB:
    181    case roles::RADIOBUTTON:
    182    case roles::RADIO_MENU_ITEM:
    183    case roles::SLIDER:
    184    case roles::CHECKBUTTON:
    185    case roles::CHECK_MENU_ITEM:
    186    case roles::SWITCH:
    187    case roles::MENUITEM:
    188      return nsIAccessibleTraversalRule::FILTER_MATCH |
    189             nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    190    case roles::LINK:
    191      return LinkMatch(aAccessible);
    192    case roles::EDITCOMBOBOX:
    193      if (aAccessible->State() & states::EDITABLE) {
    194        // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is
    195        // editable. If it's a 1.1 combobox, the combobox is just a container;
    196        // we want to stop on the textbox inside it, not the container.
    197        return nsIAccessibleTraversalRule::FILTER_MATCH |
    198               nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    199      }
    200      break;
    201    default:
    202      break;
    203  }
    204 
    205  return nsIAccessibleTraversalRule::FILTER_IGNORE;
    206 }
    207 
    208 uint16_t TraversalRule::DefaultMatch(Accessible* aAccessible) {
    209  switch (aAccessible->Role()) {
    210    case roles::COMBOBOX:
    211      // We don't want to ignore the subtree because this is often
    212      // where the list box hangs out.
    213      return nsIAccessibleTraversalRule::FILTER_MATCH;
    214    case roles::EDITCOMBOBOX:
    215      if (aAccessible->State() & states::EDITABLE) {
    216        // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is
    217        // editable. If it's a 1.1 combobox, the combobox is just a container;
    218        // we want to stop on the textbox inside it.
    219        return nsIAccessibleTraversalRule::FILTER_MATCH;
    220      }
    221      break;
    222    case roles::TEXT_LEAF:
    223    case roles::GRAPHIC:
    224      // Nameless text leaves are boring, skip them.
    225      if (HasName(aAccessible)) {
    226        return nsIAccessibleTraversalRule::FILTER_MATCH;
    227      }
    228      break;
    229    case roles::STATICTEXT:
    230      // Ignore list bullets
    231      if (!IsListItemBullet(aAccessible)) {
    232        return nsIAccessibleTraversalRule::FILTER_MATCH;
    233      }
    234      break;
    235    case roles::HEADING:
    236    case roles::COLUMNHEADER:
    237    case roles::ROWHEADER:
    238    case roles::STATUSBAR:
    239      if ((aAccessible->ChildCount() > 0 || HasName(aAccessible)) &&
    240          (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible))) {
    241        return nsIAccessibleTraversalRule::FILTER_MATCH |
    242               nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    243      }
    244      break;
    245    case roles::GRID_CELL:
    246      if (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible)) {
    247        return nsIAccessibleTraversalRule::FILTER_MATCH |
    248               nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    249      }
    250      break;
    251    case roles::LABEL:
    252      if (IsFlatSubtree(aAccessible)) {
    253        // Match if this is a label with text but no nested controls.
    254        return nsIAccessibleTraversalRule::FILTER_MATCH |
    255               nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    256      }
    257      break;
    258    case roles::MENUITEM:
    259    case roles::LINK:
    260    case roles::PAGETAB:
    261    case roles::PUSHBUTTON:
    262    case roles::CHECKBUTTON:
    263    case roles::RADIOBUTTON:
    264    case roles::PROGRESSBAR:
    265    case roles::BUTTONDROPDOWN:
    266    case roles::BUTTONMENU:
    267    case roles::CHECK_MENU_ITEM:
    268    case roles::PASSWORD_TEXT:
    269    case roles::RADIO_MENU_ITEM:
    270    case roles::TOGGLE_BUTTON:
    271    case roles::ENTRY:
    272    case roles::KEY:
    273    case roles::SLIDER:
    274    case roles::SPINBUTTON:
    275    case roles::OPTION:
    276    case roles::SWITCH:
    277    case roles::MATHML_MATH:
    278      // Ignore the subtree, if there is one. So that we don't land on
    279      // the same content that was already presented by its parent.
    280      return nsIAccessibleTraversalRule::FILTER_MATCH |
    281             nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    282    default:
    283      break;
    284  }
    285 
    286  return nsIAccessibleTraversalRule::FILTER_IGNORE;
    287 }