summaryrefslogtreecommitdiff
path: root/accessible/html/HTMLTableAccessible.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/html/HTMLTableAccessible.cpp')
-rw-r--r--accessible/html/HTMLTableAccessible.cpp1139
1 files changed, 1139 insertions, 0 deletions
diff --git a/accessible/html/HTMLTableAccessible.cpp b/accessible/html/HTMLTableAccessible.cpp
new file mode 100644
index 0000000000..b0cdc0932d
--- /dev/null
+++ b/accessible/html/HTMLTableAccessible.cpp
@@ -0,0 +1,1139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLTableAccessible.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "nsTextEquivUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#include "TreeWalker.h"
+
+#include "mozilla/dom/HTMLTableElement.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMRange.h"
+#include "nsISelectionPrivate.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsIDocument.h"
+#include "nsIMutableArray.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPresShell.h"
+#include "nsITableCellLayout.h"
+#include "nsFrameSelection.h"
+#include "nsError.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsTableCellFrame.h"
+#include "nsTableWrapperFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLTableCellAccessible::
+ HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mType = eHTMLTableCellType;
+ mGenericTypes |= eTableCell;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableCellAccessible, HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible: Accessible implementation
+
+role
+HTMLTableCellAccessible::NativeRole()
+{
+ if (mContent->IsMathMLElement(nsGkAtoms::mtd_)) {
+ return roles::MATHML_CELL;
+ }
+ return roles::CELL;
+}
+
+uint64_t
+HTMLTableCellAccessible::NativeState()
+{
+ uint64_t state = HyperTextAccessibleWrap::NativeState();
+
+ nsIFrame *frame = mContent->GetPrimaryFrame();
+ NS_ASSERTION(frame, "No frame for valid cell accessible!");
+
+ if (frame && frame->IsSelected())
+ state |= states::SELECTED;
+
+ return state;
+}
+
+uint64_t
+HTMLTableCellAccessible::NativeInteractiveState() const
+{
+ return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE;
+}
+
+already_AddRefed<nsIPersistentProperties>
+HTMLTableCellAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::NativeAttributes();
+
+ // table-cell-index attribute
+ TableAccessible* table = Table();
+ if (!table)
+ return attributes.forget();
+
+ int32_t rowIdx = -1, colIdx = -1;
+ nsresult rv = GetCellIndexes(rowIdx, colIdx);
+ if (NS_FAILED(rv))
+ return attributes.forget();
+
+ nsAutoString stringIdx;
+ stringIdx.AppendInt(table->CellIndexAt(rowIdx, colIdx));
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
+
+ // abbr attribute
+
+ // Pick up object attribute from abbr DOM element (a child of the cell) or
+ // from abbr DOM attribute.
+ nsAutoString abbrText;
+ if (ChildCount() == 1) {
+ Accessible* abbr = FirstChild();
+ if (abbr->IsAbbreviation()) {
+ nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild();
+ if (firstChildNode) {
+ nsTextEquivUtils::
+ AppendTextEquivFromTextContent(firstChildNode, &abbrText);
+ }
+ }
+ }
+ if (abbrText.IsEmpty())
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr, abbrText);
+
+ if (!abbrText.IsEmpty())
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::abbr, abbrText);
+
+ // axis attribute
+ nsAutoString axisText;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText);
+ if (!axisText.IsEmpty())
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::axis, axisText);
+
+#ifdef DEBUG
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"),
+ NS_LITERAL_STRING("HTMLTableCellAccessible"),
+ unused);
+#endif
+
+ return attributes.forget();
+}
+
+GroupPos
+HTMLTableCellAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ TableAccessible* table = Table();
+ if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
+ nsGkAtoms::aria_colcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return HyperTextAccessibleWrap::GroupPosition();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible: TableCellAccessible implementation
+
+TableAccessible*
+HTMLTableCellAccessible::Table() const
+{
+ Accessible* parent = const_cast<HTMLTableCellAccessible*>(this);
+ while ((parent = parent->Parent())) {
+ if (parent->IsTable())
+ return parent->AsTable();
+ }
+
+ return nullptr;
+}
+
+uint32_t
+HTMLTableCellAccessible::ColIdx() const
+{
+ nsITableCellLayout* cellLayout = GetCellLayout();
+ NS_ENSURE_TRUE(cellLayout, 0);
+
+ int32_t colIdx = 0;
+ cellLayout->GetColIndex(colIdx);
+ return colIdx > 0 ? static_cast<uint32_t>(colIdx) : 0;
+}
+
+uint32_t
+HTMLTableCellAccessible::RowIdx() const
+{
+ nsITableCellLayout* cellLayout = GetCellLayout();
+ NS_ENSURE_TRUE(cellLayout, 0);
+
+ int32_t rowIdx = 0;
+ cellLayout->GetRowIndex(rowIdx);
+ return rowIdx > 0 ? static_cast<uint32_t>(rowIdx) : 0;
+}
+
+uint32_t
+HTMLTableCellAccessible::ColExtent() const
+{
+ int32_t rowIdx = -1, colIdx = -1;
+ GetCellIndexes(rowIdx, colIdx);
+
+ TableAccessible* table = Table();
+ NS_ASSERTION(table, "cell not in a table!");
+ if (!table)
+ return 0;
+
+ return table->ColExtentAt(rowIdx, colIdx);
+}
+
+uint32_t
+HTMLTableCellAccessible::RowExtent() const
+{
+ int32_t rowIdx = -1, colIdx = -1;
+ GetCellIndexes(rowIdx, colIdx);
+
+ TableAccessible* table = Table();
+ NS_ASSERTION(table, "cell not in atable!");
+ if (!table)
+ return 0;
+
+ return table->RowExtentAt(rowIdx, colIdx);
+}
+
+void
+HTMLTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
+ while (Accessible* cell = itr.Next()) {
+ a11y::role cellRole = cell->Role();
+ if (cellRole == roles::COLUMNHEADER) {
+ aCells->AppendElement(cell);
+ } else if (cellRole != roles::ROWHEADER) {
+ // If referred table cell is at the same column then treat it as a column
+ // header.
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ if (tableCell && tableCell->ColIdx() == ColIdx())
+ aCells->AppendElement(cell);
+ }
+ }
+
+ if (aCells->IsEmpty())
+ TableCellAccessible::ColHeaderCells(aCells);
+}
+
+void
+HTMLTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
+ while (Accessible* cell = itr.Next()) {
+ a11y::role cellRole = cell->Role();
+ if (cellRole == roles::ROWHEADER) {
+ aCells->AppendElement(cell);
+ } else if (cellRole != roles::COLUMNHEADER) {
+ // If referred table cell is at the same row then treat it as a column
+ // header.
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ if (tableCell && tableCell->RowIdx() == RowIdx())
+ aCells->AppendElement(cell);
+ }
+ }
+
+ if (aCells->IsEmpty())
+ TableCellAccessible::RowHeaderCells(aCells);
+}
+
+bool
+HTMLTableCellAccessible::Selected()
+{
+ int32_t rowIdx = -1, colIdx = -1;
+ GetCellIndexes(rowIdx, colIdx);
+
+ TableAccessible* table = Table();
+ NS_ENSURE_TRUE(table, false);
+
+ return table->IsCellSelected(rowIdx, colIdx);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible: protected implementation
+
+nsITableCellLayout*
+HTMLTableCellAccessible::GetCellLayout() const
+{
+ return do_QueryFrame(mContent->GetPrimaryFrame());
+}
+
+nsresult
+HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const
+{
+ nsITableCellLayout *cellLayout = GetCellLayout();
+ NS_ENSURE_STATE(cellLayout);
+
+ return cellLayout->GetCellIndexes(aRowIdx, aColIdx);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableHeaderCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLTableHeaderCellAccessible::
+ HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HTMLTableCellAccessible(aContent, aDoc)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableHeaderCellAccessible: Accessible implementation
+
+role
+HTMLTableHeaderCellAccessible::NativeRole()
+{
+ // Check value of @scope attribute.
+ static nsIContent::AttrValuesArray scopeValues[] =
+ { &nsGkAtoms::col, &nsGkAtoms::colgroup,
+ &nsGkAtoms::row, &nsGkAtoms::rowgroup, nullptr };
+ int32_t valueIdx =
+ mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope,
+ scopeValues, eCaseMatters);
+
+ switch (valueIdx) {
+ case 0:
+ case 1:
+ return roles::COLUMNHEADER;
+ case 2:
+ case 3:
+ return roles::ROWHEADER;
+ }
+
+ TableAccessible* table = Table();
+ if (!table)
+ return roles::NOTHING;
+
+ // If the cell next to this one is not a header cell then assume this cell is
+ // a row header for it.
+ uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
+ Accessible* cell = table->CellAt(rowIdx, colIdx + ColExtent());
+ if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
+ return roles::ROWHEADER;
+
+ // If the cell below this one is not a header cell then assume this cell is
+ // a column header for it.
+ uint32_t rowExtent = RowExtent();
+ cell = table->CellAt(rowIdx + rowExtent, colIdx);
+ if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
+ return roles::COLUMNHEADER;
+
+ // Otherwise if this cell is surrounded by header cells only then make a guess
+ // based on its cell spanning. In other words if it is row spanned then assume
+ // it's a row header, otherwise it's a column header.
+ return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableRowAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableRowAccessible, Accessible)
+
+role
+HTMLTableRowAccessible::NativeRole()
+{
+ if (mContent->IsMathMLElement(nsGkAtoms::mtr_)) {
+ return roles::MATHML_TABLE_ROW;
+ } else if (mContent->IsMathMLElement(nsGkAtoms::mlabeledtr_)) {
+ return roles::MATHML_LABELED_ROW;
+ }
+ return roles::ROW;
+}
+
+GroupPos
+HTMLTableRowAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ Accessible* table = nsAccUtils::TableFor(this);
+ if (table && nsCoreUtils::GetUIntAttr(table->GetContent(),
+ nsGkAtoms::aria_rowcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return AccessibleWrap::GroupPosition();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible: Accessible
+
+bool
+HTMLTableAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ // Move caption accessible so that it's the first child. Check for the first
+ // caption only, because nsAccessibilityService ensures we don't create
+ // accessibles for the other captions, since only the first is actually
+ // visible.
+ return Accessible::InsertChildAt(aChild->IsHTMLCaption() ? 0 : aIndex, aChild);
+}
+
+role
+HTMLTableAccessible::NativeRole()
+{
+ if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
+ return roles::MATHML_TABLE;
+ }
+ return roles::TABLE;
+}
+
+uint64_t
+HTMLTableAccessible::NativeState()
+{
+ return Accessible::NativeState() | states::READONLY;
+}
+
+ENameValueFlag
+HTMLTableAccessible::NativeName(nsString& aName)
+{
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // Use table caption as a name.
+ Accessible* caption = Caption();
+ if (caption) {
+ nsIContent* captionContent = caption->GetContent();
+ if (captionContent) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+ }
+
+ // If no caption then use summary as a name.
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName);
+ return eNameOK;
+}
+
+already_AddRefed<nsIPersistentProperties>
+HTMLTableAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ AccessibleWrap::NativeAttributes();
+
+ if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
+ GetAccService()->MarkupAttributes(mContent, attributes);
+ }
+
+ if (IsProbablyLayoutTable()) {
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
+ NS_LITERAL_STRING("true"), unused);
+ }
+
+ return attributes.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible: Accessible
+
+Relation
+HTMLTableAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = AccessibleWrap::RelationByType(aType);
+ if (aType == RelationType::LABELLED_BY)
+ rel.AppendTarget(Caption());
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible: Table
+
+Accessible*
+HTMLTableAccessible::Caption() const
+{
+ Accessible* child = mChildren.SafeElementAt(0, nullptr);
+ return child && child->Role() == roles::CAPTION ? child : nullptr;
+}
+
+void
+HTMLTableAccessible::Summary(nsString& aSummary)
+{
+ dom::HTMLTableElement* table = dom::HTMLTableElement::FromContent(mContent);
+
+ if (table)
+ table->GetSummary(aSummary);
+}
+
+uint32_t
+HTMLTableAccessible::ColCount()
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ return tableFrame ? tableFrame->GetColCount() : 0;
+}
+
+uint32_t
+HTMLTableAccessible::RowCount()
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ return tableFrame ? tableFrame->GetRowCount() : 0;
+}
+
+uint32_t
+HTMLTableAccessible::SelectedCellCount()
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return 0;
+
+ uint32_t count = 0, rowCount = RowCount(), colCount = ColCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (!cellFrame || !cellFrame->IsSelected())
+ continue;
+
+ int32_t startRow = -1, startCol = -1;
+ cellFrame->GetRowIndex(startRow);
+ cellFrame->GetColIndex(startCol);
+ if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
+ startCol >= 0 && (uint32_t)startCol == colIdx)
+ count++;
+ }
+ }
+
+ return count;
+}
+
+uint32_t
+HTMLTableAccessible::SelectedColCount()
+{
+ uint32_t count = 0, colCount = ColCount();
+
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ if (IsColSelected(colIdx))
+ count++;
+
+ return count;
+}
+
+uint32_t
+HTMLTableAccessible::SelectedRowCount()
+{
+ uint32_t count = 0, rowCount = RowCount();
+
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ if (IsRowSelected(rowIdx))
+ count++;
+
+ return count;
+}
+
+void
+HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return;
+
+ uint32_t rowCount = RowCount(), colCount = ColCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (!cellFrame || !cellFrame->IsSelected())
+ continue;
+
+ int32_t startCol = -1, startRow = -1;
+ cellFrame->GetRowIndex(startRow);
+ cellFrame->GetColIndex(startCol);
+ if ((startRow >= 0 && (uint32_t)startRow != rowIdx) ||
+ (startCol >= 0 && (uint32_t)startCol != colIdx))
+ continue;
+
+ Accessible* cell = mDoc->GetAccessible(cellFrame->GetContent());
+ aCells->AppendElement(cell);
+ }
+ }
+}
+
+void
+HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return;
+
+ uint32_t rowCount = RowCount(), colCount = ColCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (!cellFrame || !cellFrame->IsSelected())
+ continue;
+
+ int32_t startRow = -1, startCol = -1;
+ cellFrame->GetColIndex(startCol);
+ cellFrame->GetRowIndex(startRow);
+ if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
+ startCol >= 0 && (uint32_t)startCol == colIdx)
+ aCells->AppendElement(CellIndexAt(rowIdx, colIdx));
+ }
+ }
+}
+
+void
+HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
+{
+ uint32_t colCount = ColCount();
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ if (IsColSelected(colIdx))
+ aCols->AppendElement(colIdx);
+}
+
+void
+HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
+{
+ uint32_t rowCount = RowCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ if (IsRowSelected(rowIdx))
+ aRows->AppendElement(rowIdx);
+}
+
+Accessible*
+HTMLTableAccessible::CellAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return nullptr;
+
+ nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
+ Accessible* cell = mDoc->GetAccessible(cellContent);
+
+ // XXX bug 576838: crazy tables (like table6 in tables/test_table2.html) may
+ // return itself as a cell what makes Orca hang.
+ return cell == this ? nullptr : cell;
+}
+
+int32_t
+HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return -1;
+
+ return tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx);
+}
+
+int32_t
+HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return -1;
+
+ int32_t rowIdx = -1, colIdx = -1;
+ tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
+ return colIdx;
+}
+
+int32_t
+HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return -1;
+
+ int32_t rowIdx = -1, colIdx = -1;
+ tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
+ return rowIdx;
+}
+
+void
+HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (tableFrame)
+ tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx);
+}
+
+uint32_t
+HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return 0;
+
+ return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx);
+}
+
+uint32_t
+HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return 0;
+
+ return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx);
+}
+
+bool
+HTMLTableAccessible::IsColSelected(uint32_t aColIdx)
+{
+ bool isSelected = false;
+
+ uint32_t rowCount = RowCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ isSelected = IsCellSelected(rowIdx, aColIdx);
+ if (!isSelected)
+ return false;
+ }
+
+ return isSelected;
+}
+
+bool
+HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx)
+{
+ bool isSelected = false;
+
+ uint32_t colCount = ColCount();
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ isSelected = IsCellSelected(aRowIdx, colIdx);
+ if (!isSelected)
+ return false;
+ }
+
+ return isSelected;
+}
+
+bool
+HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return false;
+
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx);
+ return cellFrame ? cellFrame->IsSelected() : false;
+}
+
+void
+HTMLTableAccessible::SelectRow(uint32_t aRowIdx)
+{
+ DebugOnly<nsresult> rv =
+ RemoveRowsOrColumnsFromSelection(aRowIdx,
+ nsISelectionPrivate::TABLESELECTION_ROW,
+ true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
+
+ AddRowOrColumnToSelection(aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW);
+}
+
+void
+HTMLTableAccessible::SelectCol(uint32_t aColIdx)
+{
+ DebugOnly<nsresult> rv =
+ RemoveRowsOrColumnsFromSelection(aColIdx,
+ nsISelectionPrivate::TABLESELECTION_COLUMN,
+ true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
+
+ AddRowOrColumnToSelection(aColIdx, nsISelectionPrivate::TABLESELECTION_COLUMN);
+}
+
+void
+HTMLTableAccessible::UnselectRow(uint32_t aRowIdx)
+{
+ RemoveRowsOrColumnsFromSelection(aRowIdx,
+ nsISelectionPrivate::TABLESELECTION_ROW,
+ false);
+}
+
+void
+HTMLTableAccessible::UnselectCol(uint32_t aColIdx)
+{
+ RemoveRowsOrColumnsFromSelection(aColIdx,
+ nsISelectionPrivate::TABLESELECTION_COLUMN,
+ false);
+}
+
+nsresult
+HTMLTableAccessible::AddRowOrColumnToSelection(int32_t aIndex, uint32_t aTarget)
+{
+ bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
+
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return NS_OK;
+
+ uint32_t count = 0;
+ if (doSelectRow)
+ count = ColCount();
+ else
+ count = RowCount();
+
+ nsIPresShell* presShell(mDoc->PresShell());
+ RefPtr<nsFrameSelection> tableSelection =
+ const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ int32_t rowIdx = doSelectRow ? aIndex : idx;
+ int32_t colIdx = doSelectRow ? idx : aIndex;
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (cellFrame && !cellFrame->IsSelected()) {
+ nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(int32_t aIndex,
+ uint32_t aTarget,
+ bool aIsOuter)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return NS_OK;
+
+ nsIPresShell* presShell(mDoc->PresShell());
+ RefPtr<nsFrameSelection> tableSelection =
+ const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
+
+ bool doUnselectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
+ uint32_t count = doUnselectRow ? ColCount() : RowCount();
+
+ int32_t startRowIdx = doUnselectRow ? aIndex : 0;
+ int32_t endRowIdx = doUnselectRow ? aIndex : count - 1;
+ int32_t startColIdx = doUnselectRow ? 0 : aIndex;
+ int32_t endColIdx = doUnselectRow ? count - 1 : aIndex;
+
+ if (aIsOuter)
+ return tableSelection->RestrictCellsToSelection(mContent,
+ startRowIdx, startColIdx,
+ endRowIdx, endColIdx);
+
+ return tableSelection->RemoveCellsFromSelection(mContent,
+ startRowIdx, startColIdx,
+ endRowIdx, endColIdx);
+}
+
+void
+HTMLTableAccessible::Description(nsString& aDescription)
+{
+ // Helpful for debugging layout vs. data tables
+ aDescription.Truncate();
+ Accessible::Description(aDescription);
+ if (!aDescription.IsEmpty())
+ return;
+
+ // Use summary as description if it weren't used as a name.
+ // XXX: get rid code duplication with NameInternal().
+ Accessible* caption = Caption();
+ if (caption) {
+ nsIContent* captionContent = caption->GetContent();
+ if (captionContent) {
+ nsAutoString captionText;
+ nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
+ &captionText);
+
+ if (!captionText.IsEmpty()) { // summary isn't used as a name.
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary,
+ aDescription);
+ }
+ }
+ }
+
+#ifdef SHOW_LAYOUT_HEURISTIC
+ if (aDescription.IsEmpty()) {
+ bool isProbablyForLayout = IsProbablyLayoutTable();
+ aDescription = mLayoutHeuristic;
+ }
+ printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
+#endif
+}
+
+bool
+HTMLTableAccessible::HasDescendant(const nsAString& aTagName, bool aAllowEmpty)
+{
+ nsCOMPtr<nsIHTMLCollection> elements =
+ mContent->AsElement()->GetElementsByTagName(aTagName);
+
+ Element* foundItem = elements->Item(0);
+ if (!foundItem)
+ return false;
+
+ if (aAllowEmpty)
+ return true;
+
+ // Make sure that the item we found has contents and either has multiple
+ // children or the found item is not a whitespace-only text node.
+ if (foundItem->GetChildCount() > 1)
+ return true; // Treat multiple child nodes as non-empty
+
+ nsIContent *innerItemContent = foundItem->GetFirstChild();
+ if (innerItemContent && !innerItemContent->TextIsOnlyWhitespace())
+ return true;
+
+ // If we found more than one node then return true not depending on
+ // aAllowEmpty flag.
+ // XXX it might be dummy but bug 501375 where we changed this addresses
+ // performance problems only. Note, currently 'aAllowEmpty' flag is used for
+ // caption element only. On another hand we create accessible object for
+ // the first entry of caption element (see
+ // HTMLTableAccessible::InsertChildAt).
+ return !!elements->Item(1);
+}
+
+bool
+HTMLTableAccessible::IsProbablyLayoutTable()
+{
+ // Implement a heuristic to determine if table is most likely used for layout
+ // XXX do we want to look for rowspan or colspan, especialy that span all but a couple cells
+ // at the beginning or end of a row/col, and especially when they occur at the edge of a table?
+ // XXX expose this info via object attributes to AT-SPI
+
+ // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
+ // This will allow release trunk builds to be used by testers to refine the algorithm
+ // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
+#ifdef SHOW_LAYOUT_HEURISTIC
+#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
+ { \
+ mLayoutHeuristic = isLayout ? \
+ NS_LITERAL_STRING("layout table: " heuristic) : \
+ NS_LITERAL_STRING("data table: " heuristic); \
+ return isLayout; \
+ }
+#else
+#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
+#endif
+
+ DocAccessible* docAccessible = Document();
+ if (docAccessible) {
+ uint64_t docState = docAccessible->State();
+ if (docState & states::EDITABLE) { // Need to see all elements while document is being edited
+ RETURN_LAYOUT_ANSWER(false, "In editable document");
+ }
+ }
+
+ // Check to see if an ARIA role overrides the role from native markup,
+ // but for which we still expose table semantics (treegrid, for example).
+ if (Role() != roles::TABLE)
+ RETURN_LAYOUT_ANSWER(false, "Has role attribute");
+
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
+ // Role attribute is present, but overridden roles have already been dealt with.
+ // Only landmarks and other roles that don't override the role from native
+ // markup are left to deal with here.
+ RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table");
+ }
+
+ NS_ASSERTION(mContent->IsHTMLElement(nsGkAtoms::table),
+ "table should not be built by CSS display:table style");
+
+ // Check if datatable attribute has "0" value.
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
+ NS_LITERAL_STRING("0"), eCaseMatters)) {
+ RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
+ }
+
+ // Check for legitimate data table attributes.
+ nsAutoString summary;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) &&
+ !summary.IsEmpty())
+ RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
+
+ // Check for legitimate data table elements.
+ Accessible* caption = FirstChild();
+ if (caption && caption->Role() == roles::CAPTION && caption->HasChildren())
+ RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures");
+
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (!childElm->IsHTMLElement())
+ continue;
+
+ if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col,
+ nsGkAtoms::colgroup,
+ nsGkAtoms::tfoot,
+ nsGkAtoms::thead)) {
+ RETURN_LAYOUT_ANSWER(false,
+ "Has col, colgroup, tfoot or thead -- legitimate table structures");
+ }
+
+ if (childElm->IsHTMLElement(nsGkAtoms::tbody)) {
+ for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
+ rowElm = rowElm->GetNextSibling()) {
+ if (rowElm->IsHTMLElement(nsGkAtoms::tr)) {
+ for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
+ cellElm = cellElm->GetNextSibling()) {
+ if (cellElm->IsHTMLElement()) {
+
+ if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
+ RETURN_LAYOUT_ANSWER(false,
+ "Has th -- legitimate table structures");
+ }
+
+ if (cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
+ cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
+ cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
+ RETURN_LAYOUT_ANSWER(false,
+ "Has headers, scope, or abbr attribute -- legitimate table structures");
+ }
+
+ Accessible* cell = mDoc->GetAccessible(cellElm);
+ if (cell && cell->ChildCount() == 1 &&
+ cell->FirstChild()->IsAbbreviation()) {
+ RETURN_LAYOUT_ANSWER(false,
+ "has abbr -- legitimate table structures");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (HasDescendant(NS_LITERAL_STRING("table"))) {
+ RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
+ }
+
+ // If only 1 column or only 1 row, it's for layout
+ uint32_t colCount = ColCount();
+ if (colCount <=1) {
+ RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
+ }
+ uint32_t rowCount = RowCount();
+ if (rowCount <=1) {
+ RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
+ }
+
+ // Check for many columns
+ if (colCount >= 5) {
+ RETURN_LAYOUT_ANSWER(false, ">=5 columns");
+ }
+
+ // Now we know there are 2-4 columns and 2 or more rows
+ // Check to see if there are visible borders on the cells
+ // XXX currently, we just check the first cell -- do we really need to do more?
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ RETURN_LAYOUT_ANSWER(false, "table with no frame!");
+
+ nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
+ if (!cellFrame)
+ RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
+
+ nsMargin border;
+ cellFrame->GetXULBorder(border);
+ if (border.top && border.bottom && border.left && border.right) {
+ RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
+ }
+
+ /**
+ * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on forward
+ */
+
+ // Check for styled background color across rows (alternating background
+ // color is a common feature for data tables).
+ uint32_t childCount = ChildCount();
+ nscolor rowColor = 0;
+ nscolor prevRowColor;
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = GetChildAt(childIdx);
+ if (child->Role() == roles::ROW) {
+ prevRowColor = rowColor;
+ nsIFrame* rowFrame = child->GetFrame();
+ rowColor = rowFrame->StyleBackground()->mBackgroundColor;
+
+ if (childIdx > 0 && prevRowColor != rowColor)
+ RETURN_LAYOUT_ANSWER(false, "2 styles of row background color, non-bordered");
+ }
+ }
+
+ // Check for many rows
+ const uint32_t kMaxLayoutRows = 20;
+ if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data
+ RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
+ }
+
+ // Check for very wide table.
+ nsIFrame* documentFrame = Document()->GetFrame();
+ nsSize documentSize = documentFrame->GetSize();
+ if (documentSize.width > 0) {
+ nsSize tableSize = GetFrame()->GetSize();
+ int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
+ if (percentageOfDocWidth > 95) {
+ // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
+ // Probably for layout
+ RETURN_LAYOUT_ANSWER(true,
+ "<= 4 columns, table width is 95% of document width");
+ }
+ }
+
+ // Two column rules
+ if (rowCount * colCount <= 10) {
+ RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
+ }
+
+ if (HasDescendant(NS_LITERAL_STRING("embed")) ||
+ HasDescendant(NS_LITERAL_STRING("object")) ||
+ HasDescendant(NS_LITERAL_STRING("applet")) ||
+ HasDescendant(NS_LITERAL_STRING("iframe"))) {
+ RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, applet or iframe, typical of advertisements");
+ }
+
+ RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data");
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLCaptionAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+Relation
+HTMLCaptionAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = HyperTextAccessible::RelationByType(aType);
+ if (aType == RelationType::LABEL_FOR)
+ rel.AppendTarget(Parent());
+
+ return rel;
+}
+
+role
+HTMLCaptionAccessible::NativeRole()
+{
+ return roles::CAPTION;
+}