1/*
2 * Copyright (C) 2009 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 "AccessibilityARIAGridCell.h"
31
32#include "AccessibilityObject.h"
33#include "AccessibilityTable.h"
34#include "AccessibilityTableRow.h"
35#include "HTMLNames.h"
36
37namespace WebCore {
38
39using namespace HTMLNames;
40
41AccessibilityARIAGridCell::AccessibilityARIAGridCell(RenderObject* renderer)
42 : AccessibilityTableCell(renderer)
43{
44}
45
46AccessibilityARIAGridCell::~AccessibilityARIAGridCell() = default;
47
48Ref<AccessibilityARIAGridCell> AccessibilityARIAGridCell::create(RenderObject* renderer)
49{
50 return adoptRef(*new AccessibilityARIAGridCell(renderer));
51}
52
53AccessibilityTable* AccessibilityARIAGridCell::parentTable() const
54{
55 // ARIA gridcells may have multiple levels of unignored ancestors that are not the parent table,
56 // including rows and interactive rowgroups. In addition, poorly-formed grids may contain elements
57 // which pass the tests for inclusion.
58 for (AccessibilityObject* parent = parentObjectUnignored(); parent; parent = parent->parentObjectUnignored()) {
59 if (is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility())
60 return downcast<AccessibilityTable>(parent);
61 }
62
63 return nullptr;
64}
65
66void AccessibilityARIAGridCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const
67{
68 AccessibilityObject* parent = parentObjectUnignored();
69 if (!parent)
70 return;
71
72 if (is<AccessibilityTableRow>(*parent)) {
73 // We already got a table row, use its API.
74 rowRange.first = downcast<AccessibilityTableRow>(*parent).rowIndex();
75 } else if (is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) {
76 // We reached the parent table, so we need to inspect its
77 // children to determine the row index for the cell in it.
78 unsigned columnCount = downcast<AccessibilityTable>(*parent).columnCount();
79 if (!columnCount)
80 return;
81
82 const auto& siblings = parent->children();
83 unsigned childrenSize = siblings.size();
84 for (unsigned k = 0; k < childrenSize; ++k) {
85 if (siblings[k].get() == this) {
86 rowRange.first = k / columnCount;
87 break;
88 }
89 }
90 }
91
92 // ARIA 1.1, aria-rowspan attribute is intended for cells and gridcells which are not contained in a native table.
93 // So we should check for that attribute here.
94 rowRange.second = axRowSpanWithRowIndex(rowRange.first);
95}
96
97unsigned AccessibilityARIAGridCell::axRowSpanWithRowIndex(unsigned rowIndex) const
98{
99 int rowSpan = AccessibilityTableCell::axRowSpan();
100 if (rowSpan == -1) {
101 std::pair<unsigned, unsigned> range;
102 AccessibilityTableCell::rowIndexRange(range);
103 return std::max(static_cast<int>(range.second), 1);
104 }
105
106 AccessibilityObject* parent = parentObjectUnignored();
107 if (!parent)
108 return 1;
109
110 // Setting the value to 0 indicates that the cell or gridcell is to span all the remaining rows in the row group.
111 if (!rowSpan) {
112 // rowSpan defaults to 1.
113 rowSpan = 1;
114 if (AccessibilityObject* parentRowGroup = this->parentRowGroup()) {
115 // If the row group is the parent table, we use total row count to calculate the span.
116 if (is<AccessibilityTable>(*parentRowGroup))
117 rowSpan = downcast<AccessibilityTable>(*parentRowGroup).rowCount() - rowIndex;
118 // Otherwise, we have to get the index for the current row within the parent row group.
119 else if (is<AccessibilityTableRow>(*parent)) {
120 const auto& siblings = parentRowGroup->children();
121 unsigned rowCount = siblings.size();
122 for (unsigned k = 0; k < rowCount; ++k) {
123 if (siblings[k].get() == parent) {
124 rowSpan = rowCount - k;
125 break;
126 }
127 }
128 }
129 }
130 }
131
132 return rowSpan;
133}
134
135void AccessibilityARIAGridCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const
136{
137 AccessibilityObject* parent = parentObjectUnignored();
138 if (!parent)
139 return;
140
141 if (!is<AccessibilityTableRow>(*parent)
142 && !(is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()))
143 return;
144
145 const AccessibilityChildrenVector& siblings = parent->children();
146 unsigned childrenSize = siblings.size();
147 unsigned indexWithSpan = 0;
148 for (unsigned k = 0; k < childrenSize; ++k) {
149 auto child = siblings[k].get();
150 if (child == this) {
151 columnRange.first = indexWithSpan;
152 break;
153 }
154 indexWithSpan += is<AccessibilityTableCell>(*child) ? std::max(downcast<AccessibilityTableCell>(*child).axColumnSpan(), 1) : 1;
155 }
156
157 // ARIA 1.1, aria-colspan attribute is intended for cells and gridcells which are not contained in a native table.
158 // So we should check for that attribute here.
159 int columnSpan = AccessibilityTableCell::axColumnSpan();
160 if (columnSpan == -1) {
161 std::pair<unsigned, unsigned> range;
162 AccessibilityTableCell::columnIndexRange(range);
163 columnSpan = range.second;
164 }
165
166 columnRange.second = std::max(columnSpan, 1);
167}
168
169AccessibilityObject* AccessibilityARIAGridCell::parentRowGroup() const
170{
171 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
172 if (parent->hasTagName(theadTag) || parent->hasTagName(tbodyTag) || parent->hasTagName(tfootTag) || parent->roleValue() == AccessibilityRole::RowGroup)
173 return parent;
174 }
175
176 // If there's no row group found, we use the parent table as the row group.
177 return parentTable();
178}
179
180String AccessibilityARIAGridCell::readOnlyValue() const
181{
182 if (hasAttribute(aria_readonlyAttr))
183 return getAttribute(aria_readonlyAttr).string().convertToASCIILowercase();
184
185 // ARIA 1.1 requires user agents to propagate the grid's aria-readonly value to all
186 // gridcell elements if the property is not present on the gridcell element itelf.
187 if (AccessibilityObject* parent = parentTable())
188 return parent->readOnlyValue();
189
190 return String();
191}
192
193} // namespace WebCore
194