78c881c18f208903f968649f38467789e2b5d155
[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     if (!m_renderer || !m_renderer->isTable())
77         return false;
78     
79     // if the developer assigned an aria role to this, then we shouldn't 
80     // expose it as a table, unless, of course, the aria role is a table
81     AccessibilityRole ariaRole = ariaRoleAttribute();
82     if (ariaRole == TableRole)
83         return true;
84     if (ariaRole != UnknownRole)
85         return false;
86     
87     RenderTable* table = static_cast<RenderTable*>(m_renderer);
88     
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
93     
94     Node* tableNode = table->element();
95     if (!tableNode || !tableNode->hasTagName(tableTag))
96         return false;
97     
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())
101         return true;
102     
103     // if someone used "rules" attribute than the table should appear
104     if (!tableElement->rules().isEmpty())
105         return true;    
106     
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();
110     if (!firstBody)
111         return false;
112     
113     int numCols = firstBody->numColumns();
114     int numRows = firstBody->numRows();
115     
116     // if there's only one cell, it's not a good AXTable candidate
117     if (numRows == 1 && numCols == 1)
118         return false;
119     
120     // store the background color of the table to check against cell's background colors
121     RenderStyle* tableStyle = table->style();
122     if (!tableStyle)
123         return false;
124     Color tableBGColor = tableStyle->backgroundColor();
125     
126     // check no more than 2 rows for the sake of speed.
127     for (int row = 0; row < numRows && row < 2; ++row) {
128         for (int col = 0; col < numCols; ++col) {            
129             RenderTableCell* cell = firstBody->cellAt(row, col).cell;
130             if (!cell)
131                 continue;
132             Node* cellNode = cell->element();
133             if (!cellNode)
134                 continue;
135             
136             HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode);
137             
138             if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() || 
139                 !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty())
140                 return true;
141             
142             RenderStyle* renderStyle = cell->style();
143             if (!renderStyle)
144                 continue;
145             
146             // at least one cell had a border, it is probably a data table
147             if (renderStyle->border().hasBorder())
148                 return true;
149             
150             // do our cells have a different color from the table and is there cell spacing?
151             // then probably this is a data table (there were no borders... spacing and 
152             // colors take the place of borders)
153             if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 && 
154                 tableBGColor != renderStyle->backgroundColor())
155                 return true;
156         }
157     }
158
159     return false;
160 }
161     
162 void AccessibilityTable::clearChildren()
163 {
164     m_children.clear();
165     m_rows.clear();
166     m_columns.clear();
167     m_haveChildren = false;
168 }
169
170 void AccessibilityTable::addChildren()
171 {
172     if (!isDataTable()) {
173         AccessibilityRenderObject::addChildren();
174         return;
175     }
176     
177     ASSERT(!m_haveChildren); 
178     
179     m_haveChildren = true;
180     if (!m_renderer)
181         return;
182     
183     RenderTable* table = static_cast<RenderTable*>(m_renderer);
184     AXObjectCache* axCache = m_renderer->document()->axObjectCache();
185
186     // go through all the available sections to pull out the rows
187     // and add them as children
188     RenderTableSection* tableSection = table->header();
189     if (!tableSection)
190         tableSection = table->firstBody();
191     
192     while (tableSection) {
193         
194         HashSet<AccessibilityObject*> appendedRows;
195
196         unsigned numRows = tableSection->numRows();
197         unsigned numCols = tableSection->numColumns();
198         for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
199             for (unsigned colIndex = 0; colIndex < numCols; ++colIndex) {
200                 
201                 RenderTableCell* cell = tableSection->cellAt(rowIndex, colIndex).cell;
202                 if (!cell)
203                     continue;
204                 
205                 AccessibilityObject* rowObject = axCache->get(cell->parent());
206                 if (!rowObject->isTableRow())
207                     continue;
208                 
209                 AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject);
210                 // we need to check every cell for a new row, because cell spans
211                 // can cause us to mess rows if we just check the first column
212                 if (appendedRows.contains(row))
213                     continue;
214                 
215                 row->setRowIndex((int)m_rows.size());        
216                 m_rows.append(row);
217                 m_children.append(row);
218                 appendedRows.add(row);
219             }
220         }
221         
222         tableSection = table->sectionBelow(tableSection, true);
223     }
224     
225     // make the columns based on the number of columns in the first body
226     unsigned length = table->firstBody()->numColumns();
227     for (unsigned i = 0; i < length; ++i) {
228         AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->get(ColumnRole));
229         column->setColumnIndex((int)i);
230         column->setParentTable(this);
231         m_columns.append(column);
232         m_children.append(column);
233     }
234     
235     AccessibilityObject* headerContainerObject = headerContainer();
236     if (headerContainerObject)
237         m_children.append(headerContainerObject);
238 }
239     
240 AccessibilityObject* AccessibilityTable::headerContainer()
241 {
242     if (m_headerContainer)
243         return m_headerContainer;
244     
245     m_headerContainer = static_cast<AccessibilityTableHeaderContainer*>(axObjectCache()->get(TableHeaderContainerRole));
246     m_headerContainer->setParentTable(this);
247     
248     return m_headerContainer;
249 }
250
251 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns()
252 {
253     if (!hasChildren())
254         addChildren();
255         
256     return m_columns;
257 }
258
259 AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows()
260 {
261     if (!hasChildren())
262         addChildren();
263
264     return m_rows;
265 }
266     
267 void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers)
268 {
269     if (!m_renderer)
270         return;
271     
272     if (!hasChildren())
273         addChildren();
274     
275     unsigned rowCount = m_rows.size();
276     for (unsigned k = 0; k < rowCount; ++k) {
277         AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject();
278         if (!header)
279             continue;
280         headers.append(header);
281     }
282 }
283
284 void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers)
285 {
286     if (!m_renderer)
287         return;
288     
289     if (!hasChildren())
290         addChildren();
291     
292     unsigned colCount = m_columns.size();
293     for (unsigned k = 0; k < colCount; ++k) {
294         AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject();
295         if (!header)
296             continue;
297         headers.append(header);
298     }
299 }
300     
301 void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells)
302 {
303     if (!m_renderer)
304         return;
305     
306     if (!hasChildren())
307         addChildren();
308     
309     int numRows = m_rows.size();
310     for (int row = 0; row < numRows; ++row) {
311         AccessibilityChildrenVector rowChildren = m_rows[row]->children();
312         cells.append(rowChildren);
313     }
314 }
315     
316 const unsigned AccessibilityTable::columnCount()
317 {
318     if (!hasChildren())
319         addChildren();
320     
321     return m_columns.size();    
322 }
323     
324 const unsigned AccessibilityTable::rowCount()
325 {
326     if (!hasChildren())
327         addChildren();
328     
329     return m_rows.size();
330 }
331     
332 AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row)
333 {
334     if (!m_renderer)
335         return 0;
336     
337     if (!hasChildren())
338         addChildren();
339     
340     RenderTable* table = static_cast<RenderTable*>(m_renderer);
341     RenderTableSection* tableSection = table->header();
342     if (!tableSection)
343         tableSection = table->firstBody();
344     
345     RenderTableCell* cell = 0;
346     unsigned rowCount = 0;
347     unsigned rowOffset = 0;
348     while (tableSection) {
349         
350         rowCount += tableSection->numRows();
351         unsigned numCols = tableSection->numColumns();
352         
353         if (row < rowCount && column < numCols) {
354             int sectionSpecificRow = row - rowOffset;
355             cell = tableSection->cellAt(sectionSpecificRow, column).cell;
356             
357             // we didn't find the cell, which means there's spanning happening
358             // search backwards to find the spanning cell
359             if (!cell) {
360                 
361                 // first try rows
362                 for (int testRow = sectionSpecificRow-1; testRow >= 0; --testRow) {
363                     cell = tableSection->cellAt(testRow, column).cell;
364                     // cell overlapped. use this one
365                     if (cell && ((cell->row() + (cell->rowSpan()-1)) >= (int)sectionSpecificRow))
366                         break;
367                     cell = 0;
368                 }
369                 
370                 if (!cell) {
371                     // try cols
372                     for (int testCol = column-1; testCol >= 0; --testCol) {
373                         cell = tableSection->cellAt(sectionSpecificRow, testCol).cell;
374                         // cell overlapped. use this one
375                         if (cell && ((cell->col() + (cell->colSpan()-1)) >= (int)column))
376                             break;
377                         cell = 0;
378                     }
379                 }
380             }
381         }
382         
383         if (cell)
384             break;
385         
386         rowOffset += rowCount;
387         // we didn't find anything between the rows we should have
388         if (row < rowOffset)
389             break;
390         tableSection = table->sectionBelow(tableSection, true);        
391     }
392     
393     if (!cell)
394         return 0;
395     
396     AccessibilityObject* cellObject = axObjectCache()->get(cell);
397     ASSERT(cellObject->isTableCell());
398     
399     return static_cast<AccessibilityTableCell*>(cellObject);
400 }
401
402 AccessibilityRole AccessibilityTable::roleValue() const
403 {
404     if (!isDataTable())
405         return AccessibilityRenderObject::roleValue();
406
407     return TableRole;
408 }
409     
410 bool AccessibilityTable::accessibilityIsIgnored() const
411 {
412     if (!isDataTable())
413         return AccessibilityRenderObject::accessibilityIsIgnored();
414     
415     return false;
416 }
417     
418 String AccessibilityTable::title() const
419 {
420     if (!isDataTable())
421         return AccessibilityRenderObject::title();
422     
423     String title;
424     if (!m_renderer)
425         return title;
426     
427     // see if there is a caption
428     Node *tableElement = m_renderer->element();
429     if (tableElement) {
430         HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(tableElement)->caption();
431         if (caption)
432             title = caption->innerText();
433     }
434     
435     // try the standard 
436     if (title.isEmpty())
437         title = AccessibilityRenderObject::title();
438     
439     return title;
440 }
441
442 bool AccessibilityTable::isDataTable() const
443 {
444     if (!m_renderer)
445         return false;
446     
447     return m_isAccessibilityTable;
448 }
449
450 } // namespace WebCore