fune/accessible/html/HTMLTableAccessible.cpp
Ciure Andrei 873d562bc3 Backed out 8 changesets (bug 1451169) on request from njn a=backout
Backed out changeset b92f856e15a8 (bug 1451169)
Backed out changeset 348e825756fa (bug 1451169)
Backed out changeset 624d82428726 (bug 1451169)
Backed out changeset 4d51610ca08e (bug 1451169)
Backed out changeset bb76a9589717 (bug 1451169)
Backed out changeset c145fbd03947 (bug 1451169)
Backed out changeset 6d36289e0f54 (bug 1451169)
Backed out changeset 914fb7cd9fc3 (bug 1451169)

--HG--
extra : histedit_source : 94de6631919f895dec422bc0e564f5baf885ba4a%2C18f52c6c11d3064d3137a6847575effb7d407894
2018-04-11 11:22:05 +03:00

1131 lines
34 KiB
C++

/* -*- 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 "nsIHTMLCollection.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;
}
////////////////////////////////////////////////////////////////////////////////
// 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr, abbrText);
if (!abbrText.IsEmpty())
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::abbr, abbrText);
// axis attribute
nsAutoString axisText;
mContent->AsElement()->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
{
nsTableCellFrame* cellFrame = GetCellFrame();
NS_ENSURE_TRUE(cellFrame, 0);
return cellFrame->ColIndex();
}
uint32_t
HTMLTableCellAccessible::RowIdx() const
{
nsTableCellFrame* cellFrame = GetCellFrame();
NS_ENSURE_TRUE(cellFrame, 0);
return cellFrame->RowIndex();
}
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());
}
nsTableCellFrame*
HTMLTableCellAccessible::GetCellFrame() 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 Element::AttrValuesArray scopeValues[] =
{ &nsGkAtoms::col, &nsGkAtoms::colgroup,
&nsGkAtoms::row, &nsGkAtoms::rowgroup, nullptr };
int32_t valueIdx =
mContent->AsElement()->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
////////////////////////////////////////////////////////////////////////////////
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
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// 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->AsElement()->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::FromNode(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;
uint32_t startRow = cellFrame->RowIndex();
uint32_t startCol = cellFrame->ColIndex();
if (startRow == rowIdx && 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;
uint32_t startRow = cellFrame->RowIndex();
uint32_t startCol = cellFrame->ColIndex();
if (startRow != rowIdx || 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;
uint32_t startCol = cellFrame->ColIndex();
uint32_t startRow = cellFrame->RowIndex();
if (startRow == rowIdx && 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->AsElement()->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->AsElement()->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->AsElement()->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->AsElement()->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->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
cellElm->AsElement()->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();
MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up");
if (!rowFrame) {
RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy");
}
rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame);
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("iframe"))) {
RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, 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;
}