Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / accessibility / AXTable.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 "core/accessibility/AXTable.h"
31
32 #include "core/accessibility/AXObjectCache.h"
33 #include "core/accessibility/AXTableCell.h"
34 #include "core/accessibility/AXTableColumn.h"
35 #include "core/accessibility/AXTableRow.h"
36 #include "core/html/HTMLTableCaptionElement.h"
37 #include "core/html/HTMLTableCellElement.h"
38 #include "core/html/HTMLTableColElement.h"
39 #include "core/html/HTMLTableElement.h"
40 #include "core/rendering/RenderTableCell.h"
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 AXTable::AXTable(RenderObject* renderer)
47     : AXRenderObject(renderer)
48     , m_headerContainer(nullptr)
49     , m_isAXTable(true)
50 {
51 }
52
53 AXTable::~AXTable()
54 {
55 }
56
57 void AXTable::init()
58 {
59     AXRenderObject::init();
60     m_isAXTable = isTableExposableThroughAccessibility();
61 }
62
63 PassRefPtr<AXTable> AXTable::create(RenderObject* renderer)
64 {
65     return adoptRef(new AXTable(renderer));
66 }
67
68 bool AXTable::hasARIARole() const
69 {
70     if (!m_renderer)
71         return false;
72
73     AccessibilityRole ariaRole = ariaRoleAttribute();
74     if (ariaRole != UnknownRole)
75         return true;
76
77     return false;
78 }
79
80 bool AXTable::isAXTable() const
81 {
82     if (!m_renderer)
83         return false;
84
85     return m_isAXTable;
86 }
87
88 bool AXTable::isDataTable() const
89 {
90     if (!m_renderer)
91         return false;
92
93     // Do not consider it a data table is it has an ARIA role.
94     if (hasARIARole())
95         return false;
96
97     // When a section of the document is contentEditable, all tables should be
98     // treated as data tables, otherwise users may not be able to work with rich
99     // text editors that allow creating and editing tables.
100     if (node() && node()->rendererIsEditable())
101         return true;
102
103     // This employs a heuristic to determine if this table should appear.
104     // Only "data" tables should be exposed as tables.
105     // Unfortunately, there is no good way to determine the difference
106     // between a "layout" table and a "data" table.
107
108     RenderTable* table = toRenderTable(m_renderer);
109     Node* tableNode = table->node();
110     if (!isHTMLTableElement(tableNode))
111         return false;
112
113     // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table
114     HTMLTableElement* tableElement = toHTMLTableElement(tableNode);
115     if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption())
116         return true;
117
118     // if someone used "rules" attribute than the table should appear
119     if (!tableElement->rules().isEmpty())
120         return true;
121
122     // if there's a colgroup or col element, it's probably a data table.
123     if (Traversal<HTMLTableColElement>::firstChild(*tableElement))
124         return true;
125
126     // go through the cell's and check for tell-tale signs of "data" table status
127     // cells have borders, or use attributes like headers, abbr, scope or axis
128     table->recalcSectionsIfNeeded();
129     RenderTableSection* firstBody = table->firstBody();
130     if (!firstBody)
131         return false;
132
133     int numCols = firstBody->numColumns();
134     int numRows = firstBody->numRows();
135
136     // If there's only one cell, it's not a good AXTable candidate.
137     if (numRows == 1 && numCols == 1)
138         return false;
139
140     // If there are at least 20 rows, we'll call it a data table.
141     if (numRows >= 20)
142         return true;
143
144     // Store the background color of the table to check against cell's background colors.
145     RenderStyle* tableStyle = table->style();
146     if (!tableStyle)
147         return false;
148     Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor);
149
150     // check enough of the cells to find if the table matches our criteria
151     // Criteria:
152     //   1) must have at least one valid cell (and)
153     //   2) at least half of cells have borders (or)
154     //   3) at least half of cells have different bg colors than the table, and there is cell spacing
155     unsigned validCellCount = 0;
156     unsigned borderedCellCount = 0;
157     unsigned backgroundDifferenceCellCount = 0;
158     unsigned cellsWithTopBorder = 0;
159     unsigned cellsWithBottomBorder = 0;
160     unsigned cellsWithLeftBorder = 0;
161     unsigned cellsWithRightBorder = 0;
162
163     Color alternatingRowColors[5];
164     int alternatingRowColorCount = 0;
165
166     int headersInFirstColumnCount = 0;
167     for (int row = 0; row < numRows; ++row) {
168
169         int headersInFirstRowCount = 0;
170         for (int col = 0; col < numCols; ++col) {
171             RenderTableCell* cell = firstBody->primaryCellAt(row, col);
172             if (!cell)
173                 continue;
174             Node* cellNode = cell->node();
175             if (!cellNode)
176                 continue;
177
178             if (cell->width() < 1 || cell->height() < 1)
179                 continue;
180
181             validCellCount++;
182
183             bool isTHCell = cellNode->hasTagName(thTag);
184             // If the first row is comprised of all <th> tags, assume it is a data table.
185             if (!row && isTHCell)
186                 headersInFirstRowCount++;
187
188             // If the first column is comprised of all <th> tags, assume it is a data table.
189             if (!col && isTHCell)
190                 headersInFirstColumnCount++;
191
192             // in this case, the developer explicitly assigned a "data" table attribute
193             if (isHTMLTableCellElement(*cellNode)) {
194                 HTMLTableCellElement& cellElement = toHTMLTableCellElement(*cellNode);
195                 if (!cellElement.headers().isEmpty() || !cellElement.abbr().isEmpty()
196                     || !cellElement.axis().isEmpty() || !cellElement.scope().isEmpty())
197                     return true;
198             }
199
200             RenderStyle* renderStyle = cell->style();
201             if (!renderStyle)
202                 continue;
203
204             // If the empty-cells style is set, we'll call it a data table.
205             if (renderStyle->emptyCells() == HIDE)
206                 return true;
207
208             // If a cell has matching bordered sides, call it a (fully) bordered cell.
209             if ((cell->borderTop() > 0 && cell->borderBottom() > 0)
210                 || (cell->borderLeft() > 0 && cell->borderRight() > 0))
211                 borderedCellCount++;
212
213             // Also keep track of each individual border, so we can catch tables where most
214             // cells have a bottom border, for example.
215             if (cell->borderTop() > 0)
216                 cellsWithTopBorder++;
217             if (cell->borderBottom() > 0)
218                 cellsWithBottomBorder++;
219             if (cell->borderLeft() > 0)
220                 cellsWithLeftBorder++;
221             if (cell->borderRight() > 0)
222                 cellsWithRightBorder++;
223
224             // If the cell has a different color from the table and there is cell spacing,
225             // then it is probably a data table cell (spacing and colors take the place of borders).
226             Color cellColor = renderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
227             if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0
228                 && tableBGColor != cellColor && cellColor.alpha() != 1)
229                 backgroundDifferenceCellCount++;
230
231             // If we've found 10 "good" cells, we don't need to keep searching.
232             if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10)
233                 return true;
234
235             // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows.
236             if (row < 5 && row == alternatingRowColorCount) {
237                 RenderObject* renderRow = cell->parent();
238                 if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow())
239                     continue;
240                 RenderStyle* rowRenderStyle = renderRow->style();
241                 if (!rowRenderStyle)
242                     continue;
243                 Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor);
244                 alternatingRowColors[alternatingRowColorCount] = rowColor;
245                 alternatingRowColorCount++;
246             }
247         }
248
249         if (!row && headersInFirstRowCount == numCols && numCols > 1)
250             return true;
251     }
252
253     if (headersInFirstColumnCount == numRows && numRows > 1)
254         return true;
255
256     // if there is less than two valid cells, it's not a data table
257     if (validCellCount <= 1)
258         return false;
259
260     // half of the cells had borders, it's a data table
261     unsigned neededCellCount = validCellCount / 2;
262     if (borderedCellCount >= neededCellCount
263         || cellsWithTopBorder >= neededCellCount
264         || cellsWithBottomBorder >= neededCellCount
265         || cellsWithLeftBorder >= neededCellCount
266         || cellsWithRightBorder >= neededCellCount)
267         return true;
268
269     // half had different background colors, it's a data table
270     if (backgroundDifferenceCellCount >= neededCellCount)
271         return true;
272
273     // Check if there is an alternating row background color indicating a zebra striped style pattern.
274     if (alternatingRowColorCount > 2) {
275         Color firstColor = alternatingRowColors[0];
276         for (int k = 1; k < alternatingRowColorCount; k++) {
277             // If an odd row was the same color as the first row, its not alternating.
278             if (k % 2 == 1 && alternatingRowColors[k] == firstColor)
279                 return false;
280             // If an even row is not the same as the first row, its not alternating.
281             if (!(k % 2) && alternatingRowColors[k] != firstColor)
282                 return false;
283         }
284         return true;
285     }
286
287     return false;
288 }
289
290 bool AXTable::isTableExposableThroughAccessibility() const
291 {
292     // The following is a heuristic used to determine if a
293     // <table> should be exposed as an AXTable. The goal
294     // is to only show "data" tables.
295
296     if (!m_renderer)
297         return false;
298
299     // If the developer assigned an aria role to this, then we
300     // shouldn't expose it as a table, unless, of course, the aria
301     // role is a table.
302     if (hasARIARole())
303         return false;
304
305     return isDataTable();
306 }
307
308 void AXTable::clearChildren()
309 {
310     AXRenderObject::clearChildren();
311     m_rows.clear();
312     m_columns.clear();
313
314     if (m_headerContainer) {
315         m_headerContainer->detachFromParent();
316         m_headerContainer = nullptr;
317     }
318 }
319
320 void AXTable::addChildren()
321 {
322     if (!isAXTable()) {
323         AXRenderObject::addChildren();
324         return;
325     }
326
327     ASSERT(!m_haveChildren);
328
329     m_haveChildren = true;
330     if (!m_renderer || !m_renderer->isTable())
331         return;
332
333     RenderTable* table = toRenderTable(m_renderer);
334     AXObjectCache* axCache = m_renderer->document().axObjectCache();
335
336     // Go through all the available sections to pull out the rows and add them as children.
337     table->recalcSectionsIfNeeded();
338     RenderTableSection* tableSection = table->topSection();
339     if (!tableSection)
340         return;
341
342     RenderTableSection* initialTableSection = tableSection;
343     while (tableSection) {
344
345         HashSet<AXObject*> appendedRows;
346         unsigned numRows = tableSection->numRows();
347         for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) {
348
349             RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex);
350             if (!renderRow)
351                 continue;
352
353             AXObject* rowObject = axCache->getOrCreate(renderRow);
354             if (!rowObject->isTableRow())
355                 continue;
356
357             AXTableRow* row = toAXTableRow(rowObject);
358             // We need to check every cell for a new row, because cell spans
359             // can cause us to miss rows if we just check the first column.
360             if (appendedRows.contains(row))
361                 continue;
362
363             row->setRowIndex(static_cast<int>(m_rows.size()));
364             m_rows.append(row);
365             if (!row->accessibilityIsIgnored())
366                 m_children.append(row);
367             appendedRows.add(row);
368         }
369
370         tableSection = table->sectionBelow(tableSection, SkipEmptySections);
371     }
372
373     // make the columns based on the number of columns in the first body
374     unsigned length = initialTableSection->numColumns();
375     for (unsigned i = 0; i < length; ++i) {
376         AXTableColumn* column = toAXTableColumn(axCache->getOrCreate(ColumnRole));
377         column->setColumnIndex((int)i);
378         column->setParent(this);
379         m_columns.append(column);
380         if (!column->accessibilityIsIgnored())
381             m_children.append(column);
382     }
383
384     AXObject* headerContainerObject = headerContainer();
385     if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
386         m_children.append(headerContainerObject);
387 }
388
389 AXObject* AXTable::headerContainer()
390 {
391     if (m_headerContainer)
392         return m_headerContainer.get();
393
394     AXMockObject* tableHeader = toAXMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole));
395     tableHeader->setParent(this);
396
397     m_headerContainer = tableHeader;
398     return m_headerContainer.get();
399 }
400
401 AXObject::AccessibilityChildrenVector& AXTable::columns()
402 {
403     updateChildrenIfNecessary();
404
405     return m_columns;
406 }
407
408 AXObject::AccessibilityChildrenVector& AXTable::rows()
409 {
410     updateChildrenIfNecessary();
411
412     return m_rows;
413 }
414
415 void AXTable::columnHeaders(AccessibilityChildrenVector& headers)
416 {
417     if (!m_renderer)
418         return;
419
420     updateChildrenIfNecessary();
421
422     unsigned colCount = m_columns.size();
423     for (unsigned k = 0; k < colCount; ++k) {
424         AXObject* header = toAXTableColumn(m_columns[k].get())->headerObject();
425         if (!header)
426             continue;
427         headers.append(header);
428     }
429 }
430
431 void AXTable::cells(AXObject::AccessibilityChildrenVector& cells)
432 {
433     if (!m_renderer)
434         return;
435
436     updateChildrenIfNecessary();
437
438     int numRows = m_rows.size();
439     for (int row = 0; row < numRows; ++row) {
440         AccessibilityChildrenVector rowChildren = m_rows[row]->children();
441         cells.appendVector(rowChildren);
442     }
443 }
444
445 unsigned AXTable::columnCount()
446 {
447     updateChildrenIfNecessary();
448
449     return m_columns.size();
450 }
451
452 unsigned AXTable::rowCount()
453 {
454     updateChildrenIfNecessary();
455
456     return m_rows.size();
457 }
458
459 AXTableCell* AXTable::cellForColumnAndRow(unsigned column, unsigned row)
460 {
461     updateChildrenIfNecessary();
462     if (column >= columnCount() || row >= rowCount())
463         return 0;
464
465     // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row.
466     for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) {
467         unsigned rowIndex = rowIndexCounter - 1;
468         AccessibilityChildrenVector children = m_rows[rowIndex]->children();
469         // Since some cells may have colspans, we have to check the actual range of each
470         // cell to determine which is the right one.
471         for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) {
472             unsigned colIndex = colIndexCounter - 1;
473             AXObject* child = children[colIndex].get();
474             ASSERT(child->isTableCell());
475             if (!child->isTableCell())
476                 continue;
477
478             pair<unsigned, unsigned> columnRange;
479             pair<unsigned, unsigned> rowRange;
480             AXTableCell* tableCellChild = toAXTableCell(child);
481             tableCellChild->columnIndexRange(columnRange);
482             tableCellChild->rowIndexRange(rowRange);
483
484             if ((column >= columnRange.first && column < (columnRange.first + columnRange.second))
485                 && (row >= rowRange.first && row < (rowRange.first + rowRange.second)))
486                 return tableCellChild;
487         }
488     }
489
490     return 0;
491 }
492
493 AccessibilityRole AXTable::roleValue() const
494 {
495     if (!isAXTable())
496         return AXRenderObject::roleValue();
497
498     return TableRole;
499 }
500
501 bool AXTable::computeAccessibilityIsIgnored() const
502 {
503     AXObjectInclusion decision = defaultObjectInclusion();
504     if (decision == IncludeObject)
505         return false;
506     if (decision == IgnoreObject)
507         return true;
508
509     if (!isAXTable())
510         return AXRenderObject::computeAccessibilityIsIgnored();
511
512     return false;
513 }
514
515 String AXTable::title() const
516 {
517     if (!isAXTable())
518         return AXRenderObject::title();
519
520     String title;
521     if (!m_renderer)
522         return title;
523
524     // see if there is a caption
525     Node* tableElement = m_renderer->node();
526     if (isHTMLTableElement(tableElement)) {
527         HTMLTableCaptionElement* caption = toHTMLTableElement(tableElement)->caption();
528         if (caption)
529             title = caption->innerText();
530     }
531
532     // try the standard
533     if (title.isEmpty())
534         title = AXRenderObject::title();
535
536     return title;
537 }
538
539 } // namespace WebCore