Bumping manifests a=b2g-bump
[gecko.git] / accessible / html / HTMLTableAccessible.cpp
blobf0b8b76d6930d7abbb8ac34a8ccf7e765291dbff
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 "HTMLTableAccessible.h"
8 #include "mozilla/DebugOnly.h"
10 #include "Accessible-inl.h"
11 #include "nsAccessibilityService.h"
12 #include "nsAccUtils.h"
13 #include "DocAccessible.h"
14 #include "nsIAccessibleRelation.h"
15 #include "nsTextEquivUtils.h"
16 #include "Relation.h"
17 #include "Role.h"
18 #include "States.h"
19 #include "TreeWalker.h"
21 #include "mozilla/dom/HTMLTableElement.h"
22 #include "nsIDOMElement.h"
23 #include "nsIDOMRange.h"
24 #include "nsISelectionPrivate.h"
25 #include "nsIDOMNodeList.h"
26 #include "nsIDOMHTMLCollection.h"
27 #include "nsIDocument.h"
28 #include "nsIMutableArray.h"
29 #include "nsIPersistentProperties2.h"
30 #include "nsIPresShell.h"
31 #include "nsITableCellLayout.h"
32 #include "nsFrameSelection.h"
33 #include "nsError.h"
34 #include "nsArrayUtils.h"
35 #include "nsComponentManagerUtils.h"
36 #include "nsNameSpaceManager.h"
37 #include "nsTableCellFrame.h"
38 #include "nsTableOuterFrame.h"
40 using namespace mozilla;
41 using namespace mozilla::dom;
42 using namespace mozilla::a11y;
44 ////////////////////////////////////////////////////////////////////////////////
45 // HTMLTableCellAccessible
46 ////////////////////////////////////////////////////////////////////////////////
48 HTMLTableCellAccessible::
49 HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
50 HyperTextAccessibleWrap(aContent, aDoc), xpcAccessibleTableCell(this)
52 mGenericTypes |= eTableCell;
55 ////////////////////////////////////////////////////////////////////////////////
56 // HTMLTableCellAccessible: nsISupports implementation
58 NS_IMPL_ISUPPORTS_INHERITED(HTMLTableCellAccessible,
59 HyperTextAccessible,
60 nsIAccessibleTableCell)
62 ////////////////////////////////////////////////////////////////////////////////
63 // HTMLTableCellAccessible: Accessible implementation
65 void
66 HTMLTableCellAccessible::Shutdown()
68 mTableCell = nullptr;
69 HyperTextAccessibleWrap::Shutdown();
72 role
73 HTMLTableCellAccessible::NativeRole()
75 return roles::CELL;
78 uint64_t
79 HTMLTableCellAccessible::NativeState()
81 uint64_t state = HyperTextAccessibleWrap::NativeState();
83 nsIFrame *frame = mContent->GetPrimaryFrame();
84 NS_ASSERTION(frame, "No frame for valid cell accessible!");
86 if (frame && frame->IsSelected())
87 state |= states::SELECTED;
89 return state;
92 uint64_t
93 HTMLTableCellAccessible::NativeInteractiveState() const
95 return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE;
98 already_AddRefed<nsIPersistentProperties>
99 HTMLTableCellAccessible::NativeAttributes()
101 nsCOMPtr<nsIPersistentProperties> attributes =
102 HyperTextAccessibleWrap::NativeAttributes();
104 // table-cell-index attribute
105 TableAccessible* table = Table();
106 if (!table)
107 return attributes.forget();
109 int32_t rowIdx = -1, colIdx = -1;
110 nsresult rv = GetCellIndexes(rowIdx, colIdx);
111 if (NS_FAILED(rv))
112 return attributes.forget();
114 nsAutoString stringIdx;
115 stringIdx.AppendInt(table->CellIndexAt(rowIdx, colIdx));
116 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
118 // abbr attribute
120 // Pick up object attribute from abbr DOM element (a child of the cell) or
121 // from abbr DOM attribute.
122 nsAutoString abbrText;
123 if (ChildCount() == 1) {
124 Accessible* abbr = FirstChild();
125 if (abbr->IsAbbreviation()) {
126 nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild();
127 if (firstChildNode) {
128 nsTextEquivUtils::
129 AppendTextEquivFromTextContent(firstChildNode, &abbrText);
133 if (abbrText.IsEmpty())
134 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr, abbrText);
136 if (!abbrText.IsEmpty())
137 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::abbr, abbrText);
139 // axis attribute
140 nsAutoString axisText;
141 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText);
142 if (!axisText.IsEmpty())
143 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::axis, axisText);
145 return attributes.forget();
148 ////////////////////////////////////////////////////////////////////////////////
149 // HTMLTableCellAccessible: nsIAccessibleTableCell implementation
151 TableAccessible*
152 HTMLTableCellAccessible::Table() const
154 Accessible* parent = const_cast<HTMLTableCellAccessible*>(this);
155 while ((parent = parent->Parent())) {
156 roles::Role role = parent->Role();
157 if (role == roles::TABLE || role == roles::TREE_TABLE)
158 return parent->AsTable();
161 return nullptr;
164 uint32_t
165 HTMLTableCellAccessible::ColIdx() const
167 nsITableCellLayout* cellLayout = GetCellLayout();
168 NS_ENSURE_TRUE(cellLayout, 0);
170 int32_t colIdx = 0;
171 cellLayout->GetColIndex(colIdx);
172 return colIdx > 0 ? static_cast<uint32_t>(colIdx) : 0;
175 uint32_t
176 HTMLTableCellAccessible::RowIdx() const
178 nsITableCellLayout* cellLayout = GetCellLayout();
179 NS_ENSURE_TRUE(cellLayout, 0);
181 int32_t rowIdx = 0;
182 cellLayout->GetRowIndex(rowIdx);
183 return rowIdx > 0 ? static_cast<uint32_t>(rowIdx) : 0;
186 uint32_t
187 HTMLTableCellAccessible::ColExtent() const
189 int32_t rowIdx = -1, colIdx = -1;
190 GetCellIndexes(rowIdx, colIdx);
192 TableAccessible* table = Table();
193 NS_ASSERTION(table, "cell not in a table!");
194 if (!table)
195 return 0;
197 return table->ColExtentAt(rowIdx, colIdx);
200 uint32_t
201 HTMLTableCellAccessible::RowExtent() const
203 int32_t rowIdx = -1, colIdx = -1;
204 GetCellIndexes(rowIdx, colIdx);
206 TableAccessible* table = Table();
207 NS_ASSERTION(table, "cell not in atable!");
208 if (!table)
209 return 0;
211 return table->RowExtentAt(rowIdx, colIdx);
214 void
215 HTMLTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells)
217 IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
218 while (Accessible* cell = itr.Next()) {
219 a11y::role cellRole = cell->Role();
220 if (cellRole == roles::COLUMNHEADER) {
221 aCells->AppendElement(cell);
222 } else if (cellRole != roles::ROWHEADER) {
223 // If referred table cell is at the same column then treat it as a column
224 // header.
225 TableCellAccessible* tableCell = cell->AsTableCell();
226 if (tableCell && tableCell->ColIdx() == ColIdx())
227 aCells->AppendElement(cell);
231 if (aCells->IsEmpty())
232 TableCellAccessible::ColHeaderCells(aCells);
235 void
236 HTMLTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells)
238 IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
239 while (Accessible* cell = itr.Next()) {
240 a11y::role cellRole = cell->Role();
241 if (cellRole == roles::ROWHEADER) {
242 aCells->AppendElement(cell);
243 } else if (cellRole != roles::COLUMNHEADER) {
244 // If referred table cell is at the same row then treat it as a column
245 // header.
246 TableCellAccessible* tableCell = cell->AsTableCell();
247 if (tableCell && tableCell->RowIdx() == RowIdx())
248 aCells->AppendElement(cell);
252 if (aCells->IsEmpty())
253 TableCellAccessible::RowHeaderCells(aCells);
256 bool
257 HTMLTableCellAccessible::Selected()
259 int32_t rowIdx = -1, colIdx = -1;
260 GetCellIndexes(rowIdx, colIdx);
262 TableAccessible* table = Table();
263 NS_ENSURE_TRUE(table, false);
265 return table->IsCellSelected(rowIdx, colIdx);
268 ////////////////////////////////////////////////////////////////////////////////
269 // HTMLTableCellAccessible: protected implementation
271 nsITableCellLayout*
272 HTMLTableCellAccessible::GetCellLayout() const
274 return do_QueryFrame(mContent->GetPrimaryFrame());
277 nsresult
278 HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const
280 nsITableCellLayout *cellLayout = GetCellLayout();
281 NS_ENSURE_STATE(cellLayout);
283 return cellLayout->GetCellIndexes(aRowIdx, aColIdx);
287 ////////////////////////////////////////////////////////////////////////////////
288 // HTMLTableHeaderCellAccessible
289 ////////////////////////////////////////////////////////////////////////////////
291 HTMLTableHeaderCellAccessible::
292 HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
293 HTMLTableCellAccessible(aContent, aDoc)
297 ////////////////////////////////////////////////////////////////////////////////
298 // HTMLTableHeaderCellAccessible: Accessible implementation
300 role
301 HTMLTableHeaderCellAccessible::NativeRole()
303 // Check value of @scope attribute.
304 static nsIContent::AttrValuesArray scopeValues[] =
305 {&nsGkAtoms::col, &nsGkAtoms::row, nullptr};
306 int32_t valueIdx =
307 mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope,
308 scopeValues, eCaseMatters);
310 switch (valueIdx) {
311 case 0:
312 return roles::COLUMNHEADER;
313 case 1:
314 return roles::ROWHEADER;
317 // Assume it's columnheader if there are headers in siblings, otherwise
318 // rowheader.
319 // This should iterate the flattened tree
320 nsIContent* parentContent = mContent->GetParent();
321 if (!parentContent) {
322 NS_ERROR("Deattached content on alive accessible?");
323 return roles::NOTHING;
326 for (nsIContent* siblingContent = mContent->GetPreviousSibling(); siblingContent;
327 siblingContent = siblingContent->GetPreviousSibling()) {
328 if (siblingContent->IsElement()) {
329 return nsCoreUtils::IsHTMLTableHeader(siblingContent) ?
330 roles::COLUMNHEADER : roles::ROWHEADER;
334 for (nsIContent* siblingContent = mContent->GetNextSibling(); siblingContent;
335 siblingContent = siblingContent->GetNextSibling()) {
336 if (siblingContent->IsElement()) {
337 return nsCoreUtils::IsHTMLTableHeader(siblingContent) ?
338 roles::COLUMNHEADER : roles::ROWHEADER;
342 // No elements in siblings what means the table has one column only. Therefore
343 // it should be column header.
344 return roles::COLUMNHEADER;
348 ////////////////////////////////////////////////////////////////////////////////
349 // HTMLTableRowAccessible
350 ////////////////////////////////////////////////////////////////////////////////
352 NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableRowAccessible, Accessible)
354 role
355 HTMLTableRowAccessible::NativeRole()
357 return roles::ROW;
360 ////////////////////////////////////////////////////////////////////////////////
361 // HTMLTableAccessible
362 ////////////////////////////////////////////////////////////////////////////////
364 NS_IMPL_ISUPPORTS_INHERITED(HTMLTableAccessible, Accessible,
365 nsIAccessibleTable)
367 ////////////////////////////////////////////////////////////////////////////////
368 // HTMLTableAccessible: Accessible
370 void
371 HTMLTableAccessible::Shutdown()
373 mTable = nullptr;
374 AccessibleWrap::Shutdown();
377 void
378 HTMLTableAccessible::CacheChildren()
380 // Move caption accessible so that it's the first child. Check for the first
381 // caption only, because nsAccessibilityService ensures we don't create
382 // accessibles for the other captions, since only the first is actually
383 // visible.
384 TreeWalker walker(this, mContent);
386 Accessible* child = nullptr;
387 while ((child = walker.NextChild())) {
388 if (child->Role() == roles::CAPTION) {
389 InsertChildAt(0, child);
390 while ((child = walker.NextChild()) && AppendChild(child));
391 break;
393 AppendChild(child);
397 role
398 HTMLTableAccessible::NativeRole()
400 return roles::TABLE;
403 uint64_t
404 HTMLTableAccessible::NativeState()
406 return Accessible::NativeState() | states::READONLY;
409 ENameValueFlag
410 HTMLTableAccessible::NativeName(nsString& aName)
412 ENameValueFlag nameFlag = Accessible::NativeName(aName);
413 if (!aName.IsEmpty())
414 return nameFlag;
416 // Use table caption as a name.
417 Accessible* caption = Caption();
418 if (caption) {
419 nsIContent* captionContent = caption->GetContent();
420 if (captionContent) {
421 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName);
422 if (!aName.IsEmpty())
423 return eNameOK;
427 // If no caption then use summary as a name.
428 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName);
429 return eNameOK;
432 already_AddRefed<nsIPersistentProperties>
433 HTMLTableAccessible::NativeAttributes()
435 nsCOMPtr<nsIPersistentProperties> attributes =
436 AccessibleWrap::NativeAttributes();
437 if (IsProbablyLayoutTable()) {
438 nsAutoString unused;
439 attributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
440 NS_LITERAL_STRING("true"), unused);
443 return attributes.forget();
446 ////////////////////////////////////////////////////////////////////////////////
447 // HTMLTableAccessible: nsIAccessible implementation
449 Relation
450 HTMLTableAccessible::RelationByType(RelationType aType)
452 Relation rel = AccessibleWrap::RelationByType(aType);
453 if (aType == RelationType::LABELLED_BY)
454 rel.AppendTarget(Caption());
456 return rel;
459 ////////////////////////////////////////////////////////////////////////////////
460 // HTMLTableAccessible: nsIAccessibleTable implementation
462 Accessible*
463 HTMLTableAccessible::Caption()
465 Accessible* child = mChildren.SafeElementAt(0, nullptr);
466 return child && child->Role() == roles::CAPTION ? child : nullptr;
469 void
470 HTMLTableAccessible::Summary(nsString& aSummary)
472 dom::HTMLTableElement* table = dom::HTMLTableElement::FromContent(mContent);
474 if (table)
475 table->GetSummary(aSummary);
478 uint32_t
479 HTMLTableAccessible::ColCount()
481 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
482 return tableFrame ? tableFrame->GetColCount() : 0;
485 uint32_t
486 HTMLTableAccessible::RowCount()
488 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
489 return tableFrame ? tableFrame->GetRowCount() : 0;
492 uint32_t
493 HTMLTableAccessible::SelectedCellCount()
495 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
496 if (!tableFrame)
497 return 0;
499 uint32_t count = 0, rowCount = RowCount(), colCount = ColCount();
500 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
501 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
502 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
503 if (!cellFrame || !cellFrame->IsSelected())
504 continue;
506 int32_t startRow = -1, startCol = -1;
507 cellFrame->GetRowIndex(startRow);
508 cellFrame->GetColIndex(startCol);
509 if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
510 startCol >= 0 && (uint32_t)startCol == colIdx)
511 count++;
515 return count;
518 uint32_t
519 HTMLTableAccessible::SelectedColCount()
521 uint32_t count = 0, colCount = ColCount();
523 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
524 if (IsColSelected(colIdx))
525 count++;
527 return count;
530 uint32_t
531 HTMLTableAccessible::SelectedRowCount()
533 uint32_t count = 0, rowCount = RowCount();
535 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
536 if (IsRowSelected(rowIdx))
537 count++;
539 return count;
542 void
543 HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
545 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
546 if (!tableFrame)
547 return;
549 uint32_t rowCount = RowCount(), colCount = ColCount();
550 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
551 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
552 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
553 if (!cellFrame || !cellFrame->IsSelected())
554 continue;
556 int32_t startCol = -1, startRow = -1;
557 cellFrame->GetRowIndex(startRow);
558 cellFrame->GetColIndex(startCol);
559 if ((startRow >= 0 && (uint32_t)startRow != rowIdx) ||
560 (startCol >= 0 && (uint32_t)startCol != colIdx))
561 continue;
563 Accessible* cell = mDoc->GetAccessible(cellFrame->GetContent());
564 aCells->AppendElement(cell);
569 void
570 HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
572 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
573 if (!tableFrame)
574 return;
576 uint32_t rowCount = RowCount(), colCount = ColCount();
577 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
578 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
579 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
580 if (!cellFrame || !cellFrame->IsSelected())
581 continue;
583 int32_t startRow = -1, startCol = -1;
584 cellFrame->GetColIndex(startCol);
585 cellFrame->GetRowIndex(startRow);
586 if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
587 startCol >= 0 && (uint32_t)startCol == colIdx)
588 aCells->AppendElement(CellIndexAt(rowIdx, colIdx));
593 void
594 HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
596 uint32_t colCount = ColCount();
597 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
598 if (IsColSelected(colIdx))
599 aCols->AppendElement(colIdx);
602 void
603 HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
605 uint32_t rowCount = RowCount();
606 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
607 if (IsRowSelected(rowIdx))
608 aRows->AppendElement(rowIdx);
611 Accessible*
612 HTMLTableAccessible::CellAt(uint32_t aRowIdx, uint32_t aColIdx)
614 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
615 if (!tableFrame)
616 return nullptr;
618 nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
619 Accessible* cell = mDoc->GetAccessible(cellContent);
621 // XXX bug 576838: crazy tables (like table6 in tables/test_table2.html) may
622 // return itself as a cell what makes Orca hang.
623 return cell == this ? nullptr : cell;
626 int32_t
627 HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx)
629 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
630 if (!tableFrame)
631 return -1;
633 return tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx);
636 int32_t
637 HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx)
639 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
640 if (!tableFrame)
641 return -1;
643 int32_t rowIdx = -1, colIdx = -1;
644 tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
645 return colIdx;
648 int32_t
649 HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx)
651 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
652 if (!tableFrame)
653 return -1;
655 int32_t rowIdx = -1, colIdx = -1;
656 tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
657 return rowIdx;
660 void
661 HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
662 int32_t* aColIdx)
664 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
665 if (tableFrame)
666 tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx);
669 uint32_t
670 HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
672 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
673 if (!tableFrame)
674 return 0;
676 return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx);
679 uint32_t
680 HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
682 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
683 if (!tableFrame)
684 return 0;
686 return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx);
689 bool
690 HTMLTableAccessible::IsColSelected(uint32_t aColIdx)
692 bool isSelected = false;
694 uint32_t rowCount = RowCount();
695 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
696 isSelected = IsCellSelected(rowIdx, aColIdx);
697 if (!isSelected)
698 return false;
701 return isSelected;
704 bool
705 HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx)
707 bool isSelected = false;
709 uint32_t colCount = ColCount();
710 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
711 isSelected = IsCellSelected(aRowIdx, colIdx);
712 if (!isSelected)
713 return false;
716 return isSelected;
719 bool
720 HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
722 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
723 if (!tableFrame)
724 return false;
726 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx);
727 return cellFrame ? cellFrame->IsSelected() : false;
730 void
731 HTMLTableAccessible::SelectRow(uint32_t aRowIdx)
733 DebugOnly<nsresult> rv =
734 RemoveRowsOrColumnsFromSelection(aRowIdx,
735 nsISelectionPrivate::TABLESELECTION_ROW,
736 true);
737 NS_ASSERTION(NS_SUCCEEDED(rv),
738 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
740 AddRowOrColumnToSelection(aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW);
743 void
744 HTMLTableAccessible::SelectCol(uint32_t aColIdx)
746 DebugOnly<nsresult> rv =
747 RemoveRowsOrColumnsFromSelection(aColIdx,
748 nsISelectionPrivate::TABLESELECTION_COLUMN,
749 true);
750 NS_ASSERTION(NS_SUCCEEDED(rv),
751 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
753 AddRowOrColumnToSelection(aColIdx, nsISelectionPrivate::TABLESELECTION_COLUMN);
756 void
757 HTMLTableAccessible::UnselectRow(uint32_t aRowIdx)
759 RemoveRowsOrColumnsFromSelection(aRowIdx,
760 nsISelectionPrivate::TABLESELECTION_ROW,
761 false);
764 void
765 HTMLTableAccessible::UnselectCol(uint32_t aColIdx)
767 RemoveRowsOrColumnsFromSelection(aColIdx,
768 nsISelectionPrivate::TABLESELECTION_COLUMN,
769 false);
772 nsresult
773 HTMLTableAccessible::AddRowOrColumnToSelection(int32_t aIndex, uint32_t aTarget)
775 bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
777 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
778 if (!tableFrame)
779 return NS_OK;
781 uint32_t count = 0;
782 if (doSelectRow)
783 count = ColCount();
784 else
785 count = RowCount();
787 nsIPresShell* presShell(mDoc->PresShell());
788 nsRefPtr<nsFrameSelection> tableSelection =
789 const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
791 for (uint32_t idx = 0; idx < count; idx++) {
792 int32_t rowIdx = doSelectRow ? aIndex : idx;
793 int32_t colIdx = doSelectRow ? idx : aIndex;
794 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
795 if (cellFrame && !cellFrame->IsSelected()) {
796 nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent());
797 NS_ENSURE_SUCCESS(rv, rv);
801 return NS_OK;
804 nsresult
805 HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(int32_t aIndex,
806 uint32_t aTarget,
807 bool aIsOuter)
809 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
810 if (!tableFrame)
811 return NS_OK;
813 nsIPresShell* presShell(mDoc->PresShell());
814 nsRefPtr<nsFrameSelection> tableSelection =
815 const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
817 bool doUnselectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
818 uint32_t count = doUnselectRow ? ColCount() : RowCount();
820 int32_t startRowIdx = doUnselectRow ? aIndex : 0;
821 int32_t endRowIdx = doUnselectRow ? aIndex : count - 1;
822 int32_t startColIdx = doUnselectRow ? 0 : aIndex;
823 int32_t endColIdx = doUnselectRow ? count - 1 : aIndex;
825 if (aIsOuter)
826 return tableSelection->RestrictCellsToSelection(mContent,
827 startRowIdx, startColIdx,
828 endRowIdx, endColIdx);
830 return tableSelection->RemoveCellsFromSelection(mContent,
831 startRowIdx, startColIdx,
832 endRowIdx, endColIdx);
835 void
836 HTMLTableAccessible::Description(nsString& aDescription)
838 // Helpful for debugging layout vs. data tables
839 aDescription.Truncate();
840 Accessible::Description(aDescription);
841 if (!aDescription.IsEmpty())
842 return;
844 // Use summary as description if it weren't used as a name.
845 // XXX: get rid code duplication with NameInternal().
846 Accessible* caption = Caption();
847 if (caption) {
848 nsIContent* captionContent = caption->GetContent();
849 if (captionContent) {
850 nsAutoString captionText;
851 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
852 &captionText);
854 if (!captionText.IsEmpty()) { // summary isn't used as a name.
855 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary,
856 aDescription);
861 #ifdef SHOW_LAYOUT_HEURISTIC
862 if (aDescription.IsEmpty()) {
863 bool isProbablyForLayout = IsProbablyLayoutTable();
864 aDescription = mLayoutHeuristic;
866 printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
867 #endif
870 bool
871 HTMLTableAccessible::HasDescendant(const nsAString& aTagName, bool aAllowEmpty)
873 nsCOMPtr<nsIHTMLCollection> elements =
874 mContent->AsElement()->GetElementsByTagName(aTagName);
876 Element* foundItem = elements->Item(0);
877 if (!foundItem)
878 return false;
880 if (aAllowEmpty)
881 return true;
883 // Make sure that the item we found has contents and either has multiple
884 // children or the found item is not a whitespace-only text node.
885 if (foundItem->GetChildCount() > 1)
886 return true; // Treat multiple child nodes as non-empty
888 nsIContent *innerItemContent = foundItem->GetFirstChild();
889 if (innerItemContent && !innerItemContent->TextIsOnlyWhitespace())
890 return true;
892 // If we found more than one node then return true not depending on
893 // aAllowEmpty flag.
894 // XXX it might be dummy but bug 501375 where we changed this addresses
895 // performance problems only. Note, currently 'aAllowEmpty' flag is used for
896 // caption element only. On another hand we create accessible object for
897 // the first entry of caption element (see
898 // HTMLTableAccessible::CacheChildren).
899 return !!elements->Item(1);
902 bool
903 HTMLTableAccessible::IsProbablyLayoutTable()
905 // Implement a heuristic to determine if table is most likely used for layout
906 // XXX do we want to look for rowspan or colspan, especialy that span all but a couple cells
907 // at the beginning or end of a row/col, and especially when they occur at the edge of a table?
908 // XXX expose this info via object attributes to AT-SPI
910 // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
911 // This will allow release trunk builds to be used by testers to refine the algorithm
912 // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
913 #ifdef SHOW_LAYOUT_HEURISTIC
914 #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
916 mLayoutHeuristic = isLayout ? \
917 NS_LITERAL_STRING("layout table: " heuristic) : \
918 NS_LITERAL_STRING("data table: " heuristic); \
919 return isLayout; \
921 #else
922 #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
923 #endif
925 DocAccessible* docAccessible = Document();
926 if (docAccessible) {
927 uint64_t docState = docAccessible->State();
928 if (docState & states::EDITABLE) { // Need to see all elements while document is being edited
929 RETURN_LAYOUT_ANSWER(false, "In editable document");
933 // Check to see if an ARIA role overrides the role from native markup,
934 // but for which we still expose table semantics (treegrid, for example).
935 if (Role() != roles::TABLE)
936 RETURN_LAYOUT_ANSWER(false, "Has role attribute");
938 if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
939 // Role attribute is present, but overridden roles have already been dealt with.
940 // Only landmarks and other roles that don't override the role from native
941 // markup are left to deal with here.
942 RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table");
945 if (mContent->Tag() != nsGkAtoms::table)
946 RETURN_LAYOUT_ANSWER(true, "table built by CSS display:table style");
948 // Check if datatable attribute has "0" value.
949 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
950 NS_LITERAL_STRING("0"), eCaseMatters)) {
951 RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
954 // Check for legitimate data table attributes.
955 nsAutoString summary;
956 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) &&
957 !summary.IsEmpty())
958 RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
960 // Check for legitimate data table elements.
961 Accessible* caption = FirstChild();
962 if (caption && caption->Role() == roles::CAPTION && caption->HasChildren())
963 RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures");
965 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
966 childElm = childElm->GetNextSibling()) {
967 if (!childElm->IsHTML())
968 continue;
970 if (childElm->Tag() == nsGkAtoms::col ||
971 childElm->Tag() == nsGkAtoms::colgroup ||
972 childElm->Tag() == nsGkAtoms::tfoot ||
973 childElm->Tag() == nsGkAtoms::thead) {
974 RETURN_LAYOUT_ANSWER(false,
975 "Has col, colgroup, tfoot or thead -- legitimate table structures");
978 if (childElm->Tag() == nsGkAtoms::tbody) {
979 for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
980 rowElm = rowElm->GetNextSibling()) {
981 if (rowElm->IsHTML() && rowElm->Tag() == nsGkAtoms::tr) {
982 for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
983 cellElm = cellElm->GetNextSibling()) {
984 if (cellElm->IsHTML()) {
986 if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
987 RETURN_LAYOUT_ANSWER(false,
988 "Has th -- legitimate table structures");
991 if (cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
992 cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
993 cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
994 RETURN_LAYOUT_ANSWER(false,
995 "Has headers, scope, or abbr attribute -- legitimate table structures");
998 Accessible* cell = mDoc->GetAccessible(cellElm);
999 if (cell && cell->ChildCount() == 1 &&
1000 cell->FirstChild()->IsAbbreviation()) {
1001 RETURN_LAYOUT_ANSWER(false,
1002 "has abbr -- legitimate table structures");
1011 if (HasDescendant(NS_LITERAL_STRING("table"))) {
1012 RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
1015 // If only 1 column or only 1 row, it's for layout
1016 int32_t columns, rows;
1017 GetColumnCount(&columns);
1018 if (columns <=1) {
1019 RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
1021 GetRowCount(&rows);
1022 if (rows <=1) {
1023 RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
1026 // Check for many columns
1027 if (columns >= 5) {
1028 RETURN_LAYOUT_ANSWER(false, ">=5 columns");
1031 // Now we know there are 2-4 columns and 2 or more rows
1032 // Check to see if there are visible borders on the cells
1033 // XXX currently, we just check the first cell -- do we really need to do more?
1034 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
1035 if (!tableFrame)
1036 RETURN_LAYOUT_ANSWER(false, "table with no frame!");
1038 nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
1039 if (!cellFrame)
1040 RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
1042 nsMargin border;
1043 cellFrame->GetBorder(border);
1044 if (border.top && border.bottom && border.left && border.right) {
1045 RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
1049 * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on forward
1052 // Check for styled background color across rows (alternating background
1053 // color is a common feature for data tables).
1054 uint32_t childCount = ChildCount();
1055 nscolor rowColor = 0;
1056 nscolor prevRowColor;
1057 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
1058 Accessible* child = GetChildAt(childIdx);
1059 if (child->Role() == roles::ROW) {
1060 prevRowColor = rowColor;
1061 nsIFrame* rowFrame = child->GetFrame();
1062 rowColor = rowFrame->StyleBackground()->mBackgroundColor;
1064 if (childIdx > 0 && prevRowColor != rowColor)
1065 RETURN_LAYOUT_ANSWER(false, "2 styles of row background color, non-bordered");
1069 // Check for many rows
1070 const int32_t kMaxLayoutRows = 20;
1071 if (rows > kMaxLayoutRows) { // A ton of rows, this is probably for data
1072 RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
1075 // Check for very wide table.
1076 nsIFrame* documentFrame = Document()->GetFrame();
1077 nsSize documentSize = documentFrame->GetSize();
1078 if (documentSize.width > 0) {
1079 nsSize tableSize = GetFrame()->GetSize();
1080 int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
1081 if (percentageOfDocWidth > 95) {
1082 // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
1083 // Probably for layout
1084 RETURN_LAYOUT_ANSWER(true,
1085 "<= 4 columns, table width is 95% of document width");
1089 // Two column rules
1090 if (rows * columns <= 10) {
1091 RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
1094 if (HasDescendant(NS_LITERAL_STRING("embed")) ||
1095 HasDescendant(NS_LITERAL_STRING("object")) ||
1096 HasDescendant(NS_LITERAL_STRING("applet")) ||
1097 HasDescendant(NS_LITERAL_STRING("iframe"))) {
1098 RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, applet or iframe, typical of advertisements");
1101 RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data");
1105 ////////////////////////////////////////////////////////////////////////////////
1106 // HTMLCaptionAccessible
1107 ////////////////////////////////////////////////////////////////////////////////
1109 Relation
1110 HTMLCaptionAccessible::RelationByType(RelationType aType)
1112 Relation rel = HyperTextAccessible::RelationByType(aType);
1113 if (aType == RelationType::LABEL_FOR)
1114 rel.AppendTarget(Parent());
1116 return rel;
1119 role
1120 HTMLCaptionAccessible::NativeRole()
1122 return roles::CAPTION;