Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
6 #include "HTMLTableAccessible.h"
8 #include "mozilla/DebugOnly.h"
10 #include "Accessible-inl.h"
11 #include "nsAccessibilityService.h"
12 #include "nsAccUtils.h"
13 #include "DocAccessible.h"
14 #include "nsIAccessibleRelation.h"
15 #include "nsTextEquivUtils.h"
16 #include "Relation.h"
17 #include "Role.h"
18 #include "States.h"
19 #include "TreeWalker.h"
21 #include "mozilla/dom/HTMLTableElement.h"
22 #include "nsIDOMElement.h"
23 #include "nsIDOMRange.h"
24 #include "nsISelectionPrivate.h"
25 #include "nsIDOMNodeList.h"
26 #include "nsIDOMHTMLCollection.h"
27 #include "nsIDocument.h"
28 #include "nsIMutableArray.h"
29 #include "nsIPersistentProperties2.h"
30 #include "nsIPresShell.h"
31 #include "nsITableCellLayout.h"
32 #include "nsFrameSelection.h"
33 #include "nsError.h"
34 #include "nsArrayUtils.h"
35 #include "nsComponentManagerUtils.h"
36 #include "nsNameSpaceManager.h"
37 #include "nsTableCellFrame.h"
38 #include "nsTableOuterFrame.h"
40 using namespace mozilla;
41 using namespace mozilla::a11y;
43 ////////////////////////////////////////////////////////////////////////////////
44 // HTMLTableCellAccessible
45 ////////////////////////////////////////////////////////////////////////////////
47 HTMLTableCellAccessible::
48 HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
49 HyperTextAccessibleWrap(aContent, aDoc), xpcAccessibleTableCell(this)
50 {
51 mGenericTypes |= eTableCell;
52 }
54 ////////////////////////////////////////////////////////////////////////////////
55 // HTMLTableCellAccessible: nsISupports implementation
57 NS_IMPL_ISUPPORTS_INHERITED(HTMLTableCellAccessible,
58 HyperTextAccessible,
59 nsIAccessibleTableCell)
61 ////////////////////////////////////////////////////////////////////////////////
62 // HTMLTableCellAccessible: Accessible implementation
64 void
65 HTMLTableCellAccessible::Shutdown()
66 {
67 mTableCell = nullptr;
68 HyperTextAccessibleWrap::Shutdown();
69 }
71 role
72 HTMLTableCellAccessible::NativeRole()
73 {
74 return roles::CELL;
75 }
77 uint64_t
78 HTMLTableCellAccessible::NativeState()
79 {
80 uint64_t state = HyperTextAccessibleWrap::NativeState();
82 nsIFrame *frame = mContent->GetPrimaryFrame();
83 NS_ASSERTION(frame, "No frame for valid cell accessible!");
85 if (frame && frame->IsSelected())
86 state |= states::SELECTED;
88 return state;
89 }
91 uint64_t
92 HTMLTableCellAccessible::NativeInteractiveState() const
93 {
94 return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE;
95 }
97 already_AddRefed<nsIPersistentProperties>
98 HTMLTableCellAccessible::NativeAttributes()
99 {
100 nsCOMPtr<nsIPersistentProperties> attributes =
101 HyperTextAccessibleWrap::NativeAttributes();
103 // table-cell-index attribute
104 TableAccessible* table = Table();
105 if (!table)
106 return attributes.forget();
108 int32_t rowIdx = -1, colIdx = -1;
109 nsresult rv = GetCellIndexes(rowIdx, colIdx);
110 if (NS_FAILED(rv))
111 return attributes.forget();
113 nsAutoString stringIdx;
114 stringIdx.AppendInt(table->CellIndexAt(rowIdx, colIdx));
115 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
117 // abbr attribute
119 // Pick up object attribute from abbr DOM element (a child of the cell) or
120 // from abbr DOM attribute.
121 nsAutoString abbrText;
122 if (ChildCount() == 1) {
123 Accessible* abbr = FirstChild();
124 if (abbr->IsAbbreviation()) {
125 nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild();
126 if (firstChildNode) {
127 nsTextEquivUtils::
128 AppendTextEquivFromTextContent(firstChildNode, &abbrText);
129 }
130 }
131 }
132 if (abbrText.IsEmpty())
133 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr, abbrText);
135 if (!abbrText.IsEmpty())
136 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::abbr, abbrText);
138 // axis attribute
139 nsAutoString axisText;
140 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText);
141 if (!axisText.IsEmpty())
142 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::axis, axisText);
144 return attributes.forget();
145 }
147 ////////////////////////////////////////////////////////////////////////////////
148 // HTMLTableCellAccessible: nsIAccessibleTableCell implementation
150 TableAccessible*
151 HTMLTableCellAccessible::Table() const
152 {
153 Accessible* parent = const_cast<HTMLTableCellAccessible*>(this);
154 while ((parent = parent->Parent())) {
155 roles::Role role = parent->Role();
156 if (role == roles::TABLE || role == roles::TREE_TABLE)
157 return parent->AsTable();
158 }
160 return nullptr;
161 }
163 uint32_t
164 HTMLTableCellAccessible::ColIdx() const
165 {
166 nsITableCellLayout* cellLayout = GetCellLayout();
167 NS_ENSURE_TRUE(cellLayout, 0);
169 int32_t colIdx = 0;
170 cellLayout->GetColIndex(colIdx);
171 return colIdx > 0 ? static_cast<uint32_t>(colIdx) : 0;
172 }
174 uint32_t
175 HTMLTableCellAccessible::RowIdx() const
176 {
177 nsITableCellLayout* cellLayout = GetCellLayout();
178 NS_ENSURE_TRUE(cellLayout, 0);
180 int32_t rowIdx = 0;
181 cellLayout->GetRowIndex(rowIdx);
182 return rowIdx > 0 ? static_cast<uint32_t>(rowIdx) : 0;
183 }
185 uint32_t
186 HTMLTableCellAccessible::ColExtent() const
187 {
188 int32_t rowIdx = -1, colIdx = -1;
189 GetCellIndexes(rowIdx, colIdx);
191 TableAccessible* table = Table();
192 NS_ASSERTION(table, "cell not in a table!");
193 if (!table)
194 return 0;
196 return table->ColExtentAt(rowIdx, colIdx);
197 }
199 uint32_t
200 HTMLTableCellAccessible::RowExtent() const
201 {
202 int32_t rowIdx = -1, colIdx = -1;
203 GetCellIndexes(rowIdx, colIdx);
205 TableAccessible* table = Table();
206 NS_ASSERTION(table, "cell not in atable!");
207 if (!table)
208 return 0;
210 return table->RowExtentAt(rowIdx, colIdx);
211 }
213 void
214 HTMLTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells)
215 {
216 IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
217 while (Accessible* cell = itr.Next()) {
218 a11y::role cellRole = cell->Role();
219 if (cellRole == roles::COLUMNHEADER) {
220 aCells->AppendElement(cell);
221 } else if (cellRole != roles::ROWHEADER) {
222 // If referred table cell is at the same column then treat it as a column
223 // header.
224 TableCellAccessible* tableCell = cell->AsTableCell();
225 if (tableCell && tableCell->ColIdx() == ColIdx())
226 aCells->AppendElement(cell);
227 }
228 }
230 if (aCells->IsEmpty())
231 TableCellAccessible::ColHeaderCells(aCells);
232 }
234 void
235 HTMLTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells)
236 {
237 IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
238 while (Accessible* cell = itr.Next()) {
239 a11y::role cellRole = cell->Role();
240 if (cellRole == roles::ROWHEADER) {
241 aCells->AppendElement(cell);
242 } else if (cellRole != roles::COLUMNHEADER) {
243 // If referred table cell is at the same row then treat it as a column
244 // header.
245 TableCellAccessible* tableCell = cell->AsTableCell();
246 if (tableCell && tableCell->RowIdx() == RowIdx())
247 aCells->AppendElement(cell);
248 }
249 }
251 if (aCells->IsEmpty())
252 TableCellAccessible::RowHeaderCells(aCells);
253 }
255 bool
256 HTMLTableCellAccessible::Selected()
257 {
258 int32_t rowIdx = -1, colIdx = -1;
259 GetCellIndexes(rowIdx, colIdx);
261 TableAccessible* table = Table();
262 NS_ENSURE_TRUE(table, false);
264 return table->IsCellSelected(rowIdx, colIdx);
265 }
267 ////////////////////////////////////////////////////////////////////////////////
268 // HTMLTableCellAccessible: protected implementation
270 nsITableCellLayout*
271 HTMLTableCellAccessible::GetCellLayout() const
272 {
273 return do_QueryFrame(mContent->GetPrimaryFrame());
274 }
276 nsresult
277 HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const
278 {
279 nsITableCellLayout *cellLayout = GetCellLayout();
280 NS_ENSURE_STATE(cellLayout);
282 return cellLayout->GetCellIndexes(aRowIdx, aColIdx);
283 }
286 ////////////////////////////////////////////////////////////////////////////////
287 // HTMLTableHeaderCellAccessible
288 ////////////////////////////////////////////////////////////////////////////////
290 HTMLTableHeaderCellAccessible::
291 HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
292 HTMLTableCellAccessible(aContent, aDoc)
293 {
294 }
296 ////////////////////////////////////////////////////////////////////////////////
297 // HTMLTableHeaderCellAccessible: Accessible implementation
299 role
300 HTMLTableHeaderCellAccessible::NativeRole()
301 {
302 // Check value of @scope attribute.
303 static nsIContent::AttrValuesArray scopeValues[] =
304 {&nsGkAtoms::col, &nsGkAtoms::row, nullptr};
305 int32_t valueIdx =
306 mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope,
307 scopeValues, eCaseMatters);
309 switch (valueIdx) {
310 case 0:
311 return roles::COLUMNHEADER;
312 case 1:
313 return roles::ROWHEADER;
314 }
316 // Assume it's columnheader if there are headers in siblings, otherwise
317 // rowheader.
318 // This should iterate the flattened tree
319 nsIContent* parentContent = mContent->GetParent();
320 if (!parentContent) {
321 NS_ERROR("Deattached content on alive accessible?");
322 return roles::NOTHING;
323 }
325 for (nsIContent* siblingContent = mContent->GetPreviousSibling(); siblingContent;
326 siblingContent = siblingContent->GetPreviousSibling()) {
327 if (siblingContent->IsElement()) {
328 return nsCoreUtils::IsHTMLTableHeader(siblingContent) ?
329 roles::COLUMNHEADER : roles::ROWHEADER;
330 }
331 }
333 for (nsIContent* siblingContent = mContent->GetNextSibling(); siblingContent;
334 siblingContent = siblingContent->GetNextSibling()) {
335 if (siblingContent->IsElement()) {
336 return nsCoreUtils::IsHTMLTableHeader(siblingContent) ?
337 roles::COLUMNHEADER : roles::ROWHEADER;
338 }
339 }
341 // No elements in siblings what means the table has one column only. Therefore
342 // it should be column header.
343 return roles::COLUMNHEADER;
344 }
347 ////////////////////////////////////////////////////////////////////////////////
348 // HTMLTableRowAccessible
349 ////////////////////////////////////////////////////////////////////////////////
351 NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableRowAccessible, Accessible)
353 role
354 HTMLTableRowAccessible::NativeRole()
355 {
356 return roles::ROW;
357 }
359 ////////////////////////////////////////////////////////////////////////////////
360 // HTMLTableAccessible
361 ////////////////////////////////////////////////////////////////////////////////
363 NS_IMPL_ISUPPORTS_INHERITED(HTMLTableAccessible, Accessible,
364 nsIAccessibleTable)
366 ////////////////////////////////////////////////////////////////////////////////
367 // HTMLTableAccessible: Accessible
369 void
370 HTMLTableAccessible::Shutdown()
371 {
372 mTable = nullptr;
373 AccessibleWrap::Shutdown();
374 }
376 void
377 HTMLTableAccessible::CacheChildren()
378 {
379 // Move caption accessible so that it's the first child. Check for the first
380 // caption only, because nsAccessibilityService ensures we don't create
381 // accessibles for the other captions, since only the first is actually
382 // visible.
383 TreeWalker walker(this, mContent);
385 Accessible* child = nullptr;
386 while ((child = walker.NextChild())) {
387 if (child->Role() == roles::CAPTION) {
388 InsertChildAt(0, child);
389 while ((child = walker.NextChild()) && AppendChild(child));
390 break;
391 }
392 AppendChild(child);
393 }
394 }
396 role
397 HTMLTableAccessible::NativeRole()
398 {
399 return roles::TABLE;
400 }
402 uint64_t
403 HTMLTableAccessible::NativeState()
404 {
405 return Accessible::NativeState() | states::READONLY;
406 }
408 ENameValueFlag
409 HTMLTableAccessible::NativeName(nsString& aName)
410 {
411 ENameValueFlag nameFlag = Accessible::NativeName(aName);
412 if (!aName.IsEmpty())
413 return nameFlag;
415 // Use table caption as a name.
416 Accessible* caption = Caption();
417 if (caption) {
418 nsIContent* captionContent = caption->GetContent();
419 if (captionContent) {
420 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName);
421 if (!aName.IsEmpty())
422 return eNameOK;
423 }
424 }
426 // If no caption then use summary as a name.
427 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName);
428 return eNameOK;
429 }
431 already_AddRefed<nsIPersistentProperties>
432 HTMLTableAccessible::NativeAttributes()
433 {
434 nsCOMPtr<nsIPersistentProperties> attributes =
435 AccessibleWrap::NativeAttributes();
436 if (IsProbablyLayoutTable()) {
437 nsAutoString unused;
438 attributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
439 NS_LITERAL_STRING("true"), unused);
440 }
442 return attributes.forget();
443 }
445 ////////////////////////////////////////////////////////////////////////////////
446 // HTMLTableAccessible: nsIAccessible implementation
448 Relation
449 HTMLTableAccessible::RelationByType(RelationType aType)
450 {
451 Relation rel = AccessibleWrap::RelationByType(aType);
452 if (aType == RelationType::LABELLED_BY)
453 rel.AppendTarget(Caption());
455 return rel;
456 }
458 ////////////////////////////////////////////////////////////////////////////////
459 // HTMLTableAccessible: nsIAccessibleTable implementation
461 Accessible*
462 HTMLTableAccessible::Caption()
463 {
464 Accessible* child = mChildren.SafeElementAt(0, nullptr);
465 return child && child->Role() == roles::CAPTION ? child : nullptr;
466 }
468 void
469 HTMLTableAccessible::Summary(nsString& aSummary)
470 {
471 dom::HTMLTableElement* table = dom::HTMLTableElement::FromContent(mContent);
473 if (table)
474 table->GetSummary(aSummary);
475 }
477 uint32_t
478 HTMLTableAccessible::ColCount()
479 {
480 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
481 return tableFrame ? tableFrame->GetColCount() : 0;
482 }
484 uint32_t
485 HTMLTableAccessible::RowCount()
486 {
487 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
488 return tableFrame ? tableFrame->GetRowCount() : 0;
489 }
491 uint32_t
492 HTMLTableAccessible::SelectedCellCount()
493 {
494 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
495 if (!tableFrame)
496 return 0;
498 uint32_t count = 0, rowCount = RowCount(), colCount = ColCount();
499 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
500 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
501 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
502 if (!cellFrame || !cellFrame->IsSelected())
503 continue;
505 int32_t startRow = -1, startCol = -1;
506 cellFrame->GetRowIndex(startRow);
507 cellFrame->GetColIndex(startCol);
508 if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
509 startCol >= 0 && (uint32_t)startCol == colIdx)
510 count++;
511 }
512 }
514 return count;
515 }
517 uint32_t
518 HTMLTableAccessible::SelectedColCount()
519 {
520 uint32_t count = 0, colCount = ColCount();
522 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
523 if (IsColSelected(colIdx))
524 count++;
526 return count;
527 }
529 uint32_t
530 HTMLTableAccessible::SelectedRowCount()
531 {
532 uint32_t count = 0, rowCount = RowCount();
534 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
535 if (IsRowSelected(rowIdx))
536 count++;
538 return count;
539 }
541 void
542 HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
543 {
544 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
545 if (!tableFrame)
546 return;
548 uint32_t rowCount = RowCount(), colCount = ColCount();
549 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
550 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
551 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
552 if (!cellFrame || !cellFrame->IsSelected())
553 continue;
555 int32_t startCol = -1, startRow = -1;
556 cellFrame->GetRowIndex(startRow);
557 cellFrame->GetColIndex(startCol);
558 if ((startRow >= 0 && (uint32_t)startRow != rowIdx) ||
559 (startCol >= 0 && (uint32_t)startCol != colIdx))
560 continue;
562 Accessible* cell = mDoc->GetAccessible(cellFrame->GetContent());
563 aCells->AppendElement(cell);
564 }
565 }
566 }
568 void
569 HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
570 {
571 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
572 if (!tableFrame)
573 return;
575 uint32_t rowCount = RowCount(), colCount = ColCount();
576 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
577 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
578 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
579 if (!cellFrame || !cellFrame->IsSelected())
580 continue;
582 int32_t startRow = -1, startCol = -1;
583 cellFrame->GetColIndex(startCol);
584 cellFrame->GetRowIndex(startRow);
585 if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
586 startCol >= 0 && (uint32_t)startCol == colIdx)
587 aCells->AppendElement(CellIndexAt(rowIdx, colIdx));
588 }
589 }
590 }
592 void
593 HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
594 {
595 uint32_t colCount = ColCount();
596 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
597 if (IsColSelected(colIdx))
598 aCols->AppendElement(colIdx);
599 }
601 void
602 HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
603 {
604 uint32_t rowCount = RowCount();
605 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
606 if (IsRowSelected(rowIdx))
607 aRows->AppendElement(rowIdx);
608 }
610 Accessible*
611 HTMLTableAccessible::CellAt(uint32_t aRowIdx, uint32_t aColIdx)
612 {
613 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
614 if (!tableFrame)
615 return nullptr;
617 nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
618 Accessible* cell = mDoc->GetAccessible(cellContent);
620 // XXX bug 576838: crazy tables (like table6 in tables/test_table2.html) may
621 // return itself as a cell what makes Orca hang.
622 return cell == this ? nullptr : cell;
623 }
625 int32_t
626 HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx)
627 {
628 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
629 if (!tableFrame)
630 return -1;
632 return tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx);
633 }
635 int32_t
636 HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx)
637 {
638 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
639 if (!tableFrame)
640 return -1;
642 int32_t rowIdx = -1, colIdx = -1;
643 tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
644 return colIdx;
645 }
647 int32_t
648 HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx)
649 {
650 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
651 if (!tableFrame)
652 return -1;
654 int32_t rowIdx = -1, colIdx = -1;
655 tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
656 return rowIdx;
657 }
659 void
660 HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
661 int32_t* aColIdx)
662 {
663 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
664 if (tableFrame)
665 tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx);
666 }
668 uint32_t
669 HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
670 {
671 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
672 if (!tableFrame)
673 return 0;
675 return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx);
676 }
678 uint32_t
679 HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
680 {
681 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
682 if (!tableFrame)
683 return 0;
685 return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx);
686 }
688 bool
689 HTMLTableAccessible::IsColSelected(uint32_t aColIdx)
690 {
691 bool isSelected = false;
693 uint32_t rowCount = RowCount();
694 for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
695 isSelected = IsCellSelected(rowIdx, aColIdx);
696 if (!isSelected)
697 return false;
698 }
700 return isSelected;
701 }
703 bool
704 HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx)
705 {
706 bool isSelected = false;
708 uint32_t colCount = ColCount();
709 for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
710 isSelected = IsCellSelected(aRowIdx, colIdx);
711 if (!isSelected)
712 return false;
713 }
715 return isSelected;
716 }
718 bool
719 HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
720 {
721 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
722 if (!tableFrame)
723 return false;
725 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx);
726 return cellFrame ? cellFrame->IsSelected() : false;
727 }
729 void
730 HTMLTableAccessible::SelectRow(uint32_t aRowIdx)
731 {
732 DebugOnly<nsresult> rv =
733 RemoveRowsOrColumnsFromSelection(aRowIdx,
734 nsISelectionPrivate::TABLESELECTION_ROW,
735 true);
736 NS_ASSERTION(NS_SUCCEEDED(rv),
737 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
739 AddRowOrColumnToSelection(aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW);
740 }
742 void
743 HTMLTableAccessible::SelectCol(uint32_t aColIdx)
744 {
745 DebugOnly<nsresult> rv =
746 RemoveRowsOrColumnsFromSelection(aColIdx,
747 nsISelectionPrivate::TABLESELECTION_COLUMN,
748 true);
749 NS_ASSERTION(NS_SUCCEEDED(rv),
750 "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
752 AddRowOrColumnToSelection(aColIdx, nsISelectionPrivate::TABLESELECTION_COLUMN);
753 }
755 void
756 HTMLTableAccessible::UnselectRow(uint32_t aRowIdx)
757 {
758 RemoveRowsOrColumnsFromSelection(aRowIdx,
759 nsISelectionPrivate::TABLESELECTION_ROW,
760 false);
761 }
763 void
764 HTMLTableAccessible::UnselectCol(uint32_t aColIdx)
765 {
766 RemoveRowsOrColumnsFromSelection(aColIdx,
767 nsISelectionPrivate::TABLESELECTION_COLUMN,
768 false);
769 }
771 nsresult
772 HTMLTableAccessible::AddRowOrColumnToSelection(int32_t aIndex, uint32_t aTarget)
773 {
774 bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
776 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
777 if (!tableFrame)
778 return NS_OK;
780 uint32_t count = 0;
781 if (doSelectRow)
782 count = ColCount();
783 else
784 count = RowCount();
786 nsIPresShell* presShell(mDoc->PresShell());
787 nsRefPtr<nsFrameSelection> tableSelection =
788 const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
790 for (uint32_t idx = 0; idx < count; idx++) {
791 int32_t rowIdx = doSelectRow ? aIndex : idx;
792 int32_t colIdx = doSelectRow ? idx : aIndex;
793 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
794 if (cellFrame && !cellFrame->IsSelected()) {
795 nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent());
796 NS_ENSURE_SUCCESS(rv, rv);
797 }
798 }
800 return NS_OK;
801 }
803 nsresult
804 HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(int32_t aIndex,
805 uint32_t aTarget,
806 bool aIsOuter)
807 {
808 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
809 if (!tableFrame)
810 return NS_OK;
812 nsIPresShell* presShell(mDoc->PresShell());
813 nsRefPtr<nsFrameSelection> tableSelection =
814 const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
816 bool doUnselectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
817 uint32_t count = doUnselectRow ? ColCount() : RowCount();
819 int32_t startRowIdx = doUnselectRow ? aIndex : 0;
820 int32_t endRowIdx = doUnselectRow ? aIndex : count - 1;
821 int32_t startColIdx = doUnselectRow ? 0 : aIndex;
822 int32_t endColIdx = doUnselectRow ? count - 1 : aIndex;
824 if (aIsOuter)
825 return tableSelection->RestrictCellsToSelection(mContent,
826 startRowIdx, startColIdx,
827 endRowIdx, endColIdx);
829 return tableSelection->RemoveCellsFromSelection(mContent,
830 startRowIdx, startColIdx,
831 endRowIdx, endColIdx);
832 }
834 void
835 HTMLTableAccessible::Description(nsString& aDescription)
836 {
837 // Helpful for debugging layout vs. data tables
838 aDescription.Truncate();
839 Accessible::Description(aDescription);
840 if (!aDescription.IsEmpty())
841 return;
843 // Use summary as description if it weren't used as a name.
844 // XXX: get rid code duplication with NameInternal().
845 Accessible* caption = Caption();
846 if (caption) {
847 nsIContent* captionContent = caption->GetContent();
848 if (captionContent) {
849 nsAutoString captionText;
850 nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
851 &captionText);
853 if (!captionText.IsEmpty()) { // summary isn't used as a name.
854 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary,
855 aDescription);
856 }
857 }
858 }
860 #ifdef SHOW_LAYOUT_HEURISTIC
861 if (aDescription.IsEmpty()) {
862 bool isProbablyForLayout = IsProbablyLayoutTable();
863 aDescription = mLayoutHeuristic;
864 }
865 printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
866 #endif
867 }
869 bool
870 HTMLTableAccessible::HasDescendant(const nsAString& aTagName, bool aAllowEmpty)
871 {
872 nsCOMPtr<nsIDOMElement> tableElt(do_QueryInterface(mContent));
873 NS_ENSURE_TRUE(tableElt, false);
875 nsCOMPtr<nsIDOMHTMLCollection> nodeList;
876 tableElt->GetElementsByTagName(aTagName, getter_AddRefs(nodeList));
877 NS_ENSURE_TRUE(nodeList, false);
879 nsCOMPtr<nsIDOMNode> foundItem;
880 nodeList->Item(0, getter_AddRefs(foundItem));
881 if (!foundItem)
882 return false;
884 if (aAllowEmpty)
885 return true;
887 // Make sure that the item we found has contents and either has multiple
888 // children or the found item is not a whitespace-only text node.
889 nsCOMPtr<nsIContent> foundItemContent = do_QueryInterface(foundItem);
890 if (foundItemContent->GetChildCount() > 1)
891 return true; // Treat multiple child nodes as non-empty
893 nsIContent *innerItemContent = foundItemContent->GetFirstChild();
894 if (innerItemContent && !innerItemContent->TextIsOnlyWhitespace())
895 return true;
897 // If we found more than one node then return true not depending on
898 // aAllowEmpty flag.
899 // XXX it might be dummy but bug 501375 where we changed this addresses
900 // performance problems only. Note, currently 'aAllowEmpty' flag is used for
901 // caption element only. On another hand we create accessible object for
902 // the first entry of caption element (see
903 // HTMLTableAccessible::CacheChildren).
904 nodeList->Item(1, getter_AddRefs(foundItem));
905 return !!foundItem;
906 }
908 bool
909 HTMLTableAccessible::IsProbablyLayoutTable()
910 {
911 // Implement a heuristic to determine if table is most likely used for layout
912 // XXX do we want to look for rowspan or colspan, especialy that span all but a couple cells
913 // at the beginning or end of a row/col, and especially when they occur at the edge of a table?
914 // XXX expose this info via object attributes to AT-SPI
916 // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
917 // This will allow release trunk builds to be used by testers to refine the algorithm
918 // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
919 #ifdef SHOW_LAYOUT_HEURISTIC
920 #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
921 { \
922 mLayoutHeuristic = isLayout ? \
923 NS_LITERAL_STRING("layout table: " heuristic) : \
924 NS_LITERAL_STRING("data table: " heuristic); \
925 return isLayout; \
926 }
927 #else
928 #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
929 #endif
931 DocAccessible* docAccessible = Document();
932 if (docAccessible) {
933 uint64_t docState = docAccessible->State();
934 if (docState & states::EDITABLE) { // Need to see all elements while document is being edited
935 RETURN_LAYOUT_ANSWER(false, "In editable document");
936 }
937 }
939 // Check to see if an ARIA role overrides the role from native markup,
940 // but for which we still expose table semantics (treegrid, for example).
941 if (Role() != roles::TABLE)
942 RETURN_LAYOUT_ANSWER(false, "Has role attribute");
944 if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
945 // Role attribute is present, but overridden roles have already been dealt with.
946 // Only landmarks and other roles that don't override the role from native
947 // markup are left to deal with here.
948 RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table");
949 }
951 if (mContent->Tag() != nsGkAtoms::table)
952 RETURN_LAYOUT_ANSWER(true, "table built by CSS display:table style");
954 // Check if datatable attribute has "0" value.
955 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
956 NS_LITERAL_STRING("0"), eCaseMatters)) {
957 RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
958 }
960 // Check for legitimate data table attributes.
961 nsAutoString summary;
962 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) &&
963 !summary.IsEmpty())
964 RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
966 // Check for legitimate data table elements.
967 Accessible* caption = FirstChild();
968 if (caption && caption->Role() == roles::CAPTION && caption->HasChildren())
969 RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures");
971 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
972 childElm = childElm->GetNextSibling()) {
973 if (!childElm->IsHTML())
974 continue;
976 if (childElm->Tag() == nsGkAtoms::col ||
977 childElm->Tag() == nsGkAtoms::colgroup ||
978 childElm->Tag() == nsGkAtoms::tfoot ||
979 childElm->Tag() == nsGkAtoms::thead) {
980 RETURN_LAYOUT_ANSWER(false,
981 "Has col, colgroup, tfoot or thead -- legitimate table structures");
982 }
984 if (childElm->Tag() == nsGkAtoms::tbody) {
985 for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
986 rowElm = rowElm->GetNextSibling()) {
987 if (rowElm->IsHTML() && rowElm->Tag() == nsGkAtoms::tr) {
988 for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
989 cellElm = cellElm->GetNextSibling()) {
990 if (cellElm->IsHTML()) {
992 if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
993 RETURN_LAYOUT_ANSWER(false,
994 "Has th -- legitimate table structures");
995 }
997 if (cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
998 cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
999 cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
1000 RETURN_LAYOUT_ANSWER(false,
1001 "Has headers, scope, or abbr attribute -- legitimate table structures");
1002 }
1004 Accessible* cell = mDoc->GetAccessible(cellElm);
1005 if (cell && cell->ChildCount() == 1 &&
1006 cell->FirstChild()->IsAbbreviation()) {
1007 RETURN_LAYOUT_ANSWER(false,
1008 "has abbr -- legitimate table structures");
1009 }
1010 }
1011 }
1012 }
1013 }
1014 }
1015 }
1017 if (HasDescendant(NS_LITERAL_STRING("table"))) {
1018 RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
1019 }
1021 // If only 1 column or only 1 row, it's for layout
1022 int32_t columns, rows;
1023 GetColumnCount(&columns);
1024 if (columns <=1) {
1025 RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
1026 }
1027 GetRowCount(&rows);
1028 if (rows <=1) {
1029 RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
1030 }
1032 // Check for many columns
1033 if (columns >= 5) {
1034 RETURN_LAYOUT_ANSWER(false, ">=5 columns");
1035 }
1037 // Now we know there are 2-4 columns and 2 or more rows
1038 // Check to see if there are visible borders on the cells
1039 // XXX currently, we just check the first cell -- do we really need to do more?
1040 nsTableOuterFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
1041 if (!tableFrame)
1042 RETURN_LAYOUT_ANSWER(false, "table with no frame!");
1044 nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
1045 if (!cellFrame)
1046 RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
1048 nsMargin border;
1049 cellFrame->GetBorder(border);
1050 if (border.top && border.bottom && border.left && border.right) {
1051 RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
1052 }
1054 /**
1055 * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on forward
1056 */
1058 // Check for styled background color across rows (alternating background
1059 // color is a common feature for data tables).
1060 uint32_t childCount = ChildCount();
1061 nscolor rowColor = 0;
1062 nscolor prevRowColor;
1063 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
1064 Accessible* child = GetChildAt(childIdx);
1065 if (child->Role() == roles::ROW) {
1066 prevRowColor = rowColor;
1067 nsIFrame* rowFrame = child->GetFrame();
1068 rowColor = rowFrame->StyleBackground()->mBackgroundColor;
1070 if (childIdx > 0 && prevRowColor != rowColor)
1071 RETURN_LAYOUT_ANSWER(false, "2 styles of row background color, non-bordered");
1072 }
1073 }
1075 // Check for many rows
1076 const int32_t kMaxLayoutRows = 20;
1077 if (rows > kMaxLayoutRows) { // A ton of rows, this is probably for data
1078 RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
1079 }
1081 // Check for very wide table.
1082 nsIFrame* documentFrame = Document()->GetFrame();
1083 nsSize documentSize = documentFrame->GetSize();
1084 if (documentSize.width > 0) {
1085 nsSize tableSize = GetFrame()->GetSize();
1086 int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
1087 if (percentageOfDocWidth > 95) {
1088 // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
1089 // Probably for layout
1090 RETURN_LAYOUT_ANSWER(true,
1091 "<= 4 columns, table width is 95% of document width");
1092 }
1093 }
1095 // Two column rules
1096 if (rows * columns <= 10) {
1097 RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
1098 }
1100 if (HasDescendant(NS_LITERAL_STRING("embed")) ||
1101 HasDescendant(NS_LITERAL_STRING("object")) ||
1102 HasDescendant(NS_LITERAL_STRING("applet")) ||
1103 HasDescendant(NS_LITERAL_STRING("iframe"))) {
1104 RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, applet or iframe, typical of advertisements");
1105 }
1107 RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data");
1108 }
1111 ////////////////////////////////////////////////////////////////////////////////
1112 // HTMLCaptionAccessible
1113 ////////////////////////////////////////////////////////////////////////////////
1115 Relation
1116 HTMLCaptionAccessible::RelationByType(RelationType aType)
1117 {
1118 Relation rel = HyperTextAccessible::RelationByType(aType);
1119 if (aType == RelationType::LABEL_FOR)
1120 rel.AppendTarget(Parent());
1122 return rel;
1123 }
1125 role
1126 HTMLCaptionAccessible::NativeRole()
1127 {
1128 return roles::CAPTION;
1129 }