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 "Accessible-inl.h"
9 #include "AccIterator.h"
10 #include "nsAccUtils.h"
11 #include "Role.h"
12 #include "States.h"
13 
14 #include "nsIMutableArray.h"
15 #include "nsIPersistentProperties2.h"
16 #include "nsComponentManagerUtils.h"
17 
18 using namespace mozilla;
19 using namespace mozilla::a11y;
20 
21 ////////////////////////////////////////////////////////////////////////////////
22 // ARIAGridAccessible
23 ////////////////////////////////////////////////////////////////////////////////
24 
25 
26 ////////////////////////////////////////////////////////////////////////////////
27 // Constructor
28 
29 ARIAGridAccessible::
ARIAGridAccessible(nsIContent * aContent,DocAccessible * aDoc)30   ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc) :
31   AccessibleWrap(aContent, aDoc)
32 {
33 }
34 
NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridAccessible,Accessible)35 NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridAccessible, Accessible)
36 
37 ////////////////////////////////////////////////////////////////////////////////
38 // Table
39 
40 uint32_t
41 ARIAGridAccessible::ColCount()
42 {
43   AccIterator rowIter(this, filters::GetRow);
44   Accessible* row = rowIter.Next();
45   if (!row)
46     return 0;
47 
48   AccIterator cellIter(row, filters::GetCell);
49   Accessible* cell = nullptr;
50 
51   uint32_t colCount = 0;
52   while ((cell = cellIter.Next()))
53     colCount++;
54 
55   return colCount;
56 }
57 
58 uint32_t
RowCount()59 ARIAGridAccessible::RowCount()
60 {
61   uint32_t rowCount = 0;
62   AccIterator rowIter(this, filters::GetRow);
63   while (rowIter.Next())
64     rowCount++;
65 
66   return rowCount;
67 }
68 
69 Accessible*
CellAt(uint32_t aRowIndex,uint32_t aColumnIndex)70 ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
71 {
72   Accessible* row = GetRowAt(aRowIndex);
73   if (!row)
74     return nullptr;
75 
76   return GetCellInRowAt(row, aColumnIndex);
77 }
78 
79 bool
IsColSelected(uint32_t aColIdx)80 ARIAGridAccessible::IsColSelected(uint32_t aColIdx)
81 {
82   if (IsARIARole(nsGkAtoms::table))
83     return false;
84 
85   AccIterator rowIter(this, filters::GetRow);
86   Accessible* row = rowIter.Next();
87   if (!row)
88     return false;
89 
90   do {
91     if (!nsAccUtils::IsARIASelected(row)) {
92       Accessible* cell = GetCellInRowAt(row, aColIdx);
93       if (!cell || !nsAccUtils::IsARIASelected(cell))
94         return false;
95     }
96   } while ((row = rowIter.Next()));
97 
98   return true;
99 }
100 
101 bool
IsRowSelected(uint32_t aRowIdx)102 ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx)
103 {
104   if (IsARIARole(nsGkAtoms::table))
105     return false;
106 
107   Accessible* row = GetRowAt(aRowIdx);
108   if(!row)
109     return false;
110 
111   if (!nsAccUtils::IsARIASelected(row)) {
112     AccIterator cellIter(row, filters::GetCell);
113     Accessible* cell = nullptr;
114     while ((cell = cellIter.Next())) {
115       if (!nsAccUtils::IsARIASelected(cell))
116         return false;
117     }
118   }
119 
120   return true;
121 }
122 
123 bool
IsCellSelected(uint32_t aRowIdx,uint32_t aColIdx)124 ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
125 {
126   if (IsARIARole(nsGkAtoms::table))
127     return false;
128 
129   Accessible* row = GetRowAt(aRowIdx);
130   if(!row)
131     return false;
132 
133   if (!nsAccUtils::IsARIASelected(row)) {
134     Accessible* cell = GetCellInRowAt(row, aColIdx);
135     if (!cell || !nsAccUtils::IsARIASelected(cell))
136       return false;
137   }
138 
139   return true;
140 }
141 
142 uint32_t
SelectedCellCount()143 ARIAGridAccessible::SelectedCellCount()
144 {
145   if (IsARIARole(nsGkAtoms::table))
146     return 0;
147 
148   uint32_t count = 0, colCount = ColCount();
149 
150   AccIterator rowIter(this, filters::GetRow);
151   Accessible* row = nullptr;
152 
153   while ((row = rowIter.Next())) {
154     if (nsAccUtils::IsARIASelected(row)) {
155       count += colCount;
156       continue;
157     }
158 
159     AccIterator cellIter(row, filters::GetCell);
160     Accessible* cell = nullptr;
161 
162     while ((cell = cellIter.Next())) {
163       if (nsAccUtils::IsARIASelected(cell))
164         count++;
165     }
166   }
167 
168   return count;
169 }
170 
171 uint32_t
SelectedColCount()172 ARIAGridAccessible::SelectedColCount()
173 {
174   if (IsARIARole(nsGkAtoms::table))
175     return 0;
176 
177   uint32_t colCount = ColCount();
178   if (!colCount)
179     return 0;
180 
181   AccIterator rowIter(this, filters::GetRow);
182   Accessible* row = rowIter.Next();
183   if (!row)
184     return 0;
185 
186   nsTArray<bool> isColSelArray(colCount);
187   isColSelArray.AppendElements(colCount);
188   memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
189 
190   uint32_t selColCount = colCount;
191   do {
192     if (nsAccUtils::IsARIASelected(row))
193       continue;
194 
195     AccIterator cellIter(row, filters::GetCell);
196     Accessible* cell = nullptr;
197     for (uint32_t colIdx = 0;
198          (cell = cellIter.Next()) && colIdx < colCount; colIdx++)
199       if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
200         isColSelArray[colIdx] = false;
201         selColCount--;
202       }
203   } while ((row = rowIter.Next()));
204 
205   return selColCount;
206 }
207 
208 uint32_t
SelectedRowCount()209 ARIAGridAccessible::SelectedRowCount()
210 {
211   if (IsARIARole(nsGkAtoms::table))
212     return 0;
213 
214   uint32_t count = 0;
215 
216   AccIterator rowIter(this, filters::GetRow);
217   Accessible* row = nullptr;
218 
219   while ((row = rowIter.Next())) {
220     if (nsAccUtils::IsARIASelected(row)) {
221       count++;
222       continue;
223     }
224 
225     AccIterator cellIter(row, filters::GetCell);
226     Accessible* cell = cellIter.Next();
227     if (!cell)
228       continue;
229 
230     bool isRowSelected = true;
231     do {
232       if (!nsAccUtils::IsARIASelected(cell)) {
233         isRowSelected = false;
234         break;
235       }
236     } while ((cell = cellIter.Next()));
237 
238     if (isRowSelected)
239       count++;
240   }
241 
242   return count;
243 }
244 
245 void
SelectedCells(nsTArray<Accessible * > * aCells)246 ARIAGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
247 {
248   if (IsARIARole(nsGkAtoms::table))
249     return;
250 
251   AccIterator rowIter(this, filters::GetRow);
252 
253   Accessible* row = nullptr;
254   while ((row = rowIter.Next())) {
255     AccIterator cellIter(row, filters::GetCell);
256     Accessible* cell = nullptr;
257 
258     if (nsAccUtils::IsARIASelected(row)) {
259       while ((cell = cellIter.Next()))
260         aCells->AppendElement(cell);
261 
262       continue;
263     }
264 
265     while ((cell = cellIter.Next())) {
266       if (nsAccUtils::IsARIASelected(cell))
267         aCells->AppendElement(cell);
268     }
269   }
270 }
271 
272 void
SelectedCellIndices(nsTArray<uint32_t> * aCells)273 ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
274 {
275   if (IsARIARole(nsGkAtoms::table))
276     return;
277 
278   uint32_t colCount = ColCount();
279 
280   AccIterator rowIter(this, filters::GetRow);
281   Accessible* row = nullptr;
282   for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
283     if (nsAccUtils::IsARIASelected(row)) {
284       for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
285         aCells->AppendElement(rowIdx * colCount + colIdx);
286 
287       continue;
288     }
289 
290     AccIterator cellIter(row, filters::GetCell);
291     Accessible* cell = nullptr;
292     for (uint32_t colIdx = 0; (cell = cellIter.Next()); colIdx++) {
293       if (nsAccUtils::IsARIASelected(cell))
294         aCells->AppendElement(rowIdx * colCount + colIdx);
295     }
296   }
297 }
298 
299 void
SelectedColIndices(nsTArray<uint32_t> * aCols)300 ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
301 {
302   if (IsARIARole(nsGkAtoms::table))
303     return;
304 
305   uint32_t colCount = ColCount();
306   if (!colCount)
307     return;
308 
309   AccIterator rowIter(this, filters::GetRow);
310   Accessible* row = rowIter.Next();
311   if (!row)
312     return;
313 
314   nsTArray<bool> isColSelArray(colCount);
315   isColSelArray.AppendElements(colCount);
316   memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
317 
318   do {
319     if (nsAccUtils::IsARIASelected(row))
320       continue;
321 
322     AccIterator cellIter(row, filters::GetCell);
323     Accessible* cell = nullptr;
324     for (uint32_t colIdx = 0;
325          (cell = cellIter.Next()) && colIdx < colCount; colIdx++)
326       if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
327         isColSelArray[colIdx] = false;
328       }
329   } while ((row = rowIter.Next()));
330 
331   for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
332     if (isColSelArray[colIdx])
333       aCols->AppendElement(colIdx);
334 }
335 
336 void
SelectedRowIndices(nsTArray<uint32_t> * aRows)337 ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
338 {
339   if (IsARIARole(nsGkAtoms::table))
340     return;
341 
342   AccIterator rowIter(this, filters::GetRow);
343   Accessible* row = nullptr;
344   for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
345     if (nsAccUtils::IsARIASelected(row)) {
346       aRows->AppendElement(rowIdx);
347       continue;
348     }
349 
350     AccIterator cellIter(row, filters::GetCell);
351     Accessible* cell = cellIter.Next();
352     if (!cell)
353       continue;
354 
355     bool isRowSelected = true;
356     do {
357       if (!nsAccUtils::IsARIASelected(cell)) {
358         isRowSelected = false;
359         break;
360       }
361     } while ((cell = cellIter.Next()));
362 
363     if (isRowSelected)
364       aRows->AppendElement(rowIdx);
365   }
366 }
367 
368 void
SelectRow(uint32_t aRowIdx)369 ARIAGridAccessible::SelectRow(uint32_t aRowIdx)
370 {
371   if (IsARIARole(nsGkAtoms::table))
372     return;
373 
374   AccIterator rowIter(this, filters::GetRow);
375 
376   Accessible* row = nullptr;
377   for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
378     DebugOnly<nsresult> rv = SetARIASelected(row, rowIdx == aRowIdx);
379     NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
380   }
381 }
382 
383 void
SelectCol(uint32_t aColIdx)384 ARIAGridAccessible::SelectCol(uint32_t aColIdx)
385 {
386   if (IsARIARole(nsGkAtoms::table))
387     return;
388 
389   AccIterator rowIter(this, filters::GetRow);
390 
391   Accessible* row = nullptr;
392   while ((row = rowIter.Next())) {
393     // Unselect all cells in the row.
394     DebugOnly<nsresult> rv = SetARIASelected(row, false);
395     NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
396 
397     // Select cell at the column index.
398     Accessible* cell = GetCellInRowAt(row, aColIdx);
399     if (cell)
400       SetARIASelected(cell, true);
401   }
402 }
403 
404 void
UnselectRow(uint32_t aRowIdx)405 ARIAGridAccessible::UnselectRow(uint32_t aRowIdx)
406 {
407   if (IsARIARole(nsGkAtoms::table))
408     return;
409 
410   Accessible* row = GetRowAt(aRowIdx);
411   if (row)
412     SetARIASelected(row, false);
413 }
414 
415 void
UnselectCol(uint32_t aColIdx)416 ARIAGridAccessible::UnselectCol(uint32_t aColIdx)
417 {
418   if (IsARIARole(nsGkAtoms::table))
419     return;
420 
421   AccIterator rowIter(this, filters::GetRow);
422 
423   Accessible* row = nullptr;
424   while ((row = rowIter.Next())) {
425     Accessible* cell = GetCellInRowAt(row, aColIdx);
426     if (cell)
427       SetARIASelected(cell, false);
428   }
429 }
430 
431 ////////////////////////////////////////////////////////////////////////////////
432 // Protected
433 
434 Accessible*
GetRowAt(int32_t aRow)435 ARIAGridAccessible::GetRowAt(int32_t aRow)
436 {
437   int32_t rowIdx = aRow;
438 
439   AccIterator rowIter(this, filters::GetRow);
440 
441   Accessible* row = rowIter.Next();
442   while (rowIdx != 0 && (row = rowIter.Next()))
443     rowIdx--;
444 
445   return row;
446 }
447 
448 Accessible*
GetCellInRowAt(Accessible * aRow,int32_t aColumn)449 ARIAGridAccessible::GetCellInRowAt(Accessible* aRow, int32_t aColumn)
450 {
451   int32_t colIdx = aColumn;
452 
453   AccIterator cellIter(aRow, filters::GetCell);
454   Accessible* cell = cellIter.Next();
455   while (colIdx != 0 && (cell = cellIter.Next()))
456     colIdx--;
457 
458   return cell;
459 }
460 
461 nsresult
SetARIASelected(Accessible * aAccessible,bool aIsSelected,bool aNotify)462 ARIAGridAccessible::SetARIASelected(Accessible* aAccessible,
463                                     bool aIsSelected, bool aNotify)
464 {
465   if (IsARIARole(nsGkAtoms::table))
466     return NS_OK;
467 
468   nsIContent *content = aAccessible->GetContent();
469   NS_ENSURE_STATE(content);
470 
471   nsresult rv = NS_OK;
472   if (aIsSelected)
473     rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
474                           NS_LITERAL_STRING("true"), aNotify);
475   else
476     rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
477                           NS_LITERAL_STRING("false"), aNotify);
478 
479   NS_ENSURE_SUCCESS(rv, rv);
480 
481   // No "smart" select/unselect for internal call.
482   if (!aNotify)
483     return NS_OK;
484 
485   // If row or cell accessible was selected then we're able to not bother about
486   // selection of its cells or its row because our algorithm is row oriented,
487   // i.e. we check selection on row firstly and then on cells.
488   if (aIsSelected)
489     return NS_OK;
490 
491   roles::Role role = aAccessible->Role();
492 
493   // If the given accessible is row that was unselected then remove
494   // aria-selected from cell accessible.
495   if (role == roles::ROW) {
496     AccIterator cellIter(aAccessible, filters::GetCell);
497     Accessible* cell = nullptr;
498 
499     while ((cell = cellIter.Next())) {
500       rv = SetARIASelected(cell, false, false);
501       NS_ENSURE_SUCCESS(rv, rv);
502     }
503     return NS_OK;
504   }
505 
506   // If the given accessible is cell that was unselected and its row is selected
507   // then remove aria-selected from row and put aria-selected on
508   // siblings cells.
509   if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
510       role == roles::COLUMNHEADER) {
511     Accessible* row = aAccessible->Parent();
512 
513     if (row && row->Role() == roles::ROW &&
514         nsAccUtils::IsARIASelected(row)) {
515       rv = SetARIASelected(row, false, false);
516       NS_ENSURE_SUCCESS(rv, rv);
517 
518       AccIterator cellIter(row, filters::GetCell);
519       Accessible* cell = nullptr;
520       while ((cell = cellIter.Next())) {
521         if (cell != aAccessible) {
522           rv = SetARIASelected(cell, true, false);
523           NS_ENSURE_SUCCESS(rv, rv);
524         }
525       }
526     }
527   }
528 
529   return NS_OK;
530 }
531 
532 
533 ////////////////////////////////////////////////////////////////////////////////
534 // ARIARowAccessible
535 ////////////////////////////////////////////////////////////////////////////////
536 
537 ARIARowAccessible::
ARIARowAccessible(nsIContent * aContent,DocAccessible * aDoc)538   ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc) :
539   AccessibleWrap(aContent, aDoc)
540 {
541   mGenericTypes |= eTableRow;
542 }
543 
NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible,Accessible)544 NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible, Accessible)
545 
546 GroupPos
547 ARIARowAccessible::GroupPosition()
548 {
549   int32_t count = 0, index = 0;
550   Accessible* table = nsAccUtils::TableFor(this);
551   if (table && nsCoreUtils::GetUIntAttr(table->GetContent(),
552                                         nsGkAtoms::aria_rowcount, &count) &&
553       nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
554     return GroupPos(0, index, count);
555   }
556 
557   return AccessibleWrap::GroupPosition();
558 }
559 
560 
561 ////////////////////////////////////////////////////////////////////////////////
562 // ARIAGridCellAccessible
563 ////////////////////////////////////////////////////////////////////////////////
564 
565 
566 ////////////////////////////////////////////////////////////////////////////////
567 // Constructor
568 
569 ARIAGridCellAccessible::
ARIAGridCellAccessible(nsIContent * aContent,DocAccessible * aDoc)570   ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
571   HyperTextAccessibleWrap(aContent, aDoc)
572 {
573   mGenericTypes |= eTableCell;
574 }
575 
NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessible,HyperTextAccessible)576 NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessible, HyperTextAccessible)
577 
578 ////////////////////////////////////////////////////////////////////////////////
579 // TableCell
580 
581 TableAccessible*
582 ARIAGridCellAccessible::Table() const
583 {
584   Accessible* table = nsAccUtils::TableFor(Row());
585   return table ? table->AsTable() : nullptr;
586 }
587 
588 uint32_t
ColIdx() const589 ARIAGridCellAccessible::ColIdx() const
590 {
591   Accessible* row = Row();
592   if (!row)
593     return 0;
594 
595   int32_t indexInRow = IndexInParent();
596   uint32_t colIdx = 0;
597   for (int32_t idx = 0; idx < indexInRow; idx++) {
598     Accessible* cell = row->GetChildAt(idx);
599     roles::Role role = cell->Role();
600     if (role == roles::CELL || role == roles::GRID_CELL ||
601         role == roles::ROWHEADER || role == roles::COLUMNHEADER)
602       colIdx++;
603   }
604 
605   return colIdx;
606 }
607 
608 uint32_t
RowIdx() const609 ARIAGridCellAccessible::RowIdx() const
610 {
611   return RowIndexFor(Row());
612 }
613 
614 bool
Selected()615 ARIAGridCellAccessible::Selected()
616 {
617   Accessible* row = Row();
618   if (!row)
619     return false;
620 
621   return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this);
622 }
623 
624 ////////////////////////////////////////////////////////////////////////////////
625 // Accessible
626 
627 void
ApplyARIAState(uint64_t * aState) const628 ARIAGridCellAccessible::ApplyARIAState(uint64_t* aState) const
629 {
630   HyperTextAccessibleWrap::ApplyARIAState(aState);
631 
632   // Return if the gridcell has aria-selected="true".
633   if (*aState & states::SELECTED)
634     return;
635 
636   // Check aria-selected="true" on the row.
637   Accessible* row = Parent();
638   if (!row || row->Role() != roles::ROW)
639     return;
640 
641   nsIContent *rowContent = row->GetContent();
642   if (nsAccUtils::HasDefinedARIAToken(rowContent,
643                                       nsGkAtoms::aria_selected) &&
644       !rowContent->AttrValueIs(kNameSpaceID_None,
645                                nsGkAtoms::aria_selected,
646                                nsGkAtoms::_false, eCaseMatters))
647     *aState |= states::SELECTABLE | states::SELECTED;
648 }
649 
650 already_AddRefed<nsIPersistentProperties>
NativeAttributes()651 ARIAGridCellAccessible::NativeAttributes()
652 {
653   nsCOMPtr<nsIPersistentProperties> attributes =
654     HyperTextAccessibleWrap::NativeAttributes();
655 
656   // Expose "table-cell-index" attribute.
657   Accessible* thisRow = Row();
658   if (!thisRow)
659     return attributes.forget();
660 
661   int32_t colIdx = 0, colCount = 0;
662   uint32_t childCount = thisRow->ChildCount();
663   for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
664     Accessible* child = thisRow->GetChildAt(childIdx);
665     if (child == this)
666       colIdx = colCount;
667 
668     roles::Role role = child->Role();
669     if (role == roles::CELL || role == roles::GRID_CELL ||
670         role == roles::ROWHEADER || role == roles::COLUMNHEADER)
671       colCount++;
672   }
673 
674   int32_t rowIdx = RowIndexFor(thisRow);
675 
676   nsAutoString stringIdx;
677   stringIdx.AppendInt(rowIdx * colCount + colIdx);
678   nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
679 
680 #ifdef DEBUG
681   nsAutoString unused;
682   attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"),
683                                 NS_LITERAL_STRING("ARIAGridCellAccessible"),
684                                 unused);
685 #endif
686 
687   return attributes.forget();
688 }
689 
690 GroupPos
GroupPosition()691 ARIAGridCellAccessible::GroupPosition()
692 {
693   int32_t count = 0, index = 0;
694   TableAccessible* table = Table();
695   if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
696                                         nsGkAtoms::aria_colcount, &count) &&
697       nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
698     return GroupPos(0, index, count);
699   }
700 
701   return GroupPos();
702 }
703