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