Rename AtomicString to AtomString
[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 "ElementIterator.h"
36 #include "HTMLElement.h"
37 #include "HTMLNames.h"
38 #include "RenderObject.h"
39 #include "RenderTableCell.h"
40
41 namespace WebCore {
42     
43 using namespace HTMLNames;
44
45 AccessibilityTableCell::AccessibilityTableCell(RenderObject* renderer)
46     : AccessibilityRenderObject(renderer)
47     , m_axColIndexFromRow(-1)
48 {
49 }
50
51 AccessibilityTableCell::~AccessibilityTableCell() = default;
52
53 Ref<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 == AccessibilityObjectInclusion::IncludeObject)
62         return false;
63     if (decision == AccessibilityObjectInclusion::IgnoreObject)
64         return true;
65     
66     // Ignore anonymous table cells as long as they're not in a table (ie. when display:table is used).
67     RenderObject* renderTable = is<RenderTableCell>(renderer()) ? downcast<RenderTableCell>(*m_renderer).table() : nullptr;
68     bool inTable = renderTable && renderTable->node() && (renderTable->node()->hasTagName(tableTag) || nodeHasRole(renderTable->node(), "grid"));
69     if (!node() && !inTable)
70         return true;
71         
72     if (!isTableCell())
73         return AccessibilityRenderObject::computeAccessibilityIsIgnored();
74     
75     return false;
76 }
77
78 AccessibilityTable* AccessibilityTableCell::parentTable() const
79 {
80     if (!is<RenderTableCell>(renderer()))
81         return nullptr;
82
83     // If the document no longer exists, we might not have an axObjectCache.
84     if (!axObjectCache())
85         return nullptr;
86     
87     // Do not use getOrCreate. parentTable() can be called while the render tree is being modified 
88     // by javascript, and creating a table element may try to access the render tree while in a bad state.
89     // By using only get() implies that the AXTable must be created before AXTableCells. This should
90     // always be the case when AT clients access a table.
91     // https://bugs.webkit.org/show_bug.cgi?id=42652
92     AccessibilityObject* parentTable = axObjectCache()->get(downcast<RenderTableCell>(*m_renderer).table());
93     if (!is<AccessibilityTable>(parentTable))
94         return nullptr;
95     
96     // The RenderTableCell's table() object might be anonymous sometimes. We should handle it gracefully
97     // by finding the right table.
98     if (!parentTable->node()) {
99         for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
100             // If this is a non-anonymous table object, but not an accessibility table, we should stop because
101             // we don't want to choose another ancestor table as this cell's table.
102             if (is<AccessibilityTable>(*parent)) {
103                 auto& parentTable = downcast<AccessibilityTable>(*parent);
104                 if (parentTable.isExposableThroughAccessibility())
105                     return &parentTable;
106                 if (parentTable.node())
107                     break;
108             }
109         }
110         return nullptr;
111     }
112     
113     return downcast<AccessibilityTable>(parentTable);
114 }
115     
116 bool AccessibilityTableCell::isTableCell() const
117 {
118     // If the parent table is an accessibility table, then we are a table cell.
119     // This used to check if the unignoredParent was a row, but that exploded performance if
120     // this was in nested tables. This check should be just as good.
121     AccessibilityObject* parentTable = this->parentTable();
122     return is<AccessibilityTable>(parentTable) && downcast<AccessibilityTable>(*parentTable).isExposableThroughAccessibility();
123 }
124     
125 AccessibilityRole AccessibilityTableCell::determineAccessibilityRole()
126 {
127     // AccessibilityRenderObject::determineAccessibleRole provides any ARIA-supplied
128     // role, falling back on the role to be used if we determine here that the element
129     // should not be exposed as a cell. Thus if we already know it's a cell, return that.
130     AccessibilityRole defaultRole = AccessibilityRenderObject::determineAccessibilityRole();
131     if (defaultRole == AccessibilityRole::ColumnHeader || defaultRole == AccessibilityRole::RowHeader || defaultRole == AccessibilityRole::Cell || defaultRole == AccessibilityRole::GridCell)
132         return defaultRole;
133
134     if (!isTableCell())
135         return defaultRole;
136     if (isColumnHeaderCell())
137         return AccessibilityRole::ColumnHeader;
138     if (isRowHeaderCell())
139         return AccessibilityRole::RowHeader;
140
141     return AccessibilityRole::Cell;
142 }
143     
144 bool AccessibilityTableCell::isTableHeaderCell() const
145 {
146     return node() && node()->hasTagName(thTag);
147 }
148
149 bool AccessibilityTableCell::isColumnHeaderCell() const
150 {
151     const AtomString& scope = getAttribute(scopeAttr);
152     if (scope == "col" || scope == "colgroup")
153         return true;
154     if (scope == "row" || scope == "rowgroup")
155         return false;
156     if (!isTableHeaderCell())
157         return false;
158
159     // We are in a situation after checking the scope attribute.
160     // It is an attempt to resolve the type of th element without support in the specification.
161     // Checking tableTag and tbodyTag allows to check the case of direct row placement in the table and lets stop the loop at the table level.
162     for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) {
163         if (parentNode->hasTagName(theadTag))
164             return true;
165         if (parentNode->hasTagName(tfootTag))
166             return false;
167         if (parentNode->hasTagName(tableTag) || parentNode->hasTagName(tbodyTag)) {
168             std::pair<unsigned, unsigned> rowRange;
169             rowIndexRange(rowRange);
170             if (!rowRange.first)
171                 return true;
172             return false;
173         }
174     }
175     return false;
176 }
177
178 bool AccessibilityTableCell::isRowHeaderCell() const
179 {
180     const AtomString& scope = getAttribute(scopeAttr);
181     if (scope == "row" || scope == "rowgroup")
182         return true;
183     if (scope == "col" || scope == "colgroup")
184         return false;
185     if (!isTableHeaderCell())
186         return false;
187
188     // We are in a situation after checking the scope attribute.
189     // It is an attempt to resolve the type of th element without support in the specification.
190     // Checking tableTag allows to check the case of direct row placement in the table and lets stop the loop at the table level.
191     for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) {
192         if (parentNode->hasTagName(tfootTag) || parentNode->hasTagName(tbodyTag) || parentNode->hasTagName(tableTag)) {
193             std::pair<unsigned, unsigned> colRange;
194             columnIndexRange(colRange);
195             if (!colRange.first)
196                 return true;
197             return false;
198         }
199         if (parentNode->hasTagName(theadTag))
200             return false;
201     }
202     return false;
203 }
204
205 bool AccessibilityTableCell::isTableCellInSameRowGroup(AccessibilityTableCell* otherTableCell)
206 {
207     Node* parentNode = node();
208     for ( ; parentNode; parentNode = parentNode->parentNode()) {
209         if (parentNode->hasTagName(theadTag) || parentNode->hasTagName(tbodyTag) || parentNode->hasTagName(tfootTag))
210             break;
211     }
212     
213     Node* otherParentNode = otherTableCell->node();
214     for ( ; otherParentNode; otherParentNode = otherParentNode->parentNode()) {
215         if (otherParentNode->hasTagName(theadTag) || otherParentNode->hasTagName(tbodyTag) || otherParentNode->hasTagName(tfootTag))
216             break;
217     }
218     
219     return otherParentNode == parentNode;
220 }
221
222
223 bool AccessibilityTableCell::isTableCellInSameColGroup(AccessibilityTableCell* tableCell)
224 {
225     std::pair<unsigned, unsigned> colRange;
226     columnIndexRange(colRange);
227     
228     std::pair<unsigned, unsigned> otherColRange;
229     tableCell->columnIndexRange(otherColRange);
230     
231     if (colRange.first <= (otherColRange.first + otherColRange.second))
232         return true;
233     return false;
234 }
235     
236 String AccessibilityTableCell::expandedTextValue() const
237 {
238     return getAttribute(abbrAttr);
239 }
240     
241 bool AccessibilityTableCell::supportsExpandedTextValue() const
242 {
243     return isTableHeaderCell() && hasAttribute(abbrAttr);
244 }
245     
246 void AccessibilityTableCell::columnHeaders(AccessibilityChildrenVector& headers)
247 {
248     AccessibilityTable* parent = parentTable();
249     if (!parent)
250         return;
251
252     // Choose columnHeaders as the place where the "headers" attribute is reported.
253     ariaElementsFromAttribute(headers, headersAttr);
254     // If the headers attribute returned valid values, then do not further search for column headers.
255     if (!headers.isEmpty())
256         return;
257     
258     std::pair<unsigned, unsigned> rowRange;
259     rowIndexRange(rowRange);
260     
261     std::pair<unsigned, unsigned> colRange;
262     columnIndexRange(colRange);
263     
264     for (unsigned row = 0; row < rowRange.first; row++) {
265         AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(colRange.first, row);
266         if (!tableCell || tableCell == this || headers.contains(tableCell))
267             continue;
268
269         std::pair<unsigned, unsigned> childRowRange;
270         tableCell->rowIndexRange(childRowRange);
271             
272         const AtomString& scope = tableCell->getAttribute(scopeAttr);
273         if (scope == "colgroup" && isTableCellInSameColGroup(tableCell))
274             headers.append(tableCell);
275         else if (tableCell->isColumnHeaderCell())
276             headers.append(tableCell);
277     }
278 }
279     
280 void AccessibilityTableCell::rowHeaders(AccessibilityChildrenVector& headers)
281 {
282     AccessibilityTable* parent = parentTable();
283     if (!parent)
284         return;
285
286     std::pair<unsigned, unsigned> rowRange;
287     rowIndexRange(rowRange);
288
289     std::pair<unsigned, unsigned> colRange;
290     columnIndexRange(colRange);
291
292     for (unsigned column = 0; column < colRange.first; column++) {
293         AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(column, rowRange.first);
294         if (!tableCell || tableCell == this || headers.contains(tableCell))
295             continue;
296         
297         const AtomString& scope = tableCell->getAttribute(scopeAttr);
298         if (scope == "rowgroup" && isTableCellInSameRowGroup(tableCell))
299             headers.append(tableCell);
300         else if (tableCell->isRowHeaderCell())
301             headers.append(tableCell);
302     }
303 }
304
305 AccessibilityTableRow* AccessibilityTableCell::parentRow() const
306 {
307     AccessibilityObject* parent = parentObjectUnignored();
308     if (!is<AccessibilityTableRow>(*parent))
309         return nullptr;
310     return downcast<AccessibilityTableRow>(parent);
311 }
312
313 void AccessibilityTableCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const
314 {
315     if (!is<RenderTableCell>(renderer()))
316         return;
317     
318     RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer);
319
320     // ARIA 1.1's aria-rowspan attribute is intended for cells and gridcells which are not contained
321     // in a native table. But if we have a valid author-provided value and do not have an explicit
322     // native host language value for the rowspan, expose the ARIA value.
323     rowRange.second = axRowSpan();
324     if (static_cast<int>(rowRange.second) == -1)
325         rowRange.second = renderCell.rowSpan();
326     
327     if (AccessibilityTableRow* parentRow = this->parentRow())
328         rowRange.first = parentRow->rowIndex();
329 }
330     
331 void AccessibilityTableCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const
332 {
333     if (!is<RenderTableCell>(renderer()))
334         return;
335     
336     const RenderTableCell& cell = downcast<RenderTableCell>(*m_renderer);
337     columnRange.first = cell.table()->colToEffCol(cell.col());
338
339     // ARIA 1.1's aria-colspan attribute is intended for cells and gridcells which are not contained
340     // in a native table. But if we have a valid author-provided value and do not have an explicit
341     // native host language value for the colspan, expose the ARIA value.
342     columnRange.second = axColumnSpan();
343     if (static_cast<int>(columnRange.second) != -1)
344         return;
345
346     columnRange.second = cell.table()->colToEffCol(cell.col() + cell.colSpan()) - columnRange.first;
347 }
348     
349 AccessibilityObject* AccessibilityTableCell::titleUIElement() const
350 {
351     // Try to find if the first cell in this row is a <th>. If it is,
352     // then it can act as the title ui element. (This is only in the
353     // case when the table is not appearing as an AXTable.)
354     if (isTableCell() || !is<RenderTableCell>(renderer()))
355         return nullptr;
356
357     // Table cells that are th cannot have title ui elements, since by definition
358     // they are title ui elements
359     Node* node = m_renderer->node();
360     if (node && node->hasTagName(thTag))
361         return nullptr;
362     
363     RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer);
364
365     // If this cell is in the first column, there is no need to continue.
366     int col = renderCell.col();
367     if (!col)
368         return nullptr;
369
370     int row = renderCell.rowIndex();
371
372     RenderTableSection* section = renderCell.section();
373     if (!section)
374         return nullptr;
375     
376     RenderTableCell* headerCell = section->primaryCellAt(row, 0);
377     if (!headerCell || headerCell == &renderCell)
378         return nullptr;
379
380     if (!headerCell->element() || !headerCell->element()->hasTagName(thTag))
381         return nullptr;
382     
383     return axObjectCache()->getOrCreate(headerCell);
384 }
385     
386 int AccessibilityTableCell::axColumnIndex() const
387 {
388     const AtomString& colIndexValue = getAttribute(aria_colindexAttr);
389     if (colIndexValue.toInt() >= 1)
390         return colIndexValue.toInt();
391
392     // "ARIA 1.1: If the set of columns which is present in the DOM is contiguous, and if there are no cells which span more than one row
393     // or column in that set, then authors may place aria-colindex on each row, setting the value to the index of the first column of the set."
394     // Here, we let its parent row to set its index beforehand, so we don't have to go through the siblings to calculate the index.
395     AccessibilityTableRow* parentRow = this->parentRow();
396     if (parentRow && m_axColIndexFromRow != -1)
397         return m_axColIndexFromRow;
398     
399     return -1;
400 }
401     
402 int AccessibilityTableCell::axRowIndex() const
403 {
404     // ARIA 1.1: Authors should place aria-rowindex on each row. Authors may also place
405     // aria-rowindex on all of the children or owned elements of each row.
406     const AtomString& rowIndexValue = getAttribute(aria_rowindexAttr);
407     if (rowIndexValue.toInt() >= 1)
408         return rowIndexValue.toInt();
409     
410     if (AccessibilityTableRow* parentRow = this->parentRow())
411         return parentRow->axRowIndex();
412     
413     return -1;
414 }
415
416 int AccessibilityTableCell::axColumnSpan() const
417 {
418     // According to the ARIA spec, "If aria-colpan is used on an element for which the host language
419     // provides an equivalent attribute, user agents must ignore the value of aria-colspan."
420     if (hasAttribute(colspanAttr))
421         return -1;
422
423     const AtomString& colSpanValue = getAttribute(aria_colspanAttr);
424     // ARIA 1.1: Authors must set the value of aria-colspan to an integer greater than or equal to 1.
425     if (colSpanValue.toInt() >= 1)
426         return colSpanValue.toInt();
427     
428     return -1;
429 }
430
431 int AccessibilityTableCell::axRowSpan() const
432 {
433     // According to the ARIA spec, "If aria-rowspan is used on an element for which the host language
434     // provides an equivalent attribute, user agents must ignore the value of aria-rowspan."
435     if (hasAttribute(rowspanAttr))
436         return -1;
437
438     const AtomString& rowSpanValue = getAttribute(aria_rowspanAttr);
439     
440     // ARIA 1.1: Authors must set the value of aria-rowspan to an integer greater than or equal to 0.
441     // Setting the value to 0 indicates that the cell or gridcell is to span all the remaining rows in the row group.
442     if (rowSpanValue == "0")
443         return 0;
444     if (rowSpanValue.toInt() >= 1)
445         return rowSpanValue.toInt();
446     
447     return -1;
448 }
449     
450 } // namespace WebCore