Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / accessible / generic / ARIAGridAccessible.cpp
blobb2c1a6df3f96d57186a7f5795a67c6d3f37787ae
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ARIAGridAccessible-inl.h"
8 #include "LocalAccessible-inl.h"
9 #include "AccAttributes.h"
10 #include "AccIterator.h"
11 #include "nsAccUtils.h"
12 #include "Role.h"
13 #include "States.h"
15 #include "mozilla/dom/Element.h"
16 #include "nsComponentManagerUtils.h"
18 using namespace mozilla;
19 using namespace mozilla::a11y;
21 ////////////////////////////////////////////////////////////////////////////////
22 // ARIAGridAccessible
23 ////////////////////////////////////////////////////////////////////////////////
25 ////////////////////////////////////////////////////////////////////////////////
26 // Constructor
28 ARIAGridAccessible::ARIAGridAccessible(nsIContent* aContent,
29 DocAccessible* aDoc)
30 : HyperTextAccessibleWrap(aContent, aDoc) {
31 mGenericTypes |= eTable;
34 role ARIAGridAccessible::NativeRole() const {
35 a11y::role r = GetAccService()->MarkupRole(mContent);
36 return r != roles::NOTHING ? r : roles::TABLE;
39 already_AddRefed<AccAttributes> ARIAGridAccessible::NativeAttributes() {
40 RefPtr<AccAttributes> attributes = AccessibleWrap::NativeAttributes();
42 if (IsProbablyLayoutTable()) {
43 attributes->SetAttribute(nsGkAtoms::layout_guess, true);
46 return attributes.forget();
49 ////////////////////////////////////////////////////////////////////////////////
50 // Table
52 uint32_t ARIAGridAccessible::ColCount() const {
53 AccIterator rowIter(this, filters::GetRow);
54 LocalAccessible* row = rowIter.Next();
55 if (!row) return 0;
57 AccIterator cellIter(row, filters::GetCell);
58 LocalAccessible* cell = nullptr;
60 uint32_t colCount = 0;
61 while ((cell = cellIter.Next())) {
62 MOZ_ASSERT(cell->IsTableCell(), "No table or grid cell!");
63 colCount += cell->AsTableCell()->ColExtent();
66 return colCount;
69 uint32_t ARIAGridAccessible::RowCount() {
70 uint32_t rowCount = 0;
71 AccIterator rowIter(this, filters::GetRow);
72 while (rowIter.Next()) rowCount++;
74 return rowCount;
77 LocalAccessible* ARIAGridAccessible::CellAt(uint32_t aRowIndex,
78 uint32_t aColumnIndex) {
79 LocalAccessible* row = RowAt(aRowIndex);
80 if (!row) return nullptr;
82 return CellInRowAt(row, aColumnIndex);
85 bool ARIAGridAccessible::IsColSelected(uint32_t aColIdx) {
86 if (IsARIARole(nsGkAtoms::table)) return false;
88 AccIterator rowIter(this, filters::GetRow);
89 LocalAccessible* row = rowIter.Next();
90 if (!row) return false;
92 do {
93 if (!nsAccUtils::IsARIASelected(row)) {
94 LocalAccessible* cell = CellInRowAt(row, aColIdx);
95 if (!cell || !nsAccUtils::IsARIASelected(cell)) return false;
97 } while ((row = rowIter.Next()));
99 return true;
102 bool ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx) {
103 if (IsARIARole(nsGkAtoms::table)) return false;
105 LocalAccessible* row = RowAt(aRowIdx);
106 if (!row) return false;
108 if (!nsAccUtils::IsARIASelected(row)) {
109 AccIterator cellIter(row, filters::GetCell);
110 LocalAccessible* cell = nullptr;
111 while ((cell = cellIter.Next())) {
112 if (!nsAccUtils::IsARIASelected(cell)) return false;
116 return true;
119 bool ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) {
120 if (IsARIARole(nsGkAtoms::table)) return false;
122 LocalAccessible* row = RowAt(aRowIdx);
123 if (!row) return false;
125 if (!nsAccUtils::IsARIASelected(row)) {
126 LocalAccessible* cell = CellInRowAt(row, aColIdx);
127 if (!cell || !nsAccUtils::IsARIASelected(cell)) return false;
130 return true;
133 uint32_t ARIAGridAccessible::SelectedCellCount() {
134 if (IsARIARole(nsGkAtoms::table)) return 0;
136 uint32_t count = 0, colCount = ColCount();
138 AccIterator rowIter(this, filters::GetRow);
139 LocalAccessible* row = nullptr;
141 while ((row = rowIter.Next())) {
142 if (nsAccUtils::IsARIASelected(row)) {
143 count += colCount;
144 continue;
147 AccIterator cellIter(row, filters::GetCell);
148 LocalAccessible* cell = nullptr;
150 while ((cell = cellIter.Next())) {
151 if (nsAccUtils::IsARIASelected(cell)) count++;
155 return count;
158 uint32_t ARIAGridAccessible::SelectedColCount() {
159 if (IsARIARole(nsGkAtoms::table)) return 0;
161 uint32_t colCount = ColCount();
162 if (!colCount) return 0;
164 AccIterator rowIter(this, filters::GetRow);
165 LocalAccessible* row = rowIter.Next();
166 if (!row) return 0;
168 nsTArray<bool> isColSelArray(colCount);
169 isColSelArray.AppendElements(colCount);
170 memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
172 uint32_t selColCount = colCount;
173 do {
174 if (nsAccUtils::IsARIASelected(row)) continue;
176 AccIterator cellIter(row, filters::GetCell);
177 LocalAccessible* cell = nullptr;
178 for (uint32_t colIdx = 0; (cell = cellIter.Next()) && colIdx < colCount;
179 colIdx++) {
180 if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
181 isColSelArray[colIdx] = false;
182 selColCount--;
185 } while ((row = rowIter.Next()));
187 return selColCount;
190 uint32_t ARIAGridAccessible::SelectedRowCount() {
191 if (IsARIARole(nsGkAtoms::table)) return 0;
193 uint32_t count = 0;
195 AccIterator rowIter(this, filters::GetRow);
196 LocalAccessible* row = nullptr;
198 while ((row = rowIter.Next())) {
199 if (nsAccUtils::IsARIASelected(row)) {
200 count++;
201 continue;
204 AccIterator cellIter(row, filters::GetCell);
205 LocalAccessible* cell = cellIter.Next();
206 if (!cell) continue;
208 bool isRowSelected = true;
209 do {
210 if (!nsAccUtils::IsARIASelected(cell)) {
211 isRowSelected = false;
212 break;
214 } while ((cell = cellIter.Next()));
216 if (isRowSelected) count++;
219 return count;
222 void ARIAGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells) {
223 if (IsARIARole(nsGkAtoms::table)) return;
225 AccIterator rowIter(this, filters::GetRow);
227 LocalAccessible* row = nullptr;
228 while ((row = rowIter.Next())) {
229 AccIterator cellIter(row, filters::GetCell);
230 LocalAccessible* cell = nullptr;
232 if (nsAccUtils::IsARIASelected(row)) {
233 while ((cell = cellIter.Next())) aCells->AppendElement(cell);
235 continue;
238 while ((cell = cellIter.Next())) {
239 if (nsAccUtils::IsARIASelected(cell)) aCells->AppendElement(cell);
244 void ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) {
245 if (IsARIARole(nsGkAtoms::table)) return;
247 uint32_t colCount = ColCount();
249 AccIterator rowIter(this, filters::GetRow);
250 LocalAccessible* row = nullptr;
251 for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
252 if (nsAccUtils::IsARIASelected(row)) {
253 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
254 aCells->AppendElement(rowIdx * colCount + colIdx);
257 continue;
260 AccIterator cellIter(row, filters::GetCell);
261 LocalAccessible* cell = nullptr;
262 for (uint32_t colIdx = 0; (cell = cellIter.Next()); colIdx++) {
263 if (nsAccUtils::IsARIASelected(cell)) {
264 aCells->AppendElement(rowIdx * colCount + colIdx);
270 void ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) {
271 if (IsARIARole(nsGkAtoms::table)) return;
273 uint32_t colCount = ColCount();
274 if (!colCount) return;
276 AccIterator rowIter(this, filters::GetRow);
277 LocalAccessible* row = rowIter.Next();
278 if (!row) return;
280 nsTArray<bool> isColSelArray(colCount);
281 isColSelArray.AppendElements(colCount);
282 memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
284 do {
285 if (nsAccUtils::IsARIASelected(row)) continue;
287 AccIterator cellIter(row, filters::GetCell);
288 LocalAccessible* cell = nullptr;
289 for (uint32_t colIdx = 0; (cell = cellIter.Next()) && colIdx < colCount;
290 colIdx++) {
291 if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
292 isColSelArray[colIdx] = false;
295 } while ((row = rowIter.Next()));
297 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
298 if (isColSelArray[colIdx]) aCols->AppendElement(colIdx);
302 void ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) {
303 if (IsARIARole(nsGkAtoms::table)) return;
305 AccIterator rowIter(this, filters::GetRow);
306 LocalAccessible* row = nullptr;
307 for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
308 if (nsAccUtils::IsARIASelected(row)) {
309 aRows->AppendElement(rowIdx);
310 continue;
313 AccIterator cellIter(row, filters::GetCell);
314 LocalAccessible* cell = cellIter.Next();
315 if (!cell) continue;
317 bool isRowSelected = true;
318 do {
319 if (!nsAccUtils::IsARIASelected(cell)) {
320 isRowSelected = false;
321 break;
323 } while ((cell = cellIter.Next()));
325 if (isRowSelected) aRows->AppendElement(rowIdx);
329 void ARIAGridAccessible::SelectRow(uint32_t aRowIdx) {
330 if (IsARIARole(nsGkAtoms::table)) return;
332 AccIterator rowIter(this, filters::GetRow);
334 LocalAccessible* row = nullptr;
335 for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
336 DebugOnly<nsresult> rv = SetARIASelected(row, rowIdx == aRowIdx);
337 NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
341 void ARIAGridAccessible::SelectCol(uint32_t aColIdx) {
342 if (IsARIARole(nsGkAtoms::table)) return;
344 AccIterator rowIter(this, filters::GetRow);
346 LocalAccessible* row = nullptr;
347 while ((row = rowIter.Next())) {
348 // Unselect all cells in the row.
349 DebugOnly<nsresult> rv = SetARIASelected(row, false);
350 NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
352 // Select cell at the column index.
353 LocalAccessible* cell = CellInRowAt(row, aColIdx);
354 if (cell) SetARIASelected(cell, true);
358 void ARIAGridAccessible::UnselectRow(uint32_t aRowIdx) {
359 if (IsARIARole(nsGkAtoms::table)) return;
361 LocalAccessible* row = RowAt(aRowIdx);
362 if (row) SetARIASelected(row, false);
365 void ARIAGridAccessible::UnselectCol(uint32_t aColIdx) {
366 if (IsARIARole(nsGkAtoms::table)) return;
368 AccIterator rowIter(this, filters::GetRow);
370 LocalAccessible* row = nullptr;
371 while ((row = rowIter.Next())) {
372 LocalAccessible* cell = CellInRowAt(row, aColIdx);
373 if (cell) SetARIASelected(cell, false);
377 ////////////////////////////////////////////////////////////////////////////////
378 // Protected
380 nsresult ARIAGridAccessible::SetARIASelected(LocalAccessible* aAccessible,
381 bool aIsSelected, bool aNotify) {
382 if (IsARIARole(nsGkAtoms::table)) return NS_OK;
384 nsIContent* content = aAccessible->GetContent();
385 NS_ENSURE_STATE(content);
387 nsresult rv = NS_OK;
388 if (content->IsElement()) {
389 if (aIsSelected) {
390 rv = content->AsElement()->SetAttr(
391 kNameSpaceID_None, nsGkAtoms::aria_selected, u"true"_ns, aNotify);
392 } else {
393 rv = content->AsElement()->SetAttr(
394 kNameSpaceID_None, nsGkAtoms::aria_selected, u"false"_ns, aNotify);
398 NS_ENSURE_SUCCESS(rv, rv);
400 // No "smart" select/unselect for internal call.
401 if (!aNotify) return NS_OK;
403 // If row or cell accessible was selected then we're able to not bother about
404 // selection of its cells or its row because our algorithm is row oriented,
405 // i.e. we check selection on row firstly and then on cells.
406 if (aIsSelected) return NS_OK;
408 roles::Role role = aAccessible->Role();
410 // If the given accessible is row that was unselected then remove
411 // aria-selected from cell accessible.
412 if (role == roles::ROW) {
413 AccIterator cellIter(aAccessible, filters::GetCell);
414 LocalAccessible* cell = nullptr;
416 while ((cell = cellIter.Next())) {
417 rv = SetARIASelected(cell, false, false);
418 NS_ENSURE_SUCCESS(rv, rv);
420 return NS_OK;
423 // If the given accessible is cell that was unselected and its row is selected
424 // then remove aria-selected from row and put aria-selected on
425 // siblings cells.
426 if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
427 role == roles::COLUMNHEADER) {
428 LocalAccessible* row = aAccessible->LocalParent();
430 if (row && row->Role() == roles::ROW && nsAccUtils::IsARIASelected(row)) {
431 rv = SetARIASelected(row, false, false);
432 NS_ENSURE_SUCCESS(rv, rv);
434 AccIterator cellIter(row, filters::GetCell);
435 LocalAccessible* cell = nullptr;
436 while ((cell = cellIter.Next())) {
437 if (cell != aAccessible) {
438 rv = SetARIASelected(cell, true, false);
439 NS_ENSURE_SUCCESS(rv, rv);
445 return NS_OK;
448 ////////////////////////////////////////////////////////////////////////////////
449 // ARIARowAccessible
450 ////////////////////////////////////////////////////////////////////////////////
452 ARIARowAccessible::ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc)
453 : HyperTextAccessibleWrap(aContent, aDoc) {
454 mGenericTypes |= eTableRow;
457 role ARIARowAccessible::NativeRole() const {
458 a11y::role r = GetAccService()->MarkupRole(mContent);
459 return r != roles::NOTHING ? r : roles::ROW;
462 GroupPos ARIARowAccessible::GroupPosition() {
463 int32_t count = 0, index = 0;
464 LocalAccessible* table = nsAccUtils::TableFor(this);
465 if (table) {
466 if (nsCoreUtils::GetUIntAttr(table->GetContent(), nsGkAtoms::aria_rowcount,
467 &count) &&
468 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
469 return GroupPos(0, index, count);
472 // Deal with the special case here that tables and grids can have rows
473 // which are wrapped in generic text container elements. Exclude tree grids
474 // because these are dealt with elsewhere.
475 if (table->Role() == roles::TABLE) {
476 LocalAccessible* row = nullptr;
477 AccIterator rowIter(table, filters::GetRow);
478 while ((row = rowIter.Next())) {
479 index++;
480 if (row == this) {
481 break;
485 if (row) {
486 count = table->AsTable()->RowCount();
487 return GroupPos(0, index, count);
492 return AccessibleWrap::GroupPosition();
495 // LocalAccessible protected
496 ENameValueFlag ARIARowAccessible::NativeName(nsString& aName) const {
497 // We want to calculate the name from content only if an ARIA role is
498 // present. ARIARowAccessible might also be used by tables with
499 // display:block; styling, in which case we do not want the name from
500 // content.
501 if (HasStrongARIARole()) {
502 return AccessibleWrap::NativeName(aName);
505 return eNameOK;
508 ////////////////////////////////////////////////////////////////////////////////
509 // ARIAGridCellAccessible
510 ////////////////////////////////////////////////////////////////////////////////
512 ////////////////////////////////////////////////////////////////////////////////
513 // Constructor
515 ARIAGridCellAccessible::ARIAGridCellAccessible(nsIContent* aContent,
516 DocAccessible* aDoc)
517 : HyperTextAccessibleWrap(aContent, aDoc) {
518 mGenericTypes |= eTableCell;
521 role ARIAGridCellAccessible::NativeRole() const {
522 a11y::role r = GetAccService()->MarkupRole(mContent);
523 return r != roles::NOTHING ? r : roles::CELL;
526 ////////////////////////////////////////////////////////////////////////////////
527 // TableCell
529 TableAccessible* ARIAGridCellAccessible::Table() const {
530 LocalAccessible* table = nsAccUtils::TableFor(Row());
531 return table ? table->AsTable() : nullptr;
534 uint32_t ARIAGridCellAccessible::ColIdx() const {
535 LocalAccessible* row = Row();
536 if (!row) return 0;
538 int32_t indexInRow = IndexInParent();
539 uint32_t colIdx = 0;
540 for (int32_t idx = 0; idx < indexInRow; idx++) {
541 LocalAccessible* cell = row->LocalChildAt(idx);
542 if (cell->IsTableCell()) {
543 colIdx += cell->AsTableCell()->ColExtent();
547 return colIdx;
550 uint32_t ARIAGridCellAccessible::RowIdx() const { return RowIndexFor(Row()); }
552 bool ARIAGridCellAccessible::Selected() {
553 LocalAccessible* row = Row();
554 if (!row) return false;
556 return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this);
559 ////////////////////////////////////////////////////////////////////////////////
560 // LocalAccessible
562 void ARIAGridCellAccessible::ApplyARIAState(uint64_t* aState) const {
563 HyperTextAccessibleWrap::ApplyARIAState(aState);
565 // Return if the gridcell has aria-selected="true".
566 if (*aState & states::SELECTED) return;
568 // Check aria-selected="true" on the row.
569 LocalAccessible* row = LocalParent();
570 if (!row || row->Role() != roles::ROW) return;
572 nsIContent* rowContent = row->GetContent();
573 if (nsAccUtils::HasDefinedARIAToken(rowContent, nsGkAtoms::aria_selected) &&
574 !rowContent->AsElement()->AttrValueIs(kNameSpaceID_None,
575 nsGkAtoms::aria_selected,
576 nsGkAtoms::_false, eCaseMatters)) {
577 *aState |= states::SELECTABLE | states::SELECTED;
581 already_AddRefed<AccAttributes> ARIAGridCellAccessible::NativeAttributes() {
582 RefPtr<AccAttributes> attributes =
583 HyperTextAccessibleWrap::NativeAttributes();
585 // Expose "table-cell-index" attribute.
586 LocalAccessible* thisRow = Row();
587 if (!thisRow) return attributes.forget();
589 int32_t rowIdx = RowIndexFor(thisRow);
590 if (rowIdx == -1) { // error
591 return attributes.forget();
594 int32_t colIdx = 0, colCount = 0;
595 uint32_t childCount = thisRow->ChildCount();
596 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
597 LocalAccessible* child = thisRow->LocalChildAt(childIdx);
598 if (child == this) colIdx = colCount;
600 roles::Role role = child->Role();
601 if (role == roles::CELL || role == roles::GRID_CELL ||
602 role == roles::ROWHEADER || role == roles::COLUMNHEADER) {
603 colCount++;
607 attributes->SetAttribute(nsGkAtoms::tableCellIndex,
608 rowIdx * colCount + colIdx);
610 #ifdef DEBUG
611 RefPtr<nsAtom> cppClass = NS_Atomize(u"cppclass"_ns);
612 attributes->SetAttributeStringCopy(cppClass, u"ARIAGridCellAccessible"_ns);
613 #endif
615 return attributes.forget();