michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ARIAGridAccessible-inl.h" michael@0: michael@0: #include "Accessible-inl.h" michael@0: #include "AccIterator.h" michael@0: #include "nsAccUtils.h" michael@0: #include "Role.h" michael@0: #include "States.h" michael@0: michael@0: #include "nsIMutableArray.h" michael@0: #include "nsIPersistentProperties2.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::a11y; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // ARIAGridAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Constructor michael@0: michael@0: ARIAGridAccessible:: michael@0: ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: AccessibleWrap(aContent, aDoc), xpcAccessibleTable(this) michael@0: { michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsISupports michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(ARIAGridAccessible, michael@0: Accessible, michael@0: nsIAccessibleTable) michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Accessible michael@0: michael@0: void michael@0: ARIAGridAccessible::Shutdown() michael@0: { michael@0: mTable = nullptr; michael@0: AccessibleWrap::Shutdown(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsIAccessibleTable michael@0: michael@0: uint32_t michael@0: ARIAGridAccessible::ColCount() michael@0: { michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = rowIter.Next(); michael@0: if (!row) michael@0: return 0; michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: michael@0: uint32_t colCount = 0; michael@0: while ((cell = cellIter.Next())) michael@0: colCount++; michael@0: michael@0: return colCount; michael@0: } michael@0: michael@0: uint32_t michael@0: ARIAGridAccessible::RowCount() michael@0: { michael@0: uint32_t rowCount = 0; michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: while (rowIter.Next()) michael@0: rowCount++; michael@0: michael@0: return rowCount; michael@0: } michael@0: michael@0: Accessible* michael@0: ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) michael@0: { michael@0: Accessible* row = GetRowAt(aRowIndex); michael@0: if (!row) michael@0: return nullptr; michael@0: michael@0: return GetCellInRowAt(row, aColumnIndex); michael@0: } michael@0: michael@0: bool michael@0: ARIAGridAccessible::IsColSelected(uint32_t aColIdx) michael@0: { michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = rowIter.Next(); michael@0: if (!row) michael@0: return false; michael@0: michael@0: do { michael@0: if (!nsAccUtils::IsARIASelected(row)) { michael@0: Accessible* cell = GetCellInRowAt(row, aColIdx); michael@0: if (!cell || !nsAccUtils::IsARIASelected(cell)) michael@0: return false; michael@0: } michael@0: } while ((row = rowIter.Next())); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx) michael@0: { michael@0: Accessible* row = GetRowAt(aRowIdx); michael@0: if(!row) michael@0: return false; michael@0: michael@0: if (!nsAccUtils::IsARIASelected(row)) { michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: while ((cell = cellIter.Next())) { michael@0: if (!nsAccUtils::IsARIASelected(cell)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) michael@0: { michael@0: Accessible* row = GetRowAt(aRowIdx); michael@0: if(!row) michael@0: return false; michael@0: michael@0: if (!nsAccUtils::IsARIASelected(row)) { michael@0: Accessible* cell = GetCellInRowAt(row, aColIdx); michael@0: if (!cell || !nsAccUtils::IsARIASelected(cell)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: ARIAGridAccessible::SelectedCellCount() michael@0: { michael@0: uint32_t count = 0, colCount = ColCount(); michael@0: michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = nullptr; michael@0: michael@0: while ((row = rowIter.Next())) { michael@0: if (nsAccUtils::IsARIASelected(row)) { michael@0: count += colCount; michael@0: continue; michael@0: } michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: michael@0: while ((cell = cellIter.Next())) { michael@0: if (nsAccUtils::IsARIASelected(cell)) michael@0: count++; michael@0: } michael@0: } michael@0: michael@0: return count; michael@0: } michael@0: michael@0: uint32_t michael@0: ARIAGridAccessible::SelectedColCount() michael@0: { michael@0: uint32_t colCount = ColCount(); michael@0: if (!colCount) michael@0: return 0; michael@0: michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = rowIter.Next(); michael@0: if (!row) michael@0: return 0; michael@0: michael@0: nsTArray isColSelArray(colCount); michael@0: isColSelArray.AppendElements(colCount); michael@0: memset(isColSelArray.Elements(), true, colCount * sizeof(bool)); michael@0: michael@0: uint32_t selColCount = colCount; michael@0: do { michael@0: if (nsAccUtils::IsARIASelected(row)) michael@0: continue; michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: for (uint32_t colIdx = 0; michael@0: (cell = cellIter.Next()) && colIdx < colCount; colIdx++) michael@0: if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) { michael@0: isColSelArray[colIdx] = false; michael@0: selColCount--; michael@0: } michael@0: } while ((row = rowIter.Next())); michael@0: michael@0: return selColCount; michael@0: } michael@0: michael@0: uint32_t michael@0: ARIAGridAccessible::SelectedRowCount() michael@0: { michael@0: uint32_t count = 0; michael@0: michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = nullptr; michael@0: michael@0: while ((row = rowIter.Next())) { michael@0: if (nsAccUtils::IsARIASelected(row)) { michael@0: count++; michael@0: continue; michael@0: } michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = cellIter.Next(); michael@0: if (!cell) michael@0: continue; michael@0: michael@0: bool isRowSelected = true; michael@0: do { michael@0: if (!nsAccUtils::IsARIASelected(cell)) { michael@0: isRowSelected = false; michael@0: break; michael@0: } michael@0: } while ((cell = cellIter.Next())); michael@0: michael@0: if (isRowSelected) michael@0: count++; michael@0: } michael@0: michael@0: return count; michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::SelectedCells(nsTArray* aCells) michael@0: { michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: michael@0: Accessible* row = nullptr; michael@0: while ((row = rowIter.Next())) { michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: michael@0: if (nsAccUtils::IsARIASelected(row)) { michael@0: while ((cell = cellIter.Next())) michael@0: aCells->AppendElement(cell); michael@0: michael@0: continue; michael@0: } michael@0: michael@0: while ((cell = cellIter.Next())) { michael@0: if (nsAccUtils::IsARIASelected(cell)) michael@0: aCells->AppendElement(cell); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::SelectedCellIndices(nsTArray* aCells) michael@0: { michael@0: uint32_t colCount = ColCount(); michael@0: michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = nullptr; michael@0: for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { michael@0: if (nsAccUtils::IsARIASelected(row)) { michael@0: for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) michael@0: aCells->AppendElement(rowIdx * colCount + colIdx); michael@0: michael@0: continue; michael@0: } michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: for (uint32_t colIdx = 0; (cell = cellIter.Next()); colIdx++) { michael@0: if (nsAccUtils::IsARIASelected(cell)) michael@0: aCells->AppendElement(rowIdx * colCount + colIdx); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::SelectedColIndices(nsTArray* aCols) michael@0: { michael@0: uint32_t colCount = ColCount(); michael@0: if (!colCount) michael@0: return; michael@0: michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = rowIter.Next(); michael@0: if (!row) michael@0: return; michael@0: michael@0: nsTArray isColSelArray(colCount); michael@0: isColSelArray.AppendElements(colCount); michael@0: memset(isColSelArray.Elements(), true, colCount * sizeof(bool)); michael@0: michael@0: do { michael@0: if (nsAccUtils::IsARIASelected(row)) michael@0: continue; michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: for (uint32_t colIdx = 0; michael@0: (cell = cellIter.Next()) && colIdx < colCount; colIdx++) michael@0: if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) { michael@0: isColSelArray[colIdx] = false; michael@0: } michael@0: } while ((row = rowIter.Next())); michael@0: michael@0: for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) michael@0: if (isColSelArray[colIdx]) michael@0: aCols->AppendElement(colIdx); michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::SelectedRowIndices(nsTArray* aRows) michael@0: { michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: Accessible* row = nullptr; michael@0: for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { michael@0: if (nsAccUtils::IsARIASelected(row)) { michael@0: aRows->AppendElement(rowIdx); michael@0: continue; michael@0: } michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = cellIter.Next(); michael@0: if (!cell) michael@0: continue; michael@0: michael@0: bool isRowSelected = true; michael@0: do { michael@0: if (!nsAccUtils::IsARIASelected(cell)) { michael@0: isRowSelected = false; michael@0: break; michael@0: } michael@0: } while ((cell = cellIter.Next())); michael@0: michael@0: if (isRowSelected) michael@0: aRows->AppendElement(rowIdx); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::SelectRow(uint32_t aRowIdx) michael@0: { michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: michael@0: Accessible* row = nullptr; michael@0: for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { michael@0: DebugOnly rv = SetARIASelected(row, rowIdx == aRowIdx); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::SelectCol(uint32_t aColIdx) michael@0: { michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: michael@0: Accessible* row = nullptr; michael@0: while ((row = rowIter.Next())) { michael@0: // Unselect all cells in the row. michael@0: DebugOnly rv = SetARIASelected(row, false); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!"); michael@0: michael@0: // Select cell at the column index. michael@0: Accessible* cell = GetCellInRowAt(row, aColIdx); michael@0: if (cell) michael@0: SetARIASelected(cell, true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::UnselectRow(uint32_t aRowIdx) michael@0: { michael@0: Accessible* row = GetRowAt(aRowIdx); michael@0: michael@0: if (row) michael@0: SetARIASelected(row, false); michael@0: } michael@0: michael@0: void michael@0: ARIAGridAccessible::UnselectCol(uint32_t aColIdx) michael@0: { michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: michael@0: Accessible* row = nullptr; michael@0: while ((row = rowIter.Next())) { michael@0: Accessible* cell = GetCellInRowAt(row, aColIdx); michael@0: if (cell) michael@0: SetARIASelected(cell, false); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Protected michael@0: michael@0: bool michael@0: ARIAGridAccessible::IsValidRow(int32_t aRow) michael@0: { michael@0: if (aRow < 0) michael@0: return false; michael@0: michael@0: int32_t rowCount = 0; michael@0: GetRowCount(&rowCount); michael@0: return aRow < rowCount; michael@0: } michael@0: michael@0: bool michael@0: ARIAGridAccessible::IsValidColumn(int32_t aColumn) michael@0: { michael@0: if (aColumn < 0) michael@0: return false; michael@0: michael@0: int32_t colCount = 0; michael@0: GetColumnCount(&colCount); michael@0: return aColumn < colCount; michael@0: } michael@0: michael@0: Accessible* michael@0: ARIAGridAccessible::GetRowAt(int32_t aRow) michael@0: { michael@0: int32_t rowIdx = aRow; michael@0: michael@0: AccIterator rowIter(this, filters::GetRow); michael@0: michael@0: Accessible* row = rowIter.Next(); michael@0: while (rowIdx != 0 && (row = rowIter.Next())) michael@0: rowIdx--; michael@0: michael@0: return row; michael@0: } michael@0: michael@0: Accessible* michael@0: ARIAGridAccessible::GetCellInRowAt(Accessible* aRow, int32_t aColumn) michael@0: { michael@0: int32_t colIdx = aColumn; michael@0: michael@0: AccIterator cellIter(aRow, filters::GetCell); michael@0: Accessible* cell = cellIter.Next(); michael@0: while (colIdx != 0 && (cell = cellIter.Next())) michael@0: colIdx--; michael@0: michael@0: return cell; michael@0: } michael@0: michael@0: nsresult michael@0: ARIAGridAccessible::SetARIASelected(Accessible* aAccessible, michael@0: bool aIsSelected, bool aNotify) michael@0: { michael@0: nsIContent *content = aAccessible->GetContent(); michael@0: NS_ENSURE_STATE(content); michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (aIsSelected) michael@0: rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, michael@0: NS_LITERAL_STRING("true"), aNotify); michael@0: else michael@0: rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, michael@0: NS_LITERAL_STRING("false"), aNotify); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // No "smart" select/unselect for internal call. michael@0: if (!aNotify) michael@0: return NS_OK; michael@0: michael@0: // If row or cell accessible was selected then we're able to not bother about michael@0: // selection of its cells or its row because our algorithm is row oriented, michael@0: // i.e. we check selection on row firstly and then on cells. michael@0: if (aIsSelected) michael@0: return NS_OK; michael@0: michael@0: roles::Role role = aAccessible->Role(); michael@0: michael@0: // If the given accessible is row that was unselected then remove michael@0: // aria-selected from cell accessible. michael@0: if (role == roles::ROW) { michael@0: AccIterator cellIter(aAccessible, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: michael@0: while ((cell = cellIter.Next())) { michael@0: rv = SetARIASelected(cell, false, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If the given accessible is cell that was unselected and its row is selected michael@0: // then remove aria-selected from row and put aria-selected on michael@0: // siblings cells. michael@0: if (role == roles::GRID_CELL || role == roles::ROWHEADER || michael@0: role == roles::COLUMNHEADER) { michael@0: Accessible* row = aAccessible->Parent(); michael@0: michael@0: if (row && row->Role() == roles::ROW && michael@0: nsAccUtils::IsARIASelected(row)) { michael@0: rv = SetARIASelected(row, false, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: AccIterator cellIter(row, filters::GetCell); michael@0: Accessible* cell = nullptr; michael@0: while ((cell = cellIter.Next())) { michael@0: if (cell != aAccessible) { michael@0: rv = SetARIASelected(cell, true, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // ARIAGridCellAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Constructor michael@0: michael@0: ARIAGridCellAccessible:: michael@0: ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: HyperTextAccessibleWrap(aContent, aDoc), xpcAccessibleTableCell(this) michael@0: { michael@0: mGenericTypes |= eTableCell; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsISupports michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(ARIAGridCellAccessible, michael@0: HyperTextAccessible, michael@0: nsIAccessibleTableCell) michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsIAccessibleTableCell michael@0: michael@0: TableAccessible* michael@0: ARIAGridCellAccessible::Table() const michael@0: { michael@0: Accessible* table = TableFor(Row()); michael@0: return table ? table->AsTable() : nullptr; michael@0: } michael@0: michael@0: uint32_t michael@0: ARIAGridCellAccessible::ColIdx() const michael@0: { michael@0: Accessible* row = Row(); michael@0: if (!row) michael@0: return 0; michael@0: michael@0: int32_t indexInRow = IndexInParent(); michael@0: uint32_t colIdx = 0; michael@0: for (int32_t idx = 0; idx < indexInRow; idx++) { michael@0: Accessible* cell = row->GetChildAt(idx); michael@0: roles::Role role = cell->Role(); michael@0: if (role == roles::GRID_CELL || role == roles::ROWHEADER || michael@0: role == roles::COLUMNHEADER) michael@0: colIdx++; michael@0: } michael@0: michael@0: return colIdx; michael@0: } michael@0: michael@0: uint32_t michael@0: ARIAGridCellAccessible::RowIdx() const michael@0: { michael@0: return RowIndexFor(Row()); michael@0: } michael@0: michael@0: bool michael@0: ARIAGridCellAccessible::Selected() michael@0: { michael@0: Accessible* row = Row(); michael@0: if (!row) michael@0: return false; michael@0: michael@0: return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Accessible michael@0: michael@0: void michael@0: ARIAGridCellAccessible::ApplyARIAState(uint64_t* aState) const michael@0: { michael@0: HyperTextAccessibleWrap::ApplyARIAState(aState); michael@0: michael@0: // Return if the gridcell has aria-selected="true". michael@0: if (*aState & states::SELECTED) michael@0: return; michael@0: michael@0: // Check aria-selected="true" on the row. michael@0: Accessible* row = Parent(); michael@0: if (!row || row->Role() != roles::ROW) michael@0: return; michael@0: michael@0: nsIContent *rowContent = row->GetContent(); michael@0: if (nsAccUtils::HasDefinedARIAToken(rowContent, michael@0: nsGkAtoms::aria_selected) && michael@0: !rowContent->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::aria_selected, michael@0: nsGkAtoms::_false, eCaseMatters)) michael@0: *aState |= states::SELECTABLE | states::SELECTED; michael@0: } michael@0: michael@0: already_AddRefed michael@0: ARIAGridCellAccessible::NativeAttributes() michael@0: { michael@0: nsCOMPtr attributes = michael@0: HyperTextAccessibleWrap::NativeAttributes(); michael@0: michael@0: // Expose "table-cell-index" attribute. michael@0: Accessible* thisRow = Row(); michael@0: if (!thisRow) michael@0: return attributes.forget(); michael@0: michael@0: int32_t colIdx = 0, colCount = 0; michael@0: uint32_t childCount = thisRow->ChildCount(); michael@0: for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { michael@0: Accessible* child = thisRow->GetChildAt(childIdx); michael@0: if (child == this) michael@0: colIdx = colCount; michael@0: michael@0: roles::Role role = child->Role(); michael@0: if (role == roles::GRID_CELL || role == roles::ROWHEADER || michael@0: role == roles::COLUMNHEADER) michael@0: colCount++; michael@0: } michael@0: michael@0: int32_t rowIdx = RowIndexFor(thisRow); michael@0: michael@0: nsAutoString stringIdx; michael@0: stringIdx.AppendInt(rowIdx * colCount + colIdx); michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx); michael@0: michael@0: return attributes.forget(); michael@0: } michael@0: michael@0: void michael@0: ARIAGridCellAccessible::Shutdown() michael@0: { michael@0: mTableCell = nullptr; michael@0: HyperTextAccessibleWrap::Shutdown(); michael@0: }