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/. */
5
6 #include "ARIAGridAccessible-inl.h"
7
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"
14
15 #include "mozilla/dom/Element.h"
16 #include "nsComponentManagerUtils.h"
17
18 using namespace mozilla;
19 using namespace mozilla::a11y;
20
21 ////////////////////////////////////////////////////////////////////////////////
22 // ARIAGridAccessible
23 ////////////////////////////////////////////////////////////////////////////////
24
25 ////////////////////////////////////////////////////////////////////////////////
26 // Constructor
27
ARIAGridAccessible(nsIContent * aContent,DocAccessible * aDoc)28 ARIAGridAccessible::ARIAGridAccessible(nsIContent* aContent,
29 DocAccessible* aDoc)
30 : HyperTextAccessibleWrap(aContent, aDoc) {
31 mGenericTypes |= eTable;
32 }
33
NativeRole() const34 role ARIAGridAccessible::NativeRole() const {
35 a11y::role r = GetAccService()->MarkupRole(mContent);
36 return r != roles::NOTHING ? r : roles::TABLE;
37 }
38
NativeAttributes()39 already_AddRefed<AccAttributes> ARIAGridAccessible::NativeAttributes() {
40 RefPtr<AccAttributes> attributes = AccessibleWrap::NativeAttributes();
41
42 if (IsProbablyLayoutTable()) {
43 attributes->SetAttribute(nsGkAtoms::layout_guess, true);
44 }
45
46 return attributes.forget();
47 }
48
49 ////////////////////////////////////////////////////////////////////////////////
50 // Table
51
ColCount() const52 uint32_t ARIAGridAccessible::ColCount() const {
53 AccIterator rowIter(this, filters::GetRow);
54 LocalAccessible* row = rowIter.Next();
55 if (!row) return 0;
56
57 AccIterator cellIter(row, filters::GetCell);
58 LocalAccessible* cell = nullptr;
59
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();
64 }
65
66 return colCount;
67 }
68
RowCount()69 uint32_t ARIAGridAccessible::RowCount() {
70 uint32_t rowCount = 0;
71 AccIterator rowIter(this, filters::GetRow);
72 while (rowIter.Next()) rowCount++;
73
74 return rowCount;
75 }
76
CellAt(uint32_t aRowIndex,uint32_t aColumnIndex)77 LocalAccessible* ARIAGridAccessible::CellAt(uint32_t aRowIndex,
78 uint32_t aColumnIndex) {
79 LocalAccessible* row = RowAt(aRowIndex);
80 if (!row) return nullptr;
81
82 return CellInRowAt(row, aColumnIndex);
83 }
84
IsColSelected(uint32_t aColIdx)85 bool ARIAGridAccessible::IsColSelected(uint32_t aColIdx) {
86 if (IsARIARole(nsGkAtoms::table)) return false;
87
88 AccIterator rowIter(this, filters::GetRow);
89 LocalAccessible* row = rowIter.Next();
90 if (!row) return false;
91
92 do {
93 if (!nsAccUtils::IsARIASelected(row)) {
94 LocalAccessible* cell = CellInRowAt(row, aColIdx);
95 if (!cell || !nsAccUtils::IsARIASelected(cell)) return false;
96 }
97 } while ((row = rowIter.Next()));
98
99 return true;
100 }
101
IsRowSelected(uint32_t aRowIdx)102 bool ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx) {
103 if (IsARIARole(nsGkAtoms::table)) return false;
104
105 LocalAccessible* row = RowAt(aRowIdx);
106 if (!row) return false;
107
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;
113 }
114 }
115
116 return true;
117 }
118
IsCellSelected(uint32_t aRowIdx,uint32_t aColIdx)119 bool ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) {
120 if (IsARIARole(nsGkAtoms::table)) return false;
121
122 LocalAccessible* row = RowAt(aRowIdx);
123 if (!row) return false;
124
125 if (!nsAccUtils::IsARIASelected(row)) {
126 LocalAccessible* cell = CellInRowAt(row, aColIdx);
127 if (!cell || !nsAccUtils::IsARIASelected(cell)) return false;
128 }
129
130 return true;
131 }
132
SelectedCellCount()133 uint32_t ARIAGridAccessible::SelectedCellCount() {
134 if (IsARIARole(nsGkAtoms::table)) return 0;
135
136 uint32_t count = 0, colCount = ColCount();
137
138 AccIterator rowIter(this, filters::GetRow);
139 LocalAccessible* row = nullptr;
140
141 while ((row = rowIter.Next())) {
142 if (nsAccUtils::IsARIASelected(row)) {
143 count += colCount;
144 continue;
145 }
146
147 AccIterator cellIter(row, filters::GetCell);
148 LocalAccessible* cell = nullptr;
149
150 while ((cell = cellIter.Next())) {
151 if (nsAccUtils::IsARIASelected(cell)) count++;
152 }
153 }
154
155 return count;
156 }
157
SelectedColCount()158 uint32_t ARIAGridAccessible::SelectedColCount() {
159 if (IsARIARole(nsGkAtoms::table)) return 0;
160
161 uint32_t colCount = ColCount();
162 if (!colCount) return 0;
163
164 AccIterator rowIter(this, filters::GetRow);
165 LocalAccessible* row = rowIter.Next();
166 if (!row) return 0;
167
168 nsTArray<bool> isColSelArray(colCount);
169 isColSelArray.AppendElements(colCount);
170 memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
171
172 uint32_t selColCount = colCount;
173 do {
174 if (nsAccUtils::IsARIASelected(row)) continue;
175
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--;
183 }
184 }
185 } while ((row = rowIter.Next()));
186
187 return selColCount;
188 }
189
SelectedRowCount()190 uint32_t ARIAGridAccessible::SelectedRowCount() {
191 if (IsARIARole(nsGkAtoms::table)) return 0;
192
193 uint32_t count = 0;
194
195 AccIterator rowIter(this, filters::GetRow);
196 LocalAccessible* row = nullptr;
197
198 while ((row = rowIter.Next())) {
199 if (nsAccUtils::IsARIASelected(row)) {
200 count++;
201 continue;
202 }
203
204 AccIterator cellIter(row, filters::GetCell);
205 LocalAccessible* cell = cellIter.Next();
206 if (!cell) continue;
207
208 bool isRowSelected = true;
209 do {
210 if (!nsAccUtils::IsARIASelected(cell)) {
211 isRowSelected = false;
212 break;
213 }
214 } while ((cell = cellIter.Next()));
215
216 if (isRowSelected) count++;
217 }
218
219 return count;
220 }
221
SelectedCells(nsTArray<LocalAccessible * > * aCells)222 void ARIAGridAccessible::SelectedCells(nsTArray<LocalAccessible*>* aCells) {
223 if (IsARIARole(nsGkAtoms::table)) return;
224
225 AccIterator rowIter(this, filters::GetRow);
226
227 LocalAccessible* row = nullptr;
228 while ((row = rowIter.Next())) {
229 AccIterator cellIter(row, filters::GetCell);
230 LocalAccessible* cell = nullptr;
231
232 if (nsAccUtils::IsARIASelected(row)) {
233 while ((cell = cellIter.Next())) aCells->AppendElement(cell);
234
235 continue;
236 }
237
238 while ((cell = cellIter.Next())) {
239 if (nsAccUtils::IsARIASelected(cell)) aCells->AppendElement(cell);
240 }
241 }
242 }
243
SelectedCellIndices(nsTArray<uint32_t> * aCells)244 void ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) {
245 if (IsARIARole(nsGkAtoms::table)) return;
246
247 uint32_t colCount = ColCount();
248
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);
255 }
256
257 continue;
258 }
259
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);
265 }
266 }
267 }
268 }
269
SelectedColIndices(nsTArray<uint32_t> * aCols)270 void ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) {
271 if (IsARIARole(nsGkAtoms::table)) return;
272
273 uint32_t colCount = ColCount();
274 if (!colCount) return;
275
276 AccIterator rowIter(this, filters::GetRow);
277 LocalAccessible* row = rowIter.Next();
278 if (!row) return;
279
280 nsTArray<bool> isColSelArray(colCount);
281 isColSelArray.AppendElements(colCount);
282 memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
283
284 do {
285 if (nsAccUtils::IsARIASelected(row)) continue;
286
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;
293 }
294 }
295 } while ((row = rowIter.Next()));
296
297 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
298 if (isColSelArray[colIdx]) aCols->AppendElement(colIdx);
299 }
300 }
301
SelectedRowIndices(nsTArray<uint32_t> * aRows)302 void ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) {
303 if (IsARIARole(nsGkAtoms::table)) return;
304
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;
311 }
312
313 AccIterator cellIter(row, filters::GetCell);
314 LocalAccessible* cell = cellIter.Next();
315 if (!cell) continue;
316
317 bool isRowSelected = true;
318 do {
319 if (!nsAccUtils::IsARIASelected(cell)) {
320 isRowSelected = false;
321 break;
322 }
323 } while ((cell = cellIter.Next()));
324
325 if (isRowSelected) aRows->AppendElement(rowIdx);
326 }
327 }
328
SelectRow(uint32_t aRowIdx)329 void ARIAGridAccessible::SelectRow(uint32_t aRowIdx) {
330 if (IsARIARole(nsGkAtoms::table)) return;
331
332 AccIterator rowIter(this, filters::GetRow);
333
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!");
338 }
339 }
340
SelectCol(uint32_t aColIdx)341 void ARIAGridAccessible::SelectCol(uint32_t aColIdx) {
342 if (IsARIARole(nsGkAtoms::table)) return;
343
344 AccIterator rowIter(this, filters::GetRow);
345
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!");
351
352 // Select cell at the column index.
353 LocalAccessible* cell = CellInRowAt(row, aColIdx);
354 if (cell) SetARIASelected(cell, true);
355 }
356 }
357
UnselectRow(uint32_t aRowIdx)358 void ARIAGridAccessible::UnselectRow(uint32_t aRowIdx) {
359 if (IsARIARole(nsGkAtoms::table)) return;
360
361 LocalAccessible* row = RowAt(aRowIdx);
362 if (row) SetARIASelected(row, false);
363 }
364
UnselectCol(uint32_t aColIdx)365 void ARIAGridAccessible::UnselectCol(uint32_t aColIdx) {
366 if (IsARIARole(nsGkAtoms::table)) return;
367
368 AccIterator rowIter(this, filters::GetRow);
369
370 LocalAccessible* row = nullptr;
371 while ((row = rowIter.Next())) {
372 LocalAccessible* cell = CellInRowAt(row, aColIdx);
373 if (cell) SetARIASelected(cell, false);
374 }
375 }
376
377 ////////////////////////////////////////////////////////////////////////////////
378 // Protected
379
SetARIASelected(LocalAccessible * aAccessible,bool aIsSelected,bool aNotify)380 nsresult ARIAGridAccessible::SetARIASelected(LocalAccessible* aAccessible,
381 bool aIsSelected, bool aNotify) {
382 if (IsARIARole(nsGkAtoms::table)) return NS_OK;
383
384 nsIContent* content = aAccessible->GetContent();
385 NS_ENSURE_STATE(content);
386
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);
395 }
396 }
397
398 NS_ENSURE_SUCCESS(rv, rv);
399
400 // No "smart" select/unselect for internal call.
401 if (!aNotify) return NS_OK;
402
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;
407
408 roles::Role role = aAccessible->Role();
409
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;
415
416 while ((cell = cellIter.Next())) {
417 rv = SetARIASelected(cell, false, false);
418 NS_ENSURE_SUCCESS(rv, rv);
419 }
420 return NS_OK;
421 }
422
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();
429
430 if (row && row->Role() == roles::ROW && nsAccUtils::IsARIASelected(row)) {
431 rv = SetARIASelected(row, false, false);
432 NS_ENSURE_SUCCESS(rv, rv);
433
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);
440 }
441 }
442 }
443 }
444
445 return NS_OK;
446 }
447
448 ////////////////////////////////////////////////////////////////////////////////
449 // ARIARowAccessible
450 ////////////////////////////////////////////////////////////////////////////////
451
ARIARowAccessible(nsIContent * aContent,DocAccessible * aDoc)452 ARIARowAccessible::ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc)
453 : HyperTextAccessibleWrap(aContent, aDoc) {
454 mGenericTypes |= eTableRow;
455 }
456
NativeRole() const457 role ARIARowAccessible::NativeRole() const {
458 a11y::role r = GetAccService()->MarkupRole(mContent);
459 return r != roles::NOTHING ? r : roles::ROW;
460 }
461
GroupPosition()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);
470 }
471
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;
482 }
483 }
484
485 if (row) {
486 count = table->AsTable()->RowCount();
487 return GroupPos(0, index, count);
488 }
489 }
490 }
491
492 return AccessibleWrap::GroupPosition();
493 }
494
495 // LocalAccessible protected
NativeName(nsString & aName) const496 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);
503 }
504
505 return eNameOK;
506 }
507
508 ////////////////////////////////////////////////////////////////////////////////
509 // ARIAGridCellAccessible
510 ////////////////////////////////////////////////////////////////////////////////
511
512 ////////////////////////////////////////////////////////////////////////////////
513 // Constructor
514
ARIAGridCellAccessible(nsIContent * aContent,DocAccessible * aDoc)515 ARIAGridCellAccessible::ARIAGridCellAccessible(nsIContent* aContent,
516 DocAccessible* aDoc)
517 : HyperTextAccessibleWrap(aContent, aDoc) {
518 mGenericTypes |= eTableCell;
519 }
520
NativeRole() const521 role ARIAGridCellAccessible::NativeRole() const {
522 a11y::role r = GetAccService()->MarkupRole(mContent);
523 return r != roles::NOTHING ? r : roles::CELL;
524 }
525
526 ////////////////////////////////////////////////////////////////////////////////
527 // TableCell
528
Table() const529 TableAccessible* ARIAGridCellAccessible::Table() const {
530 LocalAccessible* table = nsAccUtils::TableFor(Row());
531 return table ? table->AsTable() : nullptr;
532 }
533
ColIdx() const534 uint32_t ARIAGridCellAccessible::ColIdx() const {
535 LocalAccessible* row = Row();
536 if (!row) return 0;
537
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();
544 }
545 }
546
547 return colIdx;
548 }
549
RowIdx() const550 uint32_t ARIAGridCellAccessible::RowIdx() const { return RowIndexFor(Row()); }
551
Selected()552 bool ARIAGridCellAccessible::Selected() {
553 LocalAccessible* row = Row();
554 if (!row) return false;
555
556 return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this);
557 }
558
559 ////////////////////////////////////////////////////////////////////////////////
560 // LocalAccessible
561
ApplyARIAState(uint64_t * aState) const562 void ARIAGridCellAccessible::ApplyARIAState(uint64_t* aState) const {
563 HyperTextAccessibleWrap::ApplyARIAState(aState);
564
565 // Return if the gridcell has aria-selected="true".
566 if (*aState & states::SELECTED) return;
567
568 // Check aria-selected="true" on the row.
569 LocalAccessible* row = LocalParent();
570 if (!row || row->Role() != roles::ROW) return;
571
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;
578 }
579 }
580
NativeAttributes()581 already_AddRefed<AccAttributes> ARIAGridCellAccessible::NativeAttributes() {
582 RefPtr<AccAttributes> attributes =
583 HyperTextAccessibleWrap::NativeAttributes();
584
585 // Expose "table-cell-index" attribute.
586 LocalAccessible* thisRow = Row();
587 if (!thisRow) return attributes.forget();
588
589 int32_t rowIdx = RowIndexFor(thisRow);
590 if (rowIdx == -1) { // error
591 return attributes.forget();
592 }
593
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;
599
600 roles::Role role = child->Role();
601 if (role == roles::CELL || role == roles::GRID_CELL ||
602 role == roles::ROWHEADER || role == roles::COLUMNHEADER) {
603 colCount++;
604 }
605 }
606
607 attributes->SetAttribute(nsGkAtoms::tableCellIndex,
608 rowIdx * colCount + colIdx);
609
610 #ifdef DEBUG
611 RefPtr<nsAtom> cppClass = NS_Atomize(u"cppclass"_ns);
612 attributes->SetAttribute(cppClass, u"ARIAGridCellAccessible"_ns);
613 #endif
614
615 return attributes.forget();
616 }
617
GroupPosition()618 GroupPos ARIAGridCellAccessible::GroupPosition() {
619 int32_t count = 0, index = 0;
620 TableAccessible* table = Table();
621 if (table &&
622 nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
623 nsGkAtoms::aria_colcount, &count) &&
624 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
625 return GroupPos(0, index, count);
626 }
627
628 return GroupPos();
629 }
630