Use is<>() / downcast<>() for Accessibility objects
[WebKit-https.git] / Source / WebCore / accessibility / AccessibilityTable.cpp
index 56e2e94..0b7be3a 100644 (file)
@@ -10,7 +10,7 @@
  * 2.  Redistributions in binary form must reproduce the above copyright
  *     notice, this list of conditions and the following disclaimer in the
  *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
  *     its contributors may be used to endorse or promote products derived
  *     from this software without specific prior written permission.
  *
@@ -34,6 +34,7 @@
 #include "AccessibilityTableColumn.h"
 #include "AccessibilityTableHeaderContainer.h"
 #include "AccessibilityTableRow.h"
+#include "ElementIterator.h"
 #include "HTMLNames.h"
 #include "HTMLTableCaptionElement.h"
 #include "HTMLTableCellElement.h"
@@ -49,7 +50,7 @@ using namespace HTMLNames;
 
 AccessibilityTable::AccessibilityTable(RenderObject* renderer)
     : AccessibilityRenderObject(renderer)
-    , m_headerContainer(0)
+    , m_headerContainer(nullptr)
     , m_isAccessibilityTable(true)
 {
 }
@@ -89,6 +90,28 @@ bool AccessibilityTable::isAccessibilityTable() const
     return m_isAccessibilityTable;
 }
 
+HTMLTableElement* AccessibilityTable::tableElement() const
+{
+    if (!is<RenderTable>(*m_renderer))
+        return nullptr;
+    
+    RenderTable& table = downcast<RenderTable>(*m_renderer);
+    if (is<HTMLTableElement>(table.element()))
+        return downcast<HTMLTableElement>(table.element());
+    
+    // If the table has a display:table-row-group, then the RenderTable does not have a pointer to it's HTMLTableElement.
+    // We can instead find it by asking the firstSection for its parent.
+    RenderTableSection* firstBody = table.firstBody();
+    if (!firstBody || !firstBody->element())
+        return nullptr;
+    
+    Element* actualTable = firstBody->element()->parentElement();
+    if (!is<HTMLTableElement>(actualTable))
+        return nullptr;
+    
+    return downcast<HTMLTableElement>(actualTable);
+}
+    
 bool AccessibilityTable::isDataTable() const
 {
     if (!m_renderer)
@@ -101,38 +124,37 @@ bool AccessibilityTable::isDataTable() const
     // When a section of the document is contentEditable, all tables should be
     // treated as data tables, otherwise users may not be able to work with rich
     // text editors that allow creating and editing tables.
-    if (node() && node()->rendererIsEditable())
+    if (node() && node()->hasEditableStyle())
         return true;
 
+    if (!is<RenderTable>(*m_renderer))
+        return false;
+
     // This employs a heuristic to determine if this table should appear.
     // Only "data" tables should be exposed as tables.
     // Unfortunately, there is no good way to determine the difference
     // between a "layout" table and a "data" table.
-    
-    RenderTable* table = toRenderTable(m_renderer);
-    Node* tableNode = table->node();
-    if (!tableNode || !tableNode->hasTagName(tableTag))
-        return false;
-
-    // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table
-    HTMLTableElement* tableElement = static_cast<HTMLTableElement*>(tableNode);
-    if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
-        return true;
-    
-    // if someone used "rules" attribute than the table should appear
-    if (!tableElement->rules().isEmpty())
-        return true;    
-
-    // if there's a colgroup or col element, it's probably a data table.
-    for (Node* child = tableElement->firstChild(); child; child = child->nextSibling()) {
-        if (child->hasTagName(colTag) || child->hasTagName(colgroupTag))
+    if (HTMLTableElement* tableElement = this->tableElement()) {
+        // If there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table.
+        if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
             return true;
+        
+        // If someone used "rules" attribute than the table should appear.
+        if (!tableElement->rules().isEmpty())
+            return true;
+
+        // If there's a colgroup or col element, it's probably a data table.
+        for (const auto& child : childrenOfType<HTMLElement>(*tableElement)) {
+            if (child.hasTagName(colTag) || child.hasTagName(colgroupTag))
+                return true;
+        }
     }
     
+    RenderTable& table = downcast<RenderTable>(*m_renderer);
     // go through the cell's and check for tell-tale signs of "data" table status
     // cells have borders, or use attributes like headers, abbr, scope or axis
-    table->recalcSectionsIfNeeded();
-    RenderTableSection* firstBody = table->firstBody();
+    table.recalcSectionsIfNeeded();
+    RenderTableSection* firstBody = table.firstBody();
     if (!firstBody)
         return false;
     
@@ -148,10 +170,8 @@ bool AccessibilityTable::isDataTable() const
         return true;
     
     // Store the background color of the table to check against cell's background colors.
-    RenderStyle* tableStyle = table->style();
-    if (!tableStyle)
-        return false;
-    Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor);
+    const RenderStyle& tableStyle = table.style();
+    Color tableBGColor = tableStyle.visitedDependentColor(CSSPropertyBackgroundColor);
     
     // check enough of the cells to find if the table matches our criteria
     // Criteria: 
@@ -177,61 +197,60 @@ bool AccessibilityTable::isDataTable() const
             RenderTableCell* cell = firstBody->primaryCellAt(row, col);
             if (!cell)
                 continue;
-            Node* cellNode = cell->node();
-            if (!cellNode)
+
+            Element* cellElement = cell->element();
+            if (!cellElement)
                 continue;
             
             if (cell->width() < 1 || cell->height() < 1)
                 continue;
             
-            validCellCount++;
-            
-            HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode);
+            ++validCellCount;
             
             bool isTHCell = cellElement->hasTagName(thTag);
             // If the first row is comprised of all <th> tags, assume it is a data table.
             if (!row && isTHCell)
-                headersInFirstRowCount++;
+                ++headersInFirstRowCount;
 
             // If the first column is comprised of all <th> tags, assume it is a data table.
             if (!col && isTHCell)
-                headersInFirstColumnCount++;
-            
-            // in this case, the developer explicitly assigned a "data" table attribute
-            if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty()
-                || !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty())
-                return true;
+                ++headersInFirstColumnCount;
             
-            RenderStyle* renderStyle = cell->style();
-            if (!renderStyle)
-                continue;
+            // In this case, the developer explicitly assigned a "data" table attribute.
+            if (is<HTMLTableCellElement>(*cellElement)) {
+                HTMLTableCellElement& tableCellElement = downcast<HTMLTableCellElement>(*cellElement);
+                if (!tableCellElement.headers().isEmpty() || !tableCellElement.abbr().isEmpty()
+                    || !tableCellElement.axis().isEmpty() || !tableCellElement.scope().isEmpty())
+                    return true;
+            }
+            const RenderStyle& renderStyle = cell->style();
 
             // If the empty-cells style is set, we'll call it a data table.
-            if (renderStyle->emptyCells() == HIDE)
+            if (renderStyle.emptyCells() == HIDE)
                 return true;
 
             // If a cell has matching bordered sides, call it a (fully) bordered cell.
             if ((cell->borderTop() > 0 && cell->borderBottom() > 0)
                 || (cell->borderLeft() > 0 && cell->borderRight() > 0))
-                borderedCellCount++;
+                ++borderedCellCount;
 
             // Also keep track of each individual border, so we can catch tables where most
             // cells have a bottom border, for example.
             if (cell->borderTop() > 0)
-                cellsWithTopBorder++;
+                ++cellsWithTopBorder;
             if (cell->borderBottom() > 0)
-                cellsWithBottomBorder++;
+                ++cellsWithBottomBorder;
             if (cell->borderLeft() > 0)
-                cellsWithLeftBorder++;
+                ++cellsWithLeftBorder;
             if (cell->borderRight() > 0)
-                cellsWithRightBorder++;
+                ++cellsWithRightBorder;
             
             // If the cell has a different color from the table and there is cell spacing,
             // then it is probably a data table cell (spacing and colors take the place of borders).
-            Color cellColor = renderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
-            if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0
+            Color cellColor = renderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
+            if (table.hBorderSpacing() > 0 && table.vBorderSpacing() > 0
                 && tableBGColor != cellColor && cellColor.alpha() != 1)
-                backgroundDifferenceCellCount++;
+                ++backgroundDifferenceCellCount;
             
             // If we've found 10 "good" cells, we don't need to keep searching.
             if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
@@ -239,15 +258,13 @@ bool AccessibilityTable::isDataTable() const
             
             // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows.
             if (row < 5 && row == alternatingRowColorCount) {
-                RenderObject* renderRow = cell->parent();
-                if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow())
-                    continue;
-                RenderStyle* rowRenderStyle = renderRow->style();
-                if (!rowRenderStyle)
+                RenderElement* renderRow = cell->parent();
+                if (!is<RenderTableRow>(renderRow))
                     continue;
-                Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
+                const RenderStyle& rowRenderStyle = renderRow->style();
+                Color rowColor = rowRenderStyle.visitedDependentColor(CSSPropertyBackgroundColor);
                 alternatingRowColors[alternatingRowColorCount] = rowColor;
-                alternatingRowColorCount++;
+                ++alternatingRowColorCount;
             }
         }
         
@@ -308,9 +325,9 @@ bool AccessibilityTable::isTableExposableThroughAccessibility() const
         return false;
 
     // Gtk+ ATs expect all tables to be exposed as tables.
-#if PLATFORM(GTK)
-    Node* tableNode = toRenderTable(m_renderer)->node();
-    return tableNode && tableNode->hasTagName(tableTag);
+#if PLATFORM(GTK) || PLATFORM(EFL)
+    Element* tableNode = downcast<RenderTable>(*m_renderer).element();
+    return is<HTMLTableElement>(tableNode);
 #endif
 
     return isDataTable();
@@ -324,7 +341,7 @@ void AccessibilityTable::clearChildren()
 
     if (m_headerContainer) {
         m_headerContainer->detachFromParent();
-        m_headerContainer = 0;
+        m_headerContainer = nullptr;
     }
 }
 
@@ -338,95 +355,121 @@ void AccessibilityTable::addChildren()
     ASSERT(!m_haveChildren); 
     
     m_haveChildren = true;
-    if (!m_renderer || !m_renderer->isTable())
+    if (!is<RenderTable>(m_renderer))
         return;
     
-    RenderTable* table = toRenderTable(m_renderer);
-    AXObjectCache* axCache = m_renderer->document()->axObjectCache();
-
+    RenderTable& table = downcast<RenderTable>(*m_renderer);
     // Go through all the available sections to pull out the rows and add them as children.
-    table->recalcSectionsIfNeeded();
-    RenderTableSection* tableSection = table->topSection();
-    if (!tableSection)
-        return;
+    table.recalcSectionsIfNeeded();
     
-    RenderTableSection* initialTableSection = tableSection;
-    while (tableSection) {
-        
-        HashSet<AccessibilityObject*> appendedRows;
-        unsigned numRows = tableSection->numRows();
-        for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
-            
-            RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
-            if (!renderRow)
-                continue;
-            
-            AccessibilityObject* rowObject = axCache->getOrCreate(renderRow);
-            if (!rowObject->isTableRow())
-                continue;
-            
-            AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject);
-            // We need to check every cell for a new row, because cell spans
-            // can cause us to miss rows if we just check the first column.
-            if (appendedRows.contains(row))
-                continue;
-            
-            row->setRowIndex(static_cast<int>(m_rows.size()));
-            m_rows.append(row);
-            if (!row->accessibilityIsIgnored())
-                m_children.append(row);
-#if PLATFORM(GTK)
-            else
-                m_children.appendVector(row->children());
-#endif
-            appendedRows.add(row);
-        }
+    unsigned maxColumnCount = 0;
+    RenderTableSection* footer = table.footer();
     
-        tableSection = table->sectionBelow(tableSection, SkipEmptySections);
+    for (RenderTableSection* tableSection = table.topSection(); tableSection; tableSection = table.sectionBelow(tableSection, SkipEmptySections)) {
+        if (tableSection == footer)
+            continue;
+        addChildrenFromSection(tableSection, maxColumnCount);
     }
     
+    // Process the footer last, in case it was ordered earlier in the DOM.
+    if (footer)
+        addChildrenFromSection(footer, maxColumnCount);
+    
+    AXObjectCache* axCache = m_renderer->document().axObjectCache();
     // make the columns based on the number of columns in the first body
-    unsigned length = initialTableSection->numColumns();
+    unsigned length = maxColumnCount;
     for (unsigned i = 0; i < length; ++i) {
-        AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole));
-        column->setColumnIndex((int)i);
-        column->setParent(this);
-        m_columns.append(column);
-        if (!column->accessibilityIsIgnored())
-            m_children.append(column);
+        auto& column = downcast<AccessibilityTableColumn>(*axCache->getOrCreate(ColumnRole));
+        column.setColumnIndex((int)i);
+        column.setParent(this);
+        m_columns.append(&column);
+        if (!column.accessibilityIsIgnored())
+            m_children.append(&column);
     }
     
     AccessibilityObject* headerContainerObject = headerContainer();
     if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
         m_children.append(headerContainerObject);
 }
+
+void AccessibilityTable::addChildrenFromSection(RenderTableSection* tableSection, unsigned& maxColumnCount)
+{
+    ASSERT(tableSection);
+    if (!tableSection)
+        return;
+    
+    AXObjectCache* axCache = m_renderer->document().axObjectCache();
+    HashSet<AccessibilityObject*> appendedRows;
+    unsigned numRows = tableSection->numRows();
+    for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
+        
+        RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
+        if (!renderRow)
+            continue;
+        
+        AccessibilityObject& rowObject = *axCache->getOrCreate(renderRow);
+        if (!is<AccessibilityTableRow>(rowObject))
+            continue;
+        
+        auto& row = downcast<AccessibilityTableRow>(rowObject);
+        // We need to check every cell for a new row, because cell spans
+        // can cause us to miss rows if we just check the first column.
+        if (appendedRows.contains(&row))
+            continue;
+        
+        row.setRowIndex(static_cast<int>(m_rows.size()));
+        m_rows.append(&row);
+        if (!row.accessibilityIsIgnored())
+            m_children.append(&row);
+#if PLATFORM(GTK) || PLATFORM(EFL)
+        else
+            m_children.appendVector(row.children());
+#endif
+        appendedRows.add(&row);
+    }
+    
+    maxColumnCount = std::max(tableSection->numColumns(), maxColumnCount);
+}
     
 AccessibilityObject* AccessibilityTable::headerContainer()
 {
     if (m_headerContainer)
         return m_headerContainer.get();
     
-    AccessibilityMockObject* tableHeader = toAccessibilityMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole));
-    tableHeader->setParent(this);
+    auto& tableHeader = downcast<AccessibilityMockObject>(*axObjectCache()->getOrCreate(TableHeaderContainerRole));
+    tableHeader.setParent(this);
 
-    m_headerContainer = tableHeader;
+    m_headerContainer = &tableHeader;
     return m_headerContainer.get();
 }
 
-AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
+const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
 {
     updateChildrenIfNecessary();
         
     return m_columns;
 }
 
-AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
+const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
 {
     updateChildrenIfNecessary();
     
     return m_rows;
 }
+
+void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
+{
+    if (!m_renderer)
+        return;
+    
+    updateChildrenIfNecessary();
     
+    for (const auto& column : m_columns) {
+        if (AccessibilityObject* header = downcast<AccessibilityTableColumn>(*column).headerObject())
+            headers.append(header);
+    }
+}
+
 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
 {
     if (!m_renderer)
@@ -434,31 +477,25 @@ void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
     
     updateChildrenIfNecessary();
     
-    unsigned rowCount = m_rows.size();
-    for (unsigned k = 0; k < rowCount; ++k) {
-        AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject();
-        if (!header)
-            continue;
-        headers.append(header);
+    for (const auto& row : m_rows) {
+        if (AccessibilityObject* header = downcast<AccessibilityTableRow>(*row).headerObject())
+            headers.append(header);
     }
 }
 
-void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
+void AccessibilityTable::visibleRows(AccessibilityChildrenVector& rows)
 {
     if (!m_renderer)
         return;
     
     updateChildrenIfNecessary();
     
-    unsigned colCount = m_columns.size();
-    for (unsigned k = 0; k < colCount; ++k) {
-        AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject();
-        if (!header)
-            continue;
-        headers.append(header);
+    for (const auto& row : m_rows) {
+        if (row && !row->isOffScreen())
+            rows.append(row);
     }
 }
-    
+
 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
 {
     if (!m_renderer)
@@ -466,8 +503,8 @@ void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector&
     
     updateChildrenIfNecessary();
     
-    for (size_t row = 0; row < m_rows.size(); ++row)
-        cells.appendVector(m_rows[row]->children());
+    for (const auto& row : m_rows)
+        cells.appendVector(row->children());
 }
     
 unsigned AccessibilityTable::columnCount()
@@ -499,34 +536,34 @@ AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column,
 {
     updateChildrenIfNecessary();
     if (column >= columnCount() || row >= rowCount())
-        return 0;
+        return nullptr;
     
     // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row.
     for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) {
         unsigned rowIndex = rowIndexCounter - 1;
-        AccessibilityChildrenVector children = m_rows[rowIndex]->children();
+        const auto& children = m_rows[rowIndex]->children();
         // Since some cells may have colspans, we have to check the actual range of each
         // cell to determine which is the right one.
         for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) {
             unsigned colIndex = colIndexCounter - 1;
             AccessibilityObject* child = children[colIndex].get();
-            ASSERT(child->isTableCell());
-            if (!child->isTableCell())
+            ASSERT(is<AccessibilityTableCell>(*child));
+            if (!is<AccessibilityTableCell>(*child))
                 continue;
             
-            pair<unsigned, unsigned> columnRange;
-            pair<unsigned, unsigned> rowRange;
-            AccessibilityTableCell* tableCellChild = static_cast<AccessibilityTableCell*>(child);
-            tableCellChild->columnIndexRange(columnRange);
-            tableCellChild->rowIndexRange(rowRange);
+            std::pair<unsigned, unsigned> columnRange;
+            std::pair<unsigned, unsigned> rowRange;
+            auto& tableCellChild = downcast<AccessibilityTableCell>(*child);
+            tableCellChild.columnIndexRange(columnRange);
+            tableCellChild.rowIndexRange(rowRange);
             
             if ((column >= columnRange.first && column < (columnRange.first + columnRange.second))
                 && (row >= rowRange.first && row < (rowRange.first + rowRange.second)))
-                return tableCellChild;
+                return &tableCellChild;
         }
     }
     
-    return 0;
+    return nullptr;
 }
 
 AccessibilityRole AccessibilityTable::roleValue() const
@@ -550,7 +587,14 @@ bool AccessibilityTable::computeAccessibilityIsIgnored() const
         
     return false;
 }
-    
+
+void AccessibilityTable::titleElementText(Vector<AccessibilityText>& textOrder) const
+{
+    String title = this->title();
+    if (!title.isEmpty())
+        textOrder.append(AccessibilityText(title, LabelByElementText));
+}
+
 String AccessibilityTable::title() const
 {
     if (!isAccessibilityTable())
@@ -562,9 +606,8 @@ String AccessibilityTable::title() const
     
     // see if there is a caption
     Node* tableElement = m_renderer->node();
-    if (tableElement && tableElement->hasTagName(tableTag)) {
-        HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(tableElement)->caption();
-        if (caption)
+    if (is<HTMLTableElement>(tableElement)) {
+        if (HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*tableElement).caption())
             title = caption->innerText();
     }