b5adbf5bccee622208f5fba68a20f2d748730984
[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 #include <wtf/Deque.h>
48
49 namespace WebCore {
50
51 using namespace HTMLNames;
52
53 AccessibilityTable::AccessibilityTable(RenderObject* renderer)
54     : AccessibilityRenderObject(renderer)
55     , m_headerContainer(nullptr)
56     , m_isExposableThroughAccessibility(true)
57 {
58 }
59
60 AccessibilityTable::~AccessibilityTable() = default;
61
62 void AccessibilityTable::init()
63 {
64     AccessibilityRenderObject::init();
65     m_isExposableThroughAccessibility = computeIsTableExposableThroughAccessibility();
66 }
67
68 Ref<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 != AccessibilityRole::Unknown)
80         return true;
81
82     return false;
83 }
84
85 bool AccessibilityTable::isExposableThroughAccessibility() const
86 {
87     if (!m_renderer)
88         return false;
89     
90     return m_isExposableThroughAccessibility;
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     // Try to find the table element, when the AccessibilityTable is mapped to an anonymous table renderer.
102     auto* firstChild = table.firstChild();
103     if (!firstChild || !firstChild->node())
104         return nullptr;
105     if (is<HTMLTableElement>(*firstChild->node()))
106         return downcast<HTMLTableElement>(firstChild->node());
107     // FIXME: This might find an unrelated parent table element.
108     return ancestorsOfType<HTMLTableElement>(*(firstChild->node())).first();
109 }
110     
111 bool AccessibilityTable::isDataTable() const
112 {
113     if (!m_renderer)
114         return false;
115
116     // Do not consider it a data table is it has an ARIA role.
117     if (hasARIARole())
118         return false;
119
120     // When a section of the document is contentEditable, all tables should be
121     // treated as data tables, otherwise users may not be able to work with rich
122     // text editors that allow creating and editing tables.
123     if (node() && node()->hasEditableStyle())
124         return true;
125
126     if (!is<RenderTable>(*m_renderer))
127         return false;
128
129     // This employs a heuristic to determine if this table should appear.
130     // Only "data" tables should be exposed as tables.
131     // Unfortunately, there is no good way to determine the difference
132     // between a "layout" table and a "data" table.
133     if (HTMLTableElement* tableElement = this->tableElement()) {
134         // If there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table.
135         if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
136             return true;
137         
138         // If someone used "rules" attribute than the table should appear.
139         if (!tableElement->rules().isEmpty())
140             return true;
141
142         // If there's a colgroup or col element, it's probably a data table.
143         for (const auto& child : childrenOfType<HTMLElement>(*tableElement)) {
144             if (child.hasTagName(colTag) || child.hasTagName(colgroupTag))
145                 return true;
146         }
147     }
148     
149     // The following checks should only apply if this is a real <table> element.
150     if (!hasTagName(tableTag))
151         return false;
152     
153     // If the author has used ARIA to specify a valid column or row count, assume they
154     // want us to treat the table as a data table.
155     int ariaColumnCount = getAttribute(aria_colcountAttr).toInt();
156     if (ariaColumnCount == -1 || ariaColumnCount > 0)
157         return true;
158
159     int ariaRowCount = getAttribute(aria_rowcountAttr).toInt();
160     if (ariaRowCount == -1 || ariaRowCount > 0)
161         return true;
162
163     RenderTable& table = downcast<RenderTable>(*m_renderer);
164     // go through the cell's and check for tell-tale signs of "data" table status
165     // cells have borders, or use attributes like headers, abbr, scope or axis
166     table.recalcSectionsIfNeeded();
167     RenderTableSection* firstBody = table.firstBody();
168     if (!firstBody)
169         return false;
170     
171     int numCols = firstBody->numColumns();
172     int numRows = firstBody->numRows();
173     
174     // If there are at least 20 rows, we'll call it a data table.
175     if (numRows >= 20)
176         return true;
177     
178     // Store the background color of the table to check against cell's background colors.
179     const RenderStyle& tableStyle = table.style();
180     Color tableBGColor = tableStyle.visitedDependentColor(CSSPropertyBackgroundColor);
181     
182     // check enough of the cells to find if the table matches our criteria
183     // Criteria: 
184     //   1) must have at least one valid cell (and)
185     //   2) at least half of cells have borders (or)
186     //   3) at least half of cells have different bg colors than the table, and there is cell spacing (or)
187     //   4) the valid cell has an ARIA cell-related property
188     unsigned validCellCount = 0;
189     unsigned borderedCellCount = 0;
190     unsigned backgroundDifferenceCellCount = 0;
191     unsigned cellsWithTopBorder = 0;
192     unsigned cellsWithBottomBorder = 0;
193     unsigned cellsWithLeftBorder = 0;
194     unsigned cellsWithRightBorder = 0;
195     
196     Color alternatingRowColors[5];
197     int alternatingRowColorCount = 0;
198     
199     int headersInFirstColumnCount = 0;
200     for (int row = 0; row < numRows; ++row) {
201     
202         int headersInFirstRowCount = 0;
203         for (int col = 0; col < numCols; ++col) {    
204             RenderTableCell* cell = firstBody->primaryCellAt(row, col);
205             if (!cell)
206                 continue;
207
208             Element* cellElement = cell->element();
209             if (!cellElement)
210                 continue;
211             
212             if (cell->width() < 1 || cell->height() < 1)
213                 continue;
214             
215             ++validCellCount;
216             
217             bool isTHCell = cellElement->hasTagName(thTag);
218             // If the first row is comprised of all <th> tags, assume it is a data table.
219             if (!row && isTHCell)
220                 ++headersInFirstRowCount;
221
222             // If the first column is comprised of all <th> tags, assume it is a data table.
223             if (!col && isTHCell)
224                 ++headersInFirstColumnCount;
225             
226             // In this case, the developer explicitly assigned a "data" table attribute.
227             if (is<HTMLTableCellElement>(*cellElement)) {
228                 HTMLTableCellElement& tableCellElement = downcast<HTMLTableCellElement>(*cellElement);
229                 if (!tableCellElement.headers().isEmpty() || !tableCellElement.abbr().isEmpty()
230                     || !tableCellElement.axis().isEmpty() || !tableCellElement.scope().isEmpty())
231                     return true;
232             }
233
234             // If the author has used ARIA to specify a valid column or row index, assume they want us
235             // to treat the table as a data table.
236             int ariaColumnIndex = cellElement->attributeWithoutSynchronization(aria_colindexAttr).toInt();
237             if (ariaColumnIndex >= 1)
238                 return true;
239
240             int ariaRowIndex = cellElement->attributeWithoutSynchronization(aria_rowindexAttr).toInt();
241             if (ariaRowIndex >= 1)
242                 return true;
243
244             if (auto cellParentElement = cellElement->parentElement()) {
245                 ariaRowIndex = cellParentElement->attributeWithoutSynchronization(aria_rowindexAttr).toInt();
246                 if (ariaRowIndex >= 1)
247                     return true;
248             }
249
250             // If the author has used ARIA to specify a column or row span, we're supposed to ignore
251             // the value for the purposes of exposing the span. But assume they want us to treat the
252             // table as a data table.
253             int ariaColumnSpan = cellElement->attributeWithoutSynchronization(aria_colspanAttr).toInt();
254             if (ariaColumnSpan >= 1)
255                 return true;
256
257             int ariaRowSpan = cellElement->attributeWithoutSynchronization(aria_rowspanAttr).toInt();
258             if (ariaRowSpan >= 1)
259                 return true;
260
261             const RenderStyle& renderStyle = cell->style();
262
263             // If the empty-cells style is set, we'll call it a data table.
264             if (renderStyle.emptyCells() == HIDE)
265                 return true;
266
267             // If a cell has matching bordered sides, call it a (fully) bordered cell.
268             if ((cell->borderTop() > 0 && cell->borderBottom() > 0)
269                 || (cell->borderLeft() > 0 && cell->borderRight() > 0))
270                 ++borderedCellCount;
271
272             // Also keep track of each individual border, so we can catch tables where most
273             // cells have a bottom border, for example.
274             if (cell->borderTop() > 0)
275                 ++cellsWithTopBorder;
276             if (cell->borderBottom() > 0)
277                 ++cellsWithBottomBorder;
278             if (cell->borderLeft() > 0)
279                 ++cellsWithLeftBorder;
280             if (cell->borderRight() > 0)
281                 ++cellsWithRightBorder;
282             
283             // If the cell has a different color from the table and there is cell spacing,
284             // then it is probably a data table cell (spacing and colors take the place of borders).
285             Color cellColor = renderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
286             if (table.hBorderSpacing() > 0 && table.vBorderSpacing() > 0
287                 && tableBGColor != cellColor && cellColor.alpha() != 1)
288                 ++backgroundDifferenceCellCount;
289             
290             // If we've found 10 "good" cells, we don't need to keep searching.
291             if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
292                 return true;
293             
294             // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows.
295             if (row < 5 && row == alternatingRowColorCount) {
296                 RenderElement* renderRow = cell->parent();
297                 if (!is<RenderTableRow>(renderRow))
298                     continue;
299                 const RenderStyle& rowRenderStyle = renderRow->style();
300                 Color rowColor = rowRenderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
301                 alternatingRowColors[alternatingRowColorCount] = rowColor;
302                 ++alternatingRowColorCount;
303             }
304         }
305         
306         if (!row && headersInFirstRowCount == numCols && numCols > 1)
307             return true;
308     }
309
310     if (headersInFirstColumnCount == numRows && numRows > 1)
311         return true;
312     
313     // if there is less than two valid cells, it's not a data table
314     if (validCellCount <= 1)
315         return false;
316     
317     // half of the cells had borders, it's a data table
318     unsigned neededCellCount = validCellCount / 2;
319     if (borderedCellCount >= neededCellCount
320         || cellsWithTopBorder >= neededCellCount
321         || cellsWithBottomBorder >= neededCellCount
322         || cellsWithLeftBorder >= neededCellCount
323         || cellsWithRightBorder >= neededCellCount)
324         return true;
325     
326     // half had different background colors, it's a data table
327     if (backgroundDifferenceCellCount >= neededCellCount)
328         return true;
329
330     // Check if there is an alternating row background color indicating a zebra striped style pattern.
331     if (alternatingRowColorCount > 2) {
332         Color firstColor = alternatingRowColors[0];
333         for (int k = 1; k < alternatingRowColorCount; k++) {
334             // If an odd row was the same color as the first row, its not alternating.
335             if (k % 2 == 1 && alternatingRowColors[k] == firstColor)
336                 return false;
337             // If an even row is not the same as the first row, its not alternating.
338             if (!(k % 2) && alternatingRowColors[k] != firstColor)
339                 return false;
340         }
341         return true;
342     }
343     
344     return false;
345 }
346     
347 bool AccessibilityTable::computeIsTableExposableThroughAccessibility() const
348 {
349     // The following is a heuristic used to determine if a
350     // <table> should be exposed as an AXTable. The goal
351     // is to only show "data" tables.
352
353     if (!m_renderer)
354         return false;
355
356     // If the developer assigned an aria role to this, then we
357     // shouldn't expose it as a table, unless, of course, the aria
358     // role is a table.
359     if (hasARIARole())
360         return false;
361
362     return isDataTable();
363 }
364
365 void AccessibilityTable::clearChildren()
366 {
367     AccessibilityRenderObject::clearChildren();
368     m_rows.clear();
369     m_columns.clear();
370
371     if (m_headerContainer) {
372         m_headerContainer->detachFromParent();
373         m_headerContainer = nullptr;
374     }
375 }
376
377 void AccessibilityTable::addChildren()
378 {
379     if (!isExposableThroughAccessibility()) {
380         AccessibilityRenderObject::addChildren();
381         return;
382     }
383     
384     ASSERT(!m_haveChildren); 
385     
386     m_haveChildren = true;
387     if (!is<RenderTable>(renderer()))
388         return;
389     
390     RenderTable& table = downcast<RenderTable>(*m_renderer);
391     // Go through all the available sections to pull out the rows and add them as children.
392     table.recalcSectionsIfNeeded();
393     
394     if (HTMLTableElement* tableElement = this->tableElement()) {
395         if (HTMLTableCaptionElement* caption = tableElement->caption()) {
396             AccessibilityObject* axCaption = axObjectCache()->getOrCreate(caption);
397             if (axCaption && !axCaption->accessibilityIsIgnored())
398                 m_children.append(axCaption);
399         }
400     }
401
402     unsigned maxColumnCount = 0;
403     RenderTableSection* footer = table.footer();
404     
405     for (RenderTableSection* tableSection = table.topSection(); tableSection; tableSection = table.sectionBelow(tableSection, SkipEmptySections)) {
406         if (tableSection == footer)
407             continue;
408         addChildrenFromSection(tableSection, maxColumnCount);
409     }
410     
411     // Process the footer last, in case it was ordered earlier in the DOM.
412     if (footer)
413         addChildrenFromSection(footer, maxColumnCount);
414     
415     AXObjectCache* axCache = m_renderer->document().axObjectCache();
416     // make the columns based on the number of columns in the first body
417     unsigned length = maxColumnCount;
418     for (unsigned i = 0; i < length; ++i) {
419         auto& column = downcast<AccessibilityTableColumn>(*axCache->getOrCreate(AccessibilityRole::Column));
420         column.setColumnIndex((int)i);
421         column.setParent(this);
422         m_columns.append(&column);
423         if (!column.accessibilityIsIgnored())
424             m_children.append(&column);
425     }
426     
427     AccessibilityObject* headerContainerObject = headerContainer();
428     if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
429         m_children.append(headerContainerObject);
430
431     // Sometimes the cell gets the wrong role initially because it is created before the parent
432     // determines whether it is an accessibility table. Iterate all the cells and allow them to
433     // update their roles now that the table knows its status.
434     // see bug: https://bugs.webkit.org/show_bug.cgi?id=147001
435     for (const auto& row : m_rows) {
436         for (const auto& cell : row->children())
437             cell->updateAccessibilityRole();
438     }
439
440 }
441
442 void AccessibilityTable::addTableCellChild(AccessibilityObject* rowObject, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount)
443 {
444     if (!rowObject || !is<AccessibilityTableRow>(*rowObject))
445         return;
446
447     auto& row = downcast<AccessibilityTableRow>(*rowObject);
448     // We need to check every cell for a new row, because cell spans
449     // can cause us to miss rows if we just check the first column.
450     if (appendedRows.contains(&row))
451         return;
452     
453     row.setRowIndex(static_cast<int>(m_rows.size()));
454     m_rows.append(&row);
455     if (!row.accessibilityIsIgnored())
456         m_children.append(&row);
457     appendedRows.add(&row);
458         
459     // store the maximum number of columns
460     unsigned rowCellCount = row.children().size();
461     if (rowCellCount > columnCount)
462         columnCount = rowCellCount;
463 }
464
465 void AccessibilityTable::addChildrenFromSection(RenderTableSection* tableSection, unsigned& maxColumnCount)
466 {
467     ASSERT(tableSection);
468     if (!tableSection)
469         return;
470     
471     AXObjectCache* axCache = m_renderer->document().axObjectCache();
472     HashSet<AccessibilityObject*> appendedRows;
473     unsigned numRows = tableSection->numRows();
474     for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
475         
476         RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
477         if (!renderRow)
478             continue;
479         
480         AccessibilityObject& rowObject = *axCache->getOrCreate(renderRow);
481         
482         // If the row is anonymous, we should dive deeper into the descendants to try to find a valid row.
483         if (renderRow->isAnonymous()) {
484             Deque<AccessibilityObject*> queue;
485             queue.append(&rowObject);
486             
487             while (!queue.isEmpty()) {
488                 AccessibilityObject* obj = queue.takeFirst();
489                 if (obj->node() && is<AccessibilityTableRow>(*obj)) {
490                     addTableCellChild(obj, appendedRows, maxColumnCount);
491                     continue;
492                 }
493                 for (auto* child = obj->firstChild(); child; child = child->nextSibling())
494                     queue.append(child);
495             }
496         } else
497             addTableCellChild(&rowObject, appendedRows, maxColumnCount);
498     }
499     
500     maxColumnCount = std::max(tableSection->numColumns(), maxColumnCount);
501 }
502     
503 AccessibilityObject* AccessibilityTable::headerContainer()
504 {
505     if (m_headerContainer)
506         return m_headerContainer.get();
507     
508     auto& tableHeader = downcast<AccessibilityMockObject>(*axObjectCache()->getOrCreate(AccessibilityRole::TableHeaderContainer));
509     tableHeader.setParent(this);
510
511     m_headerContainer = &tableHeader;
512     return m_headerContainer.get();
513 }
514
515 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
516 {
517     updateChildrenIfNecessary();
518         
519     return m_columns;
520 }
521
522 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
523 {
524     updateChildrenIfNecessary();
525     
526     return m_rows;
527 }
528
529 void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
530 {
531     if (!m_renderer)
532         return;
533     
534     updateChildrenIfNecessary();
535     
536     // Sometimes m_columns can be reset during the iteration, we cache it here to be safe.
537     AccessibilityChildrenVector columnsCopy = m_columns;
538     
539     for (const auto& column : columnsCopy) {
540         if (AccessibilityObject* header = downcast<AccessibilityTableColumn>(*column).headerObject())
541             headers.append(header);
542     }
543 }
544
545 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
546 {
547     if (!m_renderer)
548         return;
549     
550     updateChildrenIfNecessary();
551     
552     // Sometimes m_rows can be reset during the iteration, we cache it here to be safe.
553     AccessibilityChildrenVector rowsCopy = m_rows;
554     
555     for (const auto& row : rowsCopy) {
556         if (AccessibilityObject* header = downcast<AccessibilityTableRow>(*row).headerObject())
557             headers.append(header);
558     }
559 }
560
561 void AccessibilityTable::visibleRows(AccessibilityChildrenVector& rows)
562 {
563     if (!m_renderer)
564         return;
565     
566     updateChildrenIfNecessary();
567     
568     for (const auto& row : m_rows) {
569         if (row && !row->isOffScreen())
570             rows.append(row);
571     }
572 }
573
574 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
575 {
576     if (!m_renderer)
577         return;
578     
579     updateChildrenIfNecessary();
580     
581     for (const auto& row : m_rows)
582         cells.appendVector(row->children());
583 }
584     
585 unsigned AccessibilityTable::columnCount()
586 {
587     updateChildrenIfNecessary();
588     
589     return m_columns.size();    
590 }
591     
592 unsigned AccessibilityTable::rowCount()
593 {
594     updateChildrenIfNecessary();
595     
596     return m_rows.size();
597 }
598
599 int AccessibilityTable::tableLevel() const
600 {
601     int level = 0;
602     for (AccessibilityObject* obj = static_cast<AccessibilityObject*>(const_cast<AccessibilityTable*>(this)); obj; obj = obj->parentObject()) {
603         if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
604             ++level;
605     }
606     
607     return level;
608 }
609
610 AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row)
611 {
612     updateChildrenIfNecessary();
613     if (column >= columnCount() || row >= rowCount())
614         return nullptr;
615     
616     // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row.
617     for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) {
618         unsigned rowIndex = rowIndexCounter - 1;
619         const auto& children = m_rows[rowIndex]->children();
620         // Since some cells may have colspans, we have to check the actual range of each
621         // cell to determine which is the right one.
622         for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) {
623             unsigned colIndex = colIndexCounter - 1;
624             AccessibilityObject* child = children[colIndex].get();
625             ASSERT(is<AccessibilityTableCell>(*child));
626             if (!is<AccessibilityTableCell>(*child))
627                 continue;
628             
629             std::pair<unsigned, unsigned> columnRange;
630             std::pair<unsigned, unsigned> rowRange;
631             auto& tableCellChild = downcast<AccessibilityTableCell>(*child);
632             tableCellChild.columnIndexRange(columnRange);
633             tableCellChild.rowIndexRange(rowRange);
634             
635             if ((column >= columnRange.first && column < (columnRange.first + columnRange.second))
636                 && (row >= rowRange.first && row < (rowRange.first + rowRange.second)))
637                 return &tableCellChild;
638         }
639     }
640     
641     return nullptr;
642 }
643
644 AccessibilityRole AccessibilityTable::roleValue() const
645 {
646     if (!isExposableThroughAccessibility())
647         return AccessibilityRenderObject::roleValue();
648     
649     AccessibilityRole ariaRole = ariaRoleAttribute();
650     if (ariaRole == AccessibilityRole::Grid || ariaRole == AccessibilityRole::TreeGrid)
651         return ariaRole;
652
653     return AccessibilityRole::Table;
654 }
655     
656 bool AccessibilityTable::computeAccessibilityIsIgnored() const
657 {
658     AccessibilityObjectInclusion decision = defaultObjectInclusion();
659     if (decision == AccessibilityObjectInclusion::IncludeObject)
660         return false;
661     if (decision == AccessibilityObjectInclusion::IgnoreObject)
662         return true;
663     
664     if (!isExposableThroughAccessibility())
665         return AccessibilityRenderObject::computeAccessibilityIsIgnored();
666         
667     return false;
668 }
669
670 void AccessibilityTable::titleElementText(Vector<AccessibilityText>& textOrder) const
671 {
672     String title = this->title();
673     if (!title.isEmpty())
674         textOrder.append(AccessibilityText(title, AccessibilityTextSource::LabelByElement));
675 }
676
677 String AccessibilityTable::title() const
678 {
679     if (!isExposableThroughAccessibility())
680         return AccessibilityRenderObject::title();
681     
682     String title;
683     if (!m_renderer)
684         return title;
685     
686     // see if there is a caption
687     Node* tableElement = m_renderer->node();
688     if (is<HTMLTableElement>(tableElement)) {
689         if (HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*tableElement).caption())
690             title = caption->innerText();
691     }
692     
693     // try the standard 
694     if (title.isEmpty())
695         title = AccessibilityRenderObject::title();
696     
697     return title;
698 }
699
700 int AccessibilityTable::ariaColumnCount() const
701 {
702     const AtomicString& colCountValue = getAttribute(aria_colcountAttr);
703     
704     int colCountInt = colCountValue.toInt();
705     // The ARIA spec states, "Authors must set the value of aria-colcount to an integer equal to the
706     // number of columns in the full table. If the total number of columns is unknown, authors must
707     // set the value of aria-colcount to -1 to indicate that the value should not be calculated by
708     // the user agent." If we have a valid value, make it available to platforms.
709     if (colCountInt == -1 || colCountInt >= (int)m_columns.size())
710         return colCountInt;
711     
712     return 0;
713 }
714
715 int AccessibilityTable::ariaRowCount() const
716 {
717     const AtomicString& rowCountValue = getAttribute(aria_rowcountAttr);
718     
719     int rowCountInt = rowCountValue.toInt();
720     // The ARIA spec states, "Authors must set the value of aria-rowcount to an integer equal to the
721     // number of rows in the full table. If the total number of rows is unknown, authors must set
722     // the value of aria-rowcount to -1 to indicate that the value should not be calculated by the
723     // user agent." If we have a valid value, make it available to platforms.
724     if (rowCountInt == -1 || rowCountInt >= (int)m_rows.size())
725         return rowCountInt;
726     
727     return 0;
728 }
729
730 } // namespace WebCore