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 "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 "HTMLNames.h" 38 #include "HTMLTableCaptionElement.h" 39 #include "HTMLTableCellElement.h" 40 #include "HTMLTableElement.h" 41 #include "RenderObject.h" 42 #include "RenderTable.h" 43 #include "RenderTableCell.h" 44 #include "RenderTableSection.h" 45 46 using namespace std; 47 48 namespace WebCore { 49 50 using namespace HTMLNames; 51 52 AccessibilityTable::AccessibilityTable(RenderObject* renderer) 53 : AccessibilityRenderObject(renderer), 54 m_headerContainer(0) 55 { 56 #if ACCESSIBILITY_TABLES 57 m_isAccessibilityTable = isTableExposableThroughAccessibility(); 58 #else 59 m_isAccessibilityTable = false; 60 #endif 61 } 62 63 AccessibilityTable::~AccessibilityTable() 64 { 65 } 66 67 PassRefPtr<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer) 68 { 69 return adoptRef(new AccessibilityTable(renderer)); 70 } 71 72 bool AccessibilityTable::isTableExposableThroughAccessibility() 73 { 74 // the following is a heuristic used to determine if a 75 // <table> should be exposed as an AXTable. The goal 76 // is to only show "data" tables 77 78 if (!m_renderer || !m_renderer->isTable()) 79 return false; 80 81 // if the developer assigned an aria role to this, then we shouldn't 82 // expose it as a table, unless, of course, the aria role is a table 83 AccessibilityRole ariaRole = ariaRoleAttribute(); 84 if (ariaRole != UnknownRole) 85 return false; 86 87 RenderTable* table = toRenderTable(m_renderer); 88 89 // this employs a heuristic to determine if this table should appear. 90 // Only "data" tables should be exposed as tables. 91 // Unfortunately, there is no good way to determine the difference 92 // between a "layout" table and a "data" table 93 94 Node* tableNode = table->node(); 95 if (!tableNode || !tableNode->hasTagName(tableTag)) 96 return false; 97 98 // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table 99 HTMLTableElement* tableElement = static_cast<HTMLTableElement*>(tableNode); 100 if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) 101 return true; 102 103 // if someone used "rules" attribute than the table should appear 104 if (!tableElement->rules().isEmpty()) 105 return true; 106 107 // go through the cell's and check for tell-tale signs of "data" table status 108 // cells have borders, or use attributes like headers, abbr, scope or axis 109 RenderTableSection* firstBody = table->firstBody(); 110 if (!firstBody) 111 return false; 112 113 int numCols = firstBody->numColumns(); 114 int numRows = firstBody->numRows(); 115 116 // if there's only one cell, it's not a good AXTable candidate 117 if (numRows == 1 && numCols == 1) 118 return false; 119 120 // store the background color of the table to check against cell's background colors 121 RenderStyle* tableStyle = table->style(); 122 if (!tableStyle) 123 return false; 124 Color tableBGColor = tableStyle->backgroundColor(); 125 126 // check enough of the cells to find if the table matches our criteria 127 // Criteria: 128 // 1) must have at least one valid cell (and) 129 // 2) at least half of cells have borders (or) 130 // 3) at least half of cells have different bg colors than the table, and there is cell spacing 131 unsigned validCellCount = 0; 132 unsigned borderedCellCount = 0; 133 unsigned backgroundDifferenceCellCount = 0; 134 135 for (int row = 0; row < numRows; ++row) { 136 for (int col = 0; col < numCols; ++col) { 137 RenderTableCell* cell = firstBody->cellAt(row, col).cell; 138 if (!cell) 139 continue; 140 Node* cellNode = cell->node(); 141 if (!cellNode) 142 continue; 143 144 if (cell->width() < 1 || cell->height() < 1) 145 continue; 146 147 validCellCount++; 148 149 HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode); 150 151 // in this case, the developer explicitly assigned a "data" table attribute 152 if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() 153 || !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty()) 154 return true; 155 156 RenderStyle* renderStyle = cell->style(); 157 if (!renderStyle) 158 continue; 159 160 // a cell needs to have matching bordered sides, before it can be considered a bordered cell. 161 if ((cell->borderTop() > 0 && cell->borderBottom() > 0) 162 || (cell->borderLeft() > 0 && cell->borderRight() > 0)) 163 borderedCellCount++; 164 165 // if the cell has a different color from the table and there is cell spacing, 166 // then it is probably a data table cell (spacing and colors take the place of borders) 167 Color cellColor = renderStyle->backgroundColor(); 168 if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 169 && tableBGColor != cellColor && cellColor.alpha() != 1) 170 backgroundDifferenceCellCount++; 171 172 // if we've found 10 "good" cells, we don't need to keep searching 173 if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10) 174 return true; 175 } 176 } 177 178 // if there is less than two valid cells, it's not a data table 179 if (validCellCount <= 1) 180 return false; 181 182 // half of the cells had borders, it's a data table 183 unsigned neededCellCount = validCellCount / 2; 184 if (borderedCellCount >= neededCellCount) 185 return true; 186 187 // half had different background colors, it's a data table 188 if (backgroundDifferenceCellCount >= neededCellCount) 189 return true; 190 191 return false; 192 } 193 194 void AccessibilityTable::clearChildren() 195 { 196 m_children.clear(); 197 m_rows.clear(); 198 m_columns.clear(); 199 m_haveChildren = false; 200 } 201 202 void AccessibilityTable::addChildren() 203 { 204 if (!isDataTable()) { 205 AccessibilityRenderObject::addChildren(); 206 return; 207 } 208 209 ASSERT(!m_haveChildren); 210 211 m_haveChildren = true; 212 if (!m_renderer || !m_renderer->isTable()) 213 return; 214 215 RenderTable* table = toRenderTable(m_renderer); 216 AXObjectCache* axCache = m_renderer->document()->axObjectCache(); 217 218 // go through all the available sections to pull out the rows 219 // and add them as children 220 RenderTableSection* tableSection = table->header(); 221 if (!tableSection) 222 tableSection = table->firstBody(); 223 224 if (!tableSection) 225 return; 226 227 RenderTableSection* initialTableSection = tableSection; 228 229 while (tableSection) { 230 231 HashSet<AccessibilityObject*> appendedRows; 232 233 unsigned numRows = tableSection->numRows(); 234 unsigned numCols = tableSection->numColumns(); 235 for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { 236 for (unsigned colIndex = 0; colIndex < numCols; ++colIndex) { 237 238 RenderTableCell* cell = tableSection->cellAt(rowIndex, colIndex).cell; 239 if (!cell) 240 continue; 241 242 AccessibilityObject* rowObject = axCache->getOrCreate(cell->parent()); 243 if (!rowObject->isTableRow()) 244 continue; 245 246 AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject); 247 // we need to check every cell for a new row, because cell spans 248 // can cause us to mess rows if we just check the first column 249 if (appendedRows.contains(row)) 250 continue; 251 252 row->setRowIndex((int)m_rows.size()); 253 m_rows.append(row); 254 m_children.append(row); 255 appendedRows.add(row); 256 } 257 } 258 259 tableSection = table->sectionBelow(tableSection, true); 260 } 261 262 // make the columns based on the number of columns in the first body 263 unsigned length = initialTableSection->numColumns(); 264 for (unsigned i = 0; i < length; ++i) { 265 AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole)); 266 column->setColumnIndex((int)i); 267 column->setParentTable(this); 268 m_columns.append(column); 269 m_children.append(column); 270 } 271 272 AccessibilityObject* headerContainerObject = headerContainer(); 273 if (headerContainerObject) 274 m_children.append(headerContainerObject); 275 } 276 277 AccessibilityObject* AccessibilityTable::headerContainer() 278 { 279 if (m_headerContainer) 280 return m_headerContainer; 281 282 m_headerContainer = static_cast<AccessibilityTableHeaderContainer*>(axObjectCache()->getOrCreate(TableHeaderContainerRole)); 283 m_headerContainer->setParentTable(this); 284 285 return m_headerContainer; 286 } 287 288 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns() 289 { 290 if (!hasChildren()) 291 addChildren(); 292 293 return m_columns; 294 } 295 296 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows() 297 { 298 if (!hasChildren()) 299 addChildren(); 300 301 return m_rows; 302 } 303 304 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers) 305 { 306 if (!m_renderer) 307 return; 308 309 if (!hasChildren()) 310 addChildren(); 311 312 unsigned rowCount = m_rows.size(); 313 for (unsigned k = 0; k < rowCount; ++k) { 314 AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject(); 315 if (!header) 316 continue; 317 headers.append(header); 318 } 319 } 320 321 void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers) 322 { 323 if (!m_renderer) 324 return; 325 326 if (!hasChildren()) 327 addChildren(); 328 329 unsigned colCount = m_columns.size(); 330 for (unsigned k = 0; k < colCount; ++k) { 331 AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject(); 332 if (!header) 333 continue; 334 headers.append(header); 335 } 336 } 337 338 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells) 339 { 340 if (!m_renderer) 341 return; 342 343 if (!hasChildren()) 344 addChildren(); 345 346 int numRows = m_rows.size(); 347 for (int row = 0; row < numRows; ++row) { 348 AccessibilityChildrenVector rowChildren = m_rows[row]->children(); 349 cells.append(rowChildren); 350 } 351 } 352 353 unsigned AccessibilityTable::columnCount() 354 { 355 if (!hasChildren()) 356 addChildren(); 357 358 return m_columns.size(); 359 } 360 361 unsigned AccessibilityTable::rowCount() 362 { 363 if (!hasChildren()) 364 addChildren(); 365 366 return m_rows.size(); 367 } 368 369 AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row) 370 { 371 if (!m_renderer || !m_renderer->isTable()) 372 return 0; 373 374 if (!hasChildren()) 375 addChildren(); 376 377 RenderTable* table = toRenderTable(m_renderer); 378 RenderTableSection* tableSection = table->header(); 379 if (!tableSection) 380 tableSection = table->firstBody(); 381 382 RenderTableCell* cell = 0; 383 unsigned rowCount = 0; 384 unsigned rowOffset = 0; 385 while (tableSection) { 386 387 unsigned numRows = tableSection->numRows(); 388 unsigned numCols = tableSection->numColumns(); 389 390 rowCount += numRows; 391 392 unsigned sectionSpecificRow = row - rowOffset; 393 if (row < rowCount && column < numCols && sectionSpecificRow < numRows) { 394 cell = tableSection->cellAt(sectionSpecificRow, column).cell; 395 396 // we didn't find the cell, which means there's spanning happening 397 // search backwards to find the spanning cell 398 if (!cell) { 399 400 // first try rows 401 for (int testRow = sectionSpecificRow-1; testRow >= 0; --testRow) { 402 cell = tableSection->cellAt(testRow, column).cell; 403 // cell overlapped. use this one 404 if (cell && ((cell->row() + (cell->rowSpan()-1)) >= (int)sectionSpecificRow)) 405 break; 406 cell = 0; 407 } 408 409 if (!cell) { 410 // try cols 411 for (int testCol = column-1; testCol >= 0; --testCol) { 412 cell = tableSection->cellAt(sectionSpecificRow, testCol).cell; 413 // cell overlapped. use this one 414 if (cell && ((cell->col() + (cell->colSpan()-1)) >= (int)column)) 415 break; 416 cell = 0; 417 } 418 } 419 } 420 } 421 422 if (cell) 423 break; 424 425 rowOffset += numRows; 426 // we didn't find anything between the rows we should have 427 if (row < rowCount) 428 break; 429 tableSection = table->sectionBelow(tableSection, true); 430 } 431 432 if (!cell) 433 return 0; 434 435 AccessibilityObject* cellObject = axObjectCache()->getOrCreate(cell); 436 ASSERT(cellObject->isTableCell()); 437 438 return static_cast<AccessibilityTableCell*>(cellObject); 439 } 440 441 AccessibilityRole AccessibilityTable::roleValue() const 442 { 443 if (!isDataTable()) 444 return AccessibilityRenderObject::roleValue(); 445 446 return TableRole; 447 } 448 449 bool AccessibilityTable::accessibilityIsIgnored() const 450 { 451 if (!isDataTable()) 452 return AccessibilityRenderObject::accessibilityIsIgnored(); 453 454 return false; 455 } 456 457 String AccessibilityTable::title() const 458 { 459 if (!isDataTable()) 460 return AccessibilityRenderObject::title(); 461 462 String title; 463 if (!m_renderer) 464 return title; 465 466 // see if there is a caption 467 Node* tableElement = m_renderer->node(); 468 if (tableElement && tableElement->hasTagName(tableTag)) { 469 HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(tableElement)->caption(); 470 if (caption) 471 title = caption->innerText(); 472 } 473 474 // try the standard 475 if (title.isEmpty()) 476 title = AccessibilityRenderObject::title(); 477 478 return title; 479 } 480 481 bool AccessibilityTable::isDataTable() const 482 { 483 if (!m_renderer) 484 return false; 485 486 return m_isAccessibilityTable; 487 } 488 489 } // namespace WebCore 490