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