2 * Copyright (C) 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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 Computer, 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.
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.
30 #include "AccessibilityTable.h"
32 #include "AXObjectCache.h"
33 #include "AccessibilityTableCell.h"
34 #include "AccessibilityTableColumn.h"
35 #include "AccessibilityTableHeaderContainer.h"
36 #include "AccessibilityTableRow.h"
37 #include "HTMLNames.h"
38 #include "HTMLTableCaptionElement.h"
39 #include "HTMLTableCellElement.h"
40 #include "HTMLTableElement.h"
41 #include "RenderObject.h"
42 #include "RenderTable.h"
43 #include "RenderTableCell.h"
44 #include "RenderTableSection.h"
50 using namespace HTMLNames
;
52 AccessibilityTable::AccessibilityTable(RenderObject
* renderer
)
53 : AccessibilityRenderObject(renderer
),
56 #if ACCESSIBILITY_TABLES
57 m_isAccessibilityTable
= isTableExposableThroughAccessibility();
59 m_isAccessibilityTable
= false;
63 AccessibilityTable::~AccessibilityTable()
67 PassRefPtr
<AccessibilityTable
> AccessibilityTable::create(RenderObject
* renderer
)
69 return adoptRef(new AccessibilityTable(renderer
));
72 bool AccessibilityTable::isTableExposableThroughAccessibility()
74 // the following is a heuristic used to determine if a
75 // <table> should be exposed as an AXTable. The goal
76 // is to only show "data" tables
78 if (!m_renderer
|| !m_renderer
->isTable())
81 // if the developer assigned an aria role to this, then we shouldn't
82 // expose it as a table, unless, of course, the aria role is a table
83 AccessibilityRole ariaRole
= ariaRoleAttribute();
84 if (ariaRole
!= UnknownRole
)
87 RenderTable
* table
= toRenderTable(m_renderer
);
89 // this employs a heuristic to determine if this table should appear.
90 // Only "data" tables should be exposed as tables.
91 // Unfortunately, there is no good way to determine the difference
92 // between a "layout" table and a "data" table
94 Node
* tableNode
= table
->node();
95 if (!tableNode
|| !tableNode
->hasTagName(tableTag
))
98 // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table
99 HTMLTableElement
* tableElement
= static_cast<HTMLTableElement
*>(tableNode
);
100 if (!tableElement
->summary().isEmpty() || tableElement
->tHead() || tableElement
->tFoot() || tableElement
->caption())
103 // if someone used "rules" attribute than the table should appear
104 if (!tableElement
->rules().isEmpty())
107 // go through the cell's and check for tell-tale signs of "data" table status
108 // cells have borders, or use attributes like headers, abbr, scope or axis
109 RenderTableSection
* firstBody
= table
->firstBody();
113 int numCols
= firstBody
->numColumns();
114 int numRows
= firstBody
->numRows();
116 // if there's only one cell, it's not a good AXTable candidate
117 if (numRows
== 1 && numCols
== 1)
120 // store the background color of the table to check against cell's background colors
121 RenderStyle
* tableStyle
= table
->style();
124 Color tableBGColor
= tableStyle
->backgroundColor();
126 // check enough of the cells to find if the table matches our criteria
128 // 1) must have at least one valid cell (and)
129 // 2) at least half of cells have borders (or)
130 // 3) at least half of cells have different bg colors than the table, and there is cell spacing
131 unsigned validCellCount
= 0;
132 unsigned borderedCellCount
= 0;
133 unsigned backgroundDifferenceCellCount
= 0;
135 for (int row
= 0; row
< numRows
; ++row
) {
136 for (int col
= 0; col
< numCols
; ++col
) {
137 RenderTableCell
* cell
= firstBody
->cellAt(row
, col
).cell
;
140 Node
* cellNode
= cell
->node();
144 if (cell
->width() < 1 || cell
->height() < 1)
149 HTMLTableCellElement
* cellElement
= static_cast<HTMLTableCellElement
*>(cellNode
);
151 // in this case, the developer explicitly assigned a "data" table attribute
152 if (!cellElement
->headers().isEmpty() || !cellElement
->abbr().isEmpty()
153 || !cellElement
->axis().isEmpty() || !cellElement
->scope().isEmpty())
156 RenderStyle
* renderStyle
= cell
->style();
160 // a cell needs to have matching bordered sides, before it can be considered a bordered cell.
161 if ((cell
->borderTop() > 0 && cell
->borderBottom() > 0)
162 || (cell
->borderLeft() > 0 && cell
->borderRight() > 0))
165 // if the cell has a different color from the table and there is cell spacing,
166 // then it is probably a data table cell (spacing and colors take the place of borders)
167 Color cellColor
= renderStyle
->backgroundColor();
168 if (table
->hBorderSpacing() > 0 && table
->vBorderSpacing() > 0
169 && tableBGColor
!= cellColor
&& cellColor
.alpha() != 1)
170 backgroundDifferenceCellCount
++;
172 // if we've found 10 "good" cells, we don't need to keep searching
173 if (borderedCellCount
>= 10 || backgroundDifferenceCellCount
>= 10)
178 // if there is less than two valid cells, it's not a data table
179 if (validCellCount
<= 1)
182 // half of the cells had borders, it's a data table
183 unsigned neededCellCount
= validCellCount
/ 2;
184 if (borderedCellCount
>= neededCellCount
)
187 // half had different background colors, it's a data table
188 if (backgroundDifferenceCellCount
>= neededCellCount
)
194 void AccessibilityTable::clearChildren()
199 m_haveChildren
= false;
202 void AccessibilityTable::addChildren()
204 if (!isDataTable()) {
205 AccessibilityRenderObject::addChildren();
209 ASSERT(!m_haveChildren
);
211 m_haveChildren
= true;
212 if (!m_renderer
|| !m_renderer
->isTable())
215 RenderTable
* table
= toRenderTable(m_renderer
);
216 AXObjectCache
* axCache
= m_renderer
->document()->axObjectCache();
218 // go through all the available sections to pull out the rows
219 // and add them as children
220 RenderTableSection
* tableSection
= table
->header();
222 tableSection
= table
->firstBody();
227 RenderTableSection
* initialTableSection
= tableSection
;
229 while (tableSection
) {
231 HashSet
<AccessibilityObject
*> appendedRows
;
233 unsigned numRows
= tableSection
->numRows();
234 unsigned numCols
= tableSection
->numColumns();
235 for (unsigned rowIndex
= 0; rowIndex
< numRows
; ++rowIndex
) {
236 for (unsigned colIndex
= 0; colIndex
< numCols
; ++colIndex
) {
238 RenderTableCell
* cell
= tableSection
->cellAt(rowIndex
, colIndex
).cell
;
242 AccessibilityObject
* rowObject
= axCache
->getOrCreate(cell
->parent());
243 if (!rowObject
->isTableRow())
246 AccessibilityTableRow
* row
= static_cast<AccessibilityTableRow
*>(rowObject
);
247 // we need to check every cell for a new row, because cell spans
248 // can cause us to mess rows if we just check the first column
249 if (appendedRows
.contains(row
))
252 row
->setRowIndex((int)m_rows
.size());
254 m_children
.append(row
);
255 appendedRows
.add(row
);
259 tableSection
= table
->sectionBelow(tableSection
, true);
262 // make the columns based on the number of columns in the first body
263 unsigned length
= initialTableSection
->numColumns();
264 for (unsigned i
= 0; i
< length
; ++i
) {
265 AccessibilityTableColumn
* column
= static_cast<AccessibilityTableColumn
*>(axCache
->getOrCreate(ColumnRole
));
266 column
->setColumnIndex((int)i
);
267 column
->setParentTable(this);
268 m_columns
.append(column
);
269 m_children
.append(column
);
272 AccessibilityObject
* headerContainerObject
= headerContainer();
273 if (headerContainerObject
)
274 m_children
.append(headerContainerObject
);
277 AccessibilityObject
* AccessibilityTable::headerContainer()
279 if (m_headerContainer
)
280 return m_headerContainer
;
282 m_headerContainer
= static_cast<AccessibilityTableHeaderContainer
*>(axObjectCache()->getOrCreate(TableHeaderContainerRole
));
283 m_headerContainer
->setParentTable(this);
285 return m_headerContainer
;
288 AccessibilityObject::AccessibilityChildrenVector
& AccessibilityTable::columns()
296 AccessibilityObject::AccessibilityChildrenVector
& AccessibilityTable::rows()
304 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector
& headers
)
312 unsigned rowCount
= m_rows
.size();
313 for (unsigned k
= 0; k
< rowCount
; ++k
) {
314 AccessibilityObject
* header
= static_cast<AccessibilityTableRow
*>(m_rows
[k
].get())->headerObject();
317 headers
.append(header
);
321 void AccessibilityTable::columnHeaders(AccessibilityChildrenVector
& headers
)
329 unsigned colCount
= m_columns
.size();
330 for (unsigned k
= 0; k
< colCount
; ++k
) {
331 AccessibilityObject
* header
= static_cast<AccessibilityTableColumn
*>(m_columns
[k
].get())->headerObject();
334 headers
.append(header
);
338 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector
& cells
)
346 int numRows
= m_rows
.size();
347 for (int row
= 0; row
< numRows
; ++row
) {
348 AccessibilityChildrenVector rowChildren
= m_rows
[row
]->children();
349 cells
.append(rowChildren
);
353 unsigned AccessibilityTable::columnCount()
358 return m_columns
.size();
361 unsigned AccessibilityTable::rowCount()
366 return m_rows
.size();
369 AccessibilityTableCell
* AccessibilityTable::cellForColumnAndRow(unsigned column
, unsigned row
)
371 if (!m_renderer
|| !m_renderer
->isTable())
377 RenderTable
* table
= toRenderTable(m_renderer
);
378 RenderTableSection
* tableSection
= table
->header();
380 tableSection
= table
->firstBody();
382 RenderTableCell
* cell
= 0;
383 unsigned rowCount
= 0;
384 unsigned rowOffset
= 0;
385 while (tableSection
) {
387 unsigned numRows
= tableSection
->numRows();
388 unsigned numCols
= tableSection
->numColumns();
392 unsigned sectionSpecificRow
= row
- rowOffset
;
393 if (row
< rowCount
&& column
< numCols
&& sectionSpecificRow
< numRows
) {
394 cell
= tableSection
->cellAt(sectionSpecificRow
, column
).cell
;
396 // we didn't find the cell, which means there's spanning happening
397 // search backwards to find the spanning cell
401 for (int testRow
= sectionSpecificRow
-1; testRow
>= 0; --testRow
) {
402 cell
= tableSection
->cellAt(testRow
, column
).cell
;
403 // cell overlapped. use this one
404 if (cell
&& ((cell
->row() + (cell
->rowSpan()-1)) >= (int)sectionSpecificRow
))
411 for (int testCol
= column
-1; testCol
>= 0; --testCol
) {
412 cell
= tableSection
->cellAt(sectionSpecificRow
, testCol
).cell
;
413 // cell overlapped. use this one
414 if (cell
&& ((cell
->col() + (cell
->colSpan()-1)) >= (int)column
))
425 rowOffset
+= numRows
;
426 // we didn't find anything between the rows we should have
429 tableSection
= table
->sectionBelow(tableSection
, true);
435 AccessibilityObject
* cellObject
= axObjectCache()->getOrCreate(cell
);
436 ASSERT(cellObject
->isTableCell());
438 return static_cast<AccessibilityTableCell
*>(cellObject
);
441 AccessibilityRole
AccessibilityTable::roleValue() const
444 return AccessibilityRenderObject::roleValue();
449 bool AccessibilityTable::accessibilityIsIgnored() const
452 return AccessibilityRenderObject::accessibilityIsIgnored();
457 String
AccessibilityTable::title() const
460 return AccessibilityRenderObject::title();
466 // see if there is a caption
467 Node
* tableElement
= m_renderer
->node();
468 if (tableElement
&& tableElement
->hasTagName(tableTag
)) {
469 HTMLTableCaptionElement
* caption
= static_cast<HTMLTableElement
*>(tableElement
)->caption();
471 title
= caption
->innerText();
476 title
= AccessibilityRenderObject::title();
481 bool AccessibilityTable::isDataTable() const
486 return m_isAccessibilityTable
;
489 } // namespace WebCore