9ac955d06ff6d3fd9484df34d9729dc02be99274
[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 || !isHTMLTableElement(tableNode))
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 = toHTMLTableElement(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 && isHTMLTableElement(tableNode);
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     table->recalcSectionsIfNeeded();
349     RenderTableSection* tableSection = table->topSection();
350     if (!tableSection)
351         return;
352     
353     unsigned maxColumnCount = 0;
354     while (tableSection) {
355         
356         HashSet<AccessibilityObject*> appendedRows;
357         unsigned numRows = tableSection->numRows();
358         for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
359             
360             RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
361             if (!renderRow)
362                 continue;
363             
364             AccessibilityObject* rowObject = axCache->getOrCreate(renderRow);
365             if (!rowObject->isTableRow())
366                 continue;
367             
368             AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject);
369             // We need to check every cell for a new row, because cell spans
370             // can cause us to miss rows if we just check the first column.
371             if (appendedRows.contains(row))
372                 continue;
373             
374             row->setRowIndex(static_cast<int>(m_rows.size()));
375             m_rows.append(row);
376             if (!row->accessibilityIsIgnored())
377                 m_children.append(row);
378 #if PLATFORM(GTK)
379             else
380                 m_children.appendVector(row->children());
381 #endif
382             appendedRows.add(row);
383         }
384     
385         maxColumnCount = max(tableSection->numColumns(), maxColumnCount);
386         tableSection = table->sectionBelow(tableSection, SkipEmptySections);
387     }
388     
389     // make the columns based on the number of columns in the first body
390     unsigned length = maxColumnCount;
391     for (unsigned i = 0; i < length; ++i) {
392         AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole));
393         column->setColumnIndex((int)i);
394         column->setParent(this);
395         m_columns.append(column);
396         if (!column->accessibilityIsIgnored())
397             m_children.append(column);
398     }
399     
400     AccessibilityObject* headerContainerObject = headerContainer();
401     if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
402         m_children.append(headerContainerObject);
403 }
404     
405 AccessibilityObject* AccessibilityTable::headerContainer()
406 {
407     if (m_headerContainer)
408         return m_headerContainer.get();
409     
410     AccessibilityMockObject* tableHeader = toAccessibilityMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole));
411     tableHeader->setParent(this);
412
413     m_headerContainer = tableHeader;
414     return m_headerContainer.get();
415 }
416
417 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
418 {
419     updateChildrenIfNecessary();
420         
421     return m_columns;
422 }
423
424 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
425 {
426     updateChildrenIfNecessary();
427     
428     return m_rows;
429 }
430     
431 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
432 {
433     if (!m_renderer)
434         return;
435     
436     updateChildrenIfNecessary();
437     
438     unsigned rowCount = m_rows.size();
439     for (unsigned k = 0; k < rowCount; ++k) {
440         AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject();
441         if (!header)
442             continue;
443         headers.append(header);
444     }
445 }
446
447 void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
448 {
449     if (!m_renderer)
450         return;
451     
452     updateChildrenIfNecessary();
453     
454     unsigned colCount = m_columns.size();
455     for (unsigned k = 0; k < colCount; ++k) {
456         AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject();
457         if (!header)
458             continue;
459         headers.append(header);
460     }
461 }
462     
463 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
464 {
465     if (!m_renderer)
466         return;
467     
468     updateChildrenIfNecessary();
469     
470     for (size_t row = 0; row < m_rows.size(); ++row)
471         cells.appendVector(m_rows[row]->children());
472 }
473     
474 unsigned AccessibilityTable::columnCount()
475 {
476     updateChildrenIfNecessary();
477     
478     return m_columns.size();    
479 }
480     
481 unsigned AccessibilityTable::rowCount()
482 {
483     updateChildrenIfNecessary();
484     
485     return m_rows.size();
486 }
487
488 int AccessibilityTable::tableLevel() const
489 {
490     int level = 0;
491     for (AccessibilityObject* obj = static_cast<AccessibilityObject*>(const_cast<AccessibilityTable*>(this)); obj; obj = obj->parentObject()) {
492         if (obj->isAccessibilityTable())
493             ++level;
494     }
495     
496     return level;
497 }
498
499 AccessibilityTableCell* AccessibilityTable::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             AccessibilityObject* 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             AccessibilityTableCell* tableCellChild = static_cast<AccessibilityTableCell*>(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
533 AccessibilityRole AccessibilityTable::roleValue() const
534 {
535     if (!isAccessibilityTable())
536         return AccessibilityRenderObject::roleValue();
537
538     return TableRole;
539 }
540     
541 bool AccessibilityTable::computeAccessibilityIsIgnored() const
542 {
543     AccessibilityObjectInclusion decision = defaultObjectInclusion();
544     if (decision == IncludeObject)
545         return false;
546     if (decision == IgnoreObject)
547         return true;
548     
549     if (!isAccessibilityTable())
550         return AccessibilityRenderObject::computeAccessibilityIsIgnored();
551         
552     return false;
553 }
554     
555 String AccessibilityTable::title() const
556 {
557     if (!isAccessibilityTable())
558         return AccessibilityRenderObject::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 (tableElement && 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 = AccessibilityRenderObject::title();
575     
576     return title;
577 }
578
579 } // namespace WebCore