Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "XULTreeGridAccessible.h"
#include <stdint.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 "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<Accessible*>* 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<uint32_t>* 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<uint32_t>* 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<uint32_t>* 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<nsTreeColumn> 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<nsITreeSelection> 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<int32_t>(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<int32_t>(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<int32_t>(aCellIdx / colCount);
*aColIdx = static_cast<int32_t>(aCellIdx % colCount);
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeGridAccessible: LocalAccessible implementation
role XULTreeGridAccessible::NativeRole() const {
RefPtr<nsTreeColumns> 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<XULTreeItemAccessibleBase>
XULTreeGridAccessible::CreateTreeItemAccessible(int32_t aRow) const {
RefPtr<XULTreeItemAccessibleBase> accessible = new XULTreeGridRowAccessible(
mContent, mDoc, const_cast<XULTreeGridAccessible*>(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();
RefPtr<nsTreeColumn> 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<nsTreeColumn> 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<void*>(aColumn);
XULTreeGridCellAccessible* cachedCell = mAccessibleCache.GetWeak(key);
if (cachedCell) return cachedCell;
RefPtr<XULTreeGridCellAccessible> cell = new XULTreeGridCellAccessible(
mContent, mDoc, const_cast<XULTreeGridRowAccessible*>(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<nsTreeColumns> 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<dom::Element> 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<nsTreeColumn> column = mColumn;
while ((column = nsCoreUtils::GetPreviousSensibleColumn(column))) colIdx++;
return colIdx;
}
uint32_t XULTreeGridCellAccessible::RowIdx() const { return mRow; }
void XULTreeGridCellAccessible::ColHeaderCells(
nsTArray<Accessible*>* aHeaderCells) {
dom::Element* columnElm = mColumn->Element();
LocalAccessible* headerCell = mDoc->GetAccessible(columnElm);
if (headerCell) aHeaderCells->AppendElement(headerCell);
}
bool XULTreeGridCellAccessible::Selected() {
nsCOMPtr<nsITreeSelection> 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<AccAttributes> XULTreeGridCellAccessible::NativeAttributes() {
RefPtr<AccAttributes> 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<nsITreeSelection> 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> 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<nsTreeColumn> 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<XULTreeItemAccessibleBase*>(LocalParent());
return rowAcc->GetCellAccessible(columnAtOffset);
}
void XULTreeGridCellAccessible::DispatchClickEvent(
uint32_t aActionIndex) const {
if (IsDefunct()) return;
RefPtr<dom::XULTreeElement> tree = mTree;
RefPtr<nsTreeColumn> 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);
}