From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- accessible/xul/XULTreeGridAccessible.cpp | 653 +++++++++++++++++++++++++++++++ 1 file changed, 653 insertions(+) create mode 100644 accessible/xul/XULTreeGridAccessible.cpp (limited to 'accessible/xul/XULTreeGridAccessible.cpp') diff --git a/accessible/xul/XULTreeGridAccessible.cpp b/accessible/xul/XULTreeGridAccessible.cpp new file mode 100644 index 0000000000..783f98e7c3 --- /dev/null +++ b/accessible/xul/XULTreeGridAccessible.cpp @@ -0,0 +1,653 @@ +/* -*- 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 "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 "Role.h" +#include "States.h" +#include "nsQueryObject.h" +#include "nsTreeColumns.h" + +#include "nsITreeSelection.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/PresShell.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) { + LocalAccessible* row = GetTreeItemAccessible(aRowIndex); + if (!row) return nullptr; + + RefPtr column = + nsCoreUtils::GetSensibleColumnAt(mTree, aColumnIndex); + if (!column) return nullptr; + + RefPtr rowAcc = do_QueryObject(row); + if (!rowAcc) 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); +} + +void XULTreeGridAccessible::SelectRow(uint32_t aRowIdx) { + if (!mTreeView) return; + + nsCOMPtr selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + NS_ASSERTION(selection, "GetSelection() Shouldn't fail!"); + + selection->Select(aRowIdx); +} + +void XULTreeGridAccessible::UnselectRow(uint32_t aRowIdx) { + if (!mTreeView) return; + + nsCOMPtr selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + + if (selection) selection->ClearRange(aRowIdx, aRowIdx); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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; + + RefPtr rowAcc = do_QueryObject(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); +} -- cgit v1.2.3