blob: 8fdd961a7930c4847b441535e51d9009198a64fb [file] [log] [blame]
/*
* Copyright (C) 2008 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "modules/accessibility/AXTableCell.h"
#include "core/layout/LayoutTableCell.h"
#include "modules/accessibility/AXObjectCacheImpl.h"
namespace blink {
using namespace HTMLNames;
AXTableCell::AXTableCell(LayoutObject* layoutObject, AXObjectCacheImpl& axObjectCache)
: AXLayoutObject(layoutObject, axObjectCache)
{
}
AXTableCell::~AXTableCell()
{
}
AXTableCell* AXTableCell::create(LayoutObject* layoutObject, AXObjectCacheImpl& axObjectCache)
{
return new AXTableCell(layoutObject, axObjectCache);
}
bool AXTableCell::isTableHeaderCell() const
{
return node() && node()->hasTagName(thTag);
}
bool AXTableCell::isRowHeaderCell() const
{
const AtomicString& scope = getAttribute(scopeAttr);
return equalIgnoringCase(scope, "row") || equalIgnoringCase(scope, "rowgroup");
}
bool AXTableCell::isColumnHeaderCell() const
{
const AtomicString& scope = getAttribute(scopeAttr);
return equalIgnoringCase(scope, "col") || equalIgnoringCase(scope, "colgroup");
}
bool AXTableCell::computeAccessibilityIsIgnored(IgnoredReasons* ignoredReasons) const
{
AXObjectInclusion decision = defaultObjectInclusion(ignoredReasons);
if (decision == IncludeObject)
return false;
if (decision == IgnoreObject)
return true;
if (!isTableCell())
return AXLayoutObject::computeAccessibilityIsIgnored(ignoredReasons);
return false;
}
AXObject* AXTableCell::parentTable() const
{
if (!m_layoutObject || !m_layoutObject->isTableCell())
return 0;
// If the document no longer exists, we might not have an axObjectCache.
if (isDetached())
return 0;
// Do not use getOrCreate. parentTable() can be called while the layout tree is being modified
// by javascript, and creating a table element may try to access the layout tree while in a bad state.
// By using only get() implies that the AXTable must be created before AXTableCells. This should
// always be the case when AT clients access a table.
// https://bugs.webkit.org/show_bug.cgi?id=42652
return axObjectCache().get(toLayoutTableCell(m_layoutObject)->table());
}
bool AXTableCell::isTableCell() const
{
AXObject* parent = parentObjectUnignored();
if (!parent || !parent->isTableRow())
return false;
return true;
}
static AccessibilityRole decideRoleFromSibling(LayoutTableCell* siblingCell)
{
if (!siblingCell)
return CellRole;
if (Node* siblingNode = siblingCell->node()) {
if (siblingNode->hasTagName(thTag))
return ColumnHeaderRole;
if (siblingNode->hasTagName(tdTag))
return RowHeaderRole;
}
return CellRole;
}
AccessibilityRole AXTableCell::scanToDecideHeaderRole()
{
if (!isTableHeaderCell())
return CellRole;
// Check scope attribute first.
if (isRowHeaderCell())
return RowHeaderRole;
if (isColumnHeaderCell())
return ColumnHeaderRole;
// Check the previous cell and the next cell on the same row.
LayoutTableCell* layoutCell = toLayoutTableCell(m_layoutObject);
AccessibilityRole headerRole = CellRole;
// if header is preceded by header cells on the same row, then it is a
// column header. If it is preceded by other cells then it's a row header.
if ((headerRole = decideRoleFromSibling(layoutCell->previousCell())) != CellRole)
return headerRole;
// if header is followed by header cells on the same row, then it is a
// column header. If it is followed by other cells then it's a row header.
if ((headerRole = decideRoleFromSibling(layoutCell->nextCell())) != CellRole)
return headerRole;
// If there are no other cells on that row, then it is a column header.
return ColumnHeaderRole;
}
AccessibilityRole AXTableCell::determineAccessibilityRole()
{
if (!isTableCell())
return AXLayoutObject::determineAccessibilityRole();
return scanToDecideHeaderRole();
}
void AXTableCell::rowIndexRange(pair<unsigned, unsigned>& rowRange)
{
if (!m_layoutObject || !m_layoutObject->isTableCell())
return;
LayoutTableCell* layoutCell = toLayoutTableCell(m_layoutObject);
rowRange.first = layoutCell->rowIndex();
rowRange.second = layoutCell->rowSpan();
// since our table might have multiple sections, we have to offset our row appropriately
LayoutTableSection* section = layoutCell->section();
LayoutTable* table = layoutCell->table();
if (!table || !section)
return;
LayoutTableSection* tableSection = table->topSection();
unsigned rowOffset = 0;
while (tableSection) {
if (tableSection == section)
break;
rowOffset += tableSection->numRows();
tableSection = table->sectionBelow(tableSection, SkipEmptySections);
}
rowRange.first += rowOffset;
}
void AXTableCell::columnIndexRange(pair<unsigned, unsigned>& columnRange)
{
if (!m_layoutObject || !m_layoutObject->isTableCell())
return;
LayoutTableCell* cell = toLayoutTableCell(m_layoutObject);
columnRange.first = cell->table()->colToEffCol(cell->col());
columnRange.second = cell->table()->colToEffCol(cell->col() + cell->colSpan()) - columnRange.first;
}
SortDirection AXTableCell::sortDirection() const
{
if (roleValue() != RowHeaderRole
&& roleValue() != ColumnHeaderRole)
return SortDirectionUndefined;
const AtomicString& ariaSort = getAttribute(aria_sortAttr);
if (ariaSort.isEmpty())
return SortDirectionUndefined;
if (equalIgnoringCase(ariaSort, "none"))
return SortDirectionNone;
if (equalIgnoringCase(ariaSort, "ascending"))
return SortDirectionAscending;
if (equalIgnoringCase(ariaSort, "descending"))
return SortDirectionDescending;
if (equalIgnoringCase(ariaSort, "other"))
return SortDirectionOther;
return SortDirectionUndefined;
}
AXObject* AXTableCell::deprecatedTitleUIElement() const
{
// Try to find if the first cell in this row is a <th>. If it is,
// then it can act as the title ui element. (This is only in the
// case when the table is not appearing as an AXTable.)
if (isTableCell() || !m_layoutObject || !m_layoutObject->isTableCell())
return 0;
// Table cells that are th cannot have title ui elements, since by definition
// they are title ui elements
if (isTableHeaderCell())
return 0;
LayoutTableCell* layoutCell = toLayoutTableCell(m_layoutObject);
// If this cell is in the first column, there is no need to continue.
int col = layoutCell->col();
if (!col)
return 0;
int row = layoutCell->rowIndex();
LayoutTableSection* section = layoutCell->section();
if (!section)
return 0;
LayoutTableCell* headerCell = section->primaryCellAt(row, 0);
if (!headerCell || headerCell == layoutCell)
return 0;
Node* cellElement = headerCell->node();
if (!cellElement || !cellElement->hasTagName(thTag))
return 0;
return axObjectCache().getOrCreate(headerCell);
}
} // namespace blink