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 "AccessibilityTableColumn.h"
31
32#include "AXObjectCache.h"
33#include "AccessibilityTableCell.h"
34#include "HTMLCollection.h"
35#include "HTMLElement.h"
36#include "HTMLNames.h"
37#include "RenderTable.h"
38#include "RenderTableCell.h"
39#include "RenderTableSection.h"
40
41namespace WebCore {
42
43using namespace HTMLNames;
44
45AccessibilityTableColumn::AccessibilityTableColumn() = default;
46
47AccessibilityTableColumn::~AccessibilityTableColumn() = default;
48
49Ref<AccessibilityTableColumn> AccessibilityTableColumn::create()
50{
51 return adoptRef(*new AccessibilityTableColumn());
52}
53
54void AccessibilityTableColumn::setParent(AccessibilityObject* parent)
55{
56 AccessibilityMockObject::setParent(parent);
57
58 clearChildren();
59}
60
61LayoutRect AccessibilityTableColumn::elementRect() const
62{
63 // This used to be cached during the call to addChildren(), but calling elementRect()
64 // can invalidate elements, so its better to ask for this on demand.
65 LayoutRect columnRect;
66 AccessibilityChildrenVector childrenCopy = m_children;
67 for (const auto& cell : childrenCopy)
68 columnRect.unite(cell->elementRect());
69
70 return columnRect;
71}
72
73AccessibilityObject* AccessibilityTableColumn::headerObject()
74{
75 if (!m_parent)
76 return nullptr;
77
78 RenderObject* renderer = m_parent->renderer();
79 if (!renderer)
80 return nullptr;
81 if (!is<AccessibilityTable>(*m_parent))
82 return nullptr;
83
84 auto& parentTable = downcast<AccessibilityTable>(*m_parent);
85 if (!parentTable.isExposableThroughAccessibility())
86 return nullptr;
87
88 if (parentTable.isAriaTable()) {
89 for (const auto& cell : children()) {
90 if (cell->ariaRoleAttribute() == AccessibilityRole::ColumnHeader)
91 return cell.get();
92 }
93
94 return nullptr;
95 }
96
97 if (!is<RenderTable>(*renderer))
98 return nullptr;
99
100 RenderTable& table = downcast<RenderTable>(*renderer);
101
102 // try the <thead> section first. this doesn't require th tags
103 if (auto* headerObject = headerObjectForSection(table.header(), false))
104 return headerObject;
105
106 RenderTableSection* bodySection = table.firstBody();
107 while (bodySection && bodySection->isAnonymous())
108 bodySection = table.sectionBelow(bodySection, SkipEmptySections);
109
110 // now try for <th> tags in the first body. If the first body is
111 return headerObjectForSection(bodySection, true);
112}
113
114AccessibilityObject* AccessibilityTableColumn::headerObjectForSection(RenderTableSection* section, bool thTagRequired)
115{
116 if (!section)
117 return nullptr;
118
119 unsigned numCols = section->numColumns();
120 if (m_columnIndex >= numCols)
121 return nullptr;
122
123 if (!section->numRows())
124 return nullptr;
125
126 RenderTableCell* cell = nullptr;
127 // also account for cells that have a span
128 for (int testCol = m_columnIndex; testCol >= 0; --testCol) {
129
130 // Run down the rows in case initial rows are invalid (like when a <caption> is used).
131 unsigned rowCount = section->numRows();
132 for (unsigned testRow = 0; testRow < rowCount; testRow++) {
133 RenderTableCell* testCell = section->primaryCellAt(testRow, testCol);
134 // No cell at this index, keep checking more rows and columns.
135 if (!testCell)
136 continue;
137
138 // If we've reached a cell that doesn't even overlap our column it can't be the header.
139 if ((testCell->col() + (testCell->colSpan()-1)) < m_columnIndex)
140 break;
141
142 Node* testCellNode = testCell->element();
143 // If the RenderTableCell doesn't have an element because its anonymous,
144 // try to see if we can find the original cell element to check if it has a <th> tag.
145 if (!testCellNode && testCell->isAnonymous()) {
146 if (Element* parentElement = testCell->parent() ? testCell->parent()->element() : nullptr) {
147 if (parentElement->hasTagName(trTag) && testCol < static_cast<int>(parentElement->countChildNodes()))
148 testCellNode = parentElement->traverseToChildAt(testCol);
149 }
150 }
151
152 if (!testCellNode)
153 continue;
154
155 // If th is required, but we found an element that doesn't have a th tag, we can stop looking.
156 if (thTagRequired && !testCellNode->hasTagName(thTag))
157 break;
158
159 cell = testCell;
160 break;
161 }
162 }
163
164 if (!cell)
165 return nullptr;
166
167 auto* cellObject = axObjectCache()->getOrCreate(cell);
168 if (!cellObject || cellObject->accessibilityIsIgnored())
169 return nullptr;
170
171 return cellObject;
172}
173
174bool AccessibilityTableColumn::computeAccessibilityIsIgnored() const
175{
176 if (!m_parent)
177 return true;
178
179#if PLATFORM(IOS_FAMILY) || PLATFORM(GTK)
180 return true;
181#endif
182
183 return m_parent->accessibilityIsIgnored();
184}
185
186void AccessibilityTableColumn::addChildren()
187{
188 ASSERT(!m_haveChildren);
189
190 m_haveChildren = true;
191 if (!is<AccessibilityTable>(m_parent))
192 return;
193
194 auto& parentTable = downcast<AccessibilityTable>(*m_parent);
195 if (!parentTable.isExposableThroughAccessibility())
196 return;
197
198 int numRows = parentTable.rowCount();
199
200 for (int i = 0; i < numRows; ++i) {
201 AccessibilityTableCell* cell = parentTable.cellForColumnAndRow(m_columnIndex, i);
202 if (!cell)
203 continue;
204
205 // make sure the last one isn't the same as this one (rowspan cells)
206 if (m_children.size() > 0 && m_children.last() == cell)
207 continue;
208
209 m_children.append(cell);
210 }
211}
212
213} // namespace WebCore
214