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