/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "XULTreeGridAccessible.h" #include #include "AccAttributes.h" #include "LocalAccessible-inl.h" #include "nsAccCache.h" #include "nsAccessibilityService.h" #include "nsAccUtils.h" #include "DocAccessible.h" #include "nsEventShell.h" #include "Relation.h" #include "mozilla/a11y/Role.h" #include "States.h" #include "nsQueryObject.h" #include "nsTreeColumns.h" #include "nsITreeSelection.h" #include "nsComponentManagerUtils.h" #include "mozilla/PresShell.h" #include "mozilla/a11y/TableAccessible.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/TreeColumnBinding.h" #include "mozilla/dom/XULTreeElementBinding.h" using namespace mozilla::a11y; using namespace mozilla; XULTreeGridAccessible::~XULTreeGridAccessible() {} //////////////////////////////////////////////////////////////////////////////// // XULTreeGridAccessible: Table uint32_t XULTreeGridAccessible::ColCount() const { return nsCoreUtils::GetSensibleColumnCount(mTree); } uint32_t XULTreeGridAccessible::RowCount() { if (!mTreeView) return 0; int32_t rowCount = 0; mTreeView->GetRowCount(&rowCount); return rowCount >= 0 ? rowCount : 0; } uint32_t XULTreeGridAccessible::SelectedCellCount() { return SelectedRowCount() * ColCount(); } uint32_t XULTreeGridAccessible::SelectedColCount() { // If all the row has been selected, then all the columns are selected, // because we can't select a column alone. uint32_t selectedRowCount = SelectedItemCount(); return selectedRowCount > 0 && selectedRowCount == RowCount() ? ColCount() : 0; } uint32_t XULTreeGridAccessible::SelectedRowCount() { return SelectedItemCount(); } void XULTreeGridAccessible::SelectedCells(nsTArray* aCells) { uint32_t colCount = ColCount(), rowCount = RowCount(); for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { if (IsRowSelected(rowIdx)) { for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { LocalAccessible* cell = CellAt(rowIdx, colIdx); aCells->AppendElement(cell); } } } } void XULTreeGridAccessible::SelectedCellIndices(nsTArray* aCells) { uint32_t colCount = ColCount(), rowCount = RowCount(); for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { if (IsRowSelected(rowIdx)) { for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { aCells->AppendElement(rowIdx * colCount + colIdx); } } } } void XULTreeGridAccessible::SelectedColIndices(nsTArray* aCols) { if (RowCount() != SelectedRowCount()) return; uint32_t colCount = ColCount(); aCols->SetCapacity(colCount); for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { aCols->AppendElement(colIdx); } } void XULTreeGridAccessible::SelectedRowIndices(nsTArray* aRows) { uint32_t rowCount = RowCount(); for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { if (IsRowSelected(rowIdx)) aRows->AppendElement(rowIdx); } } LocalAccessible* XULTreeGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) { XULTreeItemAccessibleBase* rowAcc = GetTreeItemAccessible(aRowIndex); if (!rowAcc) return nullptr; RefPtr column = nsCoreUtils::GetSensibleColumnAt(mTree, aColumnIndex); if (!column) return nullptr; return rowAcc->GetCellAccessible(column); } void XULTreeGridAccessible::ColDescription(uint32_t aColIdx, nsString& aDescription) { aDescription.Truncate(); LocalAccessible* treeColumns = LocalAccessible::LocalChildAt(0); if (treeColumns) { LocalAccessible* treeColumnItem = treeColumns->LocalChildAt(aColIdx); if (treeColumnItem) treeColumnItem->Name(aDescription); } } bool XULTreeGridAccessible::IsColSelected(uint32_t aColIdx) { // If all the row has been selected, then all the columns are selected. // Because we can't select a column alone. return SelectedItemCount() == RowCount(); } bool XULTreeGridAccessible::IsRowSelected(uint32_t aRowIdx) { if (!mTreeView) return false; nsCOMPtr selection; nsresult rv = mTreeView->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(rv, false); bool isSelected = false; selection->IsSelected(aRowIdx, &isSelected); return isSelected; } bool XULTreeGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { return IsRowSelected(aRowIdx); } int32_t XULTreeGridAccessible::ColIndexAt(uint32_t aCellIdx) { uint32_t colCount = ColCount(); if (colCount < 1 || aCellIdx >= colCount * RowCount()) { return -1; // Error: column count is 0 or index out of bounds. } return static_cast(aCellIdx % colCount); } int32_t XULTreeGridAccessible::RowIndexAt(uint32_t aCellIdx) { uint32_t colCount = ColCount(); if (colCount < 1 || aCellIdx >= colCount * RowCount()) { return -1; // Error: column count is 0 or index out of bounds. } return static_cast(aCellIdx / colCount); } void XULTreeGridAccessible::RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, int32_t* aColIdx) { uint32_t colCount = ColCount(); if (colCount < 1 || aCellIdx >= colCount * RowCount()) { *aRowIdx = -1; *aColIdx = -1; return; // Error: column count is 0 or index out of bounds. } *aRowIdx = static_cast(aCellIdx / colCount); *aColIdx = static_cast(aCellIdx % colCount); } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridAccessible: LocalAccessible implementation role XULTreeGridAccessible::NativeRole() const { RefPtr treeColumns = mTree->GetColumns(FlushType::None); if (!treeColumns) { NS_ERROR("No treecolumns object for tree!"); return roles::NOTHING; } nsTreeColumn* primaryColumn = treeColumns->GetPrimaryColumn(); return primaryColumn ? roles::TREE_TABLE : roles::TABLE; } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridAccessible: XULTreeAccessible implementation already_AddRefed XULTreeGridAccessible::CreateTreeItemAccessible(int32_t aRow) const { RefPtr accessible = new XULTreeGridRowAccessible( mContent, mDoc, const_cast(this), mTree, mTreeView, aRow); return accessible.forget(); } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridRowAccessible //////////////////////////////////////////////////////////////////////////////// XULTreeGridRowAccessible::XULTreeGridRowAccessible( nsIContent* aContent, DocAccessible* aDoc, LocalAccessible* aTreeAcc, dom::XULTreeElement* aTree, nsITreeView* aTreeView, int32_t aRow) : XULTreeItemAccessibleBase(aContent, aDoc, aTreeAcc, aTree, aTreeView, aRow), mAccessibleCache(kDefaultTreeCacheLength) { mGenericTypes |= eTableRow; mStateFlags |= eNoKidsFromDOM; } XULTreeGridRowAccessible::~XULTreeGridRowAccessible() {} //////////////////////////////////////////////////////////////////////////////// // XULTreeGridRowAccessible: nsISupports and cycle collection implementation NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeGridRowAccessible, XULTreeItemAccessibleBase, mAccessibleCache) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeGridRowAccessible) NS_INTERFACE_MAP_END_INHERITING(XULTreeItemAccessibleBase) NS_IMPL_ADDREF_INHERITED(XULTreeGridRowAccessible, XULTreeItemAccessibleBase) NS_IMPL_RELEASE_INHERITED(XULTreeGridRowAccessible, XULTreeItemAccessibleBase) //////////////////////////////////////////////////////////////////////////////// // XULTreeGridRowAccessible: LocalAccessible implementation void XULTreeGridRowAccessible::Shutdown() { if (mDoc && !mDoc->IsDefunct()) { UnbindCacheEntriesFromDocument(mAccessibleCache); } XULTreeItemAccessibleBase::Shutdown(); } role XULTreeGridRowAccessible::NativeRole() const { return roles::ROW; } ENameValueFlag XULTreeGridRowAccessible::Name(nsString& aName) const { aName.Truncate(); // XXX: the row name sholdn't be a concatenation of cell names (bug 664384). RefPtr column = nsCoreUtils::GetFirstSensibleColumn(mTree); while (column) { if (!aName.IsEmpty()) aName.Append(' '); nsAutoString cellName; GetCellName(column, cellName); aName.Append(cellName); column = nsCoreUtils::GetNextSensibleColumn(column); } return eNameOK; } LocalAccessible* XULTreeGridRowAccessible::LocalChildAtPoint( int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) { nsIFrame* frame = GetFrame(); if (!frame) return nullptr; nsPresContext* presContext = frame->PresContext(); PresShell* presShell = presContext->PresShell(); nsIFrame* rootFrame = presShell->GetRootFrame(); NS_ENSURE_TRUE(rootFrame, nullptr); CSSIntRect rootRect = rootFrame->GetScreenRect(); int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.X(); int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.Y(); ErrorResult rv; dom::TreeCellInfo cellInfo; mTree->GetCellAt(clientX, clientY, cellInfo, rv); // Return if we failed to find tree cell in the row for the given point. if (cellInfo.mRow != mRow || !cellInfo.mCol) return nullptr; return GetCellAccessible(cellInfo.mCol); } LocalAccessible* XULTreeGridRowAccessible::LocalChildAt(uint32_t aIndex) const { if (IsDefunct()) return nullptr; RefPtr column = nsCoreUtils::GetSensibleColumnAt(mTree, aIndex); if (!column) return nullptr; return GetCellAccessible(column); } uint32_t XULTreeGridRowAccessible::ChildCount() const { return nsCoreUtils::GetSensibleColumnCount(mTree); } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridRowAccessible: XULTreeItemAccessibleBase implementation XULTreeGridCellAccessible* XULTreeGridRowAccessible::GetCellAccessible( nsTreeColumn* aColumn) const { MOZ_ASSERT(aColumn, "No tree column!"); void* key = static_cast(aColumn); XULTreeGridCellAccessible* cachedCell = mAccessibleCache.GetWeak(key); if (cachedCell) return cachedCell; RefPtr cell = new XULTreeGridCellAccessible( mContent, mDoc, const_cast(this), mTree, mTreeView, mRow, aColumn); mAccessibleCache.InsertOrUpdate(key, RefPtr{cell}); Document()->BindToDocument(cell, nullptr); return cell; } void XULTreeGridRowAccessible::RowInvalidated(int32_t aStartColIdx, int32_t aEndColIdx) { RefPtr treeColumns = mTree->GetColumns(FlushType::None); if (!treeColumns) return; bool nameChanged = false; for (int32_t colIdx = aStartColIdx; colIdx <= aEndColIdx; ++colIdx) { nsTreeColumn* column = treeColumns->GetColumnAt(colIdx); if (column && !nsCoreUtils::IsColumnHidden(column)) { XULTreeGridCellAccessible* cell = GetCellAccessible(column); if (cell) nameChanged |= cell->CellInvalidated(); } } if (nameChanged) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); } } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible //////////////////////////////////////////////////////////////////////////////// XULTreeGridCellAccessible::XULTreeGridCellAccessible( nsIContent* aContent, DocAccessible* aDoc, XULTreeGridRowAccessible* aRowAcc, dom::XULTreeElement* aTree, nsITreeView* aTreeView, int32_t aRow, nsTreeColumn* aColumn) : LeafAccessible(aContent, aDoc), mTree(aTree), mTreeView(aTreeView), mRow(aRow), mColumn(aColumn) { mParent = aRowAcc; mStateFlags |= eSharedNode; mGenericTypes |= eTableCell; NS_ASSERTION(mTreeView, "mTreeView is null"); if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX) { mTreeView->GetCellValue(mRow, mColumn, mCachedTextEquiv); } else { mTreeView->GetCellText(mRow, mColumn, mCachedTextEquiv); } } XULTreeGridCellAccessible::~XULTreeGridCellAccessible() {} //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: nsISupports implementation NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeGridCellAccessible, LeafAccessible, mTree, mColumn) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeGridCellAccessible) NS_INTERFACE_MAP_END_INHERITING(LeafAccessible) NS_IMPL_ADDREF_INHERITED(XULTreeGridCellAccessible, LeafAccessible) NS_IMPL_RELEASE_INHERITED(XULTreeGridCellAccessible, LeafAccessible) //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: LocalAccessible void XULTreeGridCellAccessible::Shutdown() { mTree = nullptr; mTreeView = nullptr; mRow = -1; mColumn = nullptr; mParent = nullptr; // null-out to prevent base class's shutdown ops LeafAccessible::Shutdown(); } Accessible* XULTreeGridCellAccessible::FocusedChild() { return nullptr; } ENameValueFlag XULTreeGridCellAccessible::Name(nsString& aName) const { aName.Truncate(); if (!mTreeView) return eNameOK; mTreeView->GetCellText(mRow, mColumn, aName); // If there is still no name try the cell value: // This is for graphical cells. We need tree/table view implementors to // implement FooView::GetCellValue to return a meaningful string for cases // where there is something shown in the cell (non-text) such as a star icon; // in which case GetCellValue for that cell would return "starred" or // "flagged" for example. if (aName.IsEmpty()) mTreeView->GetCellValue(mRow, mColumn, aName); return eNameOK; } nsIntRect XULTreeGridCellAccessible::BoundsInCSSPixels() const { // Get bounds for tree cell and add x and y of treechildren element to // x and y of the cell. nsresult rv; nsIntRect rect = mTree->GetCoordsForCellItem(mRow, mColumn, u"cell"_ns, rv); if (NS_FAILED(rv)) { return nsIntRect(); } RefPtr bodyElement = mTree->GetTreeBody(); if (!bodyElement || !bodyElement->IsXULElement()) { return nsIntRect(); } nsIFrame* bodyFrame = bodyElement->GetPrimaryFrame(); if (!bodyFrame) { return nsIntRect(); } CSSIntRect screenRect = bodyFrame->GetScreenRect(); rect.x += screenRect.x; rect.y += screenRect.y; return rect; } nsRect XULTreeGridCellAccessible::BoundsInAppUnits() const { nsIntRect bounds = BoundsInCSSPixels(); nsPresContext* presContext = mDoc->PresContext(); return nsRect(presContext->CSSPixelsToAppUnits(bounds.X()), presContext->CSSPixelsToAppUnits(bounds.Y()), presContext->CSSPixelsToAppUnits(bounds.Width()), presContext->CSSPixelsToAppUnits(bounds.Height())); } bool XULTreeGridCellAccessible::HasPrimaryAction() const { return mColumn->Cycler() || (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX && IsEditable()); } void XULTreeGridCellAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { aName.Truncate(); if (aIndex != eAction_Click || !mTreeView) return; if (mColumn->Cycler()) { aName.AssignLiteral("cycle"); return; } if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX && IsEditable()) { nsAutoString value; mTreeView->GetCellValue(mRow, mColumn, value); if (value.EqualsLiteral("true")) { aName.AssignLiteral("uncheck"); } else { aName.AssignLiteral("check"); } } } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: TableCell TableAccessible* XULTreeGridCellAccessible::Table() const { LocalAccessible* grandParent = mParent->LocalParent(); if (grandParent) return grandParent->AsTable(); return nullptr; } uint32_t XULTreeGridCellAccessible::ColIdx() const { uint32_t colIdx = 0; RefPtr column = mColumn; while ((column = nsCoreUtils::GetPreviousSensibleColumn(column))) colIdx++; return colIdx; } uint32_t XULTreeGridCellAccessible::RowIdx() const { return mRow; } void XULTreeGridCellAccessible::ColHeaderCells( nsTArray* aHeaderCells) { dom::Element* columnElm = mColumn->Element(); LocalAccessible* headerCell = mDoc->GetAccessible(columnElm); if (headerCell) aHeaderCells->AppendElement(headerCell); } bool XULTreeGridCellAccessible::Selected() { nsCOMPtr selection; nsresult rv = mTreeView->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(rv, false); bool selected = false; selection->IsSelected(mRow, &selected); return selected; } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: LocalAccessible public implementation already_AddRefed XULTreeGridCellAccessible::NativeAttributes() { RefPtr attributes = new AccAttributes(); // "table-cell-index" attribute TableAccessible* table = Table(); if (!table) return attributes.forget(); attributes->SetAttribute(nsGkAtoms::tableCellIndex, table->CellIndexAt(mRow, ColIdx())); // "cycles" attribute if (mColumn->Cycler()) { attributes->SetAttribute(nsGkAtoms::cycles, true); } return attributes.forget(); } role XULTreeGridCellAccessible::NativeRole() const { return roles::GRID_CELL; } uint64_t XULTreeGridCellAccessible::NativeState() const { if (!mTreeView) return states::DEFUNCT; // selectable/selected state uint64_t states = states::SELECTABLE; // keep in sync with NativeInteractiveState nsCOMPtr selection; mTreeView->GetSelection(getter_AddRefs(selection)); if (selection) { bool isSelected = false; selection->IsSelected(mRow, &isSelected); if (isSelected) states |= states::SELECTED; } // checked state if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX) { states |= states::CHECKABLE; nsAutoString checked; mTreeView->GetCellValue(mRow, mColumn, checked); if (checked.EqualsIgnoreCase("true")) states |= states::CHECKED; } return states; } uint64_t XULTreeGridCellAccessible::NativeInteractiveState() const { return states::SELECTABLE; } int32_t XULTreeGridCellAccessible::IndexInParent() const { return ColIdx(); } Relation XULTreeGridCellAccessible::RelationByType(RelationType aType) const { return Relation(); } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: public implementation bool XULTreeGridCellAccessible::CellInvalidated() { nsAutoString textEquiv; if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX) { mTreeView->GetCellValue(mRow, mColumn, textEquiv); if (mCachedTextEquiv != textEquiv) { bool isEnabled = textEquiv.EqualsLiteral("true"); RefPtr accEvent = new AccStateChangeEvent(this, states::CHECKED, isEnabled); nsEventShell::FireEvent(accEvent); mCachedTextEquiv = textEquiv; return true; } return false; } mTreeView->GetCellText(mRow, mColumn, textEquiv); if (mCachedTextEquiv != textEquiv) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); mCachedTextEquiv = textEquiv; return true; } return false; } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: LocalAccessible protected implementation LocalAccessible* XULTreeGridCellAccessible::GetSiblingAtOffset( int32_t aOffset, nsresult* aError) const { if (aError) *aError = NS_OK; // fail peacefully RefPtr columnAtOffset(mColumn), column; if (aOffset < 0) { for (int32_t index = aOffset; index < 0 && columnAtOffset; index++) { column = nsCoreUtils::GetPreviousSensibleColumn(columnAtOffset); column.swap(columnAtOffset); } } else { for (int32_t index = aOffset; index > 0 && columnAtOffset; index--) { column = nsCoreUtils::GetNextSensibleColumn(columnAtOffset); column.swap(columnAtOffset); } } if (!columnAtOffset) return nullptr; XULTreeItemAccessibleBase* rowAcc = static_cast(LocalParent()); return rowAcc->GetCellAccessible(columnAtOffset); } void XULTreeGridCellAccessible::DispatchClickEvent( nsIContent* aContent, uint32_t aActionIndex) const { if (IsDefunct()) return; RefPtr tree = mTree; RefPtr column = mColumn; nsCoreUtils::DispatchClickEvent(tree, mRow, column); } //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: protected implementation bool XULTreeGridCellAccessible::IsEditable() const { // XXX: logic corresponds to tree.xml, it's preferable to have interface // method to check it. bool isEditable = false; nsresult rv = mTreeView->IsEditable(mRow, mColumn, &isEditable); if (NS_FAILED(rv) || !isEditable) return false; dom::Element* columnElm = mColumn->Element(); if (!columnElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, nsGkAtoms::_true, eCaseMatters)) { return false; } return mContent->AsElement()->AttrValueIs( kNameSpaceID_None, nsGkAtoms::editable, nsGkAtoms::_true, eCaseMatters); }