1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "HTMLTableAccessible.h"
7
8 #include "mozilla/DebugOnly.h"
9
10 #include "Accessible-inl.h"
11 #include "nsAccessibilityService.h"
12 #include "nsAccUtils.h"
13 #include "DocAccessible.h"
14 #include "nsTextEquivUtils.h"
15 #include "Relation.h"
16 #include "Role.h"
17 #include "States.h"
18 #include "TreeWalker.h"
19
20 #include "mozilla/dom/HTMLTableElement.h"
21 #include "nsIDOMElement.h"
22 #include "nsIDOMRange.h"
23 #include "nsISelectionPrivate.h"
24 #include "nsIDOMNodeList.h"
25 #include "nsIHTMLCollection.h"
26 #include "nsIDocument.h"
27 #include "nsIMutableArray.h"
28 #include "nsIPersistentProperties2.h"
29 #include "nsIPresShell.h"
30 #include "nsITableCellLayout.h"
31 #include "nsFrameSelection.h"
32 #include "nsError.h"
33 #include "nsArrayUtils.h"
34 #include "nsComponentManagerUtils.h"
35 #include "nsNameSpaceManager.h"
36 #include "nsTableCellFrame.h"
37 #include "nsTableWrapperFrame.h"
38
39 using namespace mozilla;
40 using namespace mozilla::dom;
41 using namespace mozilla::a11y;
42
43 ////////////////////////////////////////////////////////////////////////////////
44 // HTMLTableCellAccessible
45 ////////////////////////////////////////////////////////////////////////////////
46
HTMLTableCellAccessible(nsIContent * aContent,DocAccessible * aDoc)47 HTMLTableCellAccessible::HTMLTableCellAccessible(nsIContent* aContent,
48 DocAccessible* aDoc)
49 : HyperTextAccessibleWrap(aContent, aDoc) {
50 mType = eHTMLTableCellType;
51 mGenericTypes |= eTableCell;
52 }
53
54 ////////////////////////////////////////////////////////////////////////////////
55 // HTMLTableCellAccessible: Accessible implementation
56
NativeRole()57 role HTMLTableCellAccessible::NativeRole() {
58 if (mContent->IsMathMLElement(nsGkAtoms::mtd_)) {
59 return roles::MATHML_CELL;
60 }
61 return roles::CELL;
62 }
63
NativeState()64 uint64_t HTMLTableCellAccessible::NativeState() {
65 uint64_t state = HyperTextAccessibleWrap::NativeState();
66
67 nsIFrame* frame = mContent->GetPrimaryFrame();
68 NS_ASSERTION(frame, "No frame for valid cell accessible!");
69
70 if (frame && frame->IsSelected()) state |= states::SELECTED;
71
72 return state;
73 }
74
NativeInteractiveState() const75 uint64_t HTMLTableCellAccessible::NativeInteractiveState() const {
76 return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE;
77 }
78
79 already_AddRefed<nsIPersistentProperties>
NativeAttributes()80 HTMLTableCellAccessible::NativeAttributes() {
81 nsCOMPtr<nsIPersistentProperties> attributes =
82 HyperTextAccessibleWrap::NativeAttributes();
83
84 // table-cell-index attribute
85 TableAccessible* table = Table();
86 if (!table) return attributes.forget();
87
88 int32_t rowIdx = -1, colIdx = -1;
89 nsresult rv = GetCellIndexes(rowIdx, colIdx);
90 if (NS_FAILED(rv)) return attributes.forget();
91
92 nsAutoString stringIdx;
93 stringIdx.AppendInt(table->CellIndexAt(rowIdx, colIdx));
94 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
95
96 // abbr attribute
97
98 // Pick up object attribute from abbr DOM element (a child of the cell) or
99 // from abbr DOM attribute.
100 nsAutoString abbrText;
101 if (ChildCount() == 1) {
102 Accessible* abbr = FirstChild();
103 if (abbr->IsAbbreviation()) {
104 nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild();
105 if (firstChildNode) {
106 nsTextEquivUtils::AppendTextEquivFromTextContent(firstChildNode,
107 &abbrText);
108 }
109 }
110 }
111 if (abbrText.IsEmpty())
112 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr,
113 abbrText);
114
115 if (!abbrText.IsEmpty())
116 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::abbr, abbrText);
117
118 // axis attribute
119 nsAutoString axisText;
120 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText);
121 if (!axisText.IsEmpty())
122 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::axis, axisText);
123
124 #ifdef DEBUG
125 nsAutoString unused;
126 attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"),
127 NS_LITERAL_STRING("HTMLTableCellAccessible"),
128 unused);
129 #endif
130
131 return attributes.forget();
132 }
133
GroupPosition()134 GroupPos HTMLTableCellAccessible::GroupPosition() {
135 int32_t count = 0, index = 0;
136 TableAccessible* table = Table();
137 if (table &&
138 nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
139 nsGkAtoms::aria_colcount, &count) &&
140 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
141 return GroupPos(0, index, count);
142 }
143
144 return HyperTextAccessibleWrap::GroupPosition();
145 }
146
147 ////////////////////////////////////////////////////////////////////////////////
148 // HTMLTableCellAccessible: TableCellAccessible implementation
149
Table() const150 TableAccessible* HTMLTableCellAccessible::Table() const {
151 Accessible* parent = const_cast<HTMLTableCellAccessible*>(this);
152 while ((parent = parent->Parent())) {
153 if (parent->IsTable()) return parent->AsTable();
154 }
155
156 return nullptr;
157 }
158
ColIdx() const159 uint32_t HTMLTableCellAccessible::ColIdx() const {
160 nsTableCellFrame* cellFrame = GetCellFrame();
161 NS_ENSURE_TRUE(cellFrame, 0);
162 return cellFrame->ColIndex();
163 }
164
RowIdx() const165 uint32_t HTMLTableCellAccessible::RowIdx() const {
166 nsTableCellFrame* cellFrame = GetCellFrame();
167 NS_ENSURE_TRUE(cellFrame, 0);
168 return cellFrame->RowIndex();
169 }
170
ColExtent() const171 uint32_t HTMLTableCellAccessible::ColExtent() const {
172 int32_t rowIdx = -1, colIdx = -1;
173 GetCellIndexes(rowIdx, colIdx);
174
175 TableAccessible* table = Table();
176 NS_ASSERTION(table, "cell not in a table!");
177 if (!table) return 0;
178
179 return table->ColExtentAt(rowIdx, colIdx);
180 }
181
RowExtent() const182 uint32_t HTMLTableCellAccessible::RowExtent() const {
183 int32_t rowIdx = -1, colIdx = -1;
184 GetCellIndexes(rowIdx, colIdx);
185
186 TableAccessible* table = Table();
187 NS_ASSERTION(table, "cell not in atable!");
188 if (!table) return 0;
189
190 return table->RowExtentAt(rowIdx, colIdx);
191 }
192
ColHeaderCells(nsTArray<Accessible * > * aCells)193 void HTMLTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells) {
194 IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
195 while (Accessible* cell = itr.Next()) {
196 a11y::role cellRole = cell->Role();
197 if (cellRole == roles::COLUMNHEADER) {
198 aCells->AppendElement(cell);
199 } else if (cellRole != roles::ROWHEADER) {
200 // If referred table cell is at the same column then treat it as a column
201 // header.
202 TableCellAccessible* tableCell = cell->AsTableCell();
203 if (tableCell && tableCell->ColIdx() == ColIdx())
204 aCells->AppendElement(cell);
205 }
206 }
207
208 if (aCells->IsEmpty()) TableCellAccessible::ColHeaderCells(aCells);
209 }
210
RowHeaderCells(nsTArray<Accessible * > * aCells)211 void HTMLTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells) {
212 IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
213 while (Accessible* cell = itr.Next()) {
214 a11y::role cellRole = cell->Role();
215 if (cellRole == roles::ROWHEADER) {
216 aCells->AppendElement(cell);
217 } else if (cellRole != roles::COLUMNHEADER) {
218 // If referred table cell is at the same row then treat it as a column
219 // header.
220 TableCellAccessible* tableCell = cell->AsTableCell();
221 if (tableCell && tableCell->RowIdx() == RowIdx())
222 aCells->AppendElement(cell);
223 }
224 }
225
226 if (aCells->IsEmpty()) TableCellAccessible::RowHeaderCells(aCells);
227 }
228
Selected()229 bool HTMLTableCellAccessible::Selected() {
230 int32_t rowIdx = -1, colIdx = -1;
231 GetCellIndexes(rowIdx, colIdx);
232
233 TableAccessible* table = Table();
234 NS_ENSURE_TRUE(table, false);
235
236 return table->IsCellSelected(rowIdx, colIdx);
237 }
238
239 ////////////////////////////////////////////////////////////////////////////////
240 // HTMLTableCellAccessible: protected implementation
241
GetCellLayout() const242 nsITableCellLayout* HTMLTableCellAccessible::GetCellLayout() const {
243 return do_QueryFrame(mContent->GetPrimaryFrame());
244 }
245
GetCellFrame() const246 nsTableCellFrame* HTMLTableCellAccessible::GetCellFrame() const {
247 return do_QueryFrame(mContent->GetPrimaryFrame());
248 }
249
GetCellIndexes(int32_t & aRowIdx,int32_t & aColIdx) const250 nsresult HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx,
251 int32_t& aColIdx) const {
252 nsITableCellLayout* cellLayout = GetCellLayout();
253 NS_ENSURE_STATE(cellLayout);
254
255 return cellLayout->GetCellIndexes(aRowIdx, aColIdx);
256 }
257
258 ////////////////////////////////////////////////////////////////////////////////
259 // HTMLTableHeaderCellAccessible
260 ////////////////////////////////////////////////////////////////////////////////
261
HTMLTableHeaderCellAccessible(nsIContent * aContent,DocAccessible * aDoc)262 HTMLTableHeaderCellAccessible::HTMLTableHeaderCellAccessible(
263 nsIContent* aContent, DocAccessible* aDoc)
264 : HTMLTableCellAccessible(aContent, aDoc) {}
265
266 ////////////////////////////////////////////////////////////////////////////////
267 // HTMLTableHeaderCellAccessible: Accessible implementation
268
NativeRole()269 role HTMLTableHeaderCellAccessible::NativeRole() {
270 // Check value of @scope attribute.
271 static Element::AttrValuesArray scopeValues[] = {
272 &nsGkAtoms::col, &nsGkAtoms::colgroup, &nsGkAtoms::row,
273 &nsGkAtoms::rowgroup, nullptr};
274 int32_t valueIdx = mContent->AsElement()->FindAttrValueIn(
275 kNameSpaceID_None, nsGkAtoms::scope, scopeValues, eCaseMatters);
276
277 switch (valueIdx) {
278 case 0:
279 case 1:
280 return roles::COLUMNHEADER;
281 case 2:
282 case 3:
283 return roles::ROWHEADER;
284 }
285
286 TableAccessible* table = Table();
287 if (!table) return roles::NOTHING;
288
289 // If the cell next to this one is not a header cell then assume this cell is
290 // a row header for it.
291 uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
292 Accessible* cell = table->CellAt(rowIdx, colIdx + ColExtent());
293 if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
294 return roles::ROWHEADER;
295
296 // If the cell below this one is not a header cell then assume this cell is
297 // a column header for it.
298 uint32_t rowExtent = RowExtent();
299 cell = table->CellAt(rowIdx + rowExtent, colIdx);
300 if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
301 return roles::COLUMNHEADER;
302
303 // Otherwise if this cell is surrounded by header cells only then make a guess
304 // based on its cell spanning. In other words if it is row spanned then assume
305 // it's a row header, otherwise it's a column header.
306 return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER;
307 }
308
309 ////////////////////////////////////////////////////////////////////////////////
310 // HTMLTableRowAccessible
311 ////////////////////////////////////////////////////////////////////////////////
312
NativeRole()313 role HTMLTableRowAccessible::NativeRole() {
314 if (mContent->IsMathMLElement(nsGkAtoms::mtr_)) {
315 return roles::MATHML_TABLE_ROW;
316 } else if (mContent->IsMathMLElement(nsGkAtoms::mlabeledtr_)) {
317 return roles::MATHML_LABELED_ROW;
318 }
319 return roles::ROW;
320 }
321
GroupPosition()322 GroupPos HTMLTableRowAccessible::GroupPosition() {
323 int32_t count = 0, index = 0;
324 Accessible* table = nsAccUtils::TableFor(this);
325 if (table &&
326 nsCoreUtils::GetUIntAttr(table->GetContent(), nsGkAtoms::aria_rowcount,
327 &count) &&
328 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
329 return GroupPos(0, index, count);
330 }
331
332 return AccessibleWrap::GroupPosition();
333 }
334
335 ////////////////////////////////////////////////////////////////////////////////
336 // HTMLTableAccessible
337 ////////////////////////////////////////////////////////////////////////////////
338
339 ////////////////////////////////////////////////////////////////////////////////
340 // HTMLTableAccessible: Accessible
341
InsertChildAt(uint32_t aIndex,Accessible * aChild)342 bool HTMLTableAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) {
343 // Move caption accessible so that it's the first child. Check for the first
344 // caption only, because nsAccessibilityService ensures we don't create
345 // accessibles for the other captions, since only the first is actually
346 // visible.
347 return Accessible::InsertChildAt(aChild->IsHTMLCaption() ? 0 : aIndex,
348 aChild);
349 }
350
NativeRole()351 role HTMLTableAccessible::NativeRole() {
352 if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
353 return roles::MATHML_TABLE;
354 }
355 return roles::TABLE;
356 }
357
NativeState()358 uint64_t HTMLTableAccessible::NativeState() {
359 return Accessible::NativeState() | states::READONLY;
360 }
361
NativeName(nsString & aName)362 ENameValueFlag HTMLTableAccessible::NativeName(nsString& aName) {
363 ENameValueFlag nameFlag = Accessible::NativeName(aName);
364 if (!aName.IsEmpty()) return nameFlag;
365
366 // Use table caption as a name.
367 Accessible* caption = Caption();
368 if (caption) {
369 nsIContent* captionContent = caption->GetContent();
370 if (captionContent) {
371 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
372 &aName);
373 if (!aName.IsEmpty()) return eNameOK;
374 }
375 }
376
377 // If no caption then use summary as a name.
378 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName);
379 return eNameOK;
380 }
381
382 already_AddRefed<nsIPersistentProperties>
NativeAttributes()383 HTMLTableAccessible::NativeAttributes() {
384 nsCOMPtr<nsIPersistentProperties> attributes =
385 AccessibleWrap::NativeAttributes();
386
387 if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
388 GetAccService()->MarkupAttributes(mContent, attributes);
389 }
390
391 if (IsProbablyLayoutTable()) {
392 nsAutoString unused;
393 attributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
394 NS_LITERAL_STRING("true"), unused);
395 }
396
397 return attributes.forget();
398 }
399
400 ////////////////////////////////////////////////////////////////////////////////
401 // HTMLTableAccessible: Accessible
402
RelationByType(RelationType aType)403 Relation HTMLTableAccessible::RelationByType(RelationType aType) {
404 Relation rel = AccessibleWrap::RelationByType(aType);
405 if (aType == RelationType::LABELLED_BY) rel.AppendTarget(Caption());
406
407 return rel;
408 }
409
410 ////////////////////////////////////////////////////////////////////////////////
411 // HTMLTableAccessible: Table
412
Caption() const413 Accessible* HTMLTableAccessible::Caption() const {
414 Accessible* child = mChildren.SafeElementAt(0, nullptr);
415 return child && child->Role() == roles::CAPTION ? child : nullptr;
416 }
417
Summary(nsString & aSummary)418 void HTMLTableAccessible::Summary(nsString& aSummary) {
419 dom::HTMLTableElement* table = dom::HTMLTableElement::FromContent(mContent);
420
421 if (table) table->GetSummary(aSummary);
422 }
423
ColCount()424 uint32_t HTMLTableAccessible::ColCount() {
425 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
426 return tableFrame ? tableFrame->GetColCount() : 0;
427 }
428
RowCount()429 uint32_t HTMLTableAccessible::RowCount() {
430 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
431 return tableFrame ? tableFrame->GetRowCount() : 0;
432 }
433
SelectedCellCount()434 uint32_t HTMLTableAccessible::SelectedCellCount() {
435 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
436 if (!tableFrame) return 0;
437
438 uint32_t count = 0, rowCount = RowCount(), colCount = ColCount();
439 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
440 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
441 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
442 if (!cellFrame || !cellFrame->IsSelected()) continue;
443
444 uint32_t startRow = cellFrame->RowIndex();
445 uint32_t startCol = cellFrame->ColIndex();
446 if (startRow == rowIdx && startCol == colIdx) count++;
447 }
448 }
449
450 return count;
451 }
452
SelectedColCount()453 uint32_t HTMLTableAccessible::SelectedColCount() {
454 uint32_t count = 0, colCount = ColCount();
455
456 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
457 if (IsColSelected(colIdx)) count++;
458
459 return count;
460 }
461
SelectedRowCount()462 uint32_t HTMLTableAccessible::SelectedRowCount() {
463 uint32_t count = 0, rowCount = RowCount();
464
465 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
466 if (IsRowSelected(rowIdx)) count++;
467
468 return count;
469 }
470
SelectedCells(nsTArray<Accessible * > * aCells)471 void HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells) {
472 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
473 if (!tableFrame) return;
474
475 uint32_t rowCount = RowCount(), colCount = ColCount();
476 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
477 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
478 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
479 if (!cellFrame || !cellFrame->IsSelected()) continue;
480
481 uint32_t startRow = cellFrame->RowIndex();
482 uint32_t startCol = cellFrame->ColIndex();
483 if (startRow != rowIdx || startCol != colIdx) continue;
484
485 Accessible* cell = mDoc->GetAccessible(cellFrame->GetContent());
486 aCells->AppendElement(cell);
487 }
488 }
489 }
490
SelectedCellIndices(nsTArray<uint32_t> * aCells)491 void HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) {
492 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
493 if (!tableFrame) return;
494
495 uint32_t rowCount = RowCount(), colCount = ColCount();
496 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
497 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
498 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
499 if (!cellFrame || !cellFrame->IsSelected()) continue;
500
501 uint32_t startCol = cellFrame->ColIndex();
502 uint32_t startRow = cellFrame->RowIndex();
503 if (startRow == rowIdx && startCol == colIdx)
504 aCells->AppendElement(CellIndexAt(rowIdx, colIdx));
505 }
506 }
507 }
508
SelectedColIndices(nsTArray<uint32_t> * aCols)509 void HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) {
510 uint32_t colCount = ColCount();
511 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
512 if (IsColSelected(colIdx)) aCols->AppendElement(colIdx);
513 }
514
SelectedRowIndices(nsTArray<uint32_t> * aRows)515 void HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) {
516 uint32_t rowCount = RowCount();
517 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
518 if (IsRowSelected(rowIdx)) aRows->AppendElement(rowIdx);
519 }
520
CellAt(uint32_t aRowIdx,uint32_t aColIdx)521 Accessible* HTMLTableAccessible::CellAt(uint32_t aRowIdx, uint32_t aColIdx) {
522 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
523 if (!tableFrame) return nullptr;
524
525 nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
526 Accessible* cell = mDoc->GetAccessible(cellContent);
527
528 // XXX bug 576838: crazy tables (like table6 in tables/test_table2.html) may
529 // return itself as a cell what makes Orca hang.
530 return cell == this ? nullptr : cell;
531 }
532
CellIndexAt(uint32_t aRowIdx,uint32_t aColIdx)533 int32_t HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) {
534 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
535 if (!tableFrame) return -1;
536
537 return tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx);
538 }
539
ColIndexAt(uint32_t aCellIdx)540 int32_t HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx) {
541 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
542 if (!tableFrame) return -1;
543
544 int32_t rowIdx = -1, colIdx = -1;
545 tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
546 return colIdx;
547 }
548
RowIndexAt(uint32_t aCellIdx)549 int32_t HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx) {
550 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
551 if (!tableFrame) return -1;
552
553 int32_t rowIdx = -1, colIdx = -1;
554 tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
555 return rowIdx;
556 }
557
RowAndColIndicesAt(uint32_t aCellIdx,int32_t * aRowIdx,int32_t * aColIdx)558 void HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx,
559 int32_t* aRowIdx,
560 int32_t* aColIdx) {
561 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
562 if (tableFrame)
563 tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx);
564 }
565
ColExtentAt(uint32_t aRowIdx,uint32_t aColIdx)566 uint32_t HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) {
567 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
568 if (!tableFrame) return 0;
569
570 return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx);
571 }
572
RowExtentAt(uint32_t aRowIdx,uint32_t aColIdx)573 uint32_t HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) {
574 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
575 if (!tableFrame) return 0;
576
577 return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx);
578 }
579
IsColSelected(uint32_t aColIdx)580 bool HTMLTableAccessible::IsColSelected(uint32_t aColIdx) {
581 bool isSelected = false;
582
583 uint32_t rowCount = RowCount();
584 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
585 isSelected = IsCellSelected(rowIdx, aColIdx);
586 if (!isSelected) return false;
587 }
588
589 return isSelected;
590 }
591
IsRowSelected(uint32_t aRowIdx)592 bool HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx) {
593 bool isSelected = false;
594
595 uint32_t colCount = ColCount();
596 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
597 isSelected = IsCellSelected(aRowIdx, colIdx);
598 if (!isSelected) return false;
599 }
600
601 return isSelected;
602 }
603
IsCellSelected(uint32_t aRowIdx,uint32_t aColIdx)604 bool HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) {
605 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
606 if (!tableFrame) return false;
607
608 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx);
609 return cellFrame ? cellFrame->IsSelected() : false;
610 }
611
SelectRow(uint32_t aRowIdx)612 void HTMLTableAccessible::SelectRow(uint32_t aRowIdx) {
613 DebugOnly<nsresult> rv = RemoveRowsOrColumnsFromSelection(
614 aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW, true);
615 NS_ASSERTION(NS_SUCCEEDED(rv),
616 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
617
618 AddRowOrColumnToSelection(aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW);
619 }
620
SelectCol(uint32_t aColIdx)621 void HTMLTableAccessible::SelectCol(uint32_t aColIdx) {
622 DebugOnly<nsresult> rv = RemoveRowsOrColumnsFromSelection(
623 aColIdx, nsISelectionPrivate::TABLESELECTION_COLUMN, true);
624 NS_ASSERTION(NS_SUCCEEDED(rv),
625 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
626
627 AddRowOrColumnToSelection(aColIdx,
628 nsISelectionPrivate::TABLESELECTION_COLUMN);
629 }
630
UnselectRow(uint32_t aRowIdx)631 void HTMLTableAccessible::UnselectRow(uint32_t aRowIdx) {
632 RemoveRowsOrColumnsFromSelection(
633 aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW, false);
634 }
635
UnselectCol(uint32_t aColIdx)636 void HTMLTableAccessible::UnselectCol(uint32_t aColIdx) {
637 RemoveRowsOrColumnsFromSelection(
638 aColIdx, nsISelectionPrivate::TABLESELECTION_COLUMN, false);
639 }
640
AddRowOrColumnToSelection(int32_t aIndex,uint32_t aTarget)641 nsresult HTMLTableAccessible::AddRowOrColumnToSelection(int32_t aIndex,
642 uint32_t aTarget) {
643 bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
644
645 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
646 if (!tableFrame) return NS_OK;
647
648 uint32_t count = 0;
649 if (doSelectRow)
650 count = ColCount();
651 else
652 count = RowCount();
653
654 nsIPresShell* presShell(mDoc->PresShell());
655 RefPtr<nsFrameSelection> tableSelection =
656 const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
657
658 for (uint32_t idx = 0; idx < count; idx++) {
659 int32_t rowIdx = doSelectRow ? aIndex : idx;
660 int32_t colIdx = doSelectRow ? idx : aIndex;
661 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
662 if (cellFrame && !cellFrame->IsSelected()) {
663 nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent());
664 NS_ENSURE_SUCCESS(rv, rv);
665 }
666 }
667
668 return NS_OK;
669 }
670
RemoveRowsOrColumnsFromSelection(int32_t aIndex,uint32_t aTarget,bool aIsOuter)671 nsresult HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(int32_t aIndex,
672 uint32_t aTarget,
673 bool aIsOuter) {
674 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
675 if (!tableFrame) return NS_OK;
676
677 nsIPresShell* presShell(mDoc->PresShell());
678 RefPtr<nsFrameSelection> tableSelection =
679 const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
680
681 bool doUnselectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
682 uint32_t count = doUnselectRow ? ColCount() : RowCount();
683
684 int32_t startRowIdx = doUnselectRow ? aIndex : 0;
685 int32_t endRowIdx = doUnselectRow ? aIndex : count - 1;
686 int32_t startColIdx = doUnselectRow ? 0 : aIndex;
687 int32_t endColIdx = doUnselectRow ? count - 1 : aIndex;
688
689 if (aIsOuter)
690 return tableSelection->RestrictCellsToSelection(
691 mContent, startRowIdx, startColIdx, endRowIdx, endColIdx);
692
693 return tableSelection->RemoveCellsFromSelection(
694 mContent, startRowIdx, startColIdx, endRowIdx, endColIdx);
695 }
696
Description(nsString & aDescription)697 void HTMLTableAccessible::Description(nsString& aDescription) {
698 // Helpful for debugging layout vs. data tables
699 aDescription.Truncate();
700 Accessible::Description(aDescription);
701 if (!aDescription.IsEmpty()) return;
702
703 // Use summary as description if it weren't used as a name.
704 // XXX: get rid code duplication with NameInternal().
705 Accessible* caption = Caption();
706 if (caption) {
707 nsIContent* captionContent = caption->GetContent();
708 if (captionContent) {
709 nsAutoString captionText;
710 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
711 &captionText);
712
713 if (!captionText.IsEmpty()) { // summary isn't used as a name.
714 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary,
715 aDescription);
716 }
717 }
718 }
719
720 #ifdef SHOW_LAYOUT_HEURISTIC
721 if (aDescription.IsEmpty()) {
722 bool isProbablyForLayout = IsProbablyLayoutTable();
723 aDescription = mLayoutHeuristic;
724 }
725 printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
726 #endif
727 }
728
HasDescendant(const nsAString & aTagName,bool aAllowEmpty)729 bool HTMLTableAccessible::HasDescendant(const nsAString& aTagName,
730 bool aAllowEmpty) {
731 nsCOMPtr<nsIHTMLCollection> elements =
732 mContent->AsElement()->GetElementsByTagName(aTagName);
733
734 Element* foundItem = elements->Item(0);
735 if (!foundItem) return false;
736
737 if (aAllowEmpty) return true;
738
739 // Make sure that the item we found has contents and either has multiple
740 // children or the found item is not a whitespace-only text node.
741 if (foundItem->GetChildCount() > 1)
742 return true; // Treat multiple child nodes as non-empty
743
744 nsIContent* innerItemContent = foundItem->GetFirstChild();
745 if (innerItemContent && !innerItemContent->TextIsOnlyWhitespace())
746 return true;
747
748 // If we found more than one node then return true not depending on
749 // aAllowEmpty flag.
750 // XXX it might be dummy but bug 501375 where we changed this addresses
751 // performance problems only. Note, currently 'aAllowEmpty' flag is used for
752 // caption element only. On another hand we create accessible object for
753 // the first entry of caption element (see
754 // HTMLTableAccessible::InsertChildAt).
755 return !!elements->Item(1);
756 }
757
IsProbablyLayoutTable()758 bool HTMLTableAccessible::IsProbablyLayoutTable() {
759 // Implement a heuristic to determine if table is most likely used for layout
760 // XXX do we want to look for rowspan or colspan, especialy that span all but a
761 // couple cells at the beginning or end of a row/col, and especially when they
762 // occur at the edge of a table?
763 // XXX expose this info via object attributes to AT-SPI
764
765 // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
766 // This will allow release trunk builds to be used by testers to refine the
767 // algorithm Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final
768 // release
769 #ifdef SHOW_LAYOUT_HEURISTIC
770 #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
771 { \
772 mLayoutHeuristic = isLayout \
773 ? NS_LITERAL_STRING("layout table: " heuristic) \
774 : NS_LITERAL_STRING("data table: " heuristic); \
775 return isLayout; \
776 }
777 #else
778 #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
779 { return isLayout; }
780 #endif
781
782 DocAccessible* docAccessible = Document();
783 if (docAccessible) {
784 uint64_t docState = docAccessible->State();
785 if (docState & states::EDITABLE) { // Need to see all elements while
786 // document is being edited
787 RETURN_LAYOUT_ANSWER(false, "In editable document");
788 }
789 }
790
791 // Check to see if an ARIA role overrides the role from native markup,
792 // but for which we still expose table semantics (treegrid, for example).
793 if (Role() != roles::TABLE) RETURN_LAYOUT_ANSWER(false, "Has role attribute");
794
795 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
796 // Role attribute is present, but overridden roles have already been dealt
797 // with. Only landmarks and other roles that don't override the role from
798 // native markup are left to deal with here.
799 RETURN_LAYOUT_ANSWER(false,
800 "Has role attribute, weak role, and role is table");
801 }
802
803 NS_ASSERTION(mContent->IsHTMLElement(nsGkAtoms::table),
804 "table should not be built by CSS display:table style");
805
806 // Check if datatable attribute has "0" value.
807 if (mContent->AsElement()->AttrValueIs(
808 kNameSpaceID_None, nsGkAtoms::datatable, NS_LITERAL_STRING("0"),
809 eCaseMatters)) {
810 RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
811 }
812
813 // Check for legitimate data table attributes.
814 nsAutoString summary;
815 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary,
816 summary) &&
817 !summary.IsEmpty())
818 RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
819
820 // Check for legitimate data table elements.
821 Accessible* caption = FirstChild();
822 if (caption && caption->Role() == roles::CAPTION && caption->HasChildren())
823 RETURN_LAYOUT_ANSWER(false,
824 "Not empty caption -- legitimate table structures");
825
826 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
827 childElm = childElm->GetNextSibling()) {
828 if (!childElm->IsHTMLElement()) continue;
829
830 if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col, nsGkAtoms::colgroup,
831 nsGkAtoms::tfoot, nsGkAtoms::thead)) {
832 RETURN_LAYOUT_ANSWER(
833 false,
834 "Has col, colgroup, tfoot or thead -- legitimate table structures");
835 }
836
837 if (childElm->IsHTMLElement(nsGkAtoms::tbody)) {
838 for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
839 rowElm = rowElm->GetNextSibling()) {
840 if (rowElm->IsHTMLElement(nsGkAtoms::tr)) {
841 for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
842 cellElm = cellElm->GetNextSibling()) {
843 if (cellElm->IsHTMLElement()) {
844 if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
845 RETURN_LAYOUT_ANSWER(false,
846 "Has th -- legitimate table structures");
847 }
848
849 if (cellElm->AsElement()->HasAttr(kNameSpaceID_None,
850 nsGkAtoms::headers) ||
851 cellElm->AsElement()->HasAttr(kNameSpaceID_None,
852 nsGkAtoms::scope) ||
853 cellElm->AsElement()->HasAttr(kNameSpaceID_None,
854 nsGkAtoms::abbr)) {
855 RETURN_LAYOUT_ANSWER(false,
856 "Has headers, scope, or abbr attribute -- "
857 "legitimate table structures");
858 }
859
860 Accessible* cell = mDoc->GetAccessible(cellElm);
861 if (cell && cell->ChildCount() == 1 &&
862 cell->FirstChild()->IsAbbreviation()) {
863 RETURN_LAYOUT_ANSWER(false,
864 "has abbr -- legitimate table structures");
865 }
866 }
867 }
868 }
869 }
870 }
871 }
872
873 if (HasDescendant(NS_LITERAL_STRING("table"))) {
874 RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
875 }
876
877 // If only 1 column or only 1 row, it's for layout
878 uint32_t colCount = ColCount();
879 if (colCount <= 1) {
880 RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
881 }
882 uint32_t rowCount = RowCount();
883 if (rowCount <= 1) {
884 RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
885 }
886
887 // Check for many columns
888 if (colCount >= 5) {
889 RETURN_LAYOUT_ANSWER(false, ">=5 columns");
890 }
891
892 // Now we know there are 2-4 columns and 2 or more rows
893 // Check to see if there are visible borders on the cells
894 // XXX currently, we just check the first cell -- do we really need to do
895 // more?
896 nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
897 if (!tableFrame) RETURN_LAYOUT_ANSWER(false, "table with no frame!");
898
899 nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
900 if (!cellFrame)
901 RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
902
903 nsMargin border;
904 cellFrame->GetXULBorder(border);
905 if (border.top && border.bottom && border.left && border.right) {
906 RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
907 }
908
909 /**
910 * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on
911 * forward
912 */
913
914 // Check for styled background color across rows (alternating background
915 // color is a common feature for data tables).
916 uint32_t childCount = ChildCount();
917 nscolor rowColor = 0;
918 nscolor prevRowColor;
919 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
920 Accessible* child = GetChildAt(childIdx);
921 if (child->Role() == roles::ROW) {
922 prevRowColor = rowColor;
923 nsIFrame* rowFrame = child->GetFrame();
924 MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up");
925 if (!rowFrame) {
926 RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy");
927 }
928
929 rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame);
930
931 if (childIdx > 0 && prevRowColor != rowColor)
932 RETURN_LAYOUT_ANSWER(false,
933 "2 styles of row background color, non-bordered");
934 }
935 }
936
937 // Check for many rows
938 const uint32_t kMaxLayoutRows = 20;
939 if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data
940 RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
941 }
942
943 // Check for very wide table.
944 nsIFrame* documentFrame = Document()->GetFrame();
945 nsSize documentSize = documentFrame->GetSize();
946 if (documentSize.width > 0) {
947 nsSize tableSize = GetFrame()->GetSize();
948 int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
949 if (percentageOfDocWidth > 95) {
950 // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
951 // Probably for layout
952 RETURN_LAYOUT_ANSWER(
953 true, "<= 4 columns, table width is 95% of document width");
954 }
955 }
956
957 // Two column rules
958 if (rowCount * colCount <= 10) {
959 RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
960 }
961
962 if (HasDescendant(NS_LITERAL_STRING("embed")) ||
963 HasDescendant(NS_LITERAL_STRING("object")) ||
964 HasDescendant(NS_LITERAL_STRING("iframe"))) {
965 RETURN_LAYOUT_ANSWER(true,
966 "Has no borders, and has iframe, object, or iframe, "
967 "typical of advertisements");
968 }
969
970 RETURN_LAYOUT_ANSWER(false,
971 "no layout factor strong enough, so will guess data");
972 }
973
974 ////////////////////////////////////////////////////////////////////////////////
975 // HTMLCaptionAccessible
976 ////////////////////////////////////////////////////////////////////////////////
977
RelationByType(RelationType aType)978 Relation HTMLCaptionAccessible::RelationByType(RelationType aType) {
979 Relation rel = HyperTextAccessible::RelationByType(aType);
980 if (aType == RelationType::LABEL_FOR) rel.AppendTarget(Parent());
981
982 return rel;
983 }
984
NativeRole()985 role HTMLCaptionAccessible::NativeRole() { return roles::CAPTION; }
986