tor-browser

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

mozTableAccessible.mm (18102B)


      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 "mozTableAccessible.h"
      9 #import "nsCocoaUtils.h"
     10 #import "MacUtils.h"
     11 
     12 #include "AccIterator.h"
     13 #include "LocalAccessible.h"
     14 #include "mozilla/a11y/TableAccessible.h"
     15 #include "mozilla/a11y/TableCellAccessible.h"
     16 #include "nsAccessibilityService.h"
     17 #include "nsIAccessiblePivot.h"
     18 #include "XULTreeAccessible.h"
     19 #include "Pivot.h"
     20 #include "nsAccUtils.h"
     21 #include "Relation.h"
     22 
     23 using namespace mozilla;
     24 using namespace mozilla::a11y;
     25 
     26 @implementation mozColumnContainer
     27 
     28 - (id)initWithIndex:(uint32_t)aIndex andParent:(mozAccessible*)aParent {
     29  self = [super init];
     30  mIndex = aIndex;
     31  mParent = aParent;
     32  return self;
     33 }
     34 
     35 - (NSString*)moxRole {
     36  return NSAccessibilityColumnRole;
     37 }
     38 
     39 - (NSString*)moxRoleDescription {
     40  return NSAccessibilityRoleDescription(NSAccessibilityColumnRole, nil);
     41 }
     42 
     43 - (mozAccessible*)moxParent {
     44  return mParent;
     45 }
     46 
     47 - (NSArray*)moxUnignoredChildren {
     48  if (mChildren) return mChildren;
     49 
     50  mChildren = [[NSMutableArray alloc] init];
     51 
     52  TableAccessible* table = [mParent geckoAccessible]->AsTable();
     53  MOZ_ASSERT(table, "Got null table when fetching column children!");
     54  uint32_t numRows = table->RowCount();
     55 
     56  for (uint32_t j = 0; j < numRows; j++) {
     57    Accessible* cell = table->CellAt(j, mIndex);
     58    mozAccessible* nativeCell = cell ? GetNativeFromGeckoAccessible(cell) : nil;
     59    if ([nativeCell isAccessibilityElement]) {
     60      [mChildren addObject:nativeCell];
     61    }
     62  }
     63 
     64  return mChildren;
     65 }
     66 
     67 - (void)dealloc {
     68  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
     69 
     70  [self invalidateChildren];
     71  [super dealloc];
     72 
     73  NS_OBJC_END_TRY_IGNORE_BLOCK;
     74 }
     75 
     76 - (void)expire {
     77  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
     78 
     79  [self invalidateChildren];
     80 
     81  mParent = nil;
     82 
     83  [super expire];
     84 
     85  NS_OBJC_END_TRY_IGNORE_BLOCK;
     86 }
     87 
     88 - (BOOL)isExpired {
     89  MOZ_ASSERT((mChildren == nil && mParent == nil) == mIsExpired);
     90 
     91  return [super isExpired];
     92 }
     93 
     94 - (void)invalidateChildren {
     95  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
     96 
     97  // make room for new children
     98  if (mChildren) {
     99    [mChildren release];
    100    mChildren = nil;
    101  }
    102 
    103  NS_OBJC_END_TRY_IGNORE_BLOCK;
    104 }
    105 
    106 @end
    107 
    108 @implementation mozTablePartAccessible
    109 
    110 - (NSString*)moxTitle {
    111  return @"";
    112 }
    113 
    114 - (NSString*)moxRole {
    115  return [self isLayoutTablePart] ? NSAccessibilityGroupRole : [super moxRole];
    116 }
    117 
    118 - (BOOL)isLayoutTablePart {
    119  mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
    120  if ([parent isKindOfClass:[mozTablePartAccessible class]]) {
    121    return [(mozTablePartAccessible*)parent isLayoutTablePart];
    122  } else if ([parent isKindOfClass:[mozOutlineAccessible class]]) {
    123    return [(mozOutlineAccessible*)parent isLayoutTablePart];
    124  }
    125 
    126  return NO;
    127 }
    128 @end
    129 
    130 @implementation mozTableAccessible
    131 
    132 - (BOOL)isLayoutTablePart {
    133  if (mGeckoAccessible->Role() == roles::TREE_TABLE) {
    134    // tree tables are never layout tables, and we shouldn't
    135    // query IsProbablyLayoutTable() on them, so we short
    136    // circuit here
    137    return false;
    138  }
    139 
    140  // For LocalAccessible and cached RemoteAccessible, we could use
    141  // AsTable()->IsProbablyLayoutTable(). However, if the cache is enabled,
    142  // that would build the table cache, which is pointless for layout tables on
    143  // Mac because layout tables are AXGroups and do not expose table properties
    144  // like AXRows, AXColumns, etc.
    145  if (LocalAccessible* acc = mGeckoAccessible->AsLocal()) {
    146    return acc->AsTable()->IsProbablyLayoutTable();
    147  }
    148  RemoteAccessible* proxy = mGeckoAccessible->AsRemote();
    149  return proxy->TableIsProbablyForLayout();
    150 }
    151 
    152 - (void)handleAccessibleEvent:(uint32_t)eventType {
    153  if (eventType == nsIAccessibleEvent::EVENT_REORDER ||
    154      eventType == nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED) {
    155    [self invalidateColumns];
    156  }
    157 
    158  [super handleAccessibleEvent:eventType];
    159 }
    160 
    161 - (void)dealloc {
    162  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
    163 
    164  [self invalidateColumns];
    165  [super dealloc];
    166 
    167  NS_OBJC_END_TRY_IGNORE_BLOCK;
    168 }
    169 
    170 - (void)expire {
    171  [self invalidateColumns];
    172  [super expire];
    173 }
    174 
    175 - (NSNumber*)moxRowCount {
    176  MOZ_ASSERT(mGeckoAccessible);
    177 
    178  return @(mGeckoAccessible->AsTable()->RowCount());
    179 }
    180 
    181 - (NSNumber*)moxColumnCount {
    182  MOZ_ASSERT(mGeckoAccessible);
    183 
    184  return @(mGeckoAccessible->AsTable()->ColCount());
    185 }
    186 
    187 - (NSArray*)moxRows {
    188  // Create a new array with the list of table rows.
    189  NSArray* children = [self moxChildren];
    190  NSMutableArray* rows = [[[NSMutableArray alloc] init] autorelease];
    191  for (mozAccessible* curr : children) {
    192    if ([curr isKindOfClass:[mozTableRowAccessible class]]) {
    193      [rows addObject:curr];
    194    } else if ([[curr moxRole] isEqualToString:@"AXGroup"]) {
    195      // Plain thead/tbody elements are removed from the core a11y tree and
    196      // replaced with their subtree, but thead/tbody elements with click
    197      // handlers are not -- they remain as groups. We need to expose any
    198      // rows they contain as rows of the parent table.
    199      [rows
    200          addObjectsFromArray:[[curr moxChildren]
    201                                  filteredArrayUsingPredicate:
    202                                      [NSPredicate predicateWithBlock:^BOOL(
    203                                                       mozAccessible* child,
    204                                                       NSDictionary* bindings) {
    205                                        return [child
    206                                            isKindOfClass:[mozTableRowAccessible
    207                                                              class]];
    208                                      }]]];
    209    }
    210  }
    211 
    212  return rows;
    213 }
    214 
    215 - (NSArray*)moxColumns {
    216  MOZ_ASSERT(mGeckoAccessible);
    217 
    218  if (mColContainers) {
    219    return mColContainers;
    220  }
    221 
    222  mColContainers = [[NSMutableArray alloc] init];
    223  uint32_t numCols = 0;
    224 
    225  numCols = mGeckoAccessible->AsTable()->ColCount();
    226  for (uint32_t i = 0; i < numCols; i++) {
    227    mozColumnContainer* container =
    228        [[mozColumnContainer alloc] initWithIndex:i andParent:self];
    229    [mColContainers addObject:container];
    230  }
    231 
    232  return mColContainers;
    233 }
    234 
    235 - (NSArray*)moxUnignoredChildren {
    236  if (![self isLayoutTablePart]) {
    237    return [[super moxUnignoredChildren]
    238        arrayByAddingObjectsFromArray:[self moxColumns]];
    239  }
    240 
    241  return [super moxUnignoredChildren];
    242 }
    243 
    244 - (NSArray*)moxColumnHeaderUIElements {
    245  MOZ_ASSERT(mGeckoAccessible);
    246 
    247  uint32_t numCols = 0;
    248  TableAccessible* table = nullptr;
    249 
    250  table = mGeckoAccessible->AsTable();
    251  numCols = table->ColCount();
    252  NSMutableArray* colHeaders =
    253      [[[NSMutableArray alloc] initWithCapacity:numCols] autorelease];
    254 
    255  for (uint32_t i = 0; i < numCols; i++) {
    256    Accessible* cell = table->CellAt(0, i);
    257    if (cell && cell->Role() == roles::COLUMNHEADER) {
    258      mozAccessible* colHeader = GetNativeFromGeckoAccessible(cell);
    259      [colHeaders addObject:colHeader];
    260    }
    261  }
    262 
    263  return colHeaders;
    264 }
    265 
    266 - (id)moxCellForColumnAndRow:(NSArray*)columnAndRow {
    267  if (columnAndRow == nil || [columnAndRow count] != 2) {
    268    return nil;
    269  }
    270 
    271  uint32_t col = [[columnAndRow objectAtIndex:0] unsignedIntValue];
    272  uint32_t row = [[columnAndRow objectAtIndex:1] unsignedIntValue];
    273 
    274  MOZ_ASSERT(mGeckoAccessible);
    275 
    276  Accessible* cell = mGeckoAccessible->AsTable()->CellAt(row, col);
    277  if (!cell) {
    278    return nil;
    279  }
    280 
    281  return GetNativeFromGeckoAccessible(cell);
    282 }
    283 
    284 - (void)invalidateColumns {
    285  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
    286  if (mColContainers) {
    287    for (mozColumnContainer* col in mColContainers) {
    288      [col expire];
    289    }
    290    [mColContainers release];
    291    mColContainers = nil;
    292  }
    293  NS_OBJC_END_TRY_IGNORE_BLOCK;
    294 }
    295 
    296 @end
    297 
    298 @interface mozTableRowAccessible ()
    299 - (mozTableAccessible*)getTableParent;
    300 @end
    301 
    302 @implementation mozTableRowAccessible
    303 
    304 - (mozTableAccessible*)getTableParent {
    305  id tableParent = static_cast<mozTableAccessible*>(
    306      [self moxFindAncestor:^BOOL(id curr, BOOL* stop) {
    307        if ([curr isKindOfClass:[mozOutlineAccessible class]]) {
    308          // Outline rows are a kind of table row, so it's possible
    309          // we're trying to call getTableParent on an outline row here.
    310          // Stop searching.
    311          *stop = YES;
    312        }
    313        return [curr isKindOfClass:[mozTableAccessible class]];
    314      }]);
    315 
    316  return [tableParent isKindOfClass:[mozTableAccessible class]] ? tableParent
    317                                                                : nil;
    318 }
    319 
    320 - (void)handleAccessibleEvent:(uint32_t)eventType {
    321  if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
    322    // It is possible for getTableParent to return nil if we're
    323    // handling a reorder on an outilne row. Outlines don't have
    324    // columns, so there's nothing to do here and this will no-op.
    325    [[self getTableParent] invalidateColumns];
    326  }
    327 
    328  [super handleAccessibleEvent:eventType];
    329 }
    330 
    331 - (NSNumber*)moxIndex {
    332  return @([[[self getTableParent] moxRows] indexOfObjectIdenticalTo:self]);
    333 }
    334 
    335 @end
    336 
    337 @implementation mozTableCellAccessible
    338 
    339 - (NSValue*)moxRowIndexRange {
    340  MOZ_ASSERT(mGeckoAccessible);
    341 
    342  TableCellAccessible* cell = mGeckoAccessible->AsTableCell();
    343  return
    344      [NSValue valueWithRange:NSMakeRange(cell->RowIdx(), cell->RowExtent())];
    345 }
    346 
    347 - (NSValue*)moxColumnIndexRange {
    348  MOZ_ASSERT(mGeckoAccessible);
    349 
    350  TableCellAccessible* cell = mGeckoAccessible->AsTableCell();
    351  return
    352      [NSValue valueWithRange:NSMakeRange(cell->ColIdx(), cell->ColExtent())];
    353 }
    354 
    355 - (NSArray*)moxRowHeaderUIElements {
    356  MOZ_ASSERT(mGeckoAccessible);
    357 
    358  TableCellAccessible* cell = mGeckoAccessible->AsTableCell();
    359  AutoTArray<Accessible*, 10> headerCells;
    360  if (cell) {
    361    cell->RowHeaderCells(&headerCells);
    362  }
    363  return utils::ConvertToNSArray(headerCells);
    364 }
    365 
    366 - (NSArray*)moxColumnHeaderUIElements {
    367  MOZ_ASSERT(mGeckoAccessible);
    368 
    369  TableCellAccessible* cell = mGeckoAccessible->AsTableCell();
    370  AutoTArray<Accessible*, 10> headerCells;
    371  if (cell) {
    372    cell->ColHeaderCells(&headerCells);
    373  }
    374  return utils::ConvertToNSArray(headerCells);
    375 }
    376 
    377 @end
    378 
    379 /**
    380 * This rule matches all accessibles with roles::OUTLINEITEM. If
    381 * outlines are nested, it ignores the nested subtree and returns
    382 * only items which are descendants of the primary outline.
    383 */
    384 class OutlineRule : public PivotRule {
    385 public:
    386  uint16_t Match(Accessible* aAcc) override {
    387    uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
    388 
    389    if (nsAccUtils::MustPrune(aAcc)) {
    390      result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    391    }
    392 
    393    if (![GetNativeFromGeckoAccessible(aAcc) isAccessibilityElement]) {
    394      return result;
    395    }
    396 
    397    if (aAcc->Role() == roles::OUTLINE) {
    398      // if the accessible is an outline, we ignore all children
    399      result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    400    } else if (aAcc->Role() == roles::OUTLINEITEM) {
    401      // if the accessible is not an outline item, we match here
    402      result |= nsIAccessibleTraversalRule::FILTER_MATCH;
    403    }
    404 
    405    return result;
    406  }
    407 };
    408 
    409 @implementation mozOutlineAccessible
    410 
    411 - (BOOL)isLayoutTablePart {
    412  return NO;
    413 }
    414 
    415 - (NSArray*)moxRows {
    416  // Create a new array with the list of outline rows. We
    417  // use pivot here to do a deep traversal of all rows nested
    418  // in this outline, not just those which are direct
    419  // children, since that's what VO expects.
    420  NSMutableArray* allRows = [[[NSMutableArray alloc] init] autorelease];
    421  Pivot p = Pivot(mGeckoAccessible);
    422  OutlineRule rule = OutlineRule();
    423  Accessible* firstChild = mGeckoAccessible->FirstChild();
    424  Accessible* match = p.Next(firstChild, rule, true);
    425  while (match) {
    426    [allRows addObject:GetNativeFromGeckoAccessible(match)];
    427    match = p.Next(match, rule);
    428  }
    429  return allRows;
    430 }
    431 
    432 - (NSArray*)moxColumns {
    433  if (LocalAccessible* acc = mGeckoAccessible->AsLocal()) {
    434    if (acc->IsContent() && acc->GetContent()->IsXULElement(nsGkAtoms::tree)) {
    435      XULTreeAccessible* treeAcc = (XULTreeAccessible*)acc;
    436      NSMutableArray* cols = [[[NSMutableArray alloc] init] autorelease];
    437      // XUL trees store their columns in a group at the tree's first
    438      // child. Here, we iterate over that group to get each column's
    439      // native accessible and add it to our col array.
    440      LocalAccessible* treeColumns = treeAcc->LocalChildAt(0);
    441      if (treeColumns) {
    442        uint32_t colCount = treeColumns->ChildCount();
    443        for (uint32_t i = 0; i < colCount; i++) {
    444          LocalAccessible* treeColumnItem = treeColumns->LocalChildAt(i);
    445          [cols addObject:GetNativeFromGeckoAccessible(treeColumnItem)];
    446        }
    447        return cols;
    448      }
    449    }
    450  }
    451  // Webkit says we shouldn't expose any cols for aria-tree
    452  // so we return an empty array here
    453  return @[];
    454 }
    455 
    456 - (NSArray*)moxSelectedRows {
    457  NSMutableArray* selectedRows = [[[NSMutableArray alloc] init] autorelease];
    458  NSArray* allRows = [self moxRows];
    459  for (mozAccessible* row in allRows) {
    460    if ([row stateWithMask:states::SELECTED] != 0) {
    461      [selectedRows addObject:row];
    462    }
    463  }
    464 
    465  return selectedRows;
    466 }
    467 
    468 - (NSString*)moxOrientation {
    469  return NSAccessibilityVerticalOrientationValue;
    470 }
    471 
    472 @end
    473 
    474 @implementation mozOutlineRowAccessible
    475 
    476 - (BOOL)isLayoutTablePart {
    477  return NO;
    478 }
    479 
    480 - (NSNumber*)moxDisclosing {
    481  return @([self stateWithMask:states::EXPANDED] != 0);
    482 }
    483 
    484 - (void)moxSetDisclosing:(NSNumber*)disclosing {
    485  // VoiceOver requires this to be settable, but doesn't
    486  // require it actually affect our disclosing state.
    487  // We expose the attr as settable with this method
    488  // but do nothing to actually set it.
    489  return;
    490 }
    491 
    492 - (NSNumber*)moxExpanded {
    493  return @([self stateWithMask:states::EXPANDED] != 0);
    494 }
    495 
    496 - (id)moxDisclosedByRow {
    497  // According to webkit: this attr corresponds to the row
    498  // that contains this row. It should be the same as the
    499  // first parent that is a treeitem. If the parent is the tree
    500  // itself, this should be nil. This is tricky for xul trees because
    501  // all rows are direct children of the outline; they use
    502  // relations to expose their heirarchy structure.
    503 
    504  // first we check the relations to see if we're in a xul tree
    505  // with weird row semantics
    506  NSArray<mozAccessible*>* disclosingRows =
    507      [self getRelationsByType:RelationType::NODE_CHILD_OF];
    508  mozAccessible* disclosingRow = [disclosingRows firstObject];
    509 
    510  if (disclosingRow) {
    511    // if we find a row from our relation check,
    512    // verify it isn't the outline itself and return
    513    // appropriately
    514    if ([[disclosingRow moxRole] isEqualToString:@"AXOutline"]) {
    515      return nil;
    516    }
    517 
    518    return disclosingRow;
    519  }
    520 
    521  mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
    522  // otherwise, its likely we're in an aria tree, so we can use
    523  // these role and subrole checks
    524  if ([[parent moxRole] isEqualToString:@"AXOutline"]) {
    525    return nil;
    526  }
    527 
    528  if ([[parent moxSubrole] isEqualToString:@"AXOutlineRow"]) {
    529    disclosingRow = parent;
    530  }
    531 
    532  return nil;
    533 }
    534 
    535 - (NSNumber*)moxDisclosureLevel {
    536  GroupPos groupPos = mGeckoAccessible->GroupPosition();
    537 
    538  // mac expects 0-indexed levels, but groupPos.level is 1-indexed
    539  // so we subtract 1 here for levels above 0
    540  return groupPos.level > 0 ? @(groupPos.level - 1) : @(groupPos.level);
    541 }
    542 
    543 - (NSArray*)moxDisclosedRows {
    544  // According to webkit: this attr corresponds to the rows
    545  // that are considered inside this row. Again, this is weird for
    546  // xul trees so we have to use relations first and then fall-back
    547  // to the children filter for non-xul outlines.
    548 
    549  // first we check the relations to see if we're in a xul tree
    550  // with weird row semantics
    551  if (NSArray* disclosedRows =
    552          [self getRelationsByType:RelationType::NODE_PARENT_OF]) {
    553    // if we find rows from our relation check, return them here
    554    return disclosedRows;
    555  }
    556 
    557  // otherwise, filter our children for outline rows
    558  return [[self moxChildren]
    559      filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
    560                                                   mozAccessible* child,
    561                                                   NSDictionary* bindings) {
    562        return [child isKindOfClass:[mozOutlineRowAccessible class]];
    563      }]];
    564 }
    565 
    566 - (NSNumber*)moxIndex {
    567  id<MOXAccessible> outline =
    568      [self moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
    569        return [[moxAcc moxRole] isEqualToString:@"AXOutline"];
    570      }];
    571 
    572  NSUInteger index = [[outline moxRows] indexOfObjectIdenticalTo:self];
    573  return index == NSNotFound ? nil : @(index);
    574 }
    575 
    576 - (NSString*)moxLabel {
    577  nsAutoString title;
    578  mGeckoAccessible->Name(title);
    579 
    580  // XXX: When parsing outlines built with ul/lu's, we
    581  // include the bullet in this description even
    582  // though webkit doesn't. Not all outlines are built with
    583  // ul/lu's so we can't strip the first character here.
    584 
    585  return nsCocoaUtils::ToNSString(title);
    586 }
    587 
    588 - (int)checkedValue {
    589  uint64_t state = [self
    590      stateWithMask:(states::CHECKABLE | states::CHECKED | states::MIXED)];
    591 
    592  if (state & states::CHECKABLE) {
    593    if (state & states::CHECKED) {
    594      return kChecked;
    595    }
    596 
    597    if (state & states::MIXED) {
    598      return kMixed;
    599    }
    600 
    601    return kUnchecked;
    602  }
    603 
    604  return kUncheckable;
    605 }
    606 
    607 - (id)moxValue {
    608  int checkedValue = [self checkedValue];
    609  return checkedValue >= 0 ? @(checkedValue) : nil;
    610 }
    611 
    612 - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
    613  [super stateChanged:state isEnabled:enabled];
    614 
    615  if (state & states::EXPANDED) {
    616    // If the EXPANDED state is updated, fire appropriate events on the
    617    // outline row.
    618    [self moxPostNotification:(enabled
    619                                   ? NSAccessibilityRowExpandedNotification
    620                                   : NSAccessibilityRowCollapsedNotification)];
    621  }
    622 
    623  if (state & (states::CHECKED | states::CHECKABLE | states::MIXED)) {
    624    // If the MIXED, CHECKED or CHECKABLE state changes, update the value we
    625    // expose for the row, which communicates checked status.
    626    [self moxPostNotification:NSAccessibilityValueChangedNotification];
    627  }
    628 }
    629 
    630 @end