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 Computer, 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 "core/accessibility/AXTable.h" 31 32 #include "core/accessibility/AXObjectCache.h" 33 #include "core/accessibility/AXTableCell.h" 34 #include "core/accessibility/AXTableColumn.h" 35 #include "core/accessibility/AXTableRow.h" 36 #include "core/dom/ElementTraversal.h" 37 #include "core/html/HTMLCollection.h" 38 #include "core/html/HTMLTableCaptionElement.h" 39 #include "core/html/HTMLTableCellElement.h" 40 #include "core/html/HTMLTableColElement.h" 41 #include "core/html/HTMLTableElement.h" 42 #include "core/html/HTMLTableRowElement.h" 43 #include "core/html/HTMLTableRowsCollection.h" 44 #include "core/html/HTMLTableSectionElement.h" 45 #include "core/rendering/RenderTableCell.h" 46 47 namespace blink { 48 49 using namespace HTMLNames; 50 51 AXTable::AXTable(RenderObject* renderer) 52 : AXRenderObject(renderer) 53 , m_headerContainer(nullptr) 54 , m_isAXTable(true) 55 { 56 } 57 58 AXTable::~AXTable() 59 { 60 } 61 62 void AXTable::init() 63 { 64 AXRenderObject::init(); 65 m_isAXTable = isTableExposableThroughAccessibility(); 66 } 67 68 PassRefPtr<AXTable> AXTable::create(RenderObject* renderer) 69 { 70 return adoptRef(new AXTable(renderer)); 71 } 72 73 bool AXTable::hasARIARole() const 74 { 75 if (!m_renderer) 76 return false; 77 78 AccessibilityRole ariaRole = ariaRoleAttribute(); 79 if (ariaRole != UnknownRole) 80 return true; 81 82 return false; 83 } 84 85 bool AXTable::isAXTable() const 86 { 87 if (!m_renderer) 88 return false; 89 90 return m_isAXTable; 91 } 92 93 static bool elementHasAriaRole(const Element* element) 94 { 95 if (!element) 96 return false; 97 98 const AtomicString& ariaRole = element->fastGetAttribute(roleAttr); 99 return (!ariaRole.isNull() && !ariaRole.isEmpty()); 100 } 101 102 bool AXTable::isDataTable() const 103 { 104 if (!m_renderer || !node()) 105 return false; 106 107 // Do not consider it a data table is it has an ARIA role. 108 if (hasARIARole()) 109 return false; 110 111 // When a section of the document is contentEditable, all tables should be 112 // treated as data tables, otherwise users may not be able to work with rich 113 // text editors that allow creating and editing tables. 114 if (node() && node()->hasEditableStyle()) 115 return true; 116 117 // This employs a heuristic to determine if this table should appear. 118 // Only "data" tables should be exposed as tables. 119 // Unfortunately, there is no good way to determine the difference 120 // between a "layout" table and a "data" table. 121 122 RenderTable* table = toRenderTable(m_renderer); 123 Node* tableNode = table->node(); 124 if (!isHTMLTableElement(tableNode)) 125 return false; 126 127 // Do not consider it a data table if any of its descendants have an ARIA role. 128 HTMLTableElement* tableElement = toHTMLTableElement(tableNode); 129 if (elementHasAriaRole(tableElement->tHead())) 130 return false; 131 if (elementHasAriaRole(tableElement->tFoot())) 132 return false; 133 134 RefPtrWillBeRawPtr<HTMLCollection> bodies = tableElement->tBodies(); 135 for (unsigned bodyIndex = 0; bodyIndex < bodies->length(); ++bodyIndex) { 136 Element* bodyElement = bodies->item(bodyIndex); 137 if (elementHasAriaRole(bodyElement)) 138 return false; 139 } 140 141 RefPtrWillBeRawPtr<HTMLTableRowsCollection> rows = tableElement->rows(); 142 unsigned rowCount = rows->length(); 143 for (unsigned rowIndex = 0; rowIndex < rowCount; ++rowIndex) { 144 HTMLTableRowElement* rowElement = rows->item(rowIndex); 145 if (elementHasAriaRole(rowElement)) 146 return false; 147 RefPtrWillBeRawPtr<HTMLCollection> cells = rowElement->cells(); 148 for (unsigned cellIndex = 0; cellIndex < cells->length(); ++cellIndex) { 149 if (elementHasAriaRole(cells->item(cellIndex))) 150 return false; 151 } 152 } 153 154 // If there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table 155 if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) 156 return true; 157 158 // if someone used "rules" attribute than the table should appear 159 if (!tableElement->rules().isEmpty()) 160 return true; 161 162 // if there's a colgroup or col element, it's probably a data table. 163 if (Traversal<HTMLTableColElement>::firstChild(*tableElement)) 164 return true; 165 166 // go through the cell's and check for tell-tale signs of "data" table status 167 // cells have borders, or use attributes like headers, abbr, scope or axis 168 table->recalcSectionsIfNeeded(); 169 RenderTableSection* firstBody = table->firstBody(); 170 if (!firstBody) 171 return false; 172 173 int numCols = firstBody->numColumns(); 174 int numRows = firstBody->numRows(); 175 176 // If there's only one cell, it's not a good AXTable candidate. 177 if (numRows == 1 && numCols == 1) 178 return false; 179 180 // If there are at least 20 rows, we'll call it a data table. 181 if (numRows >= 20) 182 return true; 183 184 // Store the background color of the table to check against cell's background colors. 185 RenderStyle* tableStyle = table->style(); 186 if (!tableStyle) 187 return false; 188 Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor); 189 190 // check enough of the cells to find if the table matches our criteria 191 // Criteria: 192 // 1) must have at least one valid cell (and) 193 // 2) at least half of cells have borders (or) 194 // 3) at least half of cells have different bg colors than the table, and there is cell spacing 195 unsigned validCellCount = 0; 196 unsigned borderedCellCount = 0; 197 unsigned backgroundDifferenceCellCount = 0; 198 unsigned cellsWithTopBorder = 0; 199 unsigned cellsWithBottomBorder = 0; 200 unsigned cellsWithLeftBorder = 0; 201 unsigned cellsWithRightBorder = 0; 202 203 Color alternatingRowColors[5]; 204 int alternatingRowColorCount = 0; 205 206 int headersInFirstColumnCount = 0; 207 for (int row = 0; row < numRows; ++row) { 208 209 int headersInFirstRowCount = 0; 210 for (int col = 0; col < numCols; ++col) { 211 RenderTableCell* cell = firstBody->primaryCellAt(row, col); 212 if (!cell) 213 continue; 214 Node* cellNode = cell->node(); 215 if (!cellNode) 216 continue; 217 218 if (cell->width() < 1 || cell->height() < 1) 219 continue; 220 221 validCellCount++; 222 223 bool isTHCell = cellNode->hasTagName(thTag); 224 // If the first row is comprised of all <th> tags, assume it is a data table. 225 if (!row && isTHCell) 226 headersInFirstRowCount++; 227 228 // If the first column is comprised of all <th> tags, assume it is a data table. 229 if (!col && isTHCell) 230 headersInFirstColumnCount++; 231 232 // in this case, the developer explicitly assigned a "data" table attribute 233 if (isHTMLTableCellElement(*cellNode)) { 234 HTMLTableCellElement& cellElement = toHTMLTableCellElement(*cellNode); 235 if (!cellElement.headers().isEmpty() || !cellElement.abbr().isEmpty() 236 || !cellElement.axis().isEmpty() || !cellElement.scope().isEmpty()) 237 return true; 238 } 239 240 RenderStyle* renderStyle = cell->style(); 241 if (!renderStyle) 242 continue; 243 244 // If the empty-cells style is set, we'll call it a data table. 245 if (renderStyle->emptyCells() == HIDE) 246 return true; 247 248 // If a cell has matching bordered sides, call it a (fully) bordered cell. 249 if ((cell->borderTop() > 0 && cell->borderBottom() > 0) 250 || (cell->borderLeft() > 0 && cell->borderRight() > 0)) 251 borderedCellCount++; 252 253 // Also keep track of each individual border, so we can catch tables where most 254 // cells have a bottom border, for example. 255 if (cell->borderTop() > 0) 256 cellsWithTopBorder++; 257 if (cell->borderBottom() > 0) 258 cellsWithBottomBorder++; 259 if (cell->borderLeft() > 0) 260 cellsWithLeftBorder++; 261 if (cell->borderRight() > 0) 262 cellsWithRightBorder++; 263 264 // If the cell has a different color from the table and there is cell spacing, 265 // then it is probably a data table cell (spacing and colors take the place of borders). 266 Color cellColor = renderStyle->visitedDependentColor(CSSPropertyBackgroundColor); 267 if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 268 && tableBGColor != cellColor && cellColor.alpha() != 1) 269 backgroundDifferenceCellCount++; 270 271 // If we've found 10 "good" cells, we don't need to keep searching. 272 if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10) 273 return true; 274 275 // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows. 276 if (row < 5 && row == alternatingRowColorCount) { 277 RenderObject* renderRow = cell->parent(); 278 if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow()) 279 continue; 280 RenderStyle* rowRenderStyle = renderRow->style(); 281 if (!rowRenderStyle) 282 continue; 283 Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor); 284 alternatingRowColors[alternatingRowColorCount] = rowColor; 285 alternatingRowColorCount++; 286 } 287 } 288 289 if (!row && headersInFirstRowCount == numCols && numCols > 1) 290 return true; 291 } 292 293 if (headersInFirstColumnCount == numRows && numRows > 1) 294 return true; 295 296 // if there is less than two valid cells, it's not a data table 297 if (validCellCount <= 1) 298 return false; 299 300 // half of the cells had borders, it's a data table 301 unsigned neededCellCount = validCellCount / 2; 302 if (borderedCellCount >= neededCellCount 303 || cellsWithTopBorder >= neededCellCount 304 || cellsWithBottomBorder >= neededCellCount 305 || cellsWithLeftBorder >= neededCellCount 306 || cellsWithRightBorder >= neededCellCount) 307 return true; 308 309 // half had different background colors, it's a data table 310 if (backgroundDifferenceCellCount >= neededCellCount) 311 return true; 312 313 // Check if there is an alternating row background color indicating a zebra striped style pattern. 314 if (alternatingRowColorCount > 2) { 315 Color firstColor = alternatingRowColors[0]; 316 for (int k = 1; k < alternatingRowColorCount; k++) { 317 // If an odd row was the same color as the first row, its not alternating. 318 if (k % 2 == 1 && alternatingRowColors[k] == firstColor) 319 return false; 320 // If an even row is not the same as the first row, its not alternating. 321 if (!(k % 2) && alternatingRowColors[k] != firstColor) 322 return false; 323 } 324 return true; 325 } 326 327 return false; 328 } 329 330 bool AXTable::isTableExposableThroughAccessibility() const 331 { 332 // The following is a heuristic used to determine if a 333 // <table> should be exposed as an AXTable. The goal 334 // is to only show "data" tables. 335 336 if (!m_renderer) 337 return false; 338 339 // If the developer assigned an aria role to this, then we 340 // shouldn't expose it as a table, unless, of course, the aria 341 // role is a table. 342 if (hasARIARole()) 343 return false; 344 345 return isDataTable(); 346 } 347 348 void AXTable::clearChildren() 349 { 350 AXRenderObject::clearChildren(); 351 m_rows.clear(); 352 m_columns.clear(); 353 354 if (m_headerContainer) { 355 m_headerContainer->detachFromParent(); 356 m_headerContainer = nullptr; 357 } 358 } 359 360 void AXTable::addChildren() 361 { 362 if (!isAXTable()) { 363 AXRenderObject::addChildren(); 364 return; 365 } 366 367 ASSERT(!m_haveChildren); 368 369 m_haveChildren = true; 370 if (!m_renderer || !m_renderer->isTable()) 371 return; 372 373 RenderTable* table = toRenderTable(m_renderer); 374 AXObjectCache* axCache = m_renderer->document().axObjectCache(); 375 376 // Go through all the available sections to pull out the rows and add them as children. 377 table->recalcSectionsIfNeeded(); 378 RenderTableSection* tableSection = table->topSection(); 379 if (!tableSection) 380 return; 381 382 RenderTableSection* initialTableSection = tableSection; 383 while (tableSection) { 384 385 HashSet<AXObject*> appendedRows; 386 unsigned numRows = tableSection->numRows(); 387 for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { 388 389 RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex); 390 if (!renderRow) 391 continue; 392 393 AXObject* rowObject = axCache->getOrCreate(renderRow); 394 if (!rowObject->isTableRow()) 395 continue; 396 397 AXTableRow* row = toAXTableRow(rowObject); 398 // We need to check every cell for a new row, because cell spans 399 // can cause us to miss rows if we just check the first column. 400 if (appendedRows.contains(row)) 401 continue; 402 403 row->setRowIndex(static_cast<int>(m_rows.size())); 404 m_rows.append(row); 405 if (!row->accessibilityIsIgnored()) 406 m_children.append(row); 407 appendedRows.add(row); 408 } 409 410 tableSection = table->sectionBelow(tableSection, SkipEmptySections); 411 } 412 413 // make the columns based on the number of columns in the first body 414 unsigned length = initialTableSection->numColumns(); 415 for (unsigned i = 0; i < length; ++i) { 416 AXTableColumn* column = toAXTableColumn(axCache->getOrCreate(ColumnRole)); 417 column->setColumnIndex((int)i); 418 column->setParent(this); 419 m_columns.append(column); 420 if (!column->accessibilityIsIgnored()) 421 m_children.append(column); 422 } 423 424 AXObject* headerContainerObject = headerContainer(); 425 if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored()) 426 m_children.append(headerContainerObject); 427 } 428 429 AXObject* AXTable::headerContainer() 430 { 431 if (m_headerContainer) 432 return m_headerContainer.get(); 433 434 AXMockObject* tableHeader = toAXMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole)); 435 tableHeader->setParent(this); 436 437 m_headerContainer = tableHeader; 438 return m_headerContainer.get(); 439 } 440 441 AXObject::AccessibilityChildrenVector& AXTable::columns() 442 { 443 updateChildrenIfNecessary(); 444 445 return m_columns; 446 } 447 448 AXObject::AccessibilityChildrenVector& AXTable::rows() 449 { 450 updateChildrenIfNecessary(); 451 452 return m_rows; 453 } 454 455 void AXTable::columnHeaders(AccessibilityChildrenVector& headers) 456 { 457 if (!m_renderer) 458 return; 459 460 updateChildrenIfNecessary(); 461 462 unsigned colCount = m_columns.size(); 463 for (unsigned k = 0; k < colCount; ++k) { 464 AXObject* header = toAXTableColumn(m_columns[k].get())->headerObject(); 465 if (!header) 466 continue; 467 headers.append(header); 468 } 469 } 470 471 void AXTable::cells(AXObject::AccessibilityChildrenVector& cells) 472 { 473 if (!m_renderer) 474 return; 475 476 updateChildrenIfNecessary(); 477 478 int numRows = m_rows.size(); 479 for (int row = 0; row < numRows; ++row) { 480 AccessibilityChildrenVector rowChildren = m_rows[row]->children(); 481 cells.appendVector(rowChildren); 482 } 483 } 484 485 unsigned AXTable::columnCount() 486 { 487 updateChildrenIfNecessary(); 488 489 return m_columns.size(); 490 } 491 492 unsigned AXTable::rowCount() 493 { 494 updateChildrenIfNecessary(); 495 496 return m_rows.size(); 497 } 498 499 AXTableCell* AXTable::cellForColumnAndRow(unsigned column, unsigned row) 500 { 501 updateChildrenIfNecessary(); 502 if (column >= columnCount() || row >= rowCount()) 503 return 0; 504 505 // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row. 506 for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) { 507 unsigned rowIndex = rowIndexCounter - 1; 508 AccessibilityChildrenVector children = m_rows[rowIndex]->children(); 509 // Since some cells may have colspans, we have to check the actual range of each 510 // cell to determine which is the right one. 511 for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) { 512 unsigned colIndex = colIndexCounter - 1; 513 AXObject* child = children[colIndex].get(); 514 ASSERT(child->isTableCell()); 515 if (!child->isTableCell()) 516 continue; 517 518 pair<unsigned, unsigned> columnRange; 519 pair<unsigned, unsigned> rowRange; 520 AXTableCell* tableCellChild = toAXTableCell(child); 521 tableCellChild->columnIndexRange(columnRange); 522 tableCellChild->rowIndexRange(rowRange); 523 524 if ((column >= columnRange.first && column < (columnRange.first + columnRange.second)) 525 && (row >= rowRange.first && row < (rowRange.first + rowRange.second))) 526 return tableCellChild; 527 } 528 } 529 530 return 0; 531 } 532 533 AccessibilityRole AXTable::roleValue() const 534 { 535 if (!isAXTable()) 536 return AXRenderObject::roleValue(); 537 538 return TableRole; 539 } 540 541 bool AXTable::computeAccessibilityIsIgnored() const 542 { 543 AXObjectInclusion decision = defaultObjectInclusion(); 544 if (decision == IncludeObject) 545 return false; 546 if (decision == IgnoreObject) 547 return true; 548 549 if (!isAXTable()) 550 return AXRenderObject::computeAccessibilityIsIgnored(); 551 552 return false; 553 } 554 555 String AXTable::title() const 556 { 557 if (!isAXTable()) 558 return AXRenderObject::title(); 559 560 String title; 561 if (!m_renderer) 562 return title; 563 564 // see if there is a caption 565 Node* tableElement = m_renderer->node(); 566 if (isHTMLTableElement(tableElement)) { 567 HTMLTableCaptionElement* caption = toHTMLTableElement(tableElement)->caption(); 568 if (caption) 569 title = caption->innerText(); 570 } 571 572 // try the standard 573 if (title.isEmpty()) 574 title = AXRenderObject::title(); 575 576 return title; 577 } 578 579 } // namespace blink 580