gecko-dev/accessible/generic/TableCellAccessible.cpp
James Teh 7dafc35406 Bug 1638238: Cache previous column header for each cell to make TableCellAccessible::ColHeaders faster. r=eeejay
Previously, to get the column headers for a cell when there weren't explicit headers, we walked over all previous cells in the column looking for headers.
For large tables with thousands of rows, this was very expensive, particularly when Windows screen readers rendered virtual buffers, fetching headers for all cells.

To speed this up, we now lazily cache the previous column header for each cell.
We even cache whether there is no previous header, since that is quite common and we don't want to pointlessly walk in that case.
Subsequent cells utilise the cached values (if any) for prior cells.

We don't store the cache on individual TableCellAccessibles because that would require us to walk all cells to invalidate the cache, even if few or no cells had cached values.
Instead, we store the cache as a hash table on the TableAccessible.
This way, we can just clear the cache whenever a column header is added to the tree.

We invalidate the cache whenever a column header is bound to its parent.
We don't need to invalidate the cache for removals because we instead just ignore defunct Accessibles in the cache.

Differential Revision: https://phabricator.services.mozilla.com/D76106
2020-05-25 14:15:57 +00:00

101 lines
3.2 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TableCellAccessible.h"
#include "Accessible-inl.h"
#include "TableAccessible.h"
using namespace mozilla;
using namespace mozilla::a11y;
void TableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells) {
uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
TableAccessible* table = Table();
if (!table) return;
// Move to the left to find row header cells
for (uint32_t curColIdx = colIdx - 1; curColIdx < colIdx; curColIdx--) {
Accessible* cell = table->CellAt(rowIdx, curColIdx);
if (!cell) continue;
// CellAt should always return a TableCellAccessible (XXX Bug 587529)
TableCellAccessible* tableCell = cell->AsTableCell();
NS_ASSERTION(tableCell, "cell should be a table cell!");
if (!tableCell) continue;
// Avoid addding cells multiple times, if this cell spans more columns
// we'll get it later.
if (tableCell->ColIdx() == curColIdx && cell->Role() == roles::ROWHEADER)
aCells->AppendElement(cell);
}
}
Accessible* TableCellAccessible::PrevColHeader() {
TableAccessible* table = Table();
if (!table) {
return nullptr;
}
TableAccessible::HeaderCache& cache = table->GetHeaderCache();
bool inCache = false;
Accessible* cachedHeader = cache.GetWeak(this, &inCache);
if (inCache) {
// Cached but null means we know there is no previous column header.
// if defunct, the cell was removed, so behave as if there is no cached
// value.
if (!cachedHeader || !cachedHeader->IsDefunct()) {
return cachedHeader;
}
}
uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
for (uint32_t curRowIdx = rowIdx - 1; curRowIdx < rowIdx; curRowIdx--) {
Accessible* cell = table->CellAt(curRowIdx, colIdx);
if (!cell) {
continue;
}
// CellAt should always return a TableCellAccessible (XXX Bug 587529)
TableCellAccessible* tableCell = cell->AsTableCell();
MOZ_ASSERT(tableCell, "cell should be a table cell!");
if (!tableCell) {
continue;
}
// Check whether the previous table cell has a cached value.
cachedHeader = cache.GetWeak(tableCell, &inCache);
if (inCache && cell->Role() != roles::COLUMNHEADER) {
if (!cachedHeader || !cachedHeader->IsDefunct()) {
// Cache it for this cell.
cache.Put(this, RefPtr<Accessible>(cachedHeader));
return cachedHeader;
}
}
// Avoid addding cells multiple times, if this cell spans more rows
// we'll get it later.
if (cell->Role() != roles::COLUMNHEADER ||
tableCell->RowIdx() != curRowIdx) {
continue;
}
// Cache the header we found.
cache.Put(this, RefPtr<Accessible>(cell));
return cell;
}
// There's no header, so cache that fact.
cache.Put(this, RefPtr<Accessible>(nullptr));
return nullptr;
}
void TableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells) {
for (Accessible* cell = PrevColHeader(); cell;
cell = cell->AsTableCell()->PrevColHeader()) {
aCells->AppendElement(cell);
}
}