blob: bfe2094fac0c12fa0b9733e02bf845a4034e7cae [file] [log] [blame]
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +00001/*
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.
mjs@apple.com92047332014-03-15 04:08:27 +000013 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000014 * 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
cfleizach@apple.comf534eb22009-11-03 07:01:20 +000032#include "AXObjectCache.h"
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000033#include "AccessibilityTableCell.h"
34#include "AccessibilityTableColumn.h"
35#include "AccessibilityTableHeaderContainer.h"
36#include "AccessibilityTableRow.h"
antti@apple.com0494cf82013-08-31 14:12:00 +000037#include "ElementIterator.h"
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000038#include "HTMLNames.h"
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000039#include "HTMLTableCaptionElement.h"
40#include "HTMLTableCellElement.h"
cfleizach@apple.comf534eb22009-11-03 07:01:20 +000041#include "HTMLTableElement.h"
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000042#include "RenderObject.h"
43#include "RenderTable.h"
44#include "RenderTableCell.h"
45#include "RenderTableSection.h"
46
commit-queue@webkit.orgac97cff2015-08-10 01:53:10 +000047#include <wtf/Deque.h>
48
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000049namespace WebCore {
50
51using namespace HTMLNames;
52
53AccessibilityTable::AccessibilityTable(RenderObject* renderer)
cfleizach@apple.coma59d8e42013-02-14 19:22:10 +000054 : AccessibilityRenderObject(renderer)
mmaxfield@apple.com890554f2014-07-23 14:57:15 +000055 , m_headerContainer(nullptr)
cdumez@apple.comb04d5742014-10-22 21:45:20 +000056 , m_isExposableThroughAccessibility(true)
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000057{
dmazzoni@google.comc35bf3d2012-07-24 06:26:32 +000058}
59
60AccessibilityTable::~AccessibilityTable()
61{
62}
63
64void AccessibilityTable::init()
65{
66 AccessibilityRenderObject::init();
cdumez@apple.comb04d5742014-10-22 21:45:20 +000067 m_isExposableThroughAccessibility = computeIsTableExposableThroughAccessibility();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000068}
69
akling@apple.comad2beb52014-12-25 07:50:20 +000070Ref<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer)
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000071{
akling@apple.comad2beb52014-12-25 07:50:20 +000072 return adoptRef(*new AccessibilityTable(renderer));
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000073}
74
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +000075bool AccessibilityTable::hasARIARole() const
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000076{
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +000077 if (!m_renderer)
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +000078 return false;
79
cfleizach@apple.com2a72d8f2008-09-18 22:54:37 +000080 AccessibilityRole ariaRole = ariaRoleAttribute();
cfleizach@apple.com2a72d8f2008-09-18 22:54:37 +000081 if (ariaRole != UnknownRole)
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +000082 return true;
83
84 return false;
85}
86
cdumez@apple.comb04d5742014-10-22 21:45:20 +000087bool AccessibilityTable::isExposableThroughAccessibility() const
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +000088{
89 if (!m_renderer)
cfleizach@apple.com2a72d8f2008-09-18 22:54:37 +000090 return false;
91
cdumez@apple.comb04d5742014-10-22 21:45:20 +000092 return m_isExposableThroughAccessibility;
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +000093}
94
cfleizach@apple.com7d83f562014-03-13 16:28:24 +000095HTMLTableElement* AccessibilityTable::tableElement() const
96{
cdumez@apple.com8faf7722014-10-13 18:21:11 +000097 if (!is<RenderTable>(*m_renderer))
cfleizach@apple.com7d83f562014-03-13 16:28:24 +000098 return nullptr;
99
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000100 RenderTable& table = downcast<RenderTable>(*m_renderer);
101 if (is<HTMLTableElement>(table.element()))
102 return downcast<HTMLTableElement>(table.element());
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000103
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.
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000106 RenderTableSection* firstBody = table.firstBody();
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000107 if (!firstBody || !firstBody->element())
108 return nullptr;
109
110 Element* actualTable = firstBody->element()->parentElement();
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000111 if (!is<HTMLTableElement>(actualTable))
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000112 return nullptr;
113
cdumez@apple.com72754ba2014-09-23 22:03:15 +0000114 return downcast<HTMLTableElement>(actualTable);
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000115}
116
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +0000117bool 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
aboxhall@chromium.org8abadf12012-05-23 07:45:29 +0000126 // 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.
antti@apple.comf02be1e2013-12-21 18:51:04 +0000129 if (node() && node()->hasEditableStyle())
aboxhall@chromium.org8abadf12012-05-23 07:45:29 +0000130 return true;
131
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000132 if (!is<RenderTable>(*m_renderer))
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000133 return false;
134
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +0000135 // This employs a heuristic to determine if this table should appear.
136 // Only "data" tables should be exposed as tables.
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000137 // Unfortunately, there is no good way to determine the difference
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +0000138 // between a "layout" table and a "data" table.
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000139 if (HTMLTableElement* tableElement = this->tableElement()) {
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000140 // 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())
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000142 return true;
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000143
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.
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000149 for (const auto& child : childrenOfType<HTMLElement>(*tableElement)) {
cfleizach@apple.com7d83f562014-03-13 16:28:24 +0000150 if (child.hasTagName(colTag) || child.hasTagName(colgroupTag))
151 return true;
152 }
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000153 }
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000154
cfleizach@apple.com98131912015-05-10 14:52:50 +0000155 // The following checks should only apply if this is a real <table> element.
156 if (!hasTagName(tableTag))
157 return false;
158
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000159 RenderTable& table = downcast<RenderTable>(*m_renderer);
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000160 // 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
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000162 table.recalcSectionsIfNeeded();
163 RenderTableSection* firstBody = table.firstBody();
cfleizach@apple.com33a556b2008-08-26 22:29:14 +0000164 if (!firstBody)
165 return false;
166
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000167 int numCols = firstBody->numColumns();
168 int numRows = firstBody->numRows();
169
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000170 // If there's only one cell, it's not a good AXTable candidate.
cfleizach@apple.comdebb8722008-09-22 18:47:16 +0000171 if (numRows == 1 && numCols == 1)
172 return false;
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000173
174 // If there are at least 20 rows, we'll call it a data table.
175 if (numRows >= 20)
176 return true;
cfleizach@apple.comdebb8722008-09-22 18:47:16 +0000177
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000178 // Store the background color of the table to check against cell's background colors.
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000179 const RenderStyle& tableStyle = table.style();
akling@apple.com827be9c2013-10-29 02:58:43 +0000180 Color tableBGColor = tableStyle.visitedDependentColor(CSSPropertyBackgroundColor);
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000181
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000182 // 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;
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000190 unsigned cellsWithTopBorder = 0;
191 unsigned cellsWithBottomBorder = 0;
192 unsigned cellsWithLeftBorder = 0;
193 unsigned cellsWithRightBorder = 0;
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000194
cfleizach@apple.com58c01332010-07-14 21:10:50 +0000195 Color alternatingRowColors[5];
196 int alternatingRowColorCount = 0;
197
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000198 int headersInFirstColumnCount = 0;
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000199 for (int row = 0; row < numRows; ++row) {
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000200
201 int headersInFirstRowCount = 0;
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000202 for (int col = 0; col < numCols; ++col) {
jamesr@google.com36623a32010-07-23 20:22:29 +0000203 RenderTableCell* cell = firstBody->primaryCellAt(row, col);
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000204 if (!cell)
205 continue;
cfleizach@apple.com0cc25262014-02-17 22:26:54 +0000206
207 Element* cellElement = cell->element();
208 if (!cellElement)
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000209 continue;
210
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000211 if (cell->width() < 1 || cell->height() < 1)
212 continue;
213
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000214 ++validCellCount;
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000215
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000216 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)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000219 ++headersInFirstRowCount;
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000220
cfleizach@apple.com8f213752012-02-13 19:23:52 +0000221 // If the first column is comprised of all <th> tags, assume it is a data table.
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000222 if (!col && isTHCell)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000223 ++headersInFirstColumnCount;
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000224
cfleizach@apple.com0cc25262014-02-17 22:26:54 +0000225 // In this case, the developer explicitly assigned a "data" table attribute.
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000226 if (is<HTMLTableCellElement>(*cellElement)) {
cdumez@apple.comcd131532014-09-27 01:32:34 +0000227 HTMLTableCellElement& tableCellElement = downcast<HTMLTableCellElement>(*cellElement);
228 if (!tableCellElement.headers().isEmpty() || !tableCellElement.abbr().isEmpty()
229 || !tableCellElement.axis().isEmpty() || !tableCellElement.scope().isEmpty())
cfleizach@apple.com0cc25262014-02-17 22:26:54 +0000230 return true;
231 }
akling@apple.com827be9c2013-10-29 02:58:43 +0000232 const RenderStyle& renderStyle = cell->style();
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000233
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000234 // If the empty-cells style is set, we'll call it a data table.
akling@apple.com827be9c2013-10-29 02:58:43 +0000235 if (renderStyle.emptyCells() == HIDE)
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000236 return true;
237
238 // If a cell has matching bordered sides, call it a (fully) bordered cell.
cfleizach@apple.comf534eb22009-11-03 07:01:20 +0000239 if ((cell->borderTop() > 0 && cell->borderBottom() > 0)
240 || (cell->borderLeft() > 0 && cell->borderRight() > 0))
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000241 ++borderedCellCount;
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000242
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)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000246 ++cellsWithTopBorder;
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000247 if (cell->borderBottom() > 0)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000248 ++cellsWithBottomBorder;
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000249 if (cell->borderLeft() > 0)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000250 ++cellsWithLeftBorder;
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000251 if (cell->borderRight() > 0)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000252 ++cellsWithRightBorder;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000253
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000254 // 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).
akling@apple.com827be9c2013-10-29 02:58:43 +0000256 Color cellColor = renderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000257 if (table.hBorderSpacing() > 0 && table.vBorderSpacing() > 0
cfleizach@apple.comf534eb22009-11-03 07:01:20 +0000258 && tableBGColor != cellColor && cellColor.alpha() != 1)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000259 ++backgroundDifferenceCellCount;
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000260
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000261 // If we've found 10 "good" cells, we don't need to keep searching.
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000262 if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000263 return true;
cfleizach@apple.com58c01332010-07-14 21:10:50 +0000264
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) {
cdumez@apple.comdd2b64a2014-10-10 15:49:24 +0000267 RenderElement* renderRow = cell->parent();
268 if (!is<RenderTableRow>(renderRow))
cfleizach@apple.com58c01332010-07-14 21:10:50 +0000269 continue;
akling@apple.com827be9c2013-10-29 02:58:43 +0000270 const RenderStyle& rowRenderStyle = renderRow->style();
271 Color rowColor = rowRenderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
cfleizach@apple.com58c01332010-07-14 21:10:50 +0000272 alternatingRowColors[alternatingRowColorCount] = rowColor;
cdumez@apple.comdd2b64a2014-10-10 15:49:24 +0000273 ++alternatingRowColorCount;
cfleizach@apple.com58c01332010-07-14 21:10:50 +0000274 }
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000275 }
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000276
277 if (!row && headersInFirstRowCount == numCols && numCols > 1)
278 return true;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000279 }
280
cfleizach@apple.com881da5c2010-07-07 22:36:34 +0000281 if (headersInFirstColumnCount == numRows && numRows > 1)
282 return true;
283
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000284 // 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;
dmazzoni@google.comcdb4f672013-01-18 18:48:30 +0000290 if (borderedCellCount >= neededCellCount
291 || cellsWithTopBorder >= neededCellCount
292 || cellsWithBottomBorder >= neededCellCount
293 || cellsWithLeftBorder >= neededCellCount
294 || cellsWithRightBorder >= neededCellCount)
cfleizach@apple.comd6bc1e72008-10-02 23:59:49 +0000295 return true;
296
297 // half had different background colors, it's a data table
298 if (backgroundDifferenceCellCount >= neededCellCount)
299 return true;
300
cfleizach@apple.com58c01332010-07-14 21:10:50 +0000301 // 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
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000315 return false;
316}
317
cdumez@apple.comb04d5742014-10-22 21:45:20 +0000318bool AccessibilityTable::computeIsTableExposableThroughAccessibility() const
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +0000319{
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
jdiggs@igalia.coma9bb1f02015-05-05 07:39:23 +0000333 if (isDataTable())
334 return true;
335
jdiggs@igalia.com0bdce53f2015-05-12 12:01:50 +0000336 // 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
jdiggs@igalia.coma9bb1f02015-05-05 07:39:23 +0000338 // 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.
jdiggs@igalia.com0bdce53f2015-05-12 12:01:50 +0000341#if PLATFORM(EFL)
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000342 Element* tableNode = downcast<RenderTable>(*m_renderer).element();
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000343 return is<HTMLTableElement>(tableNode);
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +0000344#endif
345
jdiggs@igalia.coma9bb1f02015-05-05 07:39:23 +0000346 return false;
commit-queue@webkit.orga9398dd2010-10-26 19:19:29 +0000347}
348
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000349void AccessibilityTable::clearChildren()
350{
cfleizach@apple.come4893cb2010-03-02 01:19:25 +0000351 AccessibilityRenderObject::clearChildren();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000352 m_rows.clear();
353 m_columns.clear();
cfleizach@apple.com04c16172011-12-12 23:54:37 +0000354
355 if (m_headerContainer) {
356 m_headerContainer->detachFromParent();
mmaxfield@apple.com890554f2014-07-23 14:57:15 +0000357 m_headerContainer = nullptr;
cfleizach@apple.com04c16172011-12-12 23:54:37 +0000358 }
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000359}
360
361void AccessibilityTable::addChildren()
362{
cdumez@apple.comb04d5742014-10-22 21:45:20 +0000363 if (!isExposableThroughAccessibility()) {
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000364 AccessibilityRenderObject::addChildren();
365 return;
366 }
367
368 ASSERT(!m_haveChildren);
369
370 m_haveChildren = true;
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000371 if (!is<RenderTable>(m_renderer))
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000372 return;
373
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000374 RenderTable& table = downcast<RenderTable>(*m_renderer);
cfleizach@apple.com55378e42013-03-05 07:13:46 +0000375 // Go through all the available sections to pull out the rows and add them as children.
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000376 table.recalcSectionsIfNeeded();
cfleizach@apple.com2826b442008-10-20 17:57:12 +0000377
jdiggs@igalia.come8a090d2014-12-03 02:02:36 +0000378 if (HTMLTableElement* tableElement = this->tableElement()) {
379 if (HTMLTableCaptionElement* caption = tableElement->caption()) {
rgabor@webkit.orge42695d2014-12-21 23:56:55 +0000380 AccessibilityObject* axCaption = axObjectCache()->getOrCreate(caption);
381 if (axCaption && !axCaption->accessibilityIsIgnored())
382 m_children.append(axCaption);
jdiggs@igalia.come8a090d2014-12-03 02:02:36 +0000383 }
384 }
385
cfleizach@apple.com36282cb2013-07-22 22:04:23 +0000386 unsigned maxColumnCount = 0;
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000387 RenderTableSection* footer = table.footer();
cfleizach@apple.com55378e42013-03-05 07:13:46 +0000388
cdumez@apple.com8faf7722014-10-13 18:21:11 +0000389 for (RenderTableSection* tableSection = table.topSection(); tableSection; tableSection = table.sectionBelow(tableSection, SkipEmptySections)) {
cfleizach@apple.com7c95c0c2014-04-29 16:15:14 +0000390 if (tableSection == footer)
391 continue;
392 addChildrenFromSection(tableSection, maxColumnCount);
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000393 }
394
cfleizach@apple.com7c95c0c2014-04-29 16:15:14 +0000395 // 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();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000400 // make the columns based on the number of columns in the first body
cfleizach@apple.com36282cb2013-07-22 22:04:23 +0000401 unsigned length = maxColumnCount;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000402 for (unsigned i = 0; i < length; ++i) {
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000403 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);
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000409 }
410
411 AccessibilityObject* headerContainerObject = headerContainer();
eric@webkit.orgfad8d122010-03-08 21:06:08 +0000412 if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000413 m_children.append(headerContainerObject);
commit-queue@webkit.org9e9e3f02015-07-17 23:32:30 +0000414
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
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000424}
cfleizach@apple.com7c95c0c2014-04-29 16:15:14 +0000425
commit-queue@webkit.orgac97cff2015-08-10 01:53:10 +0000426void 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
cfleizach@apple.com7c95c0c2014-04-29 16:15:14 +0000449void 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
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000464 AccessibilityObject& rowObject = *axCache->getOrCreate(renderRow);
cfleizach@apple.com7c95c0c2014-04-29 16:15:14 +0000465
commit-queue@webkit.orgac97cff2015-08-10 01:53:10 +0000466 // 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);
cfleizach@apple.com7c95c0c2014-04-29 16:15:14 +0000482 }
483
484 maxColumnCount = std::max(tableSection->numColumns(), maxColumnCount);
485}
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000486
487AccessibilityObject* AccessibilityTable::headerContainer()
488{
489 if (m_headerContainer)
cfleizach@apple.com04c16172011-12-12 23:54:37 +0000490 return m_headerContainer.get();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000491
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000492 auto& tableHeader = downcast<AccessibilityMockObject>(*axObjectCache()->getOrCreate(TableHeaderContainerRole));
493 tableHeader.setParent(this);
cfleizach@apple.com04c16172011-12-12 23:54:37 +0000494
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000495 m_headerContainer = &tableHeader;
cfleizach@apple.com04c16172011-12-12 23:54:37 +0000496 return m_headerContainer.get();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000497}
498
cfleizach@apple.come1cbd812014-01-29 01:05:07 +0000499const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000500{
cfleizach@apple.come4893cb2010-03-02 01:19:25 +0000501 updateChildrenIfNecessary();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000502
503 return m_columns;
504}
505
cfleizach@apple.come1cbd812014-01-29 01:05:07 +0000506const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000507{
cfleizach@apple.come4893cb2010-03-02 01:19:25 +0000508 updateChildrenIfNecessary();
509
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000510 return m_rows;
511}
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000512
513void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
514{
515 if (!m_renderer)
516 return;
517
cfleizach@apple.come4893cb2010-03-02 01:19:25 +0000518 updateChildrenIfNecessary();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000519
cfleizach@apple.com421baa12014-01-14 18:09:51 +0000520 for (const auto& column : m_columns) {
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000521 if (AccessibilityObject* header = downcast<AccessibilityTableColumn>(*column).headerObject())
samuel_white@apple.come722c502013-11-19 00:32:44 +0000522 headers.append(header);
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000523 }
524}
samuel_white@apple.come722c502013-11-19 00:32:44 +0000525
526void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
527{
528 if (!m_renderer)
529 return;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000530
samuel_white@apple.come722c502013-11-19 00:32:44 +0000531 updateChildrenIfNecessary();
532
cfleizach@apple.com421baa12014-01-14 18:09:51 +0000533 for (const auto& row : m_rows) {
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000534 if (AccessibilityObject* header = downcast<AccessibilityTableRow>(*row).headerObject())
samuel_white@apple.come722c502013-11-19 00:32:44 +0000535 headers.append(header);
536 }
537}
538
539void AccessibilityTable::visibleRows(AccessibilityChildrenVector& rows)
540{
541 if (!m_renderer)
542 return;
543
544 updateChildrenIfNecessary();
545
cfleizach@apple.com421baa12014-01-14 18:09:51 +0000546 for (const auto& row : m_rows) {
samuel_white@apple.come722c502013-11-19 00:32:44 +0000547 if (row && !row->isOffScreen())
548 rows.append(row);
549 }
550}
551
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000552void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
553{
554 if (!m_renderer)
555 return;
556
cfleizach@apple.come4893cb2010-03-02 01:19:25 +0000557 updateChildrenIfNecessary();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000558
cfleizach@apple.com421baa12014-01-14 18:09:51 +0000559 for (const auto& row : m_rows)
560 cells.appendVector(row->children());
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000561}
562
darin@apple.com0558f4c2009-05-17 16:50:21 +0000563unsigned AccessibilityTable::columnCount()
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000564{
cfleizach@apple.come4893cb2010-03-02 01:19:25 +0000565 updateChildrenIfNecessary();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000566
567 return m_columns.size();
568}
569
darin@apple.com0558f4c2009-05-17 16:50:21 +0000570unsigned AccessibilityTable::rowCount()
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000571{
cfleizach@apple.come4893cb2010-03-02 01:19:25 +0000572 updateChildrenIfNecessary();
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000573
574 return m_rows.size();
575}
commit-queue@webkit.org35c6d9e2011-07-29 17:37:31 +0000576
577int AccessibilityTable::tableLevel() const
578{
579 int level = 0;
commit-queue@webkit.orge262e742011-08-17 18:28:02 +0000580 for (AccessibilityObject* obj = static_cast<AccessibilityObject*>(const_cast<AccessibilityTable*>(this)); obj; obj = obj->parentObject()) {
cdumez@apple.comb04d5742014-10-22 21:45:20 +0000581 if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
commit-queue@webkit.org35c6d9e2011-07-29 17:37:31 +0000582 ++level;
583 }
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000584
commit-queue@webkit.org35c6d9e2011-07-29 17:37:31 +0000585 return level;
586}
587
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000588AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row)
589{
cfleizach@apple.com55378e42013-03-05 07:13:46 +0000590 updateChildrenIfNecessary();
591 if (column >= columnCount() || row >= rowCount())
mmaxfield@apple.com890554f2014-07-23 14:57:15 +0000592 return nullptr;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000593
cfleizach@apple.com55378e42013-03-05 07:13:46 +0000594 // 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;
cfleizach@apple.com421baa12014-01-14 18:09:51 +0000597 const auto& children = m_rows[rowIndex]->children();
cfleizach@apple.com55378e42013-03-05 07:13:46 +0000598 // 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();
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000603 ASSERT(is<AccessibilityTableCell>(*child));
604 if (!is<AccessibilityTableCell>(*child))
cfleizach@apple.com55378e42013-03-05 07:13:46 +0000605 continue;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000606
zandobersek@gmail.com83a31192014-01-04 17:26:52 +0000607 std::pair<unsigned, unsigned> columnRange;
608 std::pair<unsigned, unsigned> rowRange;
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000609 auto& tableCellChild = downcast<AccessibilityTableCell>(*child);
610 tableCellChild.columnIndexRange(columnRange);
611 tableCellChild.rowIndexRange(rowRange);
cfleizach@apple.com55378e42013-03-05 07:13:46 +0000612
613 if ((column >= columnRange.first && column < (columnRange.first + columnRange.second))
614 && (row >= rowRange.first && row < (rowRange.first + rowRange.second)))
cdumez@apple.com234cbdc2014-10-20 19:08:40 +0000615 return &tableCellChild;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000616 }
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000617 }
618
mmaxfield@apple.com890554f2014-07-23 14:57:15 +0000619 return nullptr;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000620}
621
622AccessibilityRole AccessibilityTable::roleValue() const
623{
cdumez@apple.comb04d5742014-10-22 21:45:20 +0000624 if (!isExposableThroughAccessibility())
cfleizach@apple.com2a72d8f2008-09-18 22:54:37 +0000625 return AccessibilityRenderObject::roleValue();
commit-queue@webkit.org893f27c2015-07-10 22:04:50 +0000626
627 AccessibilityRole ariaRole = ariaRoleAttribute();
628 if (ariaRole == GridRole || ariaRole == TreeGridRole)
629 return GridRole;
cfleizach@apple.com2a72d8f2008-09-18 22:54:37 +0000630
631 return TableRole;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000632}
633
dmazzoni@google.com3b5179d2013-02-09 23:06:00 +0000634bool AccessibilityTable::computeAccessibilityIsIgnored() const
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000635{
cfleizach@apple.coma9eba452013-03-16 08:51:52 +0000636 AccessibilityObjectInclusion decision = defaultObjectInclusion();
cfleizach@apple.com14f57ce2010-03-11 22:24:33 +0000637 if (decision == IncludeObject)
638 return false;
639 if (decision == IgnoreObject)
cfleizach@apple.com6f3a5692010-03-11 00:36:02 +0000640 return true;
cfleizach@apple.com14f57ce2010-03-11 22:24:33 +0000641
cdumez@apple.comb04d5742014-10-22 21:45:20 +0000642 if (!isExposableThroughAccessibility())
dmazzoni@google.com3b5179d2013-02-09 23:06:00 +0000643 return AccessibilityRenderObject::computeAccessibilityIsIgnored();
cfleizach@apple.com6f3a5692010-03-11 00:36:02 +0000644
cfleizach@apple.com2a72d8f2008-09-18 22:54:37 +0000645 return false;
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000646}
commit-queue@webkit.org0572ed12013-10-03 23:29:25 +0000647
648void AccessibilityTable::titleElementText(Vector<AccessibilityText>& textOrder) const
649{
650 String title = this->title();
651 if (!title.isEmpty())
652 textOrder.append(AccessibilityText(title, LabelByElementText));
653}
654
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000655String AccessibilityTable::title() const
656{
cdumez@apple.comb04d5742014-10-22 21:45:20 +0000657 if (!isExposableThroughAccessibility())
cfleizach@apple.com2a72d8f2008-09-18 22:54:37 +0000658 return AccessibilityRenderObject::title();
659
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000660 String title;
661 if (!m_renderer)
662 return title;
663
664 // see if there is a caption
hyatt@apple.com7e032532009-02-11 22:06:32 +0000665 Node* tableElement = m_renderer->node();
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000666 if (is<HTMLTableElement>(tableElement)) {
cdumez@apple.com72754ba2014-09-23 22:03:15 +0000667 if (HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*tableElement).caption())
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000668 title = caption->innerText();
669 }
670
671 // try the standard
672 if (title.isEmpty())
673 title = AccessibilityRenderObject::title();
674
675 return title;
676}
677
cfleizach@apple.com7c7b5f32008-08-19 22:05:37 +0000678} // namespace WebCore