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"
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"
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
,
60 nsIAccessibleTableCell
)
62 ////////////////////////////////////////////////////////////////////////////////
63 // HTMLTableCellAccessible: Accessible implementation
66 HTMLTableCellAccessible::Shutdown()
69 HyperTextAccessibleWrap::Shutdown();
73 HTMLTableCellAccessible::NativeRole()
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
;
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();
107 return attributes
.forget();
109 int32_t rowIdx
= -1, colIdx
= -1;
110 nsresult rv
= GetCellIndexes(rowIdx
, colIdx
);
112 return attributes
.forget();
114 nsAutoString stringIdx
;
115 stringIdx
.AppendInt(table
->CellIndexAt(rowIdx
, colIdx
));
116 nsAccUtils::SetAccAttr(attributes
, nsGkAtoms::tableCellIndex
, stringIdx
);
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
) {
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
);
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
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();
165 HTMLTableCellAccessible::ColIdx() const
167 nsITableCellLayout
* cellLayout
= GetCellLayout();
168 NS_ENSURE_TRUE(cellLayout
, 0);
171 cellLayout
->GetColIndex(colIdx
);
172 return colIdx
> 0 ? static_cast<uint32_t>(colIdx
) : 0;
176 HTMLTableCellAccessible::RowIdx() const
178 nsITableCellLayout
* cellLayout
= GetCellLayout();
179 NS_ENSURE_TRUE(cellLayout
, 0);
182 cellLayout
->GetRowIndex(rowIdx
);
183 return rowIdx
> 0 ? static_cast<uint32_t>(rowIdx
) : 0;
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!");
197 return table
->ColExtentAt(rowIdx
, colIdx
);
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!");
211 return table
->RowExtentAt(rowIdx
, colIdx
);
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
225 TableCellAccessible
* tableCell
= cell
->AsTableCell();
226 if (tableCell
&& tableCell
->ColIdx() == ColIdx())
227 aCells
->AppendElement(cell
);
231 if (aCells
->IsEmpty())
232 TableCellAccessible::ColHeaderCells(aCells
);
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
246 TableCellAccessible
* tableCell
= cell
->AsTableCell();
247 if (tableCell
&& tableCell
->RowIdx() == RowIdx())
248 aCells
->AppendElement(cell
);
252 if (aCells
->IsEmpty())
253 TableCellAccessible::RowHeaderCells(aCells
);
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
272 HTMLTableCellAccessible::GetCellLayout() const
274 return do_QueryFrame(mContent
->GetPrimaryFrame());
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
301 HTMLTableHeaderCellAccessible::NativeRole()
303 // Check value of @scope attribute.
304 static nsIContent::AttrValuesArray scopeValues
[] =
305 {&nsGkAtoms::col
, &nsGkAtoms::row
, nullptr};
307 mContent
->FindAttrValueIn(kNameSpaceID_None
, nsGkAtoms::scope
,
308 scopeValues
, eCaseMatters
);
312 return roles::COLUMNHEADER
;
314 return roles::ROWHEADER
;
317 // Assume it's columnheader if there are headers in siblings, otherwise
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
)
355 HTMLTableRowAccessible::NativeRole()
360 ////////////////////////////////////////////////////////////////////////////////
361 // HTMLTableAccessible
362 ////////////////////////////////////////////////////////////////////////////////
364 NS_IMPL_ISUPPORTS_INHERITED(HTMLTableAccessible
, Accessible
,
367 ////////////////////////////////////////////////////////////////////////////////
368 // HTMLTableAccessible: Accessible
371 HTMLTableAccessible::Shutdown()
374 AccessibleWrap::Shutdown();
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
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
));
398 HTMLTableAccessible::NativeRole()
404 HTMLTableAccessible::NativeState()
406 return Accessible::NativeState() | states::READONLY
;
410 HTMLTableAccessible::NativeName(nsString
& aName
)
412 ENameValueFlag nameFlag
= Accessible::NativeName(aName
);
413 if (!aName
.IsEmpty())
416 // Use table caption as a name.
417 Accessible
* caption
= Caption();
419 nsIContent
* captionContent
= caption
->GetContent();
420 if (captionContent
) {
421 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent
, &aName
);
422 if (!aName
.IsEmpty())
427 // If no caption then use summary as a name.
428 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::summary
, aName
);
432 already_AddRefed
<nsIPersistentProperties
>
433 HTMLTableAccessible::NativeAttributes()
435 nsCOMPtr
<nsIPersistentProperties
> attributes
=
436 AccessibleWrap::NativeAttributes();
437 if (IsProbablyLayoutTable()) {
439 attributes
->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
440 NS_LITERAL_STRING("true"), unused
);
443 return attributes
.forget();
446 ////////////////////////////////////////////////////////////////////////////////
447 // HTMLTableAccessible: nsIAccessible implementation
450 HTMLTableAccessible::RelationByType(RelationType aType
)
452 Relation rel
= AccessibleWrap::RelationByType(aType
);
453 if (aType
== RelationType::LABELLED_BY
)
454 rel
.AppendTarget(Caption());
459 ////////////////////////////////////////////////////////////////////////////////
460 // HTMLTableAccessible: nsIAccessibleTable implementation
463 HTMLTableAccessible::Caption()
465 Accessible
* child
= mChildren
.SafeElementAt(0, nullptr);
466 return child
&& child
->Role() == roles::CAPTION
? child
: nullptr;
470 HTMLTableAccessible::Summary(nsString
& aSummary
)
472 dom::HTMLTableElement
* table
= dom::HTMLTableElement::FromContent(mContent
);
475 table
->GetSummary(aSummary
);
479 HTMLTableAccessible::ColCount()
481 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
482 return tableFrame
? tableFrame
->GetColCount() : 0;
486 HTMLTableAccessible::RowCount()
488 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
489 return tableFrame
? tableFrame
->GetRowCount() : 0;
493 HTMLTableAccessible::SelectedCellCount()
495 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
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())
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
)
519 HTMLTableAccessible::SelectedColCount()
521 uint32_t count
= 0, colCount
= ColCount();
523 for (uint32_t colIdx
= 0; colIdx
< colCount
; colIdx
++)
524 if (IsColSelected(colIdx
))
531 HTMLTableAccessible::SelectedRowCount()
533 uint32_t count
= 0, rowCount
= RowCount();
535 for (uint32_t rowIdx
= 0; rowIdx
< rowCount
; rowIdx
++)
536 if (IsRowSelected(rowIdx
))
543 HTMLTableAccessible::SelectedCells(nsTArray
<Accessible
*>* aCells
)
545 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
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())
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
))
563 Accessible
* cell
= mDoc
->GetAccessible(cellFrame
->GetContent());
564 aCells
->AppendElement(cell
);
570 HTMLTableAccessible::SelectedCellIndices(nsTArray
<uint32_t>* aCells
)
572 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
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())
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
));
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
);
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
);
612 HTMLTableAccessible::CellAt(uint32_t aRowIdx
, uint32_t aColIdx
)
614 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
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
;
627 HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx
, uint32_t aColIdx
)
629 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
633 return tableFrame
->GetIndexByRowAndColumn(aRowIdx
, aColIdx
);
637 HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx
)
639 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
643 int32_t rowIdx
= -1, colIdx
= -1;
644 tableFrame
->GetRowAndColumnByIndex(aCellIdx
, &rowIdx
, &colIdx
);
649 HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx
)
651 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
655 int32_t rowIdx
= -1, colIdx
= -1;
656 tableFrame
->GetRowAndColumnByIndex(aCellIdx
, &rowIdx
, &colIdx
);
661 HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx
, int32_t* aRowIdx
,
664 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
666 tableFrame
->GetRowAndColumnByIndex(aCellIdx
, aRowIdx
, aColIdx
);
670 HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx
, uint32_t aColIdx
)
672 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
676 return tableFrame
->GetEffectiveColSpanAt(aRowIdx
, aColIdx
);
680 HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx
, uint32_t aColIdx
)
682 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
686 return tableFrame
->GetEffectiveRowSpanAt(aRowIdx
, aColIdx
);
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
);
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
);
720 HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx
, uint32_t aColIdx
)
722 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
726 nsTableCellFrame
* cellFrame
= tableFrame
->GetCellFrameAt(aRowIdx
, aColIdx
);
727 return cellFrame
? cellFrame
->IsSelected() : false;
731 HTMLTableAccessible::SelectRow(uint32_t aRowIdx
)
733 DebugOnly
<nsresult
> rv
=
734 RemoveRowsOrColumnsFromSelection(aRowIdx
,
735 nsISelectionPrivate::TABLESELECTION_ROW
,
737 NS_ASSERTION(NS_SUCCEEDED(rv
),
738 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
740 AddRowOrColumnToSelection(aRowIdx
, nsISelectionPrivate::TABLESELECTION_ROW
);
744 HTMLTableAccessible::SelectCol(uint32_t aColIdx
)
746 DebugOnly
<nsresult
> rv
=
747 RemoveRowsOrColumnsFromSelection(aColIdx
,
748 nsISelectionPrivate::TABLESELECTION_COLUMN
,
750 NS_ASSERTION(NS_SUCCEEDED(rv
),
751 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
753 AddRowOrColumnToSelection(aColIdx
, nsISelectionPrivate::TABLESELECTION_COLUMN
);
757 HTMLTableAccessible::UnselectRow(uint32_t aRowIdx
)
759 RemoveRowsOrColumnsFromSelection(aRowIdx
,
760 nsISelectionPrivate::TABLESELECTION_ROW
,
765 HTMLTableAccessible::UnselectCol(uint32_t aColIdx
)
767 RemoveRowsOrColumnsFromSelection(aColIdx
,
768 nsISelectionPrivate::TABLESELECTION_COLUMN
,
773 HTMLTableAccessible::AddRowOrColumnToSelection(int32_t aIndex
, uint32_t aTarget
)
775 bool doSelectRow
= (aTarget
== nsISelectionPrivate::TABLESELECTION_ROW
);
777 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
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
);
805 HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(int32_t aIndex
,
809 nsTableOuterFrame
* tableFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
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
;
826 return tableSelection
->RestrictCellsToSelection(mContent
,
827 startRowIdx
, startColIdx
,
828 endRowIdx
, endColIdx
);
830 return tableSelection
->RemoveCellsFromSelection(mContent
,
831 startRowIdx
, startColIdx
,
832 endRowIdx
, endColIdx
);
836 HTMLTableAccessible::Description(nsString
& aDescription
)
838 // Helpful for debugging layout vs. data tables
839 aDescription
.Truncate();
840 Accessible::Description(aDescription
);
841 if (!aDescription
.IsEmpty())
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();
848 nsIContent
* captionContent
= caption
->GetContent();
849 if (captionContent
) {
850 nsAutoString captionText
;
851 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent
,
854 if (!captionText
.IsEmpty()) { // summary isn't used as a name.
855 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::summary
,
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());
871 HTMLTableAccessible::HasDescendant(const nsAString
& aTagName
, bool aAllowEmpty
)
873 nsCOMPtr
<nsIHTMLCollection
> elements
=
874 mContent
->AsElement()->GetElementsByTagName(aTagName
);
876 Element
* foundItem
= elements
->Item(0);
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())
892 // If we found more than one node then return true not depending on
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);
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); \
922 #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
925 DocAccessible
* docAccessible
= Document();
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
) &&
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())
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
);
1019 RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
1023 RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
1026 // Check for many columns
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());
1036 RETURN_LAYOUT_ANSWER(false, "table with no frame!");
1038 nsIFrame
* cellFrame
= tableFrame
->GetCellFrameAt(0, 0);
1040 RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
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");
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 ////////////////////////////////////////////////////////////////////////////////
1110 HTMLCaptionAccessible::RelationByType(RelationType aType
)
1112 Relation rel
= HyperTextAccessible::RelationByType(aType
);
1113 if (aType
== RelationType::LABEL_FOR
)
1114 rel
.AppendTarget(Parent());
1120 HTMLCaptionAccessible::NativeRole()
1122 return roles::CAPTION
;