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