AX: CrashTracer: com.apple.WebKit.WebContent at com.apple.WebCore: WebCore::Accessibi...
[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()
61 {
62 }
63
64 void AccessibilityTable::init()
65 {
66     AccessibilityRenderObject::init();
67     m_isExposableThroughAccessibility = computeIsTableExposableThroughAccessibility();
68 }
69
70 Ref<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer)
71 {
72     return adoptRef(*new AccessibilityTable(renderer));
73 }
74
75 bool AccessibilityTable::hasARIARole() const
76 {
77     if (!m_renderer)
78         return false;
79     
80     AccessibilityRole ariaRole = ariaRoleAttribute();
81     if (ariaRole != UnknownRole)
82         return true;
83
84     return false;
85 }
86
87 bool AccessibilityTable::isExposableThroughAccessibility() const
88 {
89     if (!m_renderer)
90         return false;
91     
92     return m_isExposableThroughAccessibility;
93 }
94
95 HTMLTableElement* AccessibilityTable::tableElement() const
96 {
97     if (!is<RenderTable>(*m_renderer))
98         return nullptr;
99     
100     RenderTable& table = downcast<RenderTable>(*m_renderer);
101     if (is<HTMLTableElement>(table.element()))
102         return downcast<HTMLTableElement>(table.element());
103     
104     table.forceSectionsRecalc();
105
106     // If the table has a display:table-row-group, then the RenderTable does not have a pointer to it's HTMLTableElement.
107     // We can instead find it by asking the firstSection for its parent.
108     RenderTableSection* firstBody = table.firstBody();
109     if (!firstBody || !firstBody->element())
110         return nullptr;
111     
112     return ancestorsOfType<HTMLTableElement>(*(firstBody->element())).first();
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     // The following checks should only apply if this is a real <table> element.
154     if (!hasTagName(tableTag))
155         return false;
156     
157     RenderTable& table = downcast<RenderTable>(*m_renderer);
158     // go through the cell's and check for tell-tale signs of "data" table status
159     // cells have borders, or use attributes like headers, abbr, scope or axis
160     table.recalcSectionsIfNeeded();
161     RenderTableSection* firstBody = table.firstBody();
162     if (!firstBody)
163         return false;
164     
165     int numCols = firstBody->numColumns();
166     int numRows = firstBody->numRows();
167     
168     // If there's only one cell, it's not a good AXTable candidate.
169     if (numRows == 1 && numCols == 1)
170         return false;
171
172     // If there are at least 20 rows, we'll call it a data table.
173     if (numRows >= 20)
174         return true;
175     
176     // Store the background color of the table to check against cell's background colors.
177     const RenderStyle& tableStyle = table.style();
178     Color tableBGColor = tableStyle.visitedDependentColor(CSSPropertyBackgroundColor);
179     
180     // check enough of the cells to find if the table matches our criteria
181     // Criteria: 
182     //   1) must have at least one valid cell (and)
183     //   2) at least half of cells have borders (or)
184     //   3) at least half of cells have different bg colors than the table, and there is cell spacing
185     unsigned validCellCount = 0;
186     unsigned borderedCellCount = 0;
187     unsigned backgroundDifferenceCellCount = 0;
188     unsigned cellsWithTopBorder = 0;
189     unsigned cellsWithBottomBorder = 0;
190     unsigned cellsWithLeftBorder = 0;
191     unsigned cellsWithRightBorder = 0;
192     
193     Color alternatingRowColors[5];
194     int alternatingRowColorCount = 0;
195     
196     int headersInFirstColumnCount = 0;
197     for (int row = 0; row < numRows; ++row) {
198     
199         int headersInFirstRowCount = 0;
200         for (int col = 0; col < numCols; ++col) {    
201             RenderTableCell* cell = firstBody->primaryCellAt(row, col);
202             if (!cell)
203                 continue;
204
205             Element* cellElement = cell->element();
206             if (!cellElement)
207                 continue;
208             
209             if (cell->width() < 1 || cell->height() < 1)
210                 continue;
211             
212             ++validCellCount;
213             
214             bool isTHCell = cellElement->hasTagName(thTag);
215             // If the first row is comprised of all <th> tags, assume it is a data table.
216             if (!row && isTHCell)
217                 ++headersInFirstRowCount;
218
219             // If the first column is comprised of all <th> tags, assume it is a data table.
220             if (!col && isTHCell)
221                 ++headersInFirstColumnCount;
222             
223             // In this case, the developer explicitly assigned a "data" table attribute.
224             if (is<HTMLTableCellElement>(*cellElement)) {
225                 HTMLTableCellElement& tableCellElement = downcast<HTMLTableCellElement>(*cellElement);
226                 if (!tableCellElement.headers().isEmpty() || !tableCellElement.abbr().isEmpty()
227                     || !tableCellElement.axis().isEmpty() || !tableCellElement.scope().isEmpty())
228                     return true;
229             }
230             const RenderStyle& renderStyle = cell->style();
231
232             // If the empty-cells style is set, we'll call it a data table.
233             if (renderStyle.emptyCells() == HIDE)
234                 return true;
235
236             // If a cell has matching bordered sides, call it a (fully) bordered cell.
237             if ((cell->borderTop() > 0 && cell->borderBottom() > 0)
238                 || (cell->borderLeft() > 0 && cell->borderRight() > 0))
239                 ++borderedCellCount;
240
241             // Also keep track of each individual border, so we can catch tables where most
242             // cells have a bottom border, for example.
243             if (cell->borderTop() > 0)
244                 ++cellsWithTopBorder;
245             if (cell->borderBottom() > 0)
246                 ++cellsWithBottomBorder;
247             if (cell->borderLeft() > 0)
248                 ++cellsWithLeftBorder;
249             if (cell->borderRight() > 0)
250                 ++cellsWithRightBorder;
251             
252             // If the cell has a different color from the table and there is cell spacing,
253             // then it is probably a data table cell (spacing and colors take the place of borders).
254             Color cellColor = renderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
255             if (table.hBorderSpacing() > 0 && table.vBorderSpacing() > 0
256                 && tableBGColor != cellColor && cellColor.alpha() != 1)
257                 ++backgroundDifferenceCellCount;
258             
259             // If we've found 10 "good" cells, we don't need to keep searching.
260             if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
261                 return true;
262             
263             // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows.
264             if (row < 5 && row == alternatingRowColorCount) {
265                 RenderElement* renderRow = cell->parent();
266                 if (!is<RenderTableRow>(renderRow))
267                     continue;
268                 const RenderStyle& rowRenderStyle = renderRow->style();
269                 Color rowColor = rowRenderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
270                 alternatingRowColors[alternatingRowColorCount] = rowColor;
271                 ++alternatingRowColorCount;
272             }
273         }
274         
275         if (!row && headersInFirstRowCount == numCols && numCols > 1)
276             return true;
277     }
278
279     if (headersInFirstColumnCount == numRows && numRows > 1)
280         return true;
281     
282     // if there is less than two valid cells, it's not a data table
283     if (validCellCount <= 1)
284         return false;
285     
286     // half of the cells had borders, it's a data table
287     unsigned neededCellCount = validCellCount / 2;
288     if (borderedCellCount >= neededCellCount
289         || cellsWithTopBorder >= neededCellCount
290         || cellsWithBottomBorder >= neededCellCount
291         || cellsWithLeftBorder >= neededCellCount
292         || cellsWithRightBorder >= neededCellCount)
293         return true;
294     
295     // half had different background colors, it's a data table
296     if (backgroundDifferenceCellCount >= neededCellCount)
297         return true;
298
299     // Check if there is an alternating row background color indicating a zebra striped style pattern.
300     if (alternatingRowColorCount > 2) {
301         Color firstColor = alternatingRowColors[0];
302         for (int k = 1; k < alternatingRowColorCount; k++) {
303             // If an odd row was the same color as the first row, its not alternating.
304             if (k % 2 == 1 && alternatingRowColors[k] == firstColor)
305                 return false;
306             // If an even row is not the same as the first row, its not alternating.
307             if (!(k % 2) && alternatingRowColors[k] != firstColor)
308                 return false;
309         }
310         return true;
311     }
312     
313     return false;
314 }
315     
316 bool AccessibilityTable::computeIsTableExposableThroughAccessibility() const
317 {
318     // The following is a heuristic used to determine if a
319     // <table> should be exposed as an AXTable. The goal
320     // is to only show "data" tables.
321
322     if (!m_renderer)
323         return false;
324
325     // If the developer assigned an aria role to this, then we
326     // shouldn't expose it as a table, unless, of course, the aria
327     // role is a table.
328     if (hasARIARole())
329         return false;
330
331     if (isDataTable())
332         return true;
333
334     // Gtk+ ATs used to expect all tables to be exposed as tables.
335     // N.B. Efl may wish to follow suit and also defer to WebCore. In the meantime, the following
336     // check fails for data tables with display:table-row-group. By checking for data tables first,
337     // we can handle that edge case without introducing regressions prior to switching to WebCore's
338     // default behavior for table exposure.
339 #if PLATFORM(EFL)
340     Element* tableNode = downcast<RenderTable>(*m_renderer).element();
341     return is<HTMLTableElement>(tableNode);
342 #endif
343
344     return false;
345 }
346
347 void AccessibilityTable::clearChildren()
348 {
349     AccessibilityRenderObject::clearChildren();
350     m_rows.clear();
351     m_columns.clear();
352
353     if (m_headerContainer) {
354         m_headerContainer->detachFromParent();
355         m_headerContainer = nullptr;
356     }
357 }
358
359 void AccessibilityTable::addChildren()
360 {
361     if (!isExposableThroughAccessibility()) {
362         AccessibilityRenderObject::addChildren();
363         return;
364     }
365     
366     ASSERT(!m_haveChildren); 
367     
368     m_haveChildren = true;
369     if (!is<RenderTable>(m_renderer))
370         return;
371     
372     RenderTable& table = downcast<RenderTable>(*m_renderer);
373     // Go through all the available sections to pull out the rows and add them as children.
374     table.recalcSectionsIfNeeded();
375     
376     if (HTMLTableElement* tableElement = this->tableElement()) {
377         if (HTMLTableCaptionElement* caption = tableElement->caption()) {
378             AccessibilityObject* axCaption = axObjectCache()->getOrCreate(caption);
379             if (axCaption && !axCaption->accessibilityIsIgnored())
380                 m_children.append(axCaption);
381         }
382     }
383
384     unsigned maxColumnCount = 0;
385     RenderTableSection* footer = table.footer();
386     
387     for (RenderTableSection* tableSection = table.topSection(); tableSection; tableSection = table.sectionBelow(tableSection, SkipEmptySections)) {
388         if (tableSection == footer)
389             continue;
390         addChildrenFromSection(tableSection, maxColumnCount);
391     }
392     
393     // Process the footer last, in case it was ordered earlier in the DOM.
394     if (footer)
395         addChildrenFromSection(footer, maxColumnCount);
396     
397     AXObjectCache* axCache = m_renderer->document().axObjectCache();
398     // make the columns based on the number of columns in the first body
399     unsigned length = maxColumnCount;
400     for (unsigned i = 0; i < length; ++i) {
401         auto& column = downcast<AccessibilityTableColumn>(*axCache->getOrCreate(ColumnRole));
402         column.setColumnIndex((int)i);
403         column.setParent(this);
404         m_columns.append(&column);
405         if (!column.accessibilityIsIgnored())
406             m_children.append(&column);
407     }
408     
409     AccessibilityObject* headerContainerObject = headerContainer();
410     if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
411         m_children.append(headerContainerObject);
412
413     // Sometimes the cell gets the wrong role initially because it is created before the parent
414     // determines whether it is an accessibility table. Iterate all the cells and allow them to
415     // update their roles now that the table knows its status.
416     // see bug: https://bugs.webkit.org/show_bug.cgi?id=147001
417     for (const auto& row : m_rows) {
418         for (const auto& cell : row->children())
419             cell->updateAccessibilityRole();
420     }
421
422 }
423
424 void AccessibilityTable::addTableCellChild(AccessibilityObject* rowObject, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount)
425 {
426     if (!rowObject || !is<AccessibilityTableRow>(*rowObject))
427         return;
428
429     auto& row = downcast<AccessibilityTableRow>(*rowObject);
430     // We need to check every cell for a new row, because cell spans
431     // can cause us to miss rows if we just check the first column.
432     if (appendedRows.contains(&row))
433         return;
434     
435     row.setRowIndex(static_cast<int>(m_rows.size()));
436     m_rows.append(&row);
437     if (!row.accessibilityIsIgnored())
438         m_children.append(&row);
439     appendedRows.add(&row);
440         
441     // store the maximum number of columns
442     unsigned rowCellCount = row.children().size();
443     if (rowCellCount > columnCount)
444         columnCount = rowCellCount;
445 }
446
447 void AccessibilityTable::addChildrenFromSection(RenderTableSection* tableSection, unsigned& maxColumnCount)
448 {
449     ASSERT(tableSection);
450     if (!tableSection)
451         return;
452     
453     AXObjectCache* axCache = m_renderer->document().axObjectCache();
454     HashSet<AccessibilityObject*> appendedRows;
455     unsigned numRows = tableSection->numRows();
456     for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
457         
458         RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
459         if (!renderRow)
460             continue;
461         
462         AccessibilityObject& rowObject = *axCache->getOrCreate(renderRow);
463         
464         // If the row is anonymous, we should dive deeper into the descendants to try to find a valid row.
465         if (renderRow->isAnonymous()) {
466             Deque<AccessibilityObject*> queue;
467             queue.append(&rowObject);
468             
469             while (!queue.isEmpty()) {
470                 AccessibilityObject* obj = queue.takeFirst();
471                 if (obj->node() && is<AccessibilityTableRow>(*obj)) {
472                     addTableCellChild(obj, appendedRows, maxColumnCount);
473                     continue;
474                 }
475                 for (auto* child = obj->firstChild(); child; child = child->nextSibling())
476                     queue.append(child);
477             }
478         } else
479             addTableCellChild(&rowObject, appendedRows, maxColumnCount);
480     }
481     
482     maxColumnCount = std::max(tableSection->numColumns(), maxColumnCount);
483 }
484     
485 AccessibilityObject* AccessibilityTable::headerContainer()
486 {
487     if (m_headerContainer)
488         return m_headerContainer.get();
489     
490     auto& tableHeader = downcast<AccessibilityMockObject>(*axObjectCache()->getOrCreate(TableHeaderContainerRole));
491     tableHeader.setParent(this);
492
493     m_headerContainer = &tableHeader;
494     return m_headerContainer.get();
495 }
496
497 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
498 {
499     updateChildrenIfNecessary();
500         
501     return m_columns;
502 }
503
504 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
505 {
506     updateChildrenIfNecessary();
507     
508     return m_rows;
509 }
510
511 void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
512 {
513     if (!m_renderer)
514         return;
515     
516     updateChildrenIfNecessary();
517     
518     for (const auto& column : m_columns) {
519         if (AccessibilityObject* header = downcast<AccessibilityTableColumn>(*column).headerObject())
520             headers.append(header);
521     }
522 }
523
524 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
525 {
526     if (!m_renderer)
527         return;
528     
529     updateChildrenIfNecessary();
530     
531     for (const auto& row : m_rows) {
532         if (AccessibilityObject* header = downcast<AccessibilityTableRow>(*row).headerObject())
533             headers.append(header);
534     }
535 }
536
537 void AccessibilityTable::visibleRows(AccessibilityChildrenVector& rows)
538 {
539     if (!m_renderer)
540         return;
541     
542     updateChildrenIfNecessary();
543     
544     for (const auto& row : m_rows) {
545         if (row && !row->isOffScreen())
546             rows.append(row);
547     }
548 }
549
550 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
551 {
552     if (!m_renderer)
553         return;
554     
555     updateChildrenIfNecessary();
556     
557     for (const auto& row : m_rows)
558         cells.appendVector(row->children());
559 }
560     
561 unsigned AccessibilityTable::columnCount()
562 {
563     updateChildrenIfNecessary();
564     
565     return m_columns.size();    
566 }
567     
568 unsigned AccessibilityTable::rowCount()
569 {
570     updateChildrenIfNecessary();
571     
572     return m_rows.size();
573 }
574
575 int AccessibilityTable::tableLevel() const
576 {
577     int level = 0;
578     for (AccessibilityObject* obj = static_cast<AccessibilityObject*>(const_cast<AccessibilityTable*>(this)); obj; obj = obj->parentObject()) {
579         if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
580             ++level;
581     }
582     
583     return level;
584 }
585
586 AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row)
587 {
588     updateChildrenIfNecessary();
589     if (column >= columnCount() || row >= rowCount())
590         return nullptr;
591     
592     // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row.
593     for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) {
594         unsigned rowIndex = rowIndexCounter - 1;
595         const auto& children = m_rows[rowIndex]->children();
596         // Since some cells may have colspans, we have to check the actual range of each
597         // cell to determine which is the right one.
598         for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) {
599             unsigned colIndex = colIndexCounter - 1;
600             AccessibilityObject* child = children[colIndex].get();
601             ASSERT(is<AccessibilityTableCell>(*child));
602             if (!is<AccessibilityTableCell>(*child))
603                 continue;
604             
605             std::pair<unsigned, unsigned> columnRange;
606             std::pair<unsigned, unsigned> rowRange;
607             auto& tableCellChild = downcast<AccessibilityTableCell>(*child);
608             tableCellChild.columnIndexRange(columnRange);
609             tableCellChild.rowIndexRange(rowRange);
610             
611             if ((column >= columnRange.first && column < (columnRange.first + columnRange.second))
612                 && (row >= rowRange.first && row < (rowRange.first + rowRange.second)))
613                 return &tableCellChild;
614         }
615     }
616     
617     return nullptr;
618 }
619
620 AccessibilityRole AccessibilityTable::roleValue() const
621 {
622     if (!isExposableThroughAccessibility())
623         return AccessibilityRenderObject::roleValue();
624     
625     AccessibilityRole ariaRole = ariaRoleAttribute();
626     if (ariaRole == GridRole || ariaRole == TreeGridRole)
627         return GridRole;
628
629     return TableRole;
630 }
631     
632 bool AccessibilityTable::computeAccessibilityIsIgnored() const
633 {
634     AccessibilityObjectInclusion decision = defaultObjectInclusion();
635     if (decision == IncludeObject)
636         return false;
637     if (decision == IgnoreObject)
638         return true;
639     
640     if (!isExposableThroughAccessibility())
641         return AccessibilityRenderObject::computeAccessibilityIsIgnored();
642         
643     return false;
644 }
645
646 void AccessibilityTable::titleElementText(Vector<AccessibilityText>& textOrder) const
647 {
648     String title = this->title();
649     if (!title.isEmpty())
650         textOrder.append(AccessibilityText(title, LabelByElementText));
651 }
652
653 String AccessibilityTable::title() const
654 {
655     if (!isExposableThroughAccessibility())
656         return AccessibilityRenderObject::title();
657     
658     String title;
659     if (!m_renderer)
660         return title;
661     
662     // see if there is a caption
663     Node* tableElement = m_renderer->node();
664     if (is<HTMLTableElement>(tableElement)) {
665         if (HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*tableElement).caption())
666             title = caption->innerText();
667     }
668     
669     // try the standard 
670     if (title.isEmpty())
671         title = AccessibilityRenderObject::title();
672     
673     return title;
674 }
675
676 int AccessibilityTable::ariaColumnCount() const
677 {
678     const AtomicString& colCountValue = getAttribute(aria_colcountAttr);
679     
680     int colCountInt = colCountValue.toInt();
681     // If only a portion of the columns is present in the DOM at a given moment, this attribute is needed to
682     // provide an explicit indication of the number of columns in the full table.
683     if (colCountInt > (int)m_columns.size())
684         return colCountInt;
685     
686     return -1;
687 }
688
689 int AccessibilityTable::ariaRowCount() const
690 {
691     const AtomicString& rowCountValue = getAttribute(aria_rowcountAttr);
692     
693     int rowCountInt = rowCountValue.toInt();
694     // If only a portion of the rows is present in the DOM at a given moment, this attribute is needed to
695     // provide an explicit indication of the number of rows in the full table.
696     if (rowCountInt > (int)m_rows.size())
697         return rowCountInt;
698     
699     return -1;
700 }
701
702 } // namespace WebCore