<rdar://problem/6227690> There are a bunch of tables on this page that don't seem...
[WebKit-https.git] / WebCore / page / AccessibilityTable.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 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.
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 "AccessibilityTable.h"
31
32 #include "AccessibilityTableCell.h"
33 #include "AccessibilityTableColumn.h"
34 #include "AccessibilityTableHeaderContainer.h"
35 #include "AccessibilityTableRow.h"
36 #include "AXObjectCache.h"
37 #include "HTMLNames.h"
38 #include "HTMLTableElement.h"
39 #include "HTMLTableCaptionElement.h"
40 #include "HTMLTableCellElement.h"
41 #include "RenderObject.h"
42 #include "RenderTable.h"
43 #include "RenderTableCell.h"
44 #include "RenderTableSection.h"
45
46 using namespace std;
47
48 namespace WebCore {
49
50 using namespace HTMLNames;
51
52 AccessibilityTable::AccessibilityTable(RenderObject* renderer)
53     : AccessibilityRenderObject(renderer),
54     m_headerContainer(0)
55 {
56     // AXTables should not appear in tiger or leopard, on the mac
57 #if PLATFORM(MAC) && (defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD))
58     m_isAccessibilityTable = false;
59 #else    
60     m_isAccessibilityTable = isTableExposableThroughAccessibility();
61 #endif
62
63 }
64
65 AccessibilityTable::~AccessibilityTable()
66 {
67 }
68
69 PassRefPtr<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer)
70 {
71     return adoptRef(new AccessibilityTable(renderer));
72 }
73
74 bool AccessibilityTable::isTableExposableThroughAccessibility()
75 {
76     // the following is a heuristic used to determine if a
77     // <table> should be exposed as an AXTable. The goal
78     // is to only show "data" tables
79     
80     if (!m_renderer || !m_renderer->isTable())
81         return false;
82     
83     // if the developer assigned an aria role to this, then we shouldn't 
84     // expose it as a table, unless, of course, the aria role is a table
85     AccessibilityRole ariaRole = ariaRoleAttribute();
86     if (ariaRole == TableRole)
87         return true;
88     if (ariaRole != UnknownRole)
89         return false;
90     
91     RenderTable* table = static_cast<RenderTable*>(m_renderer);
92     
93     // this employs a heuristic to determine if this table should appear. 
94     // Only "data" tables should be exposed as tables. 
95     // Unfortunately, there is no good way to determine the difference
96     // between a "layout" table and a "data" table
97     
98     Node* tableNode = table->element();
99     if (!tableNode || !tableNode->hasTagName(tableTag))
100         return false;
101     
102     // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table
103     HTMLTableElement* tableElement = static_cast<HTMLTableElement*>(tableNode);
104     if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
105         return true;
106     
107     // if someone used "rules" attribute than the table should appear
108     if (!tableElement->rules().isEmpty())
109         return true;    
110     
111     // go through the cell's and check for tell-tale signs of "data" table status
112     // cells have borders, or use attributes like headers, abbr, scope or axis
113     RenderTableSection* firstBody = table->firstBody();
114     if (!firstBody)
115         return false;
116     
117     int numCols = firstBody->numColumns();
118     int numRows = firstBody->numRows();
119     
120     // if there's only one cell, it's not a good AXTable candidate
121     if (numRows == 1 && numCols == 1)
122         return false;
123     
124     // store the background color of the table to check against cell's background colors
125     RenderStyle* tableStyle = table->style();
126     if (!tableStyle)
127         return false;
128     Color tableBGColor = tableStyle->backgroundColor();
129     
130     // check enough of the cells to find if the table matches our criteria
131     // Criteria: 
132     //   1) must have at least one valid cell (and)
133     //   2) at least half of cells have borders (or)
134     //   3) at least half of cells have different bg colors than the table, and there is cell spacing
135     unsigned validCellCount = 0;
136     unsigned borderedCellCount = 0;
137     unsigned backgroundDifferenceCellCount = 0;
138     
139     for (int row = 0; row < numRows; ++row) {
140         for (int col = 0; col < numCols; ++col) {    
141             RenderTableCell* cell = firstBody->cellAt(row, col).cell;
142             if (!cell)
143                 continue;
144             Node* cellNode = cell->element();
145             if (!cellNode)
146                 continue;
147             
148             if (cell->width() < 1 || cell->height() < 1)
149                 continue;
150             
151             validCellCount++;
152             
153             HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode);
154             
155             // in this case, the developer explicitly assigned a "data" table attribute
156             if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() || 
157                 !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty())
158                 return true;
159             
160             RenderStyle* renderStyle = cell->style();
161             if (!renderStyle)
162                 continue;
163
164             // a cell needs to have matching bordered sides, before it can be considered a bordered cell.
165             if ((cell->borderTop() > 0 && cell->borderBottom() > 0) ||
166                 (cell->borderLeft() > 0 && cell->borderRight() > 0))
167                 borderedCellCount++;
168             
169             // if the cell has a different color from the table and there is cell spacing,
170             // then it is probably a data table cell (spacing and colors take the place of borders)
171             Color cellColor = renderStyle->backgroundColor();
172             if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 && 
173                 tableBGColor != cellColor && cellColor.alpha() != 1)
174                 backgroundDifferenceCellCount++;
175             
176             // if we've found 10 "good" cells, we don't need to keep searching
177             if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
178                 return true;
179         }
180     }
181
182     // if there is less than two valid cells, it's not a data table
183     if (validCellCount <= 1)
184         return false;
185     
186     // half of the cells had borders, it's a data table
187     unsigned neededCellCount = validCellCount / 2;
188     if (borderedCellCount >= neededCellCount)
189         return true;
190     
191     // half had different background colors, it's a data table
192     if (backgroundDifferenceCellCount >= neededCellCount)
193         return true;
194
195     return false;
196 }
197     
198 void AccessibilityTable::clearChildren()
199 {
200     m_children.clear();
201     m_rows.clear();
202     m_columns.clear();
203     m_haveChildren = false;
204 }
205
206 void AccessibilityTable::addChildren()
207 {
208     if (!isDataTable()) {
209         AccessibilityRenderObject::addChildren();
210         return;
211     }
212     
213     ASSERT(!m_haveChildren); 
214     
215     m_haveChildren = true;
216     if (!m_renderer)
217         return;
218     
219     RenderTable* table = static_cast<RenderTable*>(m_renderer);
220     AXObjectCache* axCache = m_renderer->document()->axObjectCache();
221
222     // go through all the available sections to pull out the rows
223     // and add them as children
224     RenderTableSection* tableSection = table->header();
225     if (!tableSection)
226         tableSection = table->firstBody();
227     
228     while (tableSection) {
229         
230         HashSet<AccessibilityObject*> appendedRows;
231
232         unsigned numRows = tableSection->numRows();
233         unsigned numCols = tableSection->numColumns();
234         for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
235             for (unsigned colIndex = 0; colIndex < numCols; ++colIndex) {
236                 
237                 RenderTableCell* cell = tableSection->cellAt(rowIndex, colIndex).cell;
238                 if (!cell)
239                     continue;
240                 
241                 AccessibilityObject* rowObject = axCache->get(cell->parent());
242                 if (!rowObject->isTableRow())
243                     continue;
244                 
245                 AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject);
246                 // we need to check every cell for a new row, because cell spans
247                 // can cause us to mess rows if we just check the first column
248                 if (appendedRows.contains(row))
249                     continue;
250                 
251                 row->setRowIndex((int)m_rows.size());        
252                 m_rows.append(row);
253                 m_children.append(row);
254                 appendedRows.add(row);
255             }
256         }
257         
258         tableSection = table->sectionBelow(tableSection, true);
259     }
260     
261     // make the columns based on the number of columns in the first body
262     unsigned length = table->firstBody()->numColumns();
263     for (unsigned i = 0; i < length; ++i) {
264         AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->get(ColumnRole));
265         column->setColumnIndex((int)i);
266         column->setParentTable(this);
267         m_columns.append(column);
268         m_children.append(column);
269     }
270     
271     AccessibilityObject* headerContainerObject = headerContainer();
272     if (headerContainerObject)
273         m_children.append(headerContainerObject);
274 }
275     
276 AccessibilityObject* AccessibilityTable::headerContainer()
277 {
278     if (m_headerContainer)
279         return m_headerContainer;
280     
281     m_headerContainer = static_cast<AccessibilityTableHeaderContainer*>(axObjectCache()->get(TableHeaderContainerRole));
282     m_headerContainer->setParentTable(this);
283     
284     return m_headerContainer;
285 }
286
287 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
288 {
289     if (!hasChildren())
290         addChildren();
291         
292     return m_columns;
293 }
294
295 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
296 {
297     if (!hasChildren())
298         addChildren();
299
300     return m_rows;
301 }
302     
303 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
304 {
305     if (!m_renderer)
306         return;
307     
308     if (!hasChildren())
309         addChildren();
310     
311     unsigned rowCount = m_rows.size();
312     for (unsigned k = 0; k < rowCount; ++k) {
313         AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject();
314         if (!header)
315             continue;
316         headers.append(header);
317     }
318 }
319
320 void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
321 {
322     if (!m_renderer)
323         return;
324     
325     if (!hasChildren())
326         addChildren();
327     
328     unsigned colCount = m_columns.size();
329     for (unsigned k = 0; k < colCount; ++k) {
330         AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject();
331         if (!header)
332             continue;
333         headers.append(header);
334     }
335 }
336     
337 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
338 {
339     if (!m_renderer)
340         return;
341     
342     if (!hasChildren())
343         addChildren();
344     
345     int numRows = m_rows.size();
346     for (int row = 0; row < numRows; ++row) {
347         AccessibilityChildrenVector rowChildren = m_rows[row]->children();
348         cells.append(rowChildren);
349     }
350 }
351     
352 const unsigned AccessibilityTable::columnCount()
353 {
354     if (!hasChildren())
355         addChildren();
356     
357     return m_columns.size();    
358 }
359     
360 const unsigned AccessibilityTable::rowCount()
361 {
362     if (!hasChildren())
363         addChildren();
364     
365     return m_rows.size();
366 }
367     
368 AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row)
369 {
370     if (!m_renderer)
371         return 0;
372     
373     if (!hasChildren())
374         addChildren();
375     
376     RenderTable* table = static_cast<RenderTable*>(m_renderer);
377     RenderTableSection* tableSection = table->header();
378     if (!tableSection)
379         tableSection = table->firstBody();
380     
381     RenderTableCell* cell = 0;
382     unsigned rowCount = 0;
383     unsigned rowOffset = 0;
384     while (tableSection) {
385         
386         rowCount += tableSection->numRows();
387         unsigned numCols = tableSection->numColumns();
388         
389         if (row < rowCount && column < numCols) {
390             int sectionSpecificRow = row - rowOffset;
391             cell = tableSection->cellAt(sectionSpecificRow, column).cell;
392             
393             // we didn't find the cell, which means there's spanning happening
394             // search backwards to find the spanning cell
395             if (!cell) {
396                 
397                 // first try rows
398                 for (int testRow = sectionSpecificRow-1; testRow >= 0; --testRow) {
399                     cell = tableSection->cellAt(testRow, column).cell;
400                     // cell overlapped. use this one
401                     if (cell && ((cell->row() + (cell->rowSpan()-1)) >= (int)sectionSpecificRow))
402                         break;
403                     cell = 0;
404                 }
405                 
406                 if (!cell) {
407                     // try cols
408                     for (int testCol = column-1; testCol >= 0; --testCol) {
409                         cell = tableSection->cellAt(sectionSpecificRow, testCol).cell;
410                         // cell overlapped. use this one
411                         if (cell && ((cell->col() + (cell->colSpan()-1)) >= (int)column))
412                             break;
413                         cell = 0;
414                     }
415                 }
416             }
417         }
418         
419         if (cell)
420             break;
421         
422         rowOffset += rowCount;
423         // we didn't find anything between the rows we should have
424         if (row < rowOffset)
425             break;
426         tableSection = table->sectionBelow(tableSection, true);        
427     }
428     
429     if (!cell)
430         return 0;
431     
432     AccessibilityObject* cellObject = axObjectCache()->get(cell);
433     ASSERT(cellObject->isTableCell());
434     
435     return static_cast<AccessibilityTableCell*>(cellObject);
436 }
437
438 AccessibilityRole AccessibilityTable::roleValue() const
439 {
440     if (!isDataTable())
441         return AccessibilityRenderObject::roleValue();
442
443     return TableRole;
444 }
445     
446 bool AccessibilityTable::accessibilityIsIgnored() const
447 {
448     if (!isDataTable())
449         return AccessibilityRenderObject::accessibilityIsIgnored();
450     
451     return false;
452 }
453     
454 String AccessibilityTable::title() const
455 {
456     if (!isDataTable())
457         return AccessibilityRenderObject::title();
458     
459     String title;
460     if (!m_renderer)
461         return title;
462     
463     // see if there is a caption
464     Node *tableElement = m_renderer->element();
465     if (tableElement) {
466         HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(tableElement)->caption();
467         if (caption)
468             title = caption->innerText();
469     }
470     
471     // try the standard 
472     if (title.isEmpty())
473         title = AccessibilityRenderObject::title();
474     
475     return title;
476 }
477
478 bool AccessibilityTable::isDataTable() const
479 {
480     if (!m_renderer)
481         return false;
482     
483     return m_isAccessibilityTable;
484 }
485
486 } // namespace WebCore