tor-browser

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

AccGroupInfo.cpp (12861B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "AccGroupInfo.h"
      6 #include "mozilla/a11y/Accessible.h"
      7 #include "mozilla/a11y/TableAccessible.h"
      8 
      9 #include "nsAccUtils.h"
     10 #include "nsIAccessiblePivot.h"
     11 
     12 #include "Pivot.h"
     13 #include "States.h"
     14 
     15 using namespace mozilla::a11y;
     16 
     17 static role BaseRole(role aRole);
     18 
     19 // This rule finds candidate siblings for compound widget children.
     20 class CompoundWidgetSiblingRule : public PivotRule {
     21 public:
     22  CompoundWidgetSiblingRule() = delete;
     23  explicit CompoundWidgetSiblingRule(role aRole) : mRole(aRole) {}
     24 
     25  uint16_t Match(Accessible* aAcc) override {
     26    // If the acc has a matching role, that's a valid sibling. If the acc is
     27    // separator then the group is ended. Return a match for separators with
     28    // the assumption that the caller will check for the role of the returned
     29    // accessible.
     30    const role accRole = aAcc->Role();
     31    if (BaseRole(accRole) == mRole || accRole == role::SEPARATOR) {
     32      return nsIAccessibleTraversalRule::FILTER_MATCH |
     33             nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
     34    }
     35 
     36    // Ignore generic accessibles, but keep searching through the subtree for
     37    // siblings.
     38    if (aAcc->IsGeneric()) {
     39      return nsIAccessibleTraversalRule::FILTER_IGNORE;
     40    }
     41 
     42    return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
     43  }
     44 
     45 private:
     46  role mRole = role::NOTHING;
     47 };
     48 
     49 AccGroupInfo::AccGroupInfo(const Accessible* aItem, role aRole)
     50    : mPosInSet(0), mSetSize(0), mParentId(0), mItem(aItem), mRole(aRole) {
     51  MOZ_COUNT_CTOR(AccGroupInfo);
     52  Update();
     53 }
     54 
     55 void AccGroupInfo::Update() {
     56  mParentId = 0;
     57 
     58  Accessible* parent = mItem->GetNonGenericParent();
     59  if (!parent) {
     60    return;
     61  }
     62 
     63  const int32_t level = GetARIAOrDefaultLevel(mItem);
     64 
     65  // Compute position in set.
     66  mPosInSet = 1;
     67 
     68  // Search backwards through the tree for candidate siblings.
     69  Accessible* candidateSibling = const_cast<Accessible*>(mItem);
     70  Pivot pivot{parent};
     71  CompoundWidgetSiblingRule widgetSiblingRule{mRole};
     72  while ((candidateSibling = pivot.Prev(candidateSibling, widgetSiblingRule)) &&
     73         candidateSibling != parent) {
     74    // If the sibling is separator then the group is ended.
     75    if (candidateSibling->Role() == roles::SEPARATOR) {
     76      break;
     77    }
     78 
     79    const AccGroupInfo* siblingGroupInfo = candidateSibling->GetGroupInfo();
     80    // Skip invisible siblings.
     81    // If the sibling has calculated group info, that means it's visible.
     82    if (!siblingGroupInfo && candidateSibling->State() & states::INVISIBLE) {
     83      continue;
     84    }
     85 
     86    // Check if it's hierarchical flatten structure, i.e. if the sibling
     87    // level is lesser than this one then group is ended, if the sibling level
     88    // is greater than this one then the group is split by some child elements
     89    // (group will be continued).
     90    const int32_t siblingLevel = GetARIAOrDefaultLevel(candidateSibling);
     91    if (siblingLevel < level) {
     92      mParentId = candidateSibling->ID();
     93      break;
     94    }
     95 
     96    // Skip subset.
     97    if (siblingLevel > level) {
     98      continue;
     99    }
    100 
    101    // If the previous item in the group has calculated group information then
    102    // build group information for this item based on found one.
    103    if (siblingGroupInfo) {
    104      mPosInSet += siblingGroupInfo->mPosInSet;
    105      mParentId = siblingGroupInfo->mParentId;
    106      mSetSize = siblingGroupInfo->mSetSize;
    107      return;
    108    }
    109 
    110    mPosInSet++;
    111  }
    112 
    113  // Compute set size.
    114  mSetSize = mPosInSet;
    115 
    116  candidateSibling = const_cast<Accessible*>(mItem);
    117  while ((candidateSibling = pivot.Next(candidateSibling, widgetSiblingRule)) &&
    118         candidateSibling != parent) {
    119    // If the sibling is separator then the group is ended.
    120    if (candidateSibling->Role() == roles::SEPARATOR) {
    121      break;
    122    }
    123 
    124    const AccGroupInfo* siblingGroupInfo = candidateSibling->GetGroupInfo();
    125    // Skip invisible siblings.
    126    // If the sibling has calculated group info, that means it's visible.
    127    if (!siblingGroupInfo && candidateSibling->State() & states::INVISIBLE) {
    128      continue;
    129    }
    130 
    131    // and check if it's hierarchical flatten structure.
    132    const int32_t siblingLevel = GetARIAOrDefaultLevel(candidateSibling);
    133    if (siblingLevel < level) {
    134      break;
    135    }
    136 
    137    // Skip subset.
    138    if (siblingLevel > level) {
    139      continue;
    140    }
    141 
    142    // If the next item in the group has calculated group information then
    143    // build group information for this item based on found one.
    144    if (siblingGroupInfo) {
    145      mParentId = siblingGroupInfo->mParentId;
    146      mSetSize = siblingGroupInfo->mSetSize;
    147      return;
    148    }
    149 
    150    mSetSize++;
    151  }
    152 
    153  if (mParentId) {
    154    return;
    155  }
    156 
    157  roles::Role parentRole = parent->Role();
    158  if (ShouldReportRelations(mRole, parentRole)) {
    159    mParentId = parent->ID();
    160  }
    161 
    162  // ARIA tree and list can be arranged by using ARIA groups to organize levels.
    163  if (parentRole != roles::GROUPING) {
    164    return;
    165  }
    166 
    167  // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a
    168  // parent. In other words the parent of the tree item will be a group and
    169  // the previous tree item of the group is a conceptual parent of the tree
    170  // item.
    171  if (mRole == roles::OUTLINEITEM) {
    172    // Find the relevant grandparent of the item. Use that parent as the root
    173    // and find the previous outline item sibling within that root.
    174    Accessible* grandParent = parent->GetNonGenericParent();
    175    MOZ_ASSERT(grandParent);
    176    Pivot pivot{grandParent};
    177    CompoundWidgetSiblingRule parentSiblingRule{mRole};
    178    Accessible* parentPrevSibling = pivot.Prev(parent, widgetSiblingRule);
    179    if (parentPrevSibling && parentPrevSibling->Role() == mRole) {
    180      mParentId = parentPrevSibling->ID();
    181      return;
    182    }
    183  }
    184 
    185  // Way #2 for ARIA list and tree: group is a child of an item. In other words
    186  // the parent of the item will be a group and containing item of the group is
    187  // a conceptual parent of the item.
    188  if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) {
    189    Accessible* grandParent = parent->GetNonGenericParent();
    190    if (grandParent && grandParent->Role() == mRole) {
    191      mParentId = grandParent->ID();
    192    }
    193  }
    194 }
    195 
    196 AccGroupInfo* AccGroupInfo::CreateGroupInfo(const Accessible* aAccessible) {
    197  mozilla::a11y::role role = aAccessible->Role();
    198  if (role != mozilla::a11y::roles::ROW &&
    199      role != mozilla::a11y::roles::OUTLINEITEM &&
    200      role != mozilla::a11y::roles::OPTION &&
    201      role != mozilla::a11y::roles::LISTITEM &&
    202      role != mozilla::a11y::roles::MENUITEM &&
    203      role != mozilla::a11y::roles::COMBOBOX_OPTION &&
    204      role != mozilla::a11y::roles::RICH_OPTION &&
    205      role != mozilla::a11y::roles::CHECK_RICH_OPTION &&
    206      role != mozilla::a11y::roles::PARENT_MENUITEM &&
    207      role != mozilla::a11y::roles::CHECK_MENU_ITEM &&
    208      role != mozilla::a11y::roles::RADIO_MENU_ITEM &&
    209      role != mozilla::a11y::roles::RADIOBUTTON &&
    210      role != mozilla::a11y::roles::PAGETAB &&
    211      role != mozilla::a11y::roles::COMMENT) {
    212    return nullptr;
    213  }
    214 
    215  AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role));
    216  return info;
    217 }
    218 
    219 Accessible* AccGroupInfo::FirstItemOf(const Accessible* aContainer) {
    220  // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a
    221  // group is a parent) or by aria-level.
    222  a11y::role containerRole = aContainer->Role();
    223  Accessible* item = aContainer->NextSibling();
    224  if (item) {
    225    if (containerRole == roles::OUTLINEITEM &&
    226        item->Role() == roles::GROUPING) {
    227      item = item->FirstChild();
    228    }
    229 
    230    if (item) {
    231      AccGroupInfo* itemGroupInfo = item->GetOrCreateGroupInfo();
    232      if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) {
    233        return item;
    234      }
    235    }
    236  }
    237 
    238  // ARIA list and tree can be arranged by ARIA groups case #2 (group is
    239  // a child of an item).
    240  item = aContainer->LastChild();
    241  if (!item) return nullptr;
    242 
    243  if (item->Role() == roles::GROUPING &&
    244      (containerRole == roles::LISTITEM ||
    245       containerRole == roles::OUTLINEITEM)) {
    246    item = item->FirstChild();
    247    if (item) {
    248      AccGroupInfo* itemGroupInfo = item->GetOrCreateGroupInfo();
    249      if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) {
    250        return item;
    251      }
    252    }
    253  }
    254 
    255  // Otherwise, it can be a direct child if the container is a list or tree.
    256  item = aContainer->FirstChild();
    257  if (ShouldReportRelations(item->Role(), containerRole)) return item;
    258 
    259  return nullptr;
    260 }
    261 
    262 uint32_t AccGroupInfo::TotalItemCount(Accessible* aContainer,
    263                                      bool* aIsHierarchical) {
    264  uint32_t itemCount = 0;
    265  switch (aContainer->Role()) {
    266    case roles::GRID:
    267    case roles::TABLE:
    268      if (auto val = aContainer->GetIntARIAAttr(nsGkAtoms::aria_rowcount)) {
    269        if (*val >= 0) {
    270          return *val;
    271        }
    272      }
    273      if (TableAccessible* tableAcc = aContainer->AsTable()) {
    274        return tableAcc->RowCount();
    275      }
    276      break;
    277    case roles::ROW:
    278      if (Accessible* table = nsAccUtils::TableFor(aContainer)) {
    279        if (auto val = table->GetIntARIAAttr(nsGkAtoms::aria_colcount)) {
    280          if (*val >= 0) {
    281            return *val;
    282          }
    283        }
    284        if (TableAccessible* tableAcc = table->AsTable()) {
    285          return tableAcc->ColCount();
    286        }
    287      }
    288      break;
    289    case roles::OUTLINE:
    290    case roles::LIST:
    291    case roles::MENUBAR:
    292    case roles::MENUPOPUP:
    293    case roles::COMBOBOX:
    294    case roles::GROUPING:
    295    case roles::ROWGROUP:
    296    case roles::TREE_TABLE:
    297    case roles::COMBOBOX_LIST:
    298    case roles::LISTBOX:
    299    case roles::DEFINITION_LIST:
    300    case roles::EDITCOMBOBOX:
    301    case roles::RADIO_GROUP:
    302    case roles::PAGETABLIST: {
    303      Accessible* childItem = AccGroupInfo::FirstItemOf(aContainer);
    304      if (!childItem) {
    305        childItem = aContainer->FirstChild();
    306        if (childItem && childItem->IsTextLeaf()) {
    307          // First child can be a text leaf, check its sibling for an item.
    308          childItem = childItem->NextSibling();
    309        }
    310      }
    311 
    312      if (childItem) {
    313        GroupPos groupPos = childItem->GroupPosition();
    314        itemCount = groupPos.setSize;
    315        if (groupPos.level && aIsHierarchical) {
    316          *aIsHierarchical = true;
    317        }
    318      }
    319      break;
    320    }
    321    default:
    322      break;
    323  }
    324 
    325  return itemCount;
    326 }
    327 
    328 Accessible* AccGroupInfo::NextItemTo(Accessible* aItem) {
    329  AccGroupInfo* groupInfo = aItem->GetOrCreateGroupInfo();
    330  if (!groupInfo) return nullptr;
    331 
    332  // If the item in middle of the group then search next item in siblings.
    333  if (groupInfo->PosInSet() >= groupInfo->SetSize()) return nullptr;
    334 
    335  Accessible* parent = aItem->Parent();
    336  uint32_t childCount = parent->ChildCount();
    337  for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) {
    338    Accessible* nextItem = parent->ChildAt(idx);
    339    AccGroupInfo* nextGroupInfo = nextItem->GetOrCreateGroupInfo();
    340    if (nextGroupInfo &&
    341        nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
    342      return nextItem;
    343    }
    344  }
    345 
    346  MOZ_ASSERT_UNREACHABLE(
    347      "Item in the middle of the group but there's no next item!");
    348  return nullptr;
    349 }
    350 
    351 size_t AccGroupInfo::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
    352  // We don't count mParentId or mItem since they (should be) counted
    353  // as part of the document.
    354  return aMallocSizeOf(this);
    355 }
    356 
    357 bool AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole) {
    358  // We only want to report hierarchy-based node relations for items in tree or
    359  // list form.  ARIA level/owns relations are always reported.
    360  if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM) return true;
    361  if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW) return true;
    362  if (aParentRole == roles::LIST && aRole == roles::LISTITEM) return true;
    363 
    364  return false;
    365 }
    366 
    367 int32_t AccGroupInfo::GetARIAOrDefaultLevel(const Accessible* aAccessible) {
    368  int32_t level = 0;
    369  aAccessible->ARIAGroupPosition(&level, nullptr, nullptr);
    370 
    371  if (level != 0) return level;
    372 
    373  return aAccessible->GetLevel(true);
    374 }
    375 
    376 Accessible* AccGroupInfo::ConceptualParent() const {
    377  if (!mParentId) {
    378    // The conceptual parent can never be the document, so id 0 means none.
    379    return nullptr;
    380  }
    381  if (Accessible* doc =
    382          nsAccUtils::DocumentFor(const_cast<Accessible*>(mItem))) {
    383    return nsAccUtils::GetAccessibleByID(doc, mParentId);
    384  }
    385  return nullptr;
    386 }
    387 
    388 static role BaseRole(role aRole) {
    389  if (aRole == roles::CHECK_MENU_ITEM || aRole == roles::PARENT_MENUITEM ||
    390      aRole == roles::RADIO_MENU_ITEM) {
    391    return roles::MENUITEM;
    392  }
    393 
    394  if (aRole == roles::CHECK_RICH_OPTION) {
    395    return roles::RICH_OPTION;
    396  }
    397 
    398  return aRole;
    399 }