/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "ARIAGridAccessible-inl.h" #include "Accessible-inl.h" #include "AccIterator.h" #include "nsAccUtils.h" #include "Role.h" #include "States.h" #include "nsIMutableArray.h" #include "nsIPersistentProperties2.h" #include "nsComponentManagerUtils.h" using namespace mozilla; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // ARIAGridAccessible //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Constructor ARIAGridAccessible:: ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc) : AccessibleWrap(aContent, aDoc) { } NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridAccessible, Accessible) //////////////////////////////////////////////////////////////////////////////// // Table uint32_t ARIAGridAccessible::ColCount() { AccIterator rowIter(this, filters::GetRow); Accessible* row = rowIter.Next(); if (!row) return 0; AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; uint32_t colCount = 0; while ((cell = cellIter.Next())) colCount++; return colCount; } uint32_t ARIAGridAccessible::RowCount() { uint32_t rowCount = 0; AccIterator rowIter(this, filters::GetRow); while (rowIter.Next()) rowCount++; return rowCount; } Accessible* ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) { Accessible* row = GetRowAt(aRowIndex); if (!row) return nullptr; return GetCellInRowAt(row, aColumnIndex); } bool ARIAGridAccessible::IsColSelected(uint32_t aColIdx) { if (IsARIARole(nsGkAtoms::table)) return false; AccIterator rowIter(this, filters::GetRow); Accessible* row = rowIter.Next(); if (!row) return false; do { if (!nsAccUtils::IsARIASelected(row)) { Accessible* cell = GetCellInRowAt(row, aColIdx); if (!cell || !nsAccUtils::IsARIASelected(cell)) return false; } } while ((row = rowIter.Next())); return true; } bool ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx) { if (IsARIARole(nsGkAtoms::table)) return false; Accessible* row = GetRowAt(aRowIdx); if(!row) return false; if (!nsAccUtils::IsARIASelected(row)) { AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; while ((cell = cellIter.Next())) { if (!nsAccUtils::IsARIASelected(cell)) return false; } } return true; } bool ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { if (IsARIARole(nsGkAtoms::table)) return false; Accessible* row = GetRowAt(aRowIdx); if(!row) return false; if (!nsAccUtils::IsARIASelected(row)) { Accessible* cell = GetCellInRowAt(row, aColIdx); if (!cell || !nsAccUtils::IsARIASelected(cell)) return false; } return true; } uint32_t ARIAGridAccessible::SelectedCellCount() { if (IsARIARole(nsGkAtoms::table)) return 0; uint32_t count = 0, colCount = ColCount(); AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; while ((row = rowIter.Next())) { if (nsAccUtils::IsARIASelected(row)) { count += colCount; continue; } AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; while ((cell = cellIter.Next())) { if (nsAccUtils::IsARIASelected(cell)) count++; } } return count; } uint32_t ARIAGridAccessible::SelectedColCount() { if (IsARIARole(nsGkAtoms::table)) return 0; uint32_t colCount = ColCount(); if (!colCount) return 0; AccIterator rowIter(this, filters::GetRow); Accessible* row = rowIter.Next(); if (!row) return 0; nsTArray isColSelArray(colCount); isColSelArray.AppendElements(colCount); memset(isColSelArray.Elements(), true, colCount * sizeof(bool)); uint32_t selColCount = colCount; do { if (nsAccUtils::IsARIASelected(row)) continue; AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; for (uint32_t colIdx = 0; (cell = cellIter.Next()) && colIdx < colCount; colIdx++) if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) { isColSelArray[colIdx] = false; selColCount--; } } while ((row = rowIter.Next())); return selColCount; } uint32_t ARIAGridAccessible::SelectedRowCount() { if (IsARIARole(nsGkAtoms::table)) return 0; uint32_t count = 0; AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; while ((row = rowIter.Next())) { if (nsAccUtils::IsARIASelected(row)) { count++; continue; } AccIterator cellIter(row, filters::GetCell); Accessible* cell = cellIter.Next(); if (!cell) continue; bool isRowSelected = true; do { if (!nsAccUtils::IsARIASelected(cell)) { isRowSelected = false; break; } } while ((cell = cellIter.Next())); if (isRowSelected) count++; } return count; } void ARIAGridAccessible::SelectedCells(nsTArray* aCells) { if (IsARIARole(nsGkAtoms::table)) return; AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; while ((row = rowIter.Next())) { AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; if (nsAccUtils::IsARIASelected(row)) { while ((cell = cellIter.Next())) aCells->AppendElement(cell); continue; } while ((cell = cellIter.Next())) { if (nsAccUtils::IsARIASelected(cell)) aCells->AppendElement(cell); } } } void ARIAGridAccessible::SelectedCellIndices(nsTArray* aCells) { if (IsARIARole(nsGkAtoms::table)) return; uint32_t colCount = ColCount(); AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { if (nsAccUtils::IsARIASelected(row)) { for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) aCells->AppendElement(rowIdx * colCount + colIdx); continue; } AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; for (uint32_t colIdx = 0; (cell = cellIter.Next()); colIdx++) { if (nsAccUtils::IsARIASelected(cell)) aCells->AppendElement(rowIdx * colCount + colIdx); } } } void ARIAGridAccessible::SelectedColIndices(nsTArray* aCols) { if (IsARIARole(nsGkAtoms::table)) return; uint32_t colCount = ColCount(); if (!colCount) return; AccIterator rowIter(this, filters::GetRow); Accessible* row = rowIter.Next(); if (!row) return; nsTArray isColSelArray(colCount); isColSelArray.AppendElements(colCount); memset(isColSelArray.Elements(), true, colCount * sizeof(bool)); do { if (nsAccUtils::IsARIASelected(row)) continue; AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; for (uint32_t colIdx = 0; (cell = cellIter.Next()) && colIdx < colCount; colIdx++) if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) { isColSelArray[colIdx] = false; } } while ((row = rowIter.Next())); for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) if (isColSelArray[colIdx]) aCols->AppendElement(colIdx); } void ARIAGridAccessible::SelectedRowIndices(nsTArray* aRows) { if (IsARIARole(nsGkAtoms::table)) return; AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { if (nsAccUtils::IsARIASelected(row)) { aRows->AppendElement(rowIdx); continue; } AccIterator cellIter(row, filters::GetCell); Accessible* cell = cellIter.Next(); if (!cell) continue; bool isRowSelected = true; do { if (!nsAccUtils::IsARIASelected(cell)) { isRowSelected = false; break; } } while ((cell = cellIter.Next())); if (isRowSelected) aRows->AppendElement(rowIdx); } } void ARIAGridAccessible::SelectRow(uint32_t aRowIdx) { if (IsARIARole(nsGkAtoms::table)) return; AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { DebugOnly rv = SetARIASelected(row, rowIdx == aRowIdx); NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!"); } } void ARIAGridAccessible::SelectCol(uint32_t aColIdx) { if (IsARIARole(nsGkAtoms::table)) return; AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; while ((row = rowIter.Next())) { // Unselect all cells in the row. DebugOnly rv = SetARIASelected(row, false); NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!"); // Select cell at the column index. Accessible* cell = GetCellInRowAt(row, aColIdx); if (cell) SetARIASelected(cell, true); } } void ARIAGridAccessible::UnselectRow(uint32_t aRowIdx) { if (IsARIARole(nsGkAtoms::table)) return; Accessible* row = GetRowAt(aRowIdx); if (row) SetARIASelected(row, false); } void ARIAGridAccessible::UnselectCol(uint32_t aColIdx) { if (IsARIARole(nsGkAtoms::table)) return; AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; while ((row = rowIter.Next())) { Accessible* cell = GetCellInRowAt(row, aColIdx); if (cell) SetARIASelected(cell, false); } } //////////////////////////////////////////////////////////////////////////////// // Protected Accessible* ARIAGridAccessible::GetRowAt(int32_t aRow) { int32_t rowIdx = aRow; AccIterator rowIter(this, filters::GetRow); Accessible* row = rowIter.Next(); while (rowIdx != 0 && (row = rowIter.Next())) rowIdx--; return row; } Accessible* ARIAGridAccessible::GetCellInRowAt(Accessible* aRow, int32_t aColumn) { int32_t colIdx = aColumn; AccIterator cellIter(aRow, filters::GetCell); Accessible* cell = cellIter.Next(); while (colIdx != 0 && (cell = cellIter.Next())) colIdx--; return cell; } nsresult ARIAGridAccessible::SetARIASelected(Accessible* aAccessible, bool aIsSelected, bool aNotify) { if (IsARIARole(nsGkAtoms::table)) return NS_OK; nsIContent *content = aAccessible->GetContent(); NS_ENSURE_STATE(content); nsresult rv = NS_OK; if (aIsSelected) rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, NS_LITERAL_STRING("true"), aNotify); else rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, NS_LITERAL_STRING("false"), aNotify); NS_ENSURE_SUCCESS(rv, rv); // No "smart" select/unselect for internal call. if (!aNotify) return NS_OK; // If row or cell accessible was selected then we're able to not bother about // selection of its cells or its row because our algorithm is row oriented, // i.e. we check selection on row firstly and then on cells. if (aIsSelected) return NS_OK; roles::Role role = aAccessible->Role(); // If the given accessible is row that was unselected then remove // aria-selected from cell accessible. if (role == roles::ROW) { AccIterator cellIter(aAccessible, filters::GetCell); Accessible* cell = nullptr; while ((cell = cellIter.Next())) { rv = SetARIASelected(cell, false, false); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // If the given accessible is cell that was unselected and its row is selected // then remove aria-selected from row and put aria-selected on // siblings cells. if (role == roles::GRID_CELL || role == roles::ROWHEADER || role == roles::COLUMNHEADER) { Accessible* row = aAccessible->Parent(); if (row && row->Role() == roles::ROW && nsAccUtils::IsARIASelected(row)) { rv = SetARIASelected(row, false, false); NS_ENSURE_SUCCESS(rv, rv); AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; while ((cell = cellIter.Next())) { if (cell != aAccessible) { rv = SetARIASelected(cell, true, false); NS_ENSURE_SUCCESS(rv, rv); } } } } return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // ARIARowAccessible //////////////////////////////////////////////////////////////////////////////// ARIARowAccessible:: ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc) : AccessibleWrap(aContent, aDoc) { mGenericTypes |= eTableRow; } NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible, Accessible) GroupPos ARIARowAccessible::GroupPosition() { int32_t count = 0, index = 0; if (nsCoreUtils::GetUIntAttr(nsAccUtils::TableFor(this)->GetContent(), nsGkAtoms::aria_rowcount, &count) && nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) { return GroupPos(0, index, count); } return AccessibleWrap::GroupPosition(); } //////////////////////////////////////////////////////////////////////////////// // ARIAGridCellAccessible //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Constructor ARIAGridCellAccessible:: ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc) : HyperTextAccessibleWrap(aContent, aDoc) { mGenericTypes |= eTableCell; } NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessible, HyperTextAccessible) //////////////////////////////////////////////////////////////////////////////// // TableCell TableAccessible* ARIAGridCellAccessible::Table() const { Accessible* table = nsAccUtils::TableFor(Row()); return table ? table->AsTable() : nullptr; } uint32_t ARIAGridCellAccessible::ColIdx() const { Accessible* row = Row(); if (!row) return 0; int32_t indexInRow = IndexInParent(); uint32_t colIdx = 0; for (int32_t idx = 0; idx < indexInRow; idx++) { Accessible* cell = row->GetChildAt(idx); roles::Role role = cell->Role(); if (role == roles::CELL || role == roles::GRID_CELL || role == roles::ROWHEADER || role == roles::COLUMNHEADER) colIdx++; } return colIdx; } uint32_t ARIAGridCellAccessible::RowIdx() const { return RowIndexFor(Row()); } bool ARIAGridCellAccessible::Selected() { Accessible* row = Row(); if (!row) return false; return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this); } //////////////////////////////////////////////////////////////////////////////// // Accessible void ARIAGridCellAccessible::ApplyARIAState(uint64_t* aState) const { HyperTextAccessibleWrap::ApplyARIAState(aState); // Return if the gridcell has aria-selected="true". if (*aState & states::SELECTED) return; // Check aria-selected="true" on the row. Accessible* row = Parent(); if (!row || row->Role() != roles::ROW) return; nsIContent *rowContent = row->GetContent(); if (nsAccUtils::HasDefinedARIAToken(rowContent, nsGkAtoms::aria_selected) && !rowContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_selected, nsGkAtoms::_false, eCaseMatters)) *aState |= states::SELECTABLE | states::SELECTED; } already_AddRefed ARIAGridCellAccessible::NativeAttributes() { nsCOMPtr attributes = HyperTextAccessibleWrap::NativeAttributes(); // Expose "table-cell-index" attribute. Accessible* thisRow = Row(); if (!thisRow) return attributes.forget(); int32_t colIdx = 0, colCount = 0; uint32_t childCount = thisRow->ChildCount(); for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { Accessible* child = thisRow->GetChildAt(childIdx); if (child == this) colIdx = colCount; roles::Role role = child->Role(); if (role == roles::CELL || role == roles::GRID_CELL || role == roles::ROWHEADER || role == roles::COLUMNHEADER) colCount++; } int32_t rowIdx = RowIndexFor(thisRow); nsAutoString stringIdx; stringIdx.AppendInt(rowIdx * colCount + colIdx); nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx); #ifdef DEBUG nsAutoString unused; attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"), NS_LITERAL_STRING("ARIAGridCellAccessible"), unused); #endif return attributes.forget(); } GroupPos ARIAGridCellAccessible::GroupPosition() { int32_t count = 0, index = 0; Accessible* table = Table()->AsAccessible(); if (table && nsCoreUtils::GetUIntAttr(table->GetContent(), nsGkAtoms::aria_colcount, &count) && nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) { return GroupPos(0, index, count); } return GroupPos(); }