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