• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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.
16  *
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.
27  */
28 
29 #include "config.h"
30 #include "core/accessibility/AXTable.h"
31 
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/dom/ElementTraversal.h"
37 #include "core/html/HTMLCollection.h"
38 #include "core/html/HTMLTableCaptionElement.h"
39 #include "core/html/HTMLTableCellElement.h"
40 #include "core/html/HTMLTableColElement.h"
41 #include "core/html/HTMLTableElement.h"
42 #include "core/html/HTMLTableRowElement.h"
43 #include "core/html/HTMLTableRowsCollection.h"
44 #include "core/html/HTMLTableSectionElement.h"
45 #include "core/rendering/RenderTableCell.h"
46 
47 namespace blink {
48 
49 using namespace HTMLNames;
50 
AXTable(RenderObject * renderer)51 AXTable::AXTable(RenderObject* renderer)
52     : AXRenderObject(renderer)
53     , m_headerContainer(nullptr)
54     , m_isAXTable(true)
55 {
56 }
57 
~AXTable()58 AXTable::~AXTable()
59 {
60 }
61 
init()62 void AXTable::init()
63 {
64     AXRenderObject::init();
65     m_isAXTable = isTableExposableThroughAccessibility();
66 }
67 
create(RenderObject * renderer)68 PassRefPtr<AXTable> AXTable::create(RenderObject* renderer)
69 {
70     return adoptRef(new AXTable(renderer));
71 }
72 
hasARIARole() const73 bool AXTable::hasARIARole() const
74 {
75     if (!m_renderer)
76         return false;
77 
78     AccessibilityRole ariaRole = ariaRoleAttribute();
79     if (ariaRole != UnknownRole)
80         return true;
81 
82     return false;
83 }
84 
isAXTable() const85 bool AXTable::isAXTable() const
86 {
87     if (!m_renderer)
88         return false;
89 
90     return m_isAXTable;
91 }
92 
elementHasAriaRole(const Element * element)93 static bool elementHasAriaRole(const Element* element)
94 {
95     if (!element)
96         return false;
97 
98     const AtomicString& ariaRole = element->fastGetAttribute(roleAttr);
99     return (!ariaRole.isNull() && !ariaRole.isEmpty());
100 }
101 
isDataTable() const102 bool AXTable::isDataTable() const
103 {
104     if (!m_renderer || !node())
105         return false;
106 
107     // Do not consider it a data table is it has an ARIA role.
108     if (hasARIARole())
109         return false;
110 
111     // When a section of the document is contentEditable, all tables should be
112     // treated as data tables, otherwise users may not be able to work with rich
113     // text editors that allow creating and editing tables.
114     if (node() && node()->hasEditableStyle())
115         return true;
116 
117     // This employs a heuristic to determine if this table should appear.
118     // Only "data" tables should be exposed as tables.
119     // Unfortunately, there is no good way to determine the difference
120     // between a "layout" table and a "data" table.
121 
122     RenderTable* table = toRenderTable(m_renderer);
123     Node* tableNode = table->node();
124     if (!isHTMLTableElement(tableNode))
125         return false;
126 
127     // Do not consider it a data table if any of its descendants have an ARIA role.
128     HTMLTableElement* tableElement = toHTMLTableElement(tableNode);
129     if (elementHasAriaRole(tableElement->tHead()))
130         return false;
131     if (elementHasAriaRole(tableElement->tFoot()))
132         return false;
133 
134     RefPtrWillBeRawPtr<HTMLCollection> bodies = tableElement->tBodies();
135     for (unsigned bodyIndex = 0; bodyIndex < bodies->length(); ++bodyIndex) {
136         Element* bodyElement = bodies->item(bodyIndex);
137         if (elementHasAriaRole(bodyElement))
138             return false;
139     }
140 
141     RefPtrWillBeRawPtr<HTMLTableRowsCollection> rows = tableElement->rows();
142     unsigned rowCount = rows->length();
143     for (unsigned rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
144         HTMLTableRowElement* rowElement = rows->item(rowIndex);
145         if (elementHasAriaRole(rowElement))
146             return false;
147         RefPtrWillBeRawPtr<HTMLCollection> cells = rowElement->cells();
148         for (unsigned cellIndex = 0; cellIndex < cells->length(); ++cellIndex) {
149             if (elementHasAriaRole(cells->item(cellIndex)))
150                 return false;
151         }
152     }
153 
154     // If there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table
155     if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
156         return true;
157 
158     // if someone used "rules" attribute than the table should appear
159     if (!tableElement->rules().isEmpty())
160         return true;
161 
162     // if there's a colgroup or col element, it's probably a data table.
163     if (Traversal<HTMLTableColElement>::firstChild(*tableElement))
164         return true;
165 
166     // go through the cell's and check for tell-tale signs of "data" table status
167     // cells have borders, or use attributes like headers, abbr, scope or axis
168     table->recalcSectionsIfNeeded();
169     RenderTableSection* firstBody = table->firstBody();
170     if (!firstBody)
171         return false;
172 
173     int numCols = firstBody->numColumns();
174     int numRows = firstBody->numRows();
175 
176     // If there's only one cell, it's not a good AXTable candidate.
177     if (numRows == 1 && numCols == 1)
178         return false;
179 
180     // If there are at least 20 rows, we'll call it a data table.
181     if (numRows >= 20)
182         return true;
183 
184     // Store the background color of the table to check against cell's background colors.
185     RenderStyle* tableStyle = table->style();
186     if (!tableStyle)
187         return false;
188     Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor);
189 
190     // check enough of the cells to find if the table matches our criteria
191     // Criteria:
192     //   1) must have at least one valid cell (and)
193     //   2) at least half of cells have borders (or)
194     //   3) at least half of cells have different bg colors than the table, and there is cell spacing
195     unsigned validCellCount = 0;
196     unsigned borderedCellCount = 0;
197     unsigned backgroundDifferenceCellCount = 0;
198     unsigned cellsWithTopBorder = 0;
199     unsigned cellsWithBottomBorder = 0;
200     unsigned cellsWithLeftBorder = 0;
201     unsigned cellsWithRightBorder = 0;
202 
203     Color alternatingRowColors[5];
204     int alternatingRowColorCount = 0;
205 
206     int headersInFirstColumnCount = 0;
207     for (int row = 0; row < numRows; ++row) {
208 
209         int headersInFirstRowCount = 0;
210         for (int col = 0; col < numCols; ++col) {
211             RenderTableCell* cell = firstBody->primaryCellAt(row, col);
212             if (!cell)
213                 continue;
214             Node* cellNode = cell->node();
215             if (!cellNode)
216                 continue;
217 
218             if (cell->width() < 1 || cell->height() < 1)
219                 continue;
220 
221             validCellCount++;
222 
223             bool isTHCell = cellNode->hasTagName(thTag);
224             // If the first row is comprised of all <th> tags, assume it is a data table.
225             if (!row && isTHCell)
226                 headersInFirstRowCount++;
227 
228             // If the first column is comprised of all <th> tags, assume it is a data table.
229             if (!col && isTHCell)
230                 headersInFirstColumnCount++;
231 
232             // in this case, the developer explicitly assigned a "data" table attribute
233             if (isHTMLTableCellElement(*cellNode)) {
234                 HTMLTableCellElement& cellElement = toHTMLTableCellElement(*cellNode);
235                 if (!cellElement.headers().isEmpty() || !cellElement.abbr().isEmpty()
236                     || !cellElement.axis().isEmpty() || !cellElement.scope().isEmpty())
237                     return true;
238             }
239 
240             RenderStyle* renderStyle = cell->style();
241             if (!renderStyle)
242                 continue;
243 
244             // If the empty-cells style is set, we'll call it a data table.
245             if (renderStyle->emptyCells() == HIDE)
246                 return true;
247 
248             // If a cell has matching bordered sides, call it a (fully) bordered cell.
249             if ((cell->borderTop() > 0 && cell->borderBottom() > 0)
250                 || (cell->borderLeft() > 0 && cell->borderRight() > 0))
251                 borderedCellCount++;
252 
253             // Also keep track of each individual border, so we can catch tables where most
254             // cells have a bottom border, for example.
255             if (cell->borderTop() > 0)
256                 cellsWithTopBorder++;
257             if (cell->borderBottom() > 0)
258                 cellsWithBottomBorder++;
259             if (cell->borderLeft() > 0)
260                 cellsWithLeftBorder++;
261             if (cell->borderRight() > 0)
262                 cellsWithRightBorder++;
263 
264             // If the cell has a different color from the table and there is cell spacing,
265             // then it is probably a data table cell (spacing and colors take the place of borders).
266             Color cellColor = renderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
267             if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0
268                 && tableBGColor != cellColor && cellColor.alpha() != 1)
269                 backgroundDifferenceCellCount++;
270 
271             // If we've found 10 "good" cells, we don't need to keep searching.
272             if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
273                 return true;
274 
275             // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows.
276             if (row < 5 && row == alternatingRowColorCount) {
277                 RenderObject* renderRow = cell->parent();
278                 if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow())
279                     continue;
280                 RenderStyle* rowRenderStyle = renderRow->style();
281                 if (!rowRenderStyle)
282                     continue;
283                 Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
284                 alternatingRowColors[alternatingRowColorCount] = rowColor;
285                 alternatingRowColorCount++;
286             }
287         }
288 
289         if (!row && headersInFirstRowCount == numCols && numCols > 1)
290             return true;
291     }
292 
293     if (headersInFirstColumnCount == numRows && numRows > 1)
294         return true;
295 
296     // if there is less than two valid cells, it's not a data table
297     if (validCellCount <= 1)
298         return false;
299 
300     // half of the cells had borders, it's a data table
301     unsigned neededCellCount = validCellCount / 2;
302     if (borderedCellCount >= neededCellCount
303         || cellsWithTopBorder >= neededCellCount
304         || cellsWithBottomBorder >= neededCellCount
305         || cellsWithLeftBorder >= neededCellCount
306         || cellsWithRightBorder >= neededCellCount)
307         return true;
308 
309     // half had different background colors, it's a data table
310     if (backgroundDifferenceCellCount >= neededCellCount)
311         return true;
312 
313     // Check if there is an alternating row background color indicating a zebra striped style pattern.
314     if (alternatingRowColorCount > 2) {
315         Color firstColor = alternatingRowColors[0];
316         for (int k = 1; k < alternatingRowColorCount; k++) {
317             // If an odd row was the same color as the first row, its not alternating.
318             if (k % 2 == 1 && alternatingRowColors[k] == firstColor)
319                 return false;
320             // If an even row is not the same as the first row, its not alternating.
321             if (!(k % 2) && alternatingRowColors[k] != firstColor)
322                 return false;
323         }
324         return true;
325     }
326 
327     return false;
328 }
329 
isTableExposableThroughAccessibility() const330 bool AXTable::isTableExposableThroughAccessibility() const
331 {
332     // The following is a heuristic used to determine if a
333     // <table> should be exposed as an AXTable. The goal
334     // is to only show "data" tables.
335 
336     if (!m_renderer)
337         return false;
338 
339     // If the developer assigned an aria role to this, then we
340     // shouldn't expose it as a table, unless, of course, the aria
341     // role is a table.
342     if (hasARIARole())
343         return false;
344 
345     return isDataTable();
346 }
347 
clearChildren()348 void AXTable::clearChildren()
349 {
350     AXRenderObject::clearChildren();
351     m_rows.clear();
352     m_columns.clear();
353 
354     if (m_headerContainer) {
355         m_headerContainer->detachFromParent();
356         m_headerContainer = nullptr;
357     }
358 }
359 
addChildren()360 void AXTable::addChildren()
361 {
362     if (!isAXTable()) {
363         AXRenderObject::addChildren();
364         return;
365     }
366 
367     ASSERT(!m_haveChildren);
368 
369     m_haveChildren = true;
370     if (!m_renderer || !m_renderer->isTable())
371         return;
372 
373     RenderTable* table = toRenderTable(m_renderer);
374     AXObjectCache* axCache = m_renderer->document().axObjectCache();
375 
376     // Go through all the available sections to pull out the rows and add them as children.
377     table->recalcSectionsIfNeeded();
378     RenderTableSection* tableSection = table->topSection();
379     if (!tableSection)
380         return;
381 
382     RenderTableSection* initialTableSection = tableSection;
383     while (tableSection) {
384 
385         HashSet<AXObject*> appendedRows;
386         unsigned numRows = tableSection->numRows();
387         for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
388 
389             RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
390             if (!renderRow)
391                 continue;
392 
393             AXObject* rowObject = axCache->getOrCreate(renderRow);
394             if (!rowObject->isTableRow())
395                 continue;
396 
397             AXTableRow* row = toAXTableRow(rowObject);
398             // We need to check every cell for a new row, because cell spans
399             // can cause us to miss rows if we just check the first column.
400             if (appendedRows.contains(row))
401                 continue;
402 
403             row->setRowIndex(static_cast<int>(m_rows.size()));
404             m_rows.append(row);
405             if (!row->accessibilityIsIgnored())
406                 m_children.append(row);
407             appendedRows.add(row);
408         }
409 
410         tableSection = table->sectionBelow(tableSection, SkipEmptySections);
411     }
412 
413     // make the columns based on the number of columns in the first body
414     unsigned length = initialTableSection->numColumns();
415     for (unsigned i = 0; i < length; ++i) {
416         AXTableColumn* column = toAXTableColumn(axCache->getOrCreate(ColumnRole));
417         column->setColumnIndex((int)i);
418         column->setParent(this);
419         m_columns.append(column);
420         if (!column->accessibilityIsIgnored())
421             m_children.append(column);
422     }
423 
424     AXObject* headerContainerObject = headerContainer();
425     if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
426         m_children.append(headerContainerObject);
427 }
428 
headerContainer()429 AXObject* AXTable::headerContainer()
430 {
431     if (m_headerContainer)
432         return m_headerContainer.get();
433 
434     AXMockObject* tableHeader = toAXMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole));
435     tableHeader->setParent(this);
436 
437     m_headerContainer = tableHeader;
438     return m_headerContainer.get();
439 }
440 
columns()441 AXObject::AccessibilityChildrenVector& AXTable::columns()
442 {
443     updateChildrenIfNecessary();
444 
445     return m_columns;
446 }
447 
rows()448 AXObject::AccessibilityChildrenVector& AXTable::rows()
449 {
450     updateChildrenIfNecessary();
451 
452     return m_rows;
453 }
454 
columnHeaders(AccessibilityChildrenVector & headers)455 void AXTable::columnHeaders(AccessibilityChildrenVector& headers)
456 {
457     if (!m_renderer)
458         return;
459 
460     updateChildrenIfNecessary();
461 
462     unsigned colCount = m_columns.size();
463     for (unsigned k = 0; k < colCount; ++k) {
464         AXObject* header = toAXTableColumn(m_columns[k].get())->headerObject();
465         if (!header)
466             continue;
467         headers.append(header);
468     }
469 }
470 
cells(AXObject::AccessibilityChildrenVector & cells)471 void AXTable::cells(AXObject::AccessibilityChildrenVector& cells)
472 {
473     if (!m_renderer)
474         return;
475 
476     updateChildrenIfNecessary();
477 
478     int numRows = m_rows.size();
479     for (int row = 0; row < numRows; ++row) {
480         AccessibilityChildrenVector rowChildren = m_rows[row]->children();
481         cells.appendVector(rowChildren);
482     }
483 }
484 
columnCount()485 unsigned AXTable::columnCount()
486 {
487     updateChildrenIfNecessary();
488 
489     return m_columns.size();
490 }
491 
rowCount()492 unsigned AXTable::rowCount()
493 {
494     updateChildrenIfNecessary();
495 
496     return m_rows.size();
497 }
498 
cellForColumnAndRow(unsigned column,unsigned row)499 AXTableCell* AXTable::cellForColumnAndRow(unsigned column, unsigned row)
500 {
501     updateChildrenIfNecessary();
502     if (column >= columnCount() || row >= rowCount())
503         return 0;
504 
505     // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row.
506     for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) {
507         unsigned rowIndex = rowIndexCounter - 1;
508         AccessibilityChildrenVector children = m_rows[rowIndex]->children();
509         // Since some cells may have colspans, we have to check the actual range of each
510         // cell to determine which is the right one.
511         for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) {
512             unsigned colIndex = colIndexCounter - 1;
513             AXObject* child = children[colIndex].get();
514             ASSERT(child->isTableCell());
515             if (!child->isTableCell())
516                 continue;
517 
518             pair<unsigned, unsigned> columnRange;
519             pair<unsigned, unsigned> rowRange;
520             AXTableCell* tableCellChild = toAXTableCell(child);
521             tableCellChild->columnIndexRange(columnRange);
522             tableCellChild->rowIndexRange(rowRange);
523 
524             if ((column >= columnRange.first && column < (columnRange.first + columnRange.second))
525                 && (row >= rowRange.first && row < (rowRange.first + rowRange.second)))
526                 return tableCellChild;
527         }
528     }
529 
530     return 0;
531 }
532 
roleValue() const533 AccessibilityRole AXTable::roleValue() const
534 {
535     if (!isAXTable())
536         return AXRenderObject::roleValue();
537 
538     return TableRole;
539 }
540 
computeAccessibilityIsIgnored() const541 bool AXTable::computeAccessibilityIsIgnored() const
542 {
543     AXObjectInclusion decision = defaultObjectInclusion();
544     if (decision == IncludeObject)
545         return false;
546     if (decision == IgnoreObject)
547         return true;
548 
549     if (!isAXTable())
550         return AXRenderObject::computeAccessibilityIsIgnored();
551 
552     return false;
553 }
554 
title() const555 String AXTable::title() const
556 {
557     if (!isAXTable())
558         return AXRenderObject::title();
559 
560     String title;
561     if (!m_renderer)
562         return title;
563 
564     // see if there is a caption
565     Node* tableElement = m_renderer->node();
566     if (isHTMLTableElement(tableElement)) {
567         HTMLTableCaptionElement* caption = toHTMLTableElement(tableElement)->caption();
568         if (caption)
569             title = caption->innerText();
570     }
571 
572     // try the standard
573     if (title.isEmpty())
574         title = AXRenderObject::title();
575 
576     return title;
577 }
578 
579 } // namespace blink
580