Use is<>() / downcast<>() for Table render objects
[WebKit-https.git] / Source / WebCore / accessibility / AccessibilityTableCell.cpp
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 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 "AccessibilityTableCell.h"
31
32 #include "AXObjectCache.h"
33 #include "AccessibilityTable.h"
34 #include "AccessibilityTableRow.h"
35 #include "HTMLElement.h"
36 #include "HTMLNames.h"
37 #include "RenderObject.h"
38 #include "RenderTableCell.h"
39
40 namespace WebCore {
41     
42 using namespace HTMLNames;
43
44 AccessibilityTableCell::AccessibilityTableCell(RenderObject* renderer)
45     : AccessibilityRenderObject(renderer)
46 {
47 }
48
49 AccessibilityTableCell::~AccessibilityTableCell()
50 {
51 }
52
53 PassRefPtr<AccessibilityTableCell> AccessibilityTableCell::create(RenderObject* renderer)
54 {
55     return adoptRef(new AccessibilityTableCell(renderer));
56 }
57
58 bool AccessibilityTableCell::computeAccessibilityIsIgnored() const
59 {
60     AccessibilityObjectInclusion decision = defaultObjectInclusion();
61     if (decision == IncludeObject)
62         return false;
63     if (decision == IgnoreObject)
64         return true;
65     
66     // Ignore anonymous table cells.
67     if (!node())
68         return true;
69         
70     if (!isTableCell())
71         return AccessibilityRenderObject::computeAccessibilityIsIgnored();
72     
73     return false;
74 }
75
76 AccessibilityTable* AccessibilityTableCell::parentTable() const
77 {
78     if (!is<RenderTableCell>(m_renderer))
79         return nullptr;
80
81     // If the document no longer exists, we might not have an axObjectCache.
82     if (!axObjectCache())
83         return nullptr;
84     
85     // Do not use getOrCreate. parentTable() can be called while the render tree is being modified 
86     // by javascript, and creating a table element may try to access the render tree while in a bad state.
87     // By using only get() implies that the AXTable must be created before AXTableCells. This should
88     // always be the case when AT clients access a table.
89     // https://bugs.webkit.org/show_bug.cgi?id=42652
90     AccessibilityObject* parentTable = axObjectCache()->get(downcast<RenderTableCell>(*m_renderer).table());
91     if (!parentTable || !parentTable->isTable())
92         return nullptr;
93     return toAccessibilityTable(parentTable);
94 }
95     
96 bool AccessibilityTableCell::isTableCell() const
97 {
98     // If the parent table is an accessibility table, then we are a table cell.
99     // This used to check if the unignoredParent was a row, but that exploded performance if
100     // this was in nested tables. This check should be just as good.
101     AccessibilityObject* parentTable = this->parentTable();
102     return parentTable && parentTable->isAccessibilityTable();
103 }
104     
105 AccessibilityRole AccessibilityTableCell::determineAccessibilityRole()
106 {
107     // Always call determineAccessibleRole so that the ARIA role is set.
108     // Even though this object reports a Cell role, the ARIA role will be used
109     // to determine if it's a column header.
110     AccessibilityRole defaultRole = AccessibilityRenderObject::determineAccessibilityRole();
111 #if !PLATFORM(EFL) && !PLATFORM(GTK)
112     if (!isTableCell())
113         return defaultRole;
114     
115     return CellRole;
116 #endif
117     return defaultRole;
118 }
119     
120 bool AccessibilityTableCell::isTableHeaderCell() const
121 {
122     return node() && node()->hasTagName(thTag);
123 }
124
125 bool AccessibilityTableCell::isTableCellInSameRowGroup(AccessibilityTableCell* otherTableCell)
126 {
127     Node* parentNode = node();
128     for ( ; parentNode; parentNode = parentNode->parentNode()) {
129         if (parentNode->hasTagName(theadTag) || parentNode->hasTagName(tbodyTag) || parentNode->hasTagName(tfootTag))
130             break;
131     }
132     
133     Node* otherParentNode = otherTableCell->node();
134     for ( ; otherParentNode; otherParentNode = otherParentNode->parentNode()) {
135         if (otherParentNode->hasTagName(theadTag) || otherParentNode->hasTagName(tbodyTag) || otherParentNode->hasTagName(tfootTag))
136             break;
137     }
138     
139     return otherParentNode == parentNode;
140 }
141
142
143 bool AccessibilityTableCell::isTableCellInSameColGroup(AccessibilityTableCell* tableCell)
144 {
145     std::pair<unsigned, unsigned> colRange;
146     columnIndexRange(colRange);
147     
148     std::pair<unsigned, unsigned> otherColRange;
149     tableCell->columnIndexRange(otherColRange);
150     
151     if (colRange.first <= (otherColRange.first + otherColRange.second))
152         return true;
153     return false;
154 }
155     
156 String AccessibilityTableCell::expandedTextValue() const
157 {
158     return getAttribute(abbrAttr);
159 }
160     
161 bool AccessibilityTableCell::supportsExpandedTextValue() const
162 {
163     return isTableHeaderCell() && hasAttribute(abbrAttr);
164 }
165     
166 void AccessibilityTableCell::columnHeaders(AccessibilityChildrenVector& headers)
167 {
168     AccessibilityTable* parent = parentTable();
169     if (!parent)
170         return;
171
172     // Choose columnHeaders as the place where the "headers" attribute is reported.
173     ariaElementsFromAttribute(headers, headersAttr);
174     // If the headers attribute returned valid values, then do not further search for column headers.
175     if (!headers.isEmpty())
176         return;
177     
178     std::pair<unsigned, unsigned> rowRange;
179     rowIndexRange(rowRange);
180     
181     std::pair<unsigned, unsigned> colRange;
182     columnIndexRange(colRange);
183     
184     for (unsigned row = 0; row < rowRange.first; row++) {
185         AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(colRange.first, row);
186         if (!tableCell || tableCell == this || headers.contains(tableCell))
187             continue;
188
189         std::pair<unsigned, unsigned> childRowRange;
190         tableCell->rowIndexRange(childRowRange);
191             
192         const AtomicString& scope = tableCell->getAttribute(scopeAttr);
193         if (scope == "col" || tableCell->isTableHeaderCell())
194             headers.append(tableCell);
195         else if (scope == "colgroup" && isTableCellInSameColGroup(tableCell))
196             headers.append(tableCell);
197     }
198 }
199     
200 void AccessibilityTableCell::rowHeaders(AccessibilityChildrenVector& headers)
201 {
202     AccessibilityTable* parent = parentTable();
203     if (!parent)
204         return;
205
206     std::pair<unsigned, unsigned> rowRange;
207     rowIndexRange(rowRange);
208
209     std::pair<unsigned, unsigned> colRange;
210     columnIndexRange(colRange);
211
212     for (unsigned column = 0; column < colRange.first; column++) {
213         AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(column, rowRange.first);
214         if (!tableCell || tableCell == this || headers.contains(tableCell))
215             continue;
216         
217         const AtomicString& scope = tableCell->getAttribute(scopeAttr);
218         if (scope == "row")
219             headers.append(tableCell);
220         else if (scope == "rowgroup" && isTableCellInSameRowGroup(tableCell))
221             headers.append(tableCell);
222     }
223 }
224     
225 void AccessibilityTableCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange)
226 {
227     if (!is<RenderTableCell>(m_renderer))
228         return;
229     
230     RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer);
231     rowRange.first = renderCell.rowIndex();
232     rowRange.second = renderCell.rowSpan();
233     
234     // since our table might have multiple sections, we have to offset our row appropriately
235     RenderTableSection* section = renderCell.section();
236     RenderTable* table = renderCell.table();
237     if (!table || !section)
238         return;
239
240     RenderTableSection* footerSection = table->footer();
241     unsigned rowOffset = 0;
242     for (RenderTableSection* tableSection = table->topSection(); tableSection; tableSection = table->sectionBelow(tableSection, SkipEmptySections)) {
243         // Don't add row offsets for bottom sections that are placed in before the body section.
244         if (tableSection == footerSection)
245             continue;
246         if (tableSection == section)
247             break;
248         rowOffset += tableSection->numRows();
249     }
250
251     rowRange.first += rowOffset;
252 }
253     
254 void AccessibilityTableCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange)
255 {
256     if (!is<RenderTableCell>(m_renderer))
257         return;
258     
259     const RenderTableCell& cell = downcast<RenderTableCell>(*m_renderer);
260     columnRange.first = cell.table()->colToEffCol(cell.col());
261     columnRange.second = cell.table()->colToEffCol(cell.col() + cell.colSpan()) - columnRange.first;
262 }
263     
264 AccessibilityObject* AccessibilityTableCell::titleUIElement() const
265 {
266     // Try to find if the first cell in this row is a <th>. If it is,
267     // then it can act as the title ui element. (This is only in the
268     // case when the table is not appearing as an AXTable.)
269     if (isTableCell() || !is<RenderTableCell>(m_renderer))
270         return nullptr;
271
272     // Table cells that are th cannot have title ui elements, since by definition
273     // they are title ui elements
274     Node* node = m_renderer->node();
275     if (node && node->hasTagName(thTag))
276         return nullptr;
277     
278     RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer);
279
280     // If this cell is in the first column, there is no need to continue.
281     int col = renderCell.col();
282     if (!col)
283         return nullptr;
284
285     int row = renderCell.rowIndex();
286
287     RenderTableSection* section = renderCell.section();
288     if (!section)
289         return nullptr;
290     
291     RenderTableCell* headerCell = section->primaryCellAt(row, 0);
292     if (!headerCell || headerCell == &renderCell)
293         return nullptr;
294
295     if (!headerCell->element() || !headerCell->element()->hasTagName(thTag))
296         return nullptr;
297     
298     return axObjectCache()->getOrCreate(headerCell);
299 }
300     
301 } // namespace WebCore