Home | History | Annotate | Download | only in accessibility
      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