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 "core/accessibility/AXTable.h"
32 #include "core/accessibility/AXObjectCache.h"
33 #include "core/accessibility/AXTableCell.h"
34 #include "core/accessibility/AXTableColumn.h"
35 #include "core/accessibility/AXTableRow.h"
36 #include "core/html/HTMLTableCaptionElement.h"
37 #include "core/html/HTMLTableCellElement.h"
38 #include "core/html/HTMLTableColElement.h"
39 #include "core/html/HTMLTableElement.h"
40 #include "core/rendering/RenderTableCell.h"
44 using namespace HTMLNames;
46 AXTable::AXTable(RenderObject* renderer)
47 : AXRenderObject(renderer)
48 , m_headerContainer(nullptr)
59 AXRenderObject::init();
60 m_isAXTable = isTableExposableThroughAccessibility();
63 PassRefPtr<AXTable> AXTable::create(RenderObject* renderer)
65 return adoptRef(new AXTable(renderer));
68 bool AXTable::hasARIARole() const
73 AccessibilityRole ariaRole = ariaRoleAttribute();
74 if (ariaRole != UnknownRole)
80 bool AXTable::isAXTable() const
88 bool AXTable::isDataTable() const
93 // Do not consider it a data table is it has an ARIA role.
97 // When a section of the document is contentEditable, all tables should be
98 // treated as data tables, otherwise users may not be able to work with rich
99 // text editors that allow creating and editing tables.
100 if (node() && node()->rendererIsEditable())
103 // This employs a heuristic to determine if this table should appear.
104 // Only "data" tables should be exposed as tables.
105 // Unfortunately, there is no good way to determine the difference
106 // between a "layout" table and a "data" table.
108 RenderTable* table = toRenderTable(m_renderer);
109 Node* tableNode = table->node();
110 if (!isHTMLTableElement(tableNode))
113 // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table
114 HTMLTableElement* tableElement = toHTMLTableElement(tableNode);
115 if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
118 // if someone used "rules" attribute than the table should appear
119 if (!tableElement->rules().isEmpty())
122 // if there's a colgroup or col element, it's probably a data table.
123 if (Traversal<HTMLTableColElement>::firstChild(*tableElement))
126 // go through the cell's and check for tell-tale signs of "data" table status
127 // cells have borders, or use attributes like headers, abbr, scope or axis
128 table->recalcSectionsIfNeeded();
129 RenderTableSection* firstBody = table->firstBody();
133 int numCols = firstBody->numColumns();
134 int numRows = firstBody->numRows();
136 // If there's only one cell, it's not a good AXTable candidate.
137 if (numRows == 1 && numCols == 1)
140 // If there are at least 20 rows, we'll call it a data table.
144 // Store the background color of the table to check against cell's background colors.
145 RenderStyle* tableStyle = table->style();
148 Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor);
150 // check enough of the cells to find if the table matches our criteria
152 // 1) must have at least one valid cell (and)
153 // 2) at least half of cells have borders (or)
154 // 3) at least half of cells have different bg colors than the table, and there is cell spacing
155 unsigned validCellCount = 0;
156 unsigned borderedCellCount = 0;
157 unsigned backgroundDifferenceCellCount = 0;
158 unsigned cellsWithTopBorder = 0;
159 unsigned cellsWithBottomBorder = 0;
160 unsigned cellsWithLeftBorder = 0;
161 unsigned cellsWithRightBorder = 0;
163 Color alternatingRowColors[5];
164 int alternatingRowColorCount = 0;
166 int headersInFirstColumnCount = 0;
167 for (int row = 0; row < numRows; ++row) {
169 int headersInFirstRowCount = 0;
170 for (int col = 0; col < numCols; ++col) {
171 RenderTableCell* cell = firstBody->primaryCellAt(row, col);
174 Node* cellNode = cell->node();
178 if (cell->width() < 1 || cell->height() < 1)
183 bool isTHCell = cellNode->hasTagName(thTag);
184 // If the first row is comprised of all <th> tags, assume it is a data table.
185 if (!row && isTHCell)
186 headersInFirstRowCount++;
188 // If the first column is comprised of all <th> tags, assume it is a data table.
189 if (!col && isTHCell)
190 headersInFirstColumnCount++;
192 // in this case, the developer explicitly assigned a "data" table attribute
193 if (isHTMLTableCellElement(*cellNode)) {
194 HTMLTableCellElement& cellElement = toHTMLTableCellElement(*cellNode);
195 if (!cellElement.headers().isEmpty() || !cellElement.abbr().isEmpty()
196 || !cellElement.axis().isEmpty() || !cellElement.scope().isEmpty())
200 RenderStyle* renderStyle = cell->style();
204 // If the empty-cells style is set, we'll call it a data table.
205 if (renderStyle->emptyCells() == HIDE)
208 // If a cell has matching bordered sides, call it a (fully) bordered cell.
209 if ((cell->borderTop() > 0 && cell->borderBottom() > 0)
210 || (cell->borderLeft() > 0 && cell->borderRight() > 0))
213 // Also keep track of each individual border, so we can catch tables where most
214 // cells have a bottom border, for example.
215 if (cell->borderTop() > 0)
216 cellsWithTopBorder++;
217 if (cell->borderBottom() > 0)
218 cellsWithBottomBorder++;
219 if (cell->borderLeft() > 0)
220 cellsWithLeftBorder++;
221 if (cell->borderRight() > 0)
222 cellsWithRightBorder++;
224 // If the cell has a different color from the table and there is cell spacing,
225 // then it is probably a data table cell (spacing and colors take the place of borders).
226 Color cellColor = renderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
227 if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0
228 && tableBGColor != cellColor && cellColor.alpha() != 1)
229 backgroundDifferenceCellCount++;
231 // If we've found 10 "good" cells, we don't need to keep searching.
232 if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
235 // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows.
236 if (row < 5 && row == alternatingRowColorCount) {
237 RenderObject* renderRow = cell->parent();
238 if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow())
240 RenderStyle* rowRenderStyle = renderRow->style();
243 Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
244 alternatingRowColors[alternatingRowColorCount] = rowColor;
245 alternatingRowColorCount++;
249 if (!row && headersInFirstRowCount == numCols && numCols > 1)
253 if (headersInFirstColumnCount == numRows && numRows > 1)
256 // if there is less than two valid cells, it's not a data table
257 if (validCellCount <= 1)
260 // half of the cells had borders, it's a data table
261 unsigned neededCellCount = validCellCount / 2;
262 if (borderedCellCount >= neededCellCount
263 || cellsWithTopBorder >= neededCellCount
264 || cellsWithBottomBorder >= neededCellCount
265 || cellsWithLeftBorder >= neededCellCount
266 || cellsWithRightBorder >= neededCellCount)
269 // half had different background colors, it's a data table
270 if (backgroundDifferenceCellCount >= neededCellCount)
273 // Check if there is an alternating row background color indicating a zebra striped style pattern.
274 if (alternatingRowColorCount > 2) {
275 Color firstColor = alternatingRowColors[0];
276 for (int k = 1; k < alternatingRowColorCount; k++) {
277 // If an odd row was the same color as the first row, its not alternating.
278 if (k % 2 == 1 && alternatingRowColors[k] == firstColor)
280 // If an even row is not the same as the first row, its not alternating.
281 if (!(k % 2) && alternatingRowColors[k] != firstColor)
290 bool AXTable::isTableExposableThroughAccessibility() const
292 // The following is a heuristic used to determine if a
293 // <table> should be exposed as an AXTable. The goal
294 // is to only show "data" tables.
299 // If the developer assigned an aria role to this, then we
300 // shouldn't expose it as a table, unless, of course, the aria
305 return isDataTable();
308 void AXTable::clearChildren()
310 AXRenderObject::clearChildren();
314 if (m_headerContainer) {
315 m_headerContainer->detachFromParent();
316 m_headerContainer = nullptr;
320 void AXTable::addChildren()
323 AXRenderObject::addChildren();
327 ASSERT(!m_haveChildren);
329 m_haveChildren = true;
330 if (!m_renderer || !m_renderer->isTable())
333 RenderTable* table = toRenderTable(m_renderer);
334 AXObjectCache* axCache = m_renderer->document().axObjectCache();
336 // Go through all the available sections to pull out the rows and add them as children.
337 table->recalcSectionsIfNeeded();
338 RenderTableSection* tableSection = table->topSection();
342 RenderTableSection* initialTableSection = tableSection;
343 while (tableSection) {
345 HashSet<AXObject*> appendedRows;
346 unsigned numRows = tableSection->numRows();
347 for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
349 RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
353 AXObject* rowObject = axCache->getOrCreate(renderRow);
354 if (!rowObject->isTableRow())
357 AXTableRow* row = toAXTableRow(rowObject);
358 // We need to check every cell for a new row, because cell spans
359 // can cause us to miss rows if we just check the first column.
360 if (appendedRows.contains(row))
363 row->setRowIndex(static_cast<int>(m_rows.size()));
365 if (!row->accessibilityIsIgnored())
366 m_children.append(row);
367 appendedRows.add(row);
370 tableSection = table->sectionBelow(tableSection, SkipEmptySections);
373 // make the columns based on the number of columns in the first body
374 unsigned length = initialTableSection->numColumns();
375 for (unsigned i = 0; i < length; ++i) {
376 AXTableColumn* column = toAXTableColumn(axCache->getOrCreate(ColumnRole));
377 column->setColumnIndex((int)i);
378 column->setParent(this);
379 m_columns.append(column);
380 if (!column->accessibilityIsIgnored())
381 m_children.append(column);
384 AXObject* headerContainerObject = headerContainer();
385 if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
386 m_children.append(headerContainerObject);
389 AXObject* AXTable::headerContainer()
391 if (m_headerContainer)
392 return m_headerContainer.get();
394 AXMockObject* tableHeader = toAXMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole));
395 tableHeader->setParent(this);
397 m_headerContainer = tableHeader;
398 return m_headerContainer.get();
401 AXObject::AccessibilityChildrenVector& AXTable::columns()
403 updateChildrenIfNecessary();
408 AXObject::AccessibilityChildrenVector& AXTable::rows()
410 updateChildrenIfNecessary();
415 void AXTable::columnHeaders(AccessibilityChildrenVector& headers)
420 updateChildrenIfNecessary();
422 unsigned colCount = m_columns.size();
423 for (unsigned k = 0; k < colCount; ++k) {
424 AXObject* header = toAXTableColumn(m_columns[k].get())->headerObject();
427 headers.append(header);
431 void AXTable::cells(AXObject::AccessibilityChildrenVector& cells)
436 updateChildrenIfNecessary();
438 int numRows = m_rows.size();
439 for (int row = 0; row < numRows; ++row) {
440 AccessibilityChildrenVector rowChildren = m_rows[row]->children();
441 cells.appendVector(rowChildren);
445 unsigned AXTable::columnCount()
447 updateChildrenIfNecessary();
449 return m_columns.size();
452 unsigned AXTable::rowCount()
454 updateChildrenIfNecessary();
456 return m_rows.size();
459 AXTableCell* AXTable::cellForColumnAndRow(unsigned column, unsigned row)
461 updateChildrenIfNecessary();
462 if (column >= columnCount() || row >= rowCount())
465 // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row.
466 for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) {
467 unsigned rowIndex = rowIndexCounter - 1;
468 AccessibilityChildrenVector children = m_rows[rowIndex]->children();
469 // Since some cells may have colspans, we have to check the actual range of each
470 // cell to determine which is the right one.
471 for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) {
472 unsigned colIndex = colIndexCounter - 1;
473 AXObject* child = children[colIndex].get();
474 ASSERT(child->isTableCell());
475 if (!child->isTableCell())
478 pair<unsigned, unsigned> columnRange;
479 pair<unsigned, unsigned> rowRange;
480 AXTableCell* tableCellChild = toAXTableCell(child);
481 tableCellChild->columnIndexRange(columnRange);
482 tableCellChild->rowIndexRange(rowRange);
484 if ((column >= columnRange.first && column < (columnRange.first + columnRange.second))
485 && (row >= rowRange.first && row < (rowRange.first + rowRange.second)))
486 return tableCellChild;
493 AccessibilityRole AXTable::roleValue() const
496 return AXRenderObject::roleValue();
501 bool AXTable::computeAccessibilityIsIgnored() const
503 AXObjectInclusion decision = defaultObjectInclusion();
504 if (decision == IncludeObject)
506 if (decision == IgnoreObject)
510 return AXRenderObject::computeAccessibilityIsIgnored();
515 String AXTable::title() const
518 return AXRenderObject::title();
524 // see if there is a caption
525 Node* tableElement = m_renderer->node();
526 if (isHTMLTableElement(tableElement)) {
527 HTMLTableCaptionElement* caption = toHTMLTableElement(tableElement)->caption();
529 title = caption->innerText();
534 title = AXRenderObject::title();
539 } // namespace WebCore