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
40namespace WebCore {
41
42using namespace HTMLNames;
43
44AccessibilityTableCell::AccessibilityTableCell(RenderObject* renderer)
45    : AccessibilityRenderObject(renderer)
46{
47}
48
49AccessibilityTableCell::~AccessibilityTableCell()
50{
51}
52
53PassRefPtr<AccessibilityTableCell> AccessibilityTableCell::create(RenderObject* renderer)
54{
55    return adoptRef(new AccessibilityTableCell(renderer));
56}
57
58bool 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
76AccessibilityTable* AccessibilityTableCell::parentTable() const
77{
78    if (!m_renderer || !m_renderer->isTableCell())
79        return 0;
80
81    // If the document no longer exists, we might not have an axObjectCache.
82    if (!axObjectCache())
83        return 0;
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    return toAccessibilityTable(axObjectCache()->get(toRenderTableCell(m_renderer)->table()));
91}
92
93bool AccessibilityTableCell::isTableCell() const
94{
95    AccessibilityObject* parent = parentObjectUnignored();
96    if (!parent || !parent->isTableRow())
97        return false;
98
99    return true;
100}
101
102AccessibilityRole AccessibilityTableCell::determineAccessibilityRole()
103{
104    // Always call determineAccessibleRole so that the ARIA role is set.
105    // Even though this object reports a Cell role, the ARIA role will be used
106    // to determine if it's a column header.
107    AccessibilityRole defaultRole = AccessibilityRenderObject::determineAccessibilityRole();
108    if (!isTableCell())
109        return defaultRole;
110
111    return CellRole;
112}
113
114bool AccessibilityTableCell::isTableHeaderCell() const
115{
116    return node() && node()->hasTagName(thTag);
117}
118
119bool AccessibilityTableCell::isTableCellInSameRowGroup(AccessibilityTableCell* otherTableCell)
120{
121    Node* parentNode = node();
122    for ( ; parentNode; parentNode = parentNode->parentNode()) {
123        if (parentNode->hasTagName(theadTag) || parentNode->hasTagName(tbodyTag) || parentNode->hasTagName(tfootTag))
124            break;
125    }
126
127    Node* otherParentNode = otherTableCell->node();
128    for ( ; otherParentNode; otherParentNode = otherParentNode->parentNode()) {
129        if (otherParentNode->hasTagName(theadTag) || otherParentNode->hasTagName(tbodyTag) || otherParentNode->hasTagName(tfootTag))
130            break;
131    }
132
133    return otherParentNode == parentNode;
134}
135
136
137bool AccessibilityTableCell::isTableCellInSameColGroup(AccessibilityTableCell* tableCell)
138{
139    std::pair<unsigned, unsigned> colRange;
140    columnIndexRange(colRange);
141
142    std::pair<unsigned, unsigned> otherColRange;
143    tableCell->columnIndexRange(otherColRange);
144
145    if (colRange.first <= (otherColRange.first + otherColRange.second))
146        return true;
147    return false;
148}
149
150String AccessibilityTableCell::expandedTextValue() const
151{
152    return getAttribute(abbrAttr);
153}
154
155bool AccessibilityTableCell::supportsExpandedTextValue() const
156{
157    return isTableHeaderCell() && hasAttribute(abbrAttr);
158}
159
160void AccessibilityTableCell::columnHeaders(AccessibilityChildrenVector& headers)
161{
162    AccessibilityTable* parent = parentTable();
163    if (!parent)
164        return;
165
166    // Choose columnHeaders as the place where the "headers" attribute is reported.
167    ariaElementsFromAttribute(headers, headersAttr);
168    // If the headers attribute returned valid values, then do not further search for column headers.
169    if (!headers.isEmpty())
170        return;
171
172    std::pair<unsigned, unsigned> rowRange;
173    rowIndexRange(rowRange);
174
175    std::pair<unsigned, unsigned> colRange;
176    columnIndexRange(colRange);
177
178    for (unsigned row = 0; row < rowRange.first; row++) {
179        AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(colRange.first, row);
180        if (!tableCell || tableCell == this || headers.contains(tableCell))
181            continue;
182
183        std::pair<unsigned, unsigned> childRowRange;
184        tableCell->rowIndexRange(childRowRange);
185
186        const AtomicString& scope = tableCell->getAttribute(scopeAttr);
187        if (scope == "col" || tableCell->isTableHeaderCell())
188            headers.append(tableCell);
189        else if (scope == "colgroup" && isTableCellInSameColGroup(tableCell))
190            headers.append(tableCell);
191    }
192}
193
194void AccessibilityTableCell::rowHeaders(AccessibilityChildrenVector& headers)
195{
196    AccessibilityTable* parent = parentTable();
197    if (!parent)
198        return;
199
200    std::pair<unsigned, unsigned> rowRange;
201    rowIndexRange(rowRange);
202
203    std::pair<unsigned, unsigned> colRange;
204    columnIndexRange(colRange);
205
206    for (unsigned column = 0; column < colRange.first; column++) {
207        AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(column, rowRange.first);
208        if (!tableCell || tableCell == this || headers.contains(tableCell))
209            continue;
210
211        const AtomicString& scope = tableCell->getAttribute(scopeAttr);
212        if (scope == "row")
213            headers.append(tableCell);
214        else if (scope == "rowgroup" && isTableCellInSameRowGroup(tableCell))
215            headers.append(tableCell);
216    }
217}
218
219void AccessibilityTableCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange)
220{
221    if (!m_renderer || !m_renderer->isTableCell())
222        return;
223
224    RenderTableCell* renderCell = toRenderTableCell(m_renderer);
225    rowRange.first = renderCell->rowIndex();
226    rowRange.second = renderCell->rowSpan();
227
228    // since our table might have multiple sections, we have to offset our row appropriately
229    RenderTableSection* section = renderCell->section();
230    RenderTable* table = renderCell->table();
231    if (!table || !section)
232        return;
233
234    RenderTableSection* footerSection = table->footer();
235    unsigned rowOffset = 0;
236    for (RenderTableSection* tableSection = table->topSection(); tableSection; tableSection = table->sectionBelow(tableSection, SkipEmptySections)) {
237        // Don't add row offsets for bottom sections that are placed in before the body section.
238        if (tableSection == footerSection)
239            continue;
240        if (tableSection == section)
241            break;
242        rowOffset += tableSection->numRows();
243    }
244
245    rowRange.first += rowOffset;
246}
247
248void AccessibilityTableCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange)
249{
250    if (!m_renderer || !m_renderer->isTableCell())
251        return;
252
253    const RenderTableCell& cell = *toRenderTableCell(m_renderer);
254    columnRange.first = cell.table()->colToEffCol(cell.col());
255    columnRange.second = cell.table()->colToEffCol(cell.col() + cell.colSpan()) - columnRange.first;
256}
257
258AccessibilityObject* AccessibilityTableCell::titleUIElement() const
259{
260    // Try to find if the first cell in this row is a <th>. If it is,
261    // then it can act as the title ui element. (This is only in the
262    // case when the table is not appearing as an AXTable.)
263    if (isTableCell() || !m_renderer || !m_renderer->isTableCell())
264        return 0;
265
266    // Table cells that are th cannot have title ui elements, since by definition
267    // they are title ui elements
268    Node* node = m_renderer->node();
269    if (node && node->hasTagName(thTag))
270        return 0;
271
272    RenderTableCell* renderCell = toRenderTableCell(m_renderer);
273
274    // If this cell is in the first column, there is no need to continue.
275    int col = renderCell->col();
276    if (!col)
277        return 0;
278
279    int row = renderCell->rowIndex();
280
281    RenderTableSection* section = renderCell->section();
282    if (!section)
283        return 0;
284
285    RenderTableCell* headerCell = section->primaryCellAt(row, 0);
286    if (!headerCell || headerCell == renderCell)
287        return 0;
288
289    if (!headerCell->element() || !headerCell->element()->hasTagName(thTag))
290        return 0;
291
292    return axObjectCache()->getOrCreate(headerCell);
293}
294
295} // namespace WebCore
296