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