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