2
* Copyright (C) 2008 Apple Inc. All rights reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
8
* 1. Redistributions of source code must retain the above copyright
9
* notice, this list of conditions and the following disclaimer.
10
* 2. Redistributions in binary form must reproduce the above copyright
11
* notice, this list of conditions and the following disclaimer in the
12
* documentation and/or other materials provided with the distribution.
13
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14
* its contributors may be used to endorse or promote products derived
15
* from this software without specific prior written permission.
17
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
#include "AccessibilityTable.h"
32
#include "AccessibilityTableCell.h"
33
#include "AccessibilityTableColumn.h"
34
#include "AccessibilityTableHeaderContainer.h"
35
#include "AccessibilityTableRow.h"
36
#include "AXObjectCache.h"
37
#include "HTMLNames.h"
38
#include "HTMLTableElement.h"
39
#include "HTMLTableCaptionElement.h"
40
#include "HTMLTableCellElement.h"
41
#include "RenderObject.h"
42
#include "RenderTable.h"
43
#include "RenderTableCell.h"
44
#include "RenderTableSection.h"
50
using namespace HTMLNames;
52
AccessibilityTable::AccessibilityTable(RenderObject* renderer)
53
: AccessibilityRenderObject(renderer),
56
// FIXME: We need to disable Accessibility Tables entirely on the Mac until <rdar://problem/6372481> is resolved.
58
m_isAccessibilityTable = false;
60
m_isAccessibilityTable = isTableExposableThroughAccessibility();
65
AccessibilityTable::~AccessibilityTable()
69
PassRefPtr<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer)
71
return adoptRef(new AccessibilityTable(renderer));
74
bool AccessibilityTable::isTableExposableThroughAccessibility()
76
// the following is a heuristic used to determine if a
77
// <table> should be exposed as an AXTable. The goal
78
// is to only show "data" tables
80
if (!m_renderer || !m_renderer->isTable())
83
// if the developer assigned an aria role to this, then we shouldn't
84
// expose it as a table, unless, of course, the aria role is a table
85
AccessibilityRole ariaRole = ariaRoleAttribute();
86
if (ariaRole == TableRole)
88
if (ariaRole != UnknownRole)
91
RenderTable* table = static_cast<RenderTable*>(m_renderer);
93
// this employs a heuristic to determine if this table should appear.
94
// Only "data" tables should be exposed as tables.
95
// Unfortunately, there is no good way to determine the difference
96
// between a "layout" table and a "data" table
98
Node* tableNode = table->element();
99
if (!tableNode || !tableNode->hasTagName(tableTag))
102
// if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table
103
HTMLTableElement* tableElement = static_cast<HTMLTableElement*>(tableNode);
104
if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
107
// if someone used "rules" attribute than the table should appear
108
if (!tableElement->rules().isEmpty())
111
// go through the cell's and check for tell-tale signs of "data" table status
112
// cells have borders, or use attributes like headers, abbr, scope or axis
113
RenderTableSection* firstBody = table->firstBody();
117
int numCols = firstBody->numColumns();
118
int numRows = firstBody->numRows();
120
// if there's only one cell, it's not a good AXTable candidate
121
if (numRows == 1 && numCols == 1)
124
// store the background color of the table to check against cell's background colors
125
RenderStyle* tableStyle = table->style();
128
Color tableBGColor = tableStyle->backgroundColor();
130
// check enough of the cells to find if the table matches our criteria
132
// 1) must have at least one valid cell (and)
133
// 2) at least half of cells have borders (or)
134
// 3) at least half of cells have different bg colors than the table, and there is cell spacing
135
unsigned validCellCount = 0;
136
unsigned borderedCellCount = 0;
137
unsigned backgroundDifferenceCellCount = 0;
139
for (int row = 0; row < numRows; ++row) {
140
for (int col = 0; col < numCols; ++col) {
141
RenderTableCell* cell = firstBody->cellAt(row, col).cell;
144
Node* cellNode = cell->element();
148
if (cell->width() < 1 || cell->height() < 1)
153
HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode);
155
// in this case, the developer explicitly assigned a "data" table attribute
156
if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() ||
157
!cellElement->axis().isEmpty() || !cellElement->scope().isEmpty())
160
RenderStyle* renderStyle = cell->style();
164
// a cell needs to have matching bordered sides, before it can be considered a bordered cell.
165
if ((cell->borderTop() > 0 && cell->borderBottom() > 0) ||
166
(cell->borderLeft() > 0 && cell->borderRight() > 0))
169
// if the cell has a different color from the table and there is cell spacing,
170
// then it is probably a data table cell (spacing and colors take the place of borders)
171
Color cellColor = renderStyle->backgroundColor();
172
if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 &&
173
tableBGColor != cellColor && cellColor.alpha() != 1)
174
backgroundDifferenceCellCount++;
176
// if we've found 10 "good" cells, we don't need to keep searching
177
if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
182
// if there is less than two valid cells, it's not a data table
183
if (validCellCount <= 1)
186
// half of the cells had borders, it's a data table
187
unsigned neededCellCount = validCellCount / 2;
188
if (borderedCellCount >= neededCellCount)
191
// half had different background colors, it's a data table
192
if (backgroundDifferenceCellCount >= neededCellCount)
198
void AccessibilityTable::clearChildren()
203
m_haveChildren = false;
206
void AccessibilityTable::addChildren()
208
if (!isDataTable()) {
209
AccessibilityRenderObject::addChildren();
213
ASSERT(!m_haveChildren);
215
m_haveChildren = true;
219
RenderTable* table = static_cast<RenderTable*>(m_renderer);
220
AXObjectCache* axCache = m_renderer->document()->axObjectCache();
222
// go through all the available sections to pull out the rows
223
// and add them as children
224
RenderTableSection* tableSection = table->header();
226
tableSection = table->firstBody();
231
RenderTableSection* initialTableSection = tableSection;
233
while (tableSection) {
235
HashSet<AccessibilityObject*> appendedRows;
237
unsigned numRows = tableSection->numRows();
238
unsigned numCols = tableSection->numColumns();
239
for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
240
for (unsigned colIndex = 0; colIndex < numCols; ++colIndex) {
242
RenderTableCell* cell = tableSection->cellAt(rowIndex, colIndex).cell;
246
AccessibilityObject* rowObject = axCache->get(cell->parent());
247
if (!rowObject->isTableRow())
250
AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject);
251
// we need to check every cell for a new row, because cell spans
252
// can cause us to mess rows if we just check the first column
253
if (appendedRows.contains(row))
256
row->setRowIndex((int)m_rows.size());
258
m_children.append(row);
259
appendedRows.add(row);
263
tableSection = table->sectionBelow(tableSection, true);
266
// make the columns based on the number of columns in the first body
267
unsigned length = initialTableSection->numColumns();
268
for (unsigned i = 0; i < length; ++i) {
269
AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->get(ColumnRole));
270
column->setColumnIndex((int)i);
271
column->setParentTable(this);
272
m_columns.append(column);
273
m_children.append(column);
276
AccessibilityObject* headerContainerObject = headerContainer();
277
if (headerContainerObject)
278
m_children.append(headerContainerObject);
281
AccessibilityObject* AccessibilityTable::headerContainer()
283
if (m_headerContainer)
284
return m_headerContainer;
286
m_headerContainer = static_cast<AccessibilityTableHeaderContainer*>(axObjectCache()->get(TableHeaderContainerRole));
287
m_headerContainer->setParentTable(this);
289
return m_headerContainer;
292
AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
300
AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
308
void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
316
unsigned rowCount = m_rows.size();
317
for (unsigned k = 0; k < rowCount; ++k) {
318
AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject();
321
headers.append(header);
325
void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
333
unsigned colCount = m_columns.size();
334
for (unsigned k = 0; k < colCount; ++k) {
335
AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject();
338
headers.append(header);
342
void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
350
int numRows = m_rows.size();
351
for (int row = 0; row < numRows; ++row) {
352
AccessibilityChildrenVector rowChildren = m_rows[row]->children();
353
cells.append(rowChildren);
357
const unsigned AccessibilityTable::columnCount()
362
return m_columns.size();
365
const unsigned AccessibilityTable::rowCount()
370
return m_rows.size();
373
AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row)
381
RenderTable* table = static_cast<RenderTable*>(m_renderer);
382
RenderTableSection* tableSection = table->header();
384
tableSection = table->firstBody();
386
RenderTableCell* cell = 0;
387
unsigned rowCount = 0;
388
unsigned rowOffset = 0;
389
while (tableSection) {
391
rowCount += tableSection->numRows();
392
unsigned numCols = tableSection->numColumns();
394
if (row < rowCount && column < numCols) {
395
int sectionSpecificRow = row - rowOffset;
396
cell = tableSection->cellAt(sectionSpecificRow, column).cell;
398
// we didn't find the cell, which means there's spanning happening
399
// search backwards to find the spanning cell
403
for (int testRow = sectionSpecificRow-1; testRow >= 0; --testRow) {
404
cell = tableSection->cellAt(testRow, column).cell;
405
// cell overlapped. use this one
406
if (cell && ((cell->row() + (cell->rowSpan()-1)) >= (int)sectionSpecificRow))
413
for (int testCol = column-1; testCol >= 0; --testCol) {
414
cell = tableSection->cellAt(sectionSpecificRow, testCol).cell;
415
// cell overlapped. use this one
416
if (cell && ((cell->col() + (cell->colSpan()-1)) >= (int)column))
427
rowOffset += rowCount;
428
// we didn't find anything between the rows we should have
431
tableSection = table->sectionBelow(tableSection, true);
437
AccessibilityObject* cellObject = axObjectCache()->get(cell);
438
ASSERT(cellObject->isTableCell());
440
return static_cast<AccessibilityTableCell*>(cellObject);
443
AccessibilityRole AccessibilityTable::roleValue() const
446
return AccessibilityRenderObject::roleValue();
451
bool AccessibilityTable::accessibilityIsIgnored() const
454
return AccessibilityRenderObject::accessibilityIsIgnored();
459
String AccessibilityTable::title() const
462
return AccessibilityRenderObject::title();
468
// see if there is a caption
469
Node *tableElement = m_renderer->element();
471
HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(tableElement)->caption();
473
title = caption->innerText();
478
title = AccessibilityRenderObject::title();
483
bool AccessibilityTable::isDataTable() const
488
return m_isAccessibilityTable;
491
} // namespace WebCore