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 "HTMLTableAccessible.h"
7 
8 #include "mozilla/DebugOnly.h"
9 
10 #include "nsAccessibilityService.h"
11 #include "nsAccUtils.h"
12 #include "AccAttributes.h"
13 #include "DocAccessible.h"
14 #include "LocalAccessible-inl.h"
15 #include "nsTextEquivUtils.h"
16 #include "Relation.h"
17 #include "Role.h"
18 #include "States.h"
19 #include "TreeWalker.h"
20 
21 #include "mozilla/PresShell.h"
22 #include "mozilla/dom/HTMLTableElement.h"
23 #include "nsIHTMLCollection.h"
24 #include "mozilla/dom/Document.h"
25 #include "nsITableCellLayout.h"
26 #include "nsFrameSelection.h"
27 #include "nsError.h"
28 #include "nsArrayUtils.h"
29 #include "nsComponentManagerUtils.h"
30 #include "nsNameSpaceManager.h"
31 #include "nsTableCellFrame.h"
32 #include "nsTableWrapperFrame.h"
33 
34 using namespace mozilla;
35 using namespace mozilla::dom;
36 using namespace mozilla::a11y;
37 
38 ////////////////////////////////////////////////////////////////////////////////
39 // HTMLTableCellAccessible
40 ////////////////////////////////////////////////////////////////////////////////
41 
HTMLTableCellAccessible(nsIContent * aContent,DocAccessible * aDoc)42 HTMLTableCellAccessible::HTMLTableCellAccessible(nsIContent* aContent,
43                                                  DocAccessible* aDoc)
44     : HyperTextAccessibleWrap(aContent, aDoc) {
45   mType = eHTMLTableCellType;
46   mGenericTypes |= eTableCell;
47 }
48 
49 ////////////////////////////////////////////////////////////////////////////////
50 // HTMLTableCellAccessible: LocalAccessible implementation
51 
NativeRole() const52 role HTMLTableCellAccessible::NativeRole() const {
53   if (mContent->IsMathMLElement(nsGkAtoms::mtd_)) {
54     return roles::MATHML_CELL;
55   }
56   return roles::CELL;
57 }
58 
NativeState() const59 uint64_t HTMLTableCellAccessible::NativeState() const {
60   uint64_t state = HyperTextAccessibleWrap::NativeState();
61 
62   nsIFrame* frame = mContent->GetPrimaryFrame();
63   NS_ASSERTION(frame, "No frame for valid cell accessible!");
64 
65   if (frame && frame->IsSelected()) state |= states::SELECTED;
66 
67   return state;
68 }
69 
NativeInteractiveState() const70 uint64_t HTMLTableCellAccessible::NativeInteractiveState() const {
71   return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE;
72 }
73 
NativeAttributes()74 already_AddRefed<AccAttributes> HTMLTableCellAccessible::NativeAttributes() {
75   RefPtr<AccAttributes> attributes =
76       HyperTextAccessibleWrap::NativeAttributes();
77 
78   // table-cell-index attribute
79   TableAccessible* table = Table();
80   if (!table) return attributes.forget();
81 
82   int32_t rowIdx = -1, colIdx = -1;
83   nsresult rv = GetCellIndexes(rowIdx, colIdx);
84   if (NS_FAILED(rv)) return attributes.forget();
85 
86   attributes->SetAttribute(nsGkAtoms::tableCellIndex,
87                            table->CellIndexAt(rowIdx, colIdx));
88 
89   // abbr attribute
90 
91   // Pick up object attribute from abbr DOM element (a child of the cell) or
92   // from abbr DOM attribute.
93   nsString abbrText;
94   if (ChildCount() == 1) {
95     LocalAccessible* abbr = LocalFirstChild();
96     if (abbr->IsAbbreviation()) {
97       nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild();
98       if (firstChildNode) {
99         nsTextEquivUtils::AppendTextEquivFromTextContent(firstChildNode,
100                                                          &abbrText);
101       }
102     }
103   }
104   if (abbrText.IsEmpty()) {
105     mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr,
106                                    abbrText);
107   }
108 
109   if (!abbrText.IsEmpty()) {
110     attributes->SetAttribute(nsGkAtoms::abbr, std::move(abbrText));
111   }
112 
113   // axis attribute
114   nsString axisText;
115   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText);
116   if (!axisText.IsEmpty()) {
117     attributes->SetAttribute(nsGkAtoms::axis, std::move(axisText));
118   }
119 
120 #ifdef DEBUG
121   RefPtr<nsAtom> cppClass = NS_Atomize(u"cppclass"_ns);
122   attributes->SetAttributeStringCopy(cppClass, u"HTMLTableCellAccessible"_ns);
123 #endif
124 
125   return attributes.forget();
126 }
127 
GroupPosition()128 GroupPos HTMLTableCellAccessible::GroupPosition() {
129   int32_t count = 0, index = 0;
130   TableAccessible* table = Table();
131   if (table &&
132       nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
133                                nsGkAtoms::aria_colcount, &count) &&
134       nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
135     return GroupPos(0, index, count);
136   }
137 
138   return HyperTextAccessibleWrap::GroupPosition();
139 }
140 
DOMAttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue,uint64_t aOldState)141 void HTMLTableCellAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
142                                                   nsAtom* aAttribute,
143                                                   int32_t aModType,
144                                                   const nsAttrValue* aOldValue,
145                                                   uint64_t aOldState) {
146   HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute,
147                                                aModType, aOldValue, aOldState);
148 
149   if (aAttribute == nsGkAtoms::headers || aAttribute == nsGkAtoms::abbr ||
150       aAttribute == nsGkAtoms::scope) {
151     mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
152                            this);
153   }
154 }
155 
156 ////////////////////////////////////////////////////////////////////////////////
157 // HTMLTableCellAccessible: TableCellAccessible implementation
158 
Table() const159 TableAccessible* HTMLTableCellAccessible::Table() const {
160   LocalAccessible* parent = const_cast<HTMLTableCellAccessible*>(this);
161   while ((parent = parent->LocalParent())) {
162     if (parent->IsTable()) return parent->AsTable();
163   }
164 
165   return nullptr;
166 }
167 
ColIdx() const168 uint32_t HTMLTableCellAccessible::ColIdx() const {
169   nsTableCellFrame* cellFrame = GetCellFrame();
170   NS_ENSURE_TRUE(cellFrame, 0);
171   return cellFrame->ColIndex();
172 }
173 
RowIdx() const174 uint32_t HTMLTableCellAccessible::RowIdx() const {
175   nsTableCellFrame* cellFrame = GetCellFrame();
176   NS_ENSURE_TRUE(cellFrame, 0);
177   return cellFrame->RowIndex();
178 }
179 
ColExtent() const180 uint32_t HTMLTableCellAccessible::ColExtent() const {
181   int32_t rowIdx = -1, colIdx = -1;
182   GetCellIndexes(rowIdx, colIdx);
183 
184   TableAccessible* table = Table();
185   NS_ASSERTION(table, "cell not in a table!");
186   if (!table) return 0;
187 
188   return table->ColExtentAt(rowIdx, colIdx);
189 }
190 
RowExtent() const191 uint32_t HTMLTableCellAccessible::RowExtent() const {
192   int32_t rowIdx = -1, colIdx = -1;
193   GetCellIndexes(rowIdx, colIdx);
194 
195   TableAccessible* table = Table();
196   NS_ASSERTION(table, "cell not in atable!");
197   if (!table) return 0;
198 
199   return table->RowExtentAt(rowIdx, colIdx);
200 }
201 
ColHeaderCells(nsTArray<LocalAccessible * > * aCells)202 void HTMLTableCellAccessible::ColHeaderCells(
203     nsTArray<LocalAccessible*>* aCells) {
204   IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
205   while (LocalAccessible* cell = itr.Next()) {
206     a11y::role cellRole = cell->Role();
207     if (cellRole == roles::COLUMNHEADER) {
208       aCells->AppendElement(cell);
209     } else if (cellRole != roles::ROWHEADER) {
210       // If referred table cell is at the same column then treat it as a column
211       // header.
212       TableCellAccessible* tableCell = cell->AsTableCell();
213       if (tableCell && tableCell->ColIdx() == ColIdx()) {
214         aCells->AppendElement(cell);
215       }
216     }
217   }
218 
219   if (aCells->IsEmpty()) TableCellAccessible::ColHeaderCells(aCells);
220 }
221 
RowHeaderCells(nsTArray<LocalAccessible * > * aCells)222 void HTMLTableCellAccessible::RowHeaderCells(
223     nsTArray<LocalAccessible*>* aCells) {
224   IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
225   while (LocalAccessible* cell = itr.Next()) {
226     a11y::role cellRole = cell->Role();
227     if (cellRole == roles::ROWHEADER) {
228       aCells->AppendElement(cell);
229     } else if (cellRole != roles::COLUMNHEADER) {
230       // If referred table cell is at the same row then treat it as a column
231       // header.
232       TableCellAccessible* tableCell = cell->AsTableCell();
233       if (tableCell && tableCell->RowIdx() == RowIdx()) {
234         aCells->AppendElement(cell);
235       }
236     }
237   }
238 
239   if (aCells->IsEmpty()) TableCellAccessible::RowHeaderCells(aCells);
240 }
241 
Selected()242 bool HTMLTableCellAccessible::Selected() {
243   int32_t rowIdx = -1, colIdx = -1;
244   GetCellIndexes(rowIdx, colIdx);
245 
246   TableAccessible* table = Table();
247   NS_ENSURE_TRUE(table, false);
248 
249   return table->IsCellSelected(rowIdx, colIdx);
250 }
251 
252 ////////////////////////////////////////////////////////////////////////////////
253 // HTMLTableCellAccessible: protected implementation
254 
GetCellLayout() const255 nsITableCellLayout* HTMLTableCellAccessible::GetCellLayout() const {
256   return do_QueryFrame(mContent->GetPrimaryFrame());
257 }
258 
GetCellFrame() const259 nsTableCellFrame* HTMLTableCellAccessible::GetCellFrame() const {
260   return do_QueryFrame(mContent->GetPrimaryFrame());
261 }
262 
GetCellIndexes(int32_t & aRowIdx,int32_t & aColIdx) const263 nsresult HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx,
264                                                  int32_t& aColIdx) const {
265   nsITableCellLayout* cellLayout = GetCellLayout();
266   NS_ENSURE_STATE(cellLayout);
267 
268   return cellLayout->GetCellIndexes(aRowIdx, aColIdx);
269 }
270 
271 ////////////////////////////////////////////////////////////////////////////////
272 // HTMLTableHeaderCellAccessible
273 ////////////////////////////////////////////////////////////////////////////////
274 
HTMLTableHeaderCellAccessible(nsIContent * aContent,DocAccessible * aDoc)275 HTMLTableHeaderCellAccessible::HTMLTableHeaderCellAccessible(
276     nsIContent* aContent, DocAccessible* aDoc)
277     : HTMLTableCellAccessible(aContent, aDoc) {}
278 
279 ////////////////////////////////////////////////////////////////////////////////
280 // HTMLTableHeaderCellAccessible: LocalAccessible implementation
281 
NativeRole() const282 role HTMLTableHeaderCellAccessible::NativeRole() const {
283   // Check value of @scope attribute.
284   static Element::AttrValuesArray scopeValues[] = {
285       nsGkAtoms::col, nsGkAtoms::colgroup, nsGkAtoms::row, nsGkAtoms::rowgroup,
286       nullptr};
287   int32_t valueIdx = mContent->AsElement()->FindAttrValueIn(
288       kNameSpaceID_None, nsGkAtoms::scope, scopeValues, eCaseMatters);
289 
290   switch (valueIdx) {
291     case 0:
292     case 1:
293       return roles::COLUMNHEADER;
294     case 2:
295     case 3:
296       return roles::ROWHEADER;
297   }
298 
299   TableAccessible* table = Table();
300   if (!table) return roles::NOTHING;
301 
302   // If the cell next to this one is not a header cell then assume this cell is
303   // a row header for it.
304   uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
305   LocalAccessible* cell = table->CellAt(rowIdx, colIdx + ColExtent());
306   if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) {
307     return roles::ROWHEADER;
308   }
309 
310   // If the cell below this one is not a header cell then assume this cell is
311   // a column header for it.
312   uint32_t rowExtent = RowExtent();
313   cell = table->CellAt(rowIdx + rowExtent, colIdx);
314   if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) {
315     return roles::COLUMNHEADER;
316   }
317 
318   // Otherwise if this cell is surrounded by header cells only then make a guess
319   // based on its cell spanning. In other words if it is row spanned then assume
320   // it's a row header, otherwise it's a column header.
321   return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER;
322 }
323 
324 ////////////////////////////////////////////////////////////////////////////////
325 // HTMLTableRowAccessible
326 ////////////////////////////////////////////////////////////////////////////////
327 
NativeRole() const328 role HTMLTableRowAccessible::NativeRole() const {
329   if (mContent->IsMathMLElement(nsGkAtoms::mtr_)) {
330     return roles::MATHML_TABLE_ROW;
331   } else if (mContent->IsMathMLElement(nsGkAtoms::mlabeledtr_)) {
332     return roles::MATHML_LABELED_ROW;
333   }
334   return roles::ROW;
335 }
336 
GroupPosition()337 GroupPos HTMLTableRowAccessible::GroupPosition() {
338   int32_t count = 0, index = 0;
339   LocalAccessible* table = nsAccUtils::TableFor(this);
340   if (table &&
341       nsCoreUtils::GetUIntAttr(table->GetContent(), nsGkAtoms::aria_rowcount,
342                                &count) &&
343       nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
344     return GroupPos(0, index, count);
345   }
346 
347   return AccessibleWrap::GroupPosition();
348 }
349 
350 // LocalAccessible protected
NativeName(nsString & aName) const351 ENameValueFlag HTMLTableRowAccessible::NativeName(nsString& aName) const {
352   // For table row accessibles, we only want to calculate the name from the
353   // sub tree if an ARIA role is present.
354   if (HasStrongARIARole()) {
355     return AccessibleWrap::NativeName(aName);
356   }
357 
358   return eNameOK;
359 }
360 
361 ////////////////////////////////////////////////////////////////////////////////
362 // HTMLTableAccessible
363 ////////////////////////////////////////////////////////////////////////////////
364 
365 ////////////////////////////////////////////////////////////////////////////////
366 // HTMLTableAccessible: LocalAccessible
367 
InsertChildAt(uint32_t aIndex,LocalAccessible * aChild)368 bool HTMLTableAccessible::InsertChildAt(uint32_t aIndex,
369                                         LocalAccessible* aChild) {
370   // Move caption accessible so that it's the first child. Check for the first
371   // caption only, because nsAccessibilityService ensures we don't create
372   // accessibles for the other captions, since only the first is actually
373   // visible.
374   return LocalAccessible::InsertChildAt(aChild->IsHTMLCaption() ? 0 : aIndex,
375                                         aChild);
376 }
377 
NativeRole() const378 role HTMLTableAccessible::NativeRole() const {
379   if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
380     return roles::MATHML_TABLE;
381   }
382   return roles::TABLE;
383 }
384 
NativeState() const385 uint64_t HTMLTableAccessible::NativeState() const {
386   return LocalAccessible::NativeState() | states::READONLY;
387 }
388 
NativeName(nsString & aName) const389 ENameValueFlag HTMLTableAccessible::NativeName(nsString& aName) const {
390   ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
391   if (!aName.IsEmpty()) return nameFlag;
392 
393   // Use table caption as a name.
394   LocalAccessible* caption = Caption();
395   if (caption) {
396     nsIContent* captionContent = caption->GetContent();
397     if (captionContent) {
398       nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
399                                                    &aName);
400       if (!aName.IsEmpty()) return eNameOK;
401     }
402   }
403 
404   // If no caption then use summary as a name.
405   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName);
406   return eNameOK;
407 }
408 
DOMAttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue,uint64_t aOldState)409 void HTMLTableAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
410                                               nsAtom* aAttribute,
411                                               int32_t aModType,
412                                               const nsAttrValue* aOldValue,
413                                               uint64_t aOldState) {
414   HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute,
415                                                aModType, aOldValue, aOldState);
416 
417   if (aAttribute == nsGkAtoms::summary) {
418     nsAutoString name;
419     ARIAName(name);
420     if (name.IsEmpty()) {
421       if (!Caption()) {
422         // XXX: Should really be checking if caption provides a name.
423         mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
424       }
425     }
426 
427     mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
428                            this);
429   }
430 }
431 
NativeAttributes()432 already_AddRefed<AccAttributes> HTMLTableAccessible::NativeAttributes() {
433   RefPtr<AccAttributes> attributes = AccessibleWrap::NativeAttributes();
434 
435   if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
436     GetAccService()->MarkupAttributes(mContent, attributes);
437   }
438 
439   if (IsProbablyLayoutTable()) {
440     attributes->SetAttribute(nsGkAtoms::layout_guess, true);
441   }
442 
443   return attributes.forget();
444 }
445 
446 ////////////////////////////////////////////////////////////////////////////////
447 // HTMLTableAccessible: LocalAccessible
448 
RelationByType(RelationType aType) const449 Relation HTMLTableAccessible::RelationByType(RelationType aType) const {
450   Relation rel = AccessibleWrap::RelationByType(aType);
451   if (aType == RelationType::LABELLED_BY) rel.AppendTarget(Caption());
452 
453   return rel;
454 }
455 
456 ////////////////////////////////////////////////////////////////////////////////
457 // HTMLTableAccessible: Table
458 
Caption() const459 LocalAccessible* HTMLTableAccessible::Caption() const {
460   LocalAccessible* child = mChildren.SafeElementAt(0, nullptr);
461   // Since this is an HTML table the caption needs to be a caption
462   // element with no ARIA role (except for a reduntant role='caption').
463   // If we did a full Role() calculation here we risk getting into an infinite
464   // loop where the parent role would depend on its name which would need to be
465   // calculated by retrieving the caption (bug 1420773.)
466   return child && child->NativeRole() == roles::CAPTION &&
467                  (!child->HasStrongARIARole() ||
468                   child->IsARIARole(nsGkAtoms::caption))
469              ? child
470              : nullptr;
471 }
472 
Summary(nsString & aSummary)473 void HTMLTableAccessible::Summary(nsString& aSummary) {
474   dom::HTMLTableElement* table = dom::HTMLTableElement::FromNode(mContent);
475 
476   if (table) table->GetSummary(aSummary);
477 }
478 
ColCount() const479 uint32_t HTMLTableAccessible::ColCount() const {
480   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
481   return tableFrame ? tableFrame->GetColCount() : 0;
482 }
483 
RowCount()484 uint32_t HTMLTableAccessible::RowCount() {
485   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
486   return tableFrame ? tableFrame->GetRowCount() : 0;
487 }
488 
SelectedCellCount()489 uint32_t HTMLTableAccessible::SelectedCellCount() {
490   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
491   if (!tableFrame) return 0;
492 
493   uint32_t count = 0, rowCount = RowCount(), colCount = ColCount();
494   for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
495     for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
496       nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
497       if (!cellFrame || !cellFrame->IsSelected()) continue;
498 
499       uint32_t startRow = cellFrame->RowIndex();
500       uint32_t startCol = cellFrame->ColIndex();
501       if (startRow == rowIdx && startCol == colIdx) count++;
502     }
503   }
504 
505   return count;
506 }
507 
SelectedColCount()508 uint32_t HTMLTableAccessible::SelectedColCount() {
509   uint32_t count = 0, colCount = ColCount();
510 
511   for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
512     if (IsColSelected(colIdx)) count++;
513   }
514 
515   return count;
516 }
517 
SelectedRowCount()518 uint32_t HTMLTableAccessible::SelectedRowCount() {
519   uint32_t count = 0, rowCount = RowCount();
520 
521   for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
522     if (IsRowSelected(rowIdx)) count++;
523   }
524 
525   return count;
526 }
527 
SelectedCells(nsTArray<LocalAccessible * > * aCells)528 void HTMLTableAccessible::SelectedCells(nsTArray<LocalAccessible*>* aCells) {
529   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
530   if (!tableFrame) return;
531 
532   uint32_t rowCount = RowCount(), colCount = ColCount();
533   for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
534     for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
535       nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
536       if (!cellFrame || !cellFrame->IsSelected()) continue;
537 
538       uint32_t startRow = cellFrame->RowIndex();
539       uint32_t startCol = cellFrame->ColIndex();
540       if (startRow != rowIdx || startCol != colIdx) continue;
541 
542       LocalAccessible* cell = mDoc->GetAccessible(cellFrame->GetContent());
543       aCells->AppendElement(cell);
544     }
545   }
546 }
547 
SelectedCellIndices(nsTArray<uint32_t> * aCells)548 void HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) {
549   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
550   if (!tableFrame) return;
551 
552   uint32_t rowCount = RowCount(), colCount = ColCount();
553   for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
554     for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
555       nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
556       if (!cellFrame || !cellFrame->IsSelected()) continue;
557 
558       uint32_t startCol = cellFrame->ColIndex();
559       uint32_t startRow = cellFrame->RowIndex();
560       if (startRow == rowIdx && startCol == colIdx) {
561         aCells->AppendElement(CellIndexAt(rowIdx, colIdx));
562       }
563     }
564   }
565 }
566 
SelectedColIndices(nsTArray<uint32_t> * aCols)567 void HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) {
568   uint32_t colCount = ColCount();
569   for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
570     if (IsColSelected(colIdx)) aCols->AppendElement(colIdx);
571   }
572 }
573 
SelectedRowIndices(nsTArray<uint32_t> * aRows)574 void HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) {
575   uint32_t rowCount = RowCount();
576   for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
577     if (IsRowSelected(rowIdx)) aRows->AppendElement(rowIdx);
578   }
579 }
580 
CellAt(uint32_t aRowIdx,uint32_t aColIdx)581 LocalAccessible* HTMLTableAccessible::CellAt(uint32_t aRowIdx,
582                                              uint32_t aColIdx) {
583   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
584   if (!tableFrame) return nullptr;
585 
586   nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
587   LocalAccessible* cell = mDoc->GetAccessible(cellContent);
588 
589   // Sometimes, the accessible returned here is a row accessible instead of
590   // a cell accessible, for example when a cell has CSS display:block; set.
591   // In such cases, iterate through the cells in this row differently to find
592   // it.
593   if (cell && cell->IsTableRow()) {
594     return CellInRowAt(cell, aColIdx);
595   }
596 
597   // XXX bug 576838: bizarre tables (like table6 in tables/test_table2.html) may
598   // return itself as a cell what makes Orca hang.
599   return cell == this ? nullptr : cell;
600 }
601 
CellIndexAt(uint32_t aRowIdx,uint32_t aColIdx)602 int32_t HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) {
603   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
604   if (!tableFrame) return -1;
605 
606   int32_t cellIndex = tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx);
607   if (cellIndex == -1) {
608     // Sometimes, the accessible returned here is a row accessible instead of
609     // a cell accessible, for example when a cell has CSS display:block; set.
610     // In such cases, iterate through the cells in this row differently to find
611     // it.
612     nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
613     LocalAccessible* cell = mDoc->GetAccessible(cellContent);
614     if (cell && cell->IsTableRow()) {
615       return TableAccessible::CellIndexAt(aRowIdx, aColIdx);
616     }
617   }
618 
619   return cellIndex;
620 }
621 
ColIndexAt(uint32_t aCellIdx)622 int32_t HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx) {
623   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
624   if (!tableFrame) return -1;
625 
626   int32_t rowIdx = -1, colIdx = -1;
627   tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
628 
629   if (colIdx == -1) {
630     // Sometimes, the index returned indicates that this is not a regular
631     // cell, for example when a cell has CSS display:block; set.
632     // In such cases, try the super class method to find it.
633     return TableAccessible::ColIndexAt(aCellIdx);
634   }
635 
636   return colIdx;
637 }
638 
RowIndexAt(uint32_t aCellIdx)639 int32_t HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx) {
640   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
641   if (!tableFrame) return -1;
642 
643   int32_t rowIdx = -1, colIdx = -1;
644   tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
645 
646   if (rowIdx == -1) {
647     // Sometimes, the index returned indicates that this is not a regular
648     // cell, for example when a cell has CSS display:block; set.
649     // In such cases, try the super class method to find it.
650     return TableAccessible::RowIndexAt(aCellIdx);
651   }
652 
653   return rowIdx;
654 }
655 
RowAndColIndicesAt(uint32_t aCellIdx,int32_t * aRowIdx,int32_t * aColIdx)656 void HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx,
657                                              int32_t* aRowIdx,
658                                              int32_t* aColIdx) {
659   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
660   if (tableFrame) {
661     tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx);
662     if (*aRowIdx == -1 || *aColIdx == -1) {
663       // Sometimes, the index returned indicates that this is not a regular
664       // cell, for example when a cell has CSS display:block; set.
665       // In such cases, try the super class method to find it.
666       TableAccessible::RowAndColIndicesAt(aCellIdx, aRowIdx, aColIdx);
667     }
668   }
669 }
670 
ColExtentAt(uint32_t aRowIdx,uint32_t aColIdx)671 uint32_t HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) {
672   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
673   if (!tableFrame) return 0;
674 
675   uint32_t colExtent = tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx);
676   if (colExtent == 0) {
677     nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
678     LocalAccessible* cell = mDoc->GetAccessible(cellContent);
679     if (cell && cell->IsTableRow()) {
680       return TableAccessible::ColExtentAt(aRowIdx, aColIdx);
681     }
682   }
683 
684   return colExtent;
685 }
686 
RowExtentAt(uint32_t aRowIdx,uint32_t aColIdx)687 uint32_t HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) {
688   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
689   if (!tableFrame) return 0;
690 
691   return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx);
692 }
693 
IsColSelected(uint32_t aColIdx)694 bool HTMLTableAccessible::IsColSelected(uint32_t aColIdx) {
695   bool isSelected = false;
696 
697   uint32_t rowCount = RowCount();
698   for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
699     isSelected = IsCellSelected(rowIdx, aColIdx);
700     if (!isSelected) return false;
701   }
702 
703   return isSelected;
704 }
705 
IsRowSelected(uint32_t aRowIdx)706 bool HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx) {
707   bool isSelected = false;
708 
709   uint32_t colCount = ColCount();
710   for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
711     isSelected = IsCellSelected(aRowIdx, colIdx);
712     if (!isSelected) return false;
713   }
714 
715   return isSelected;
716 }
717 
IsCellSelected(uint32_t aRowIdx,uint32_t aColIdx)718 bool HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) {
719   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
720   if (!tableFrame) return false;
721 
722   nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx);
723   return cellFrame ? cellFrame->IsSelected() : false;
724 }
725 
SelectRow(uint32_t aRowIdx)726 void HTMLTableAccessible::SelectRow(uint32_t aRowIdx) {
727   DebugOnly<nsresult> rv =
728       RemoveRowsOrColumnsFromSelection(aRowIdx, TableSelectionMode::Row, true);
729   NS_ASSERTION(NS_SUCCEEDED(rv),
730                "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
731 
732   AddRowOrColumnToSelection(aRowIdx, TableSelectionMode::Row);
733 }
734 
SelectCol(uint32_t aColIdx)735 void HTMLTableAccessible::SelectCol(uint32_t aColIdx) {
736   DebugOnly<nsresult> rv = RemoveRowsOrColumnsFromSelection(
737       aColIdx, TableSelectionMode::Column, true);
738   NS_ASSERTION(NS_SUCCEEDED(rv),
739                "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
740 
741   AddRowOrColumnToSelection(aColIdx, TableSelectionMode::Column);
742 }
743 
UnselectRow(uint32_t aRowIdx)744 void HTMLTableAccessible::UnselectRow(uint32_t aRowIdx) {
745   RemoveRowsOrColumnsFromSelection(aRowIdx, TableSelectionMode::Row, false);
746 }
747 
UnselectCol(uint32_t aColIdx)748 void HTMLTableAccessible::UnselectCol(uint32_t aColIdx) {
749   RemoveRowsOrColumnsFromSelection(aColIdx, TableSelectionMode::Column, false);
750 }
751 
752 ////////////////////////////////////////////////////////////////////////////////
753 // HTMLTableAccessible: protected implementation
754 
AddRowOrColumnToSelection(int32_t aIndex,TableSelectionMode aTarget)755 nsresult HTMLTableAccessible::AddRowOrColumnToSelection(
756     int32_t aIndex, TableSelectionMode aTarget) {
757   bool doSelectRow = (aTarget == TableSelectionMode::Row);
758 
759   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
760   if (!tableFrame) return NS_OK;
761 
762   uint32_t count = 0;
763   if (doSelectRow) {
764     count = ColCount();
765   } else {
766     count = RowCount();
767   }
768 
769   PresShell* presShell = mDoc->PresShellPtr();
770   RefPtr<nsFrameSelection> tableSelection =
771       const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
772 
773   for (uint32_t idx = 0; idx < count; idx++) {
774     int32_t rowIdx = doSelectRow ? aIndex : idx;
775     int32_t colIdx = doSelectRow ? idx : aIndex;
776     nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
777     if (cellFrame && !cellFrame->IsSelected()) {
778       nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent());
779       NS_ENSURE_SUCCESS(rv, rv);
780     }
781   }
782 
783   return NS_OK;
784 }
785 
RemoveRowsOrColumnsFromSelection(int32_t aIndex,TableSelectionMode aTarget,bool aIsOuter)786 nsresult HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(
787     int32_t aIndex, TableSelectionMode aTarget, bool aIsOuter) {
788   nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
789   if (!tableFrame) return NS_OK;
790 
791   PresShell* presShell = mDoc->PresShellPtr();
792   RefPtr<nsFrameSelection> tableSelection =
793       const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
794 
795   bool doUnselectRow = (aTarget == TableSelectionMode::Row);
796   uint32_t count = doUnselectRow ? ColCount() : RowCount();
797 
798   int32_t startRowIdx = doUnselectRow ? aIndex : 0;
799   int32_t endRowIdx = doUnselectRow ? aIndex : count - 1;
800   int32_t startColIdx = doUnselectRow ? 0 : aIndex;
801   int32_t endColIdx = doUnselectRow ? count - 1 : aIndex;
802 
803   if (aIsOuter) {
804     return tableSelection->RestrictCellsToSelection(
805         mContent, startRowIdx, startColIdx, endRowIdx, endColIdx);
806   }
807 
808   return tableSelection->RemoveCellsFromSelection(
809       mContent, startRowIdx, startColIdx, endRowIdx, endColIdx);
810 }
811 
Description(nsString & aDescription) const812 void HTMLTableAccessible::Description(nsString& aDescription) const {
813   // Helpful for debugging layout vs. data tables
814   aDescription.Truncate();
815   LocalAccessible::Description(aDescription);
816   if (!aDescription.IsEmpty()) return;
817 
818   // Use summary as description if it weren't used as a name.
819   // XXX: get rid code duplication with NameInternal().
820   LocalAccessible* caption = Caption();
821   if (caption) {
822     nsIContent* captionContent = caption->GetContent();
823     if (captionContent) {
824       nsAutoString captionText;
825       nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
826                                                    &captionText);
827 
828       if (!captionText.IsEmpty()) {  // summary isn't used as a name.
829         mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary,
830                                        aDescription);
831       }
832     }
833   }
834 
835 #ifdef SHOW_LAYOUT_HEURISTIC
836   if (aDescription.IsEmpty()) {
837     bool isProbablyForLayout = IsProbablyLayoutTable();
838     aDescription = mLayoutHeuristic;
839   }
840   printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
841 #endif
842 }
843 
GetTableWrapperFrame() const844 nsTableWrapperFrame* HTMLTableAccessible::GetTableWrapperFrame() const {
845   nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
846   if (tableFrame &&
847       tableFrame->GetChildList(nsIFrame::kPrincipalList).FirstChild()) {
848     return tableFrame;
849   }
850 
851   return nullptr;
852 }
853 
854 ////////////////////////////////////////////////////////////////////////////////
855 // HTMLCaptionAccessible
856 ////////////////////////////////////////////////////////////////////////////////
857 
RelationByType(RelationType aType) const858 Relation HTMLCaptionAccessible::RelationByType(RelationType aType) const {
859   Relation rel = HyperTextAccessible::RelationByType(aType);
860   if (aType == RelationType::LABEL_FOR) {
861     rel.AppendTarget(LocalParent());
862   }
863 
864   return rel;
865 }
866 
NativeRole() const867 role HTMLCaptionAccessible::NativeRole() const { return roles::CAPTION; }
868