tor-browser

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

CachedTableAccessible.cpp (15887B)


      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 "CachedTableAccessible.h"
      8 
      9 #include "AccIterator.h"
     10 #include "HTMLTableAccessible.h"
     11 #include "mozilla/ClearOnShutdown.h"
     12 #include "mozilla/StaticPtr.h"
     13 #include "mozilla/UniquePtr.h"
     14 #include "nsAccUtils.h"
     15 #include "nsIAccessiblePivot.h"
     16 #include "nsThreadUtils.h"
     17 #include "Pivot.h"
     18 #include "RemoteAccessible.h"
     19 
     20 namespace mozilla::a11y {
     21 
     22 // Used to search for table descendants relevant to table structure.
     23 class TablePartRule : public PivotRule {
     24 public:
     25  virtual uint16_t Match(Accessible* aAcc) override {
     26    role accRole = aAcc->Role();
     27    if (accRole == roles::CAPTION || aAcc->IsTableCell()) {
     28      return nsIAccessibleTraversalRule::FILTER_MATCH |
     29             nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
     30    }
     31    if (aAcc->IsTableRow()) {
     32      return nsIAccessibleTraversalRule::FILTER_MATCH;
     33    }
     34    if (aAcc->IsTable() ||
     35        // Generic containers.
     36        accRole == roles::TEXT || accRole == roles::TEXT_CONTAINER ||
     37        accRole == roles::SECTION ||
     38        // Row groups.
     39        accRole == roles::ROWGROUP) {
     40      // Walk inside these, but don't match them.
     41      return nsIAccessibleTraversalRule::FILTER_IGNORE;
     42    }
     43    return nsIAccessibleTraversalRule::FILTER_IGNORE |
     44           nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
     45  }
     46 };
     47 
     48 // The Accessible* keys should only be used for lookup. They should not be
     49 // dereferenced.
     50 using CachedTablesMap = nsTHashMap<Accessible*, CachedTableAccessible>;
     51 // We use a global map rather than a map in each document for three reasons:
     52 // 1. We don't have a common base class for local and remote documents.
     53 // 2. It avoids wasting memory in a document that doesn't have any tables.
     54 // 3. It allows the cache management to be encapsulated here in
     55 // CachedTableAccessible.
     56 static StaticAutoPtr<CachedTablesMap> sCachedTables;
     57 
     58 /* static */
     59 CachedTableAccessible* CachedTableAccessible::GetFrom(Accessible* aAcc) {
     60  MOZ_ASSERT(aAcc->IsTable());
     61  if (!sCachedTables) {
     62    sCachedTables = new CachedTablesMap();
     63    if (NS_IsMainThread()) {
     64      ClearOnShutdown(&sCachedTables);
     65    } else {
     66 #ifdef ANDROID
     67      NS_DispatchToMainThread(
     68          NS_NewRunnableFunction("CachedTableAccessible::GetFrom",
     69                                 [] { ClearOnShutdown(&sCachedTables); }));
     70 #else
     71      MOZ_ASSERT_UNREACHABLE("Querying a table on the wrong thread!");
     72 #endif
     73    }
     74  }
     75  return &sCachedTables->LookupOrInsertWith(
     76      aAcc, [&] { return CachedTableAccessible(aAcc); });
     77 }
     78 
     79 /* static */
     80 void CachedTableAccessible::Invalidate(Accessible* aAcc) {
     81  if (!sCachedTables) {
     82    return;
     83  }
     84 
     85  if (Accessible* table = nsAccUtils::TableFor(aAcc)) {
     86    // Destroy the instance (if any). We'll create a new one the next time it
     87    // is requested.
     88    sCachedTables->Remove(table);
     89  }
     90 }
     91 
     92 CachedTableAccessible::CachedTableAccessible(Accessible* aAcc) : mAcc(aAcc) {
     93  MOZ_ASSERT(mAcc);
     94  // Build the cache. The cache can only be built once per instance. When it's
     95  // invalidated, we just throw away the instance and create a new one when
     96  // the cache is next needed.
     97  int32_t rowIdx = -1;
     98  uint32_t colIdx = 0;
     99  // Maps a column index to the cell index of its previous implicit column
    100  // header.
    101  nsTHashMap<uint32_t, uint32_t> prevColHeaders;
    102  Pivot pivot(mAcc);
    103  TablePartRule rule;
    104  for (Accessible* part = pivot.Next(mAcc, rule); part;
    105       part = pivot.Next(part, rule)) {
    106    role partRole = part->Role();
    107    if (partRole == roles::CAPTION) {
    108      // If there are multiple captions, use the first.
    109      if (!mCaptionAccID) {
    110        mCaptionAccID = part->ID();
    111      }
    112      continue;
    113    }
    114    if (part->IsTableRow()) {
    115      ++rowIdx;
    116      colIdx = 0;
    117      // This might be an empty row, so ensure a row here, as our row count is
    118      // based on the length of mRowColToCellIdx.
    119      EnsureRow(rowIdx);
    120      continue;
    121    }
    122    MOZ_ASSERT(part->IsTableCell());
    123    if (rowIdx == -1) {
    124      // We haven't created a row yet, so this cell must be outside a row.
    125      continue;
    126    }
    127    // Check for a cell spanning multiple rows which already occupies this
    128    // position. Keep incrementing until we find a vacant position.
    129    for (;;) {
    130      EnsureRowCol(rowIdx, colIdx);
    131      if (mRowColToCellIdx[rowIdx][colIdx] == kNoCellIdx) {
    132        // This position is not occupied.
    133        break;
    134      }
    135      // This position is occupied.
    136      ++colIdx;
    137    }
    138    // Create the cell.
    139    uint32_t cellIdx = mCells.Length();
    140    auto prevColHeader = prevColHeaders.MaybeGet(colIdx);
    141    auto cell = mCells.AppendElement(
    142        CachedTableCellAccessible(part->ID(), part, rowIdx, colIdx,
    143                                  prevColHeader ? *prevColHeader : kNoCellIdx));
    144    mAccToCellIdx.InsertOrUpdate(part, cellIdx);
    145    // Update our row/col map.
    146    // This cell might span multiple rows and/or columns. In that case, we need
    147    // to occupy multiple coordinates in the row/col map.
    148    uint32_t lastRowForCell =
    149        static_cast<uint32_t>(rowIdx) + cell->RowExtent() - 1;
    150    MOZ_ASSERT(lastRowForCell >= static_cast<uint32_t>(rowIdx));
    151    uint32_t lastColForCell = colIdx + cell->ColExtent() - 1;
    152    MOZ_ASSERT(lastColForCell >= colIdx);
    153    for (uint32_t spannedRow = static_cast<uint32_t>(rowIdx);
    154         spannedRow <= lastRowForCell; ++spannedRow) {
    155      for (uint32_t spannedCol = colIdx; spannedCol <= lastColForCell;
    156           ++spannedCol) {
    157        EnsureRowCol(spannedRow, spannedCol);
    158        auto& rowCol = mRowColToCellIdx[spannedRow][spannedCol];
    159        // If a cell already occupies this position, it overlaps with this one;
    160        // e.g. r1..2c2 and r2c1..2. In that case, we want to prefer the first
    161        // cell.
    162        if (rowCol == kNoCellIdx) {
    163          rowCol = cellIdx;
    164        }
    165      }
    166    }
    167    if (partRole == roles::COLUMNHEADER) {
    168      for (uint32_t spannedCol = colIdx; spannedCol <= lastColForCell;
    169           ++spannedCol) {
    170        prevColHeaders.InsertOrUpdate(spannedCol, cellIdx);
    171      }
    172    }
    173    // Increment for the next cell.
    174    colIdx = lastColForCell + 1;
    175  }
    176 }
    177 
    178 void CachedTableAccessible::EnsureRow(uint32_t aRowIdx) {
    179  if (mRowColToCellIdx.Length() <= aRowIdx) {
    180    mRowColToCellIdx.AppendElements(aRowIdx - mRowColToCellIdx.Length() + 1);
    181  }
    182  MOZ_ASSERT(mRowColToCellIdx.Length() > aRowIdx);
    183 }
    184 
    185 void CachedTableAccessible::EnsureRowCol(uint32_t aRowIdx, uint32_t aColIdx) {
    186  EnsureRow(aRowIdx);
    187  auto& row = mRowColToCellIdx[aRowIdx];
    188  if (mColCount <= aColIdx) {
    189    mColCount = aColIdx + 1;
    190  }
    191  row.SetCapacity(mColCount);
    192  for (uint32_t newCol = row.Length(); newCol <= aColIdx; ++newCol) {
    193    // An entry doesn't yet exist for this column in this row.
    194    row.AppendElement(kNoCellIdx);
    195  }
    196  MOZ_ASSERT(row.Length() > aColIdx);
    197 }
    198 
    199 Accessible* CachedTableAccessible::Caption() const {
    200  if (mCaptionAccID) {
    201    Accessible* caption = nsAccUtils::GetAccessibleByID(
    202        nsAccUtils::DocumentFor(mAcc), mCaptionAccID);
    203    MOZ_ASSERT(caption, "Dead caption Accessible!");
    204    MOZ_ASSERT(caption->Role() == roles::CAPTION, "Caption has wrong role");
    205    return caption;
    206  }
    207  return nullptr;
    208 }
    209 
    210 void CachedTableAccessible::Summary(nsString& aSummary) {
    211  if (Caption()) {
    212    // If there's a caption, we map caption to Name and summary to Description.
    213    mAcc->Description(aSummary);
    214  } else {
    215    // If there's no caption, we map summary to Name.
    216    mAcc->Name(aSummary);
    217  }
    218 }
    219 
    220 Accessible* CachedTableAccessible::CellAt(uint32_t aRowIdx, uint32_t aColIdx) {
    221  int32_t cellIdx = CellIndexAt(aRowIdx, aColIdx);
    222  if (cellIdx == -1) {
    223    return nullptr;
    224  }
    225  return mCells[cellIdx].Acc(mAcc);
    226 }
    227 
    228 bool CachedTableAccessible::IsProbablyLayoutTable() {
    229  if (RemoteAccessible* remoteAcc = mAcc->AsRemote()) {
    230    return remoteAcc->TableIsProbablyForLayout();
    231  }
    232  if (auto* localTable = HTMLTableAccessible::GetFrom(mAcc->AsLocal())) {
    233    return localTable->IsProbablyLayoutTable();
    234  }
    235  return false;
    236 }
    237 
    238 /* static */
    239 CachedTableCellAccessible* CachedTableCellAccessible::GetFrom(
    240    Accessible* aAcc) {
    241  MOZ_ASSERT(aAcc->IsTableCell());
    242  for (Accessible* parent = aAcc; parent; parent = parent->Parent()) {
    243    if (parent->IsDoc()) {
    244      break;  // Never cross document boundaries.
    245    }
    246    TableAccessible* table = parent->AsTable();
    247    if (!table) {
    248      continue;
    249    }
    250    if (LocalAccessible* local = parent->AsLocal()) {
    251      nsIContent* content = local->GetContent();
    252      if (content && content->IsXULElement()) {
    253        // XUL tables don't use CachedTableAccessible.
    254        break;
    255      }
    256    }
    257    // Non-XUL tables only use CachedTableAccessible.
    258    auto* cachedTable = static_cast<CachedTableAccessible*>(table);
    259    if (auto cellIdx = cachedTable->mAccToCellIdx.Lookup(aAcc)) {
    260      return &cachedTable->mCells[*cellIdx];
    261    }
    262    // We found a table, but it doesn't know about this cell. This can happen
    263    // if a cell is outside of a row due to authoring error. We must not search
    264    // ancestor tables, since this cell's data is not valid there and vice
    265    // versa.
    266    break;
    267  }
    268  return nullptr;
    269 }
    270 
    271 Accessible* CachedTableCellAccessible::Acc(Accessible* aTableAcc) const {
    272  Accessible* acc =
    273      nsAccUtils::GetAccessibleByID(nsAccUtils::DocumentFor(aTableAcc), mAccID);
    274  MOZ_DIAGNOSTIC_ASSERT(acc == mAcc, "Cell's cached mAcc is dead!");
    275  return acc;
    276 }
    277 
    278 TableAccessible* CachedTableCellAccessible::Table() const {
    279  for (const Accessible* acc = mAcc; acc; acc = acc->Parent()) {
    280    // Since the caller has this cell, the table is already created, so it's
    281    // okay to ignore the const restriction here.
    282    if (TableAccessible* table = const_cast<Accessible*>(acc)->AsTable()) {
    283      return table;
    284    }
    285  }
    286  return nullptr;
    287 }
    288 
    289 uint32_t CachedTableCellAccessible::ColExtent() const {
    290  if (RemoteAccessible* remoteAcc = mAcc->AsRemote()) {
    291    if (RequestDomainsIfInactive(CacheDomain::Table)) {
    292      return 1;
    293    }
    294    if (remoteAcc->mCachedFields) {
    295      if (auto colSpan = remoteAcc->mCachedFields->GetAttribute<int32_t>(
    296              CacheKey::ColSpan)) {
    297        MOZ_ASSERT(*colSpan > 0);
    298        return *colSpan;
    299      }
    300    }
    301  } else if (auto* cell = HTMLTableCellAccessible::GetFrom(mAcc->AsLocal())) {
    302    // For HTML table cells, we must use the HTMLTableCellAccessible
    303    // GetColExtent method rather than using the DOM attributes directly.
    304    // This is because of things like rowspan="0" which depend on knowing
    305    // about thead, tbody, etc., which is info we don't have in the a11y tree.
    306    uint32_t colExtent = cell->ColExtent();
    307    MOZ_ASSERT(colExtent > 0);
    308    if (colExtent > 0) {
    309      return colExtent;
    310    }
    311  }
    312  return 1;
    313 }
    314 
    315 uint32_t CachedTableCellAccessible::RowExtent() const {
    316  if (RemoteAccessible* remoteAcc = mAcc->AsRemote()) {
    317    if (RequestDomainsIfInactive(CacheDomain::Table)) {
    318      return 1;
    319    }
    320    if (remoteAcc->mCachedFields) {
    321      if (auto rowSpan = remoteAcc->mCachedFields->GetAttribute<int32_t>(
    322              CacheKey::RowSpan)) {
    323        MOZ_ASSERT(*rowSpan > 0);
    324        return *rowSpan;
    325      }
    326    }
    327  } else if (auto* cell = HTMLTableCellAccessible::GetFrom(mAcc->AsLocal())) {
    328    // For HTML table cells, we must use the HTMLTableCellAccessible
    329    // GetRowExtent method rather than using the DOM attributes directly.
    330    // This is because of things like rowspan="0" which depend on knowing
    331    // about thead, tbody, etc., which is info we don't have in the a11y tree.
    332    uint32_t rowExtent = cell->RowExtent();
    333    MOZ_ASSERT(rowExtent > 0);
    334    if (rowExtent > 0) {
    335      return rowExtent;
    336    }
    337  }
    338  return 1;
    339 }
    340 
    341 UniquePtr<AccIterable> CachedTableCellAccessible::GetExplicitHeadersIterator() {
    342  if (RemoteAccessible* remoteAcc = mAcc->AsRemote()) {
    343    if (RequestDomainsIfInactive(CacheDomain::Table)) {
    344      return nullptr;
    345    }
    346    if (remoteAcc->mCachedFields) {
    347      if (auto headers =
    348              remoteAcc->mCachedFields->GetAttribute<nsTArray<uint64_t>>(
    349                  CacheKey::CellHeaders)) {
    350        return MakeUnique<RemoteAccIterator>(*headers, remoteAcc->Document());
    351      }
    352    }
    353  } else if (LocalAccessible* localAcc = mAcc->AsLocal()) {
    354    return MakeUnique<AssociatedElementsIterator>(
    355        localAcc->Document(), localAcc->GetContent(), nsGkAtoms::headers);
    356  }
    357  return nullptr;
    358 }
    359 
    360 void CachedTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells) {
    361  auto* table = static_cast<CachedTableAccessible*>(Table());
    362  if (!table) {
    363    return;
    364  }
    365  if (mAcc->IsRemote() && RequestDomainsIfInactive(CacheDomain::Table)) {
    366    return;
    367  }
    368  if (auto iter = GetExplicitHeadersIterator()) {
    369    while (Accessible* header = iter->Next()) {
    370      role headerRole = header->Role();
    371      if (headerRole == roles::COLUMNHEADER) {
    372        aCells->AppendElement(header);
    373      } else if (headerRole != roles::ROWHEADER) {
    374        // Treat this cell as a column header only if it's in the same column.
    375        if (auto cellIdx = table->mAccToCellIdx.Lookup(header)) {
    376          CachedTableCellAccessible& cell = table->mCells[*cellIdx];
    377          if (cell.ColIdx() == ColIdx()) {
    378            aCells->AppendElement(header);
    379          }
    380        }
    381      }
    382    }
    383    if (!aCells->IsEmpty()) {
    384      return;
    385    }
    386  }
    387  Accessible* doc = nsAccUtils::DocumentFor(table->AsAccessible());
    388  // Each cell stores its previous implicit column header, effectively forming a
    389  // linked list. We traverse that to get all the headers.
    390  CachedTableCellAccessible* cell = this;
    391  for (;;) {
    392    if (cell->mPrevColHeaderCellIdx == kNoCellIdx) {
    393      break;  // No more headers.
    394    }
    395    cell = &table->mCells[cell->mPrevColHeaderCellIdx];
    396    Accessible* cellAcc = nsAccUtils::GetAccessibleByID(doc, cell->mAccID);
    397    aCells->AppendElement(cellAcc);
    398  }
    399 }
    400 
    401 void CachedTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells) {
    402  auto* table = static_cast<CachedTableAccessible*>(Table());
    403  if (!table) {
    404    return;
    405  }
    406  if (auto iter = GetExplicitHeadersIterator()) {
    407    while (Accessible* header = iter->Next()) {
    408      role headerRole = header->Role();
    409      if (headerRole == roles::ROWHEADER) {
    410        aCells->AppendElement(header);
    411      } else if (headerRole != roles::COLUMNHEADER) {
    412        // Treat this cell as a row header only if it's in the same row.
    413        if (auto cellIdx = table->mAccToCellIdx.Lookup(header)) {
    414          CachedTableCellAccessible& cell = table->mCells[*cellIdx];
    415          if (cell.RowIdx() == RowIdx()) {
    416            aCells->AppendElement(header);
    417          }
    418        }
    419      }
    420    }
    421    if (!aCells->IsEmpty()) {
    422      return;
    423    }
    424  }
    425  Accessible* doc = nsAccUtils::DocumentFor(table->AsAccessible());
    426  // We don't cache implicit row headers because there are usually not that many
    427  // cells per row. Get all the row headers on the row before this cell.
    428  uint32_t row = RowIdx();
    429  uint32_t thisCol = ColIdx();
    430  for (uint32_t col = thisCol - 1; col < thisCol; --col) {
    431    int32_t cellIdx = table->CellIndexAt(row, col);
    432    if (cellIdx == -1) {
    433      continue;
    434    }
    435    CachedTableCellAccessible& cell = table->mCells[cellIdx];
    436    Accessible* cellAcc = nsAccUtils::GetAccessibleByID(doc, cell.mAccID);
    437    MOZ_ASSERT(cellAcc);
    438    // cell might span multiple columns. We don't want to visit it multiple
    439    // times, so ensure col is set to cell's starting column.
    440    col = cell.ColIdx();
    441    if (cellAcc->Role() != roles::ROWHEADER) {
    442      continue;
    443    }
    444    aCells->AppendElement(cellAcc);
    445  }
    446 }
    447 
    448 bool CachedTableCellAccessible::Selected() {
    449  return mAcc->State() & states::SELECTED;
    450 }
    451 
    452 }  // namespace mozilla::a11y