gecko-dev/accessible/xul/XULTreeAccessible.cpp
James Teh 14367430eb Bug 468497: Inform the accessibility FocusManager when a XUL tree's view changes. r=MarcoZ
Without this, accessibility clients don't get notified about the newly focused item.
For example, this meant that nothing was reported by screen readers when switching tabs in Thunderbird with control+tab.

MozReview-Commit-ID: F7vqvLXzeJR

--HG--
extra : rebase_source : debd649415cdc7417660c5846a923a5cc8edad79
2018-07-05 16:33:24 +10:00

1184 lines
31 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "XULTreeAccessible.h"
#include "Accessible-inl.h"
#include "DocAccessible-inl.h"
#include "nsAccCache.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "nsEventShell.h"
#include "DocAccessible.h"
#include "Relation.h"
#include "Role.h"
#include "States.h"
#include "XULTreeGridAccessible.h"
#include "nsQueryObject.h"
#include "nsComponentManagerUtils.h"
#include "nsIAccessibleRelation.h"
#include "nsIAutoCompleteInput.h"
#include "nsIAutoCompletePopup.h"
#include "nsIBoxObject.h"
#include "nsIDOMXULMenuListElement.h"
#include "nsIDOMXULMultSelectCntrlEl.h"
#include "nsITreeSelection.h"
#include "nsIMutableArray.h"
#include "nsTreeBodyFrame.h"
#include "nsTreeColumns.h"
#include "nsTreeUtils.h"
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible
////////////////////////////////////////////////////////////////////////////////
XULTreeAccessible::
XULTreeAccessible(nsIContent* aContent, DocAccessible* aDoc,
nsTreeBodyFrame* aTreeFrame) :
AccessibleWrap(aContent, aDoc),
mAccessibleCache(kDefaultTreeCacheLength)
{
mType = eXULTreeType;
mGenericTypes |= eSelect;
nsCOMPtr<nsITreeView> view = aTreeFrame->GetExistingView();
mTreeView = view;
mTree = nsCoreUtils::GetTreeBoxObject(aContent);
NS_ASSERTION(mTree, "Can't get mTree!\n");
nsIContent* parentContent = mContent->GetParent();
if (parentContent) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(parentContent);
if (autoCompletePopupElm)
mGenericTypes |= eAutoCompletePopup;
}
}
XULTreeAccessible::~XULTreeAccessible()
{
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: nsISupports and cycle collection implementation
NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeAccessible, Accessible,
mTree, mAccessibleCache)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeAccessible)
NS_INTERFACE_MAP_END_INHERITING(Accessible)
NS_IMPL_ADDREF_INHERITED(XULTreeAccessible, Accessible)
NS_IMPL_RELEASE_INHERITED(XULTreeAccessible, Accessible)
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: Accessible implementation
uint64_t
XULTreeAccessible::NativeState() const
{
// Get focus status from base class.
uint64_t state = Accessible::NativeState();
// readonly state
state |= states::READONLY;
// multiselectable state.
if (!mTreeView)
return state;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
NS_ENSURE_TRUE(selection, state);
bool isSingle = false;
nsresult rv = selection->GetSingle(&isSingle);
NS_ENSURE_SUCCESS(rv, state);
if (!isSingle)
state |= states::MULTISELECTABLE;
return state;
}
void
XULTreeAccessible::Value(nsString& aValue) const
{
aValue.Truncate();
if (!mTreeView)
return;
// Return the value is the first selected child.
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (!selection)
return;
int32_t currentIndex;
selection->GetCurrentIndex(&currentIndex);
if (currentIndex >= 0) {
RefPtr<nsTreeColumn> keyCol;
RefPtr<nsTreeColumns> cols;
mTree->GetColumns(getter_AddRefs(cols));
if (cols)
keyCol = cols->GetKeyColumn();
mTreeView->GetCellText(currentIndex, keyCol, aValue);
}
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: Accessible implementation
void
XULTreeAccessible::Shutdown()
{
if (mDoc && !mDoc->IsDefunct()) {
UnbindCacheEntriesFromDocument(mAccessibleCache);
}
mTree = nullptr;
mTreeView = nullptr;
AccessibleWrap::Shutdown();
}
role
XULTreeAccessible::NativeRole() const
{
// No primary column means we're in a list. In fact, history and mail turn off
// the primary flag when switching to a flat view.
nsIContent* child = nsTreeUtils::GetDescendantChild(mContent, nsGkAtoms::treechildren);
NS_ASSERTION(child, "tree without treechildren!");
nsTreeBodyFrame* treeFrame = do_QueryFrame(child->GetPrimaryFrame());
NS_ASSERTION(treeFrame, "xul tree accessible for tree without a frame!");
if (!treeFrame)
return roles::LIST;
RefPtr<nsTreeColumns> cols = treeFrame->Columns();
nsTreeColumn* primaryCol = cols->GetPrimaryColumn();
return primaryCol ? roles::OUTLINE : roles::LIST;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: Accessible implementation (DON'T put methods here)
Accessible*
XULTreeAccessible::ChildAtPoint(int32_t aX, int32_t aY,
EWhichChildAtPoint aWhichChild)
{
nsIFrame *frame = GetFrame();
if (!frame)
return nullptr;
nsPresContext *presContext = frame->PresContext();
nsIPresShell* 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();
int32_t row = -1;
RefPtr<nsTreeColumn> column;
nsAutoString childEltUnused;
mTree->GetCellAt(clientX, clientY, &row, getter_AddRefs(column),
childEltUnused);
// If we failed to find tree cell for the given point then it might be
// tree columns.
if (row == -1 || !column)
return AccessibleWrap::ChildAtPoint(aX, aY, aWhichChild);
Accessible* child = GetTreeItemAccessible(row);
if (aWhichChild == eDeepestChild && child) {
// Look for accessible cell for the found item accessible.
RefPtr<XULTreeItemAccessibleBase> treeitem = do_QueryObject(child);
Accessible* cell = treeitem->GetCellAccessible(column);
if (cell)
child = cell;
}
return child;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: SelectAccessible
Accessible*
XULTreeAccessible::CurrentItem() const
{
if (!mTreeView)
return nullptr;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
int32_t currentIndex = -1;
selection->GetCurrentIndex(&currentIndex);
if (currentIndex >= 0)
return GetTreeItemAccessible(currentIndex);
}
return nullptr;
}
void
XULTreeAccessible::SetCurrentItem(const Accessible* aItem)
{
NS_ERROR("XULTreeAccessible::SetCurrentItem not implemented");
}
void
XULTreeAccessible::SelectedItems(nsTArray<Accessible*>* aItems)
{
if (!mTreeView)
return;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (!selection)
return;
int32_t rangeCount = 0;
selection->GetRangeCount(&rangeCount);
for (int32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
int32_t firstIdx = 0, lastIdx = -1;
selection->GetRangeAt(rangeIdx, &firstIdx, &lastIdx);
for (int32_t rowIdx = firstIdx; rowIdx <= lastIdx; rowIdx++) {
Accessible* item = GetTreeItemAccessible(rowIdx);
if (item)
aItems->AppendElement(item);
}
}
}
uint32_t
XULTreeAccessible::SelectedItemCount()
{
if (!mTreeView)
return 0;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
int32_t count = 0;
selection->GetCount(&count);
return count;
}
return 0;
}
bool
XULTreeAccessible::AddItemToSelection(uint32_t aIndex)
{
if (!mTreeView)
return false;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
bool isSelected = false;
selection->IsSelected(aIndex, &isSelected);
if (!isSelected)
selection->ToggleSelect(aIndex);
return true;
}
return false;
}
bool
XULTreeAccessible::RemoveItemFromSelection(uint32_t aIndex)
{
if (!mTreeView)
return false;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
bool isSelected = false;
selection->IsSelected(aIndex, &isSelected);
if (isSelected)
selection->ToggleSelect(aIndex);
return true;
}
return false;
}
bool
XULTreeAccessible::IsItemSelected(uint32_t aIndex)
{
if (!mTreeView)
return false;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
bool isSelected = false;
selection->IsSelected(aIndex, &isSelected);
return isSelected;
}
return false;
}
bool
XULTreeAccessible::UnselectAll()
{
if (!mTreeView)
return false;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (!selection)
return false;
selection->ClearSelection();
return true;
}
Accessible*
XULTreeAccessible::GetSelectedItem(uint32_t aIndex)
{
if (!mTreeView)
return nullptr;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (!selection)
return nullptr;
uint32_t selCount = 0;
int32_t rangeCount = 0;
selection->GetRangeCount(&rangeCount);
for (int32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
int32_t firstIdx = 0, lastIdx = -1;
selection->GetRangeAt(rangeIdx, &firstIdx, &lastIdx);
for (int32_t rowIdx = firstIdx; rowIdx <= lastIdx; rowIdx++) {
if (selCount == aIndex)
return GetTreeItemAccessible(rowIdx);
selCount++;
}
}
return nullptr;
}
bool
XULTreeAccessible::SelectAll()
{
// see if we are multiple select if so set ourselves as such
if (!mTreeView)
return false;
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
bool single = false;
selection->GetSingle(&single);
if (!single) {
selection->SelectAll();
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: Accessible implementation
Accessible*
XULTreeAccessible::GetChildAt(uint32_t aIndex) const
{
uint32_t childCount = Accessible::ChildCount();
if (aIndex < childCount)
return Accessible::GetChildAt(aIndex);
return GetTreeItemAccessible(aIndex - childCount);
}
uint32_t
XULTreeAccessible::ChildCount() const
{
// Tree's children count is row count + treecols count.
uint32_t childCount = Accessible::ChildCount();
if (!mTreeView)
return childCount;
int32_t rowCount = 0;
mTreeView->GetRowCount(&rowCount);
childCount += rowCount;
return childCount;
}
Relation
XULTreeAccessible::RelationByType(RelationType aType) const
{
if (aType == RelationType::NODE_PARENT_OF) {
if (mTreeView)
return Relation(new XULTreeItemIterator(this, mTreeView, -1));
return Relation();
}
return Accessible::RelationByType(aType);
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: Widgets
bool
XULTreeAccessible::IsWidget() const
{
return true;
}
bool
XULTreeAccessible::IsActiveWidget() const
{
if (IsAutoCompletePopup()) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(mContent->GetParent());
if (autoCompletePopupElm) {
bool isOpen = false;
autoCompletePopupElm->GetPopupOpen(&isOpen);
return isOpen;
}
}
return FocusMgr()->HasDOMFocus(mContent);
}
bool
XULTreeAccessible::AreItemsOperable() const
{
if (IsAutoCompletePopup()) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(mContent->GetParent());
if (autoCompletePopupElm) {
bool isOpen = false;
autoCompletePopupElm->GetPopupOpen(&isOpen);
return isOpen;
}
}
return true;
}
Accessible*
XULTreeAccessible::ContainerWidget() const
{
if (IsAutoCompletePopup()) {
// This works for XUL autocompletes. It doesn't work for HTML forms
// autocomplete because of potential crossprocess calls (when autocomplete
// lives in content process while popup lives in chrome process). If that's
// a problem then rethink Widgets interface.
nsCOMPtr<nsIDOMXULMenuListElement> menuListElm =
do_QueryInterface(mContent->GetParent());
if (menuListElm) {
RefPtr<mozilla::dom::Element> inputElm;
menuListElm->GetInputField(getter_AddRefs(inputElm));
if (inputElm) {
Accessible* input = mDoc->GetAccessible(inputElm);
return input ? input->ContainerWidget() : nullptr;
}
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: public implementation
Accessible*
XULTreeAccessible::GetTreeItemAccessible(int32_t aRow) const
{
if (aRow < 0 || IsDefunct() || !mTreeView)
return nullptr;
int32_t rowCount = 0;
nsresult rv = mTreeView->GetRowCount(&rowCount);
if (NS_FAILED(rv) || aRow >= rowCount)
return nullptr;
void *key = reinterpret_cast<void*>(intptr_t(aRow));
Accessible* cachedTreeItem = mAccessibleCache.GetWeak(key);
if (cachedTreeItem)
return cachedTreeItem;
RefPtr<Accessible> treeItem = CreateTreeItemAccessible(aRow);
if (treeItem) {
mAccessibleCache.Put(key, treeItem);
Document()->BindToDocument(treeItem, nullptr);
return treeItem;
}
return nullptr;
}
void
XULTreeAccessible::InvalidateCache(int32_t aRow, int32_t aCount)
{
if (IsDefunct())
return;
if (!mTreeView) {
UnbindCacheEntriesFromDocument(mAccessibleCache);
return;
}
// Do not invalidate the cache if rows have been inserted.
if (aCount > 0)
return;
DocAccessible* document = Document();
// Fire destroy event for removed tree items and delete them from caches.
for (int32_t rowIdx = aRow; rowIdx < aRow - aCount; rowIdx++) {
void* key = reinterpret_cast<void*>(intptr_t(rowIdx));
Accessible* treeItem = mAccessibleCache.GetWeak(key);
if (treeItem) {
RefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_HIDE, treeItem);
nsEventShell::FireEvent(event);
// Unbind from document, shutdown and remove from tree cache.
document->UnbindFromDocument(treeItem);
mAccessibleCache.Remove(key);
}
}
// We dealt with removed tree items already however we may keep tree items
// having row indexes greater than row count. We should remove these dead tree
// items silently from caches.
int32_t newRowCount = 0;
nsresult rv = mTreeView->GetRowCount(&newRowCount);
if (NS_FAILED(rv))
return;
int32_t oldRowCount = newRowCount - aCount;
for (int32_t rowIdx = newRowCount; rowIdx < oldRowCount; ++rowIdx) {
void *key = reinterpret_cast<void*>(intptr_t(rowIdx));
Accessible* treeItem = mAccessibleCache.GetWeak(key);
if (treeItem) {
// Unbind from document, shutdown and remove from tree cache.
document->UnbindFromDocument(treeItem);
mAccessibleCache.Remove(key);
}
}
}
void
XULTreeAccessible::TreeViewInvalidated(int32_t aStartRow, int32_t aEndRow,
int32_t aStartCol, int32_t aEndCol)
{
if (IsDefunct())
return;
if (!mTreeView) {
UnbindCacheEntriesFromDocument(mAccessibleCache);
return;
}
int32_t endRow = aEndRow;
nsresult rv;
if (endRow == -1) {
int32_t rowCount = 0;
rv = mTreeView->GetRowCount(&rowCount);
if (NS_FAILED(rv))
return;
endRow = rowCount - 1;
}
RefPtr<nsTreeColumns> treeColumns;
mTree->GetColumns(getter_AddRefs(treeColumns));
if (!treeColumns)
return;
int32_t endCol = aEndCol;
if (endCol == -1) {
// We need to make sure to cast to int32_t before we do the subtraction, in
// case the column count is 0.
endCol = static_cast<int32_t>(treeColumns->Count()) - 1;
}
for (int32_t rowIdx = aStartRow; rowIdx <= endRow; ++rowIdx) {
void *key = reinterpret_cast<void*>(intptr_t(rowIdx));
Accessible* accessible = mAccessibleCache.GetWeak(key);
if (accessible) {
RefPtr<XULTreeItemAccessibleBase> treeitemAcc = do_QueryObject(accessible);
NS_ASSERTION(treeitemAcc, "Wrong accessible at the given key!");
treeitemAcc->RowInvalidated(aStartCol, endCol);
}
}
}
void
XULTreeAccessible::TreeViewChanged(nsITreeView* aView)
{
if (IsDefunct())
return;
// Fire reorder event on tree accessible on accessible tree (do not fire
// show/hide events on tree items because it can be expensive to fire them for
// each tree item.
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
Document()->FireDelayedEvent(reorderEvent);
// Clear cache.
UnbindCacheEntriesFromDocument(mAccessibleCache);
mTreeView = aView;
Accessible* item = CurrentItem();
if (item) {
FocusMgr()->ActiveItemChanged(item, true);
}
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeAccessible: protected implementation
already_AddRefed<Accessible>
XULTreeAccessible::CreateTreeItemAccessible(int32_t aRow) const
{
RefPtr<Accessible> accessible =
new XULTreeItemAccessible(mContent, mDoc, const_cast<XULTreeAccessible*>(this),
mTree, mTreeView, aRow);
return accessible.forget();
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessibleBase
////////////////////////////////////////////////////////////////////////////////
XULTreeItemAccessibleBase::
XULTreeItemAccessibleBase(nsIContent* aContent, DocAccessible* aDoc,
Accessible* aParent, nsITreeBoxObject* aTree,
nsITreeView* aTreeView, int32_t aRow) :
AccessibleWrap(aContent, aDoc),
mTree(aTree), mTreeView(aTreeView), mRow(aRow)
{
mParent = aParent;
mStateFlags |= eSharedNode;
}
XULTreeItemAccessibleBase::~XULTreeItemAccessibleBase()
{
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessibleBase: nsISupports implementation
NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessibleBase, Accessible,
mTree)
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessibleBase,
Accessible,
XULTreeItemAccessibleBase)
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessibleBase: Accessible
Accessible*
XULTreeItemAccessibleBase::FocusedChild()
{
return FocusMgr()->FocusedAccessible() == this ? this : nullptr;
}
nsIntRect
XULTreeItemAccessibleBase::BoundsInCSSPixels() const
{
// Get x coordinate and width from treechildren element, get y coordinate and
// height from tree cell.
nsCOMPtr<nsIBoxObject> boxObj = nsCoreUtils::GetTreeBodyBoxObject(mTree);
if (!boxObj) {
return nsIntRect();
}
RefPtr<nsTreeColumn> column = nsCoreUtils::GetFirstSensibleColumn(mTree);
int32_t x = 0, y = 0, width = 0, height = 0;
nsresult rv = mTree->GetCoordsForCellItem(mRow, column, EmptyString(),
&x, &y, &width, &height);
if (NS_FAILED(rv)) {
return nsIntRect();
}
boxObj->GetWidth(&width);
int32_t tcX = 0, tcY = 0;
boxObj->GetScreenX(&tcX);
boxObj->GetScreenY(&tcY);
x = tcX;
y += tcY;
return nsIntRect(x, y, width, height);
}
nsRect
XULTreeItemAccessibleBase::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()));
}
void
XULTreeItemAccessibleBase::SetSelected(bool aSelect)
{
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
bool isSelected = false;
selection->IsSelected(mRow, &isSelected);
if (isSelected != aSelect)
selection->ToggleSelect(mRow);
}
}
void
XULTreeItemAccessibleBase::TakeFocus() const
{
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection)
selection->SetCurrentIndex(mRow);
// focus event will be fired here
Accessible::TakeFocus();
}
Relation
XULTreeItemAccessibleBase::RelationByType(RelationType aType) const
{
switch (aType) {
case RelationType::NODE_CHILD_OF: {
int32_t parentIndex = -1;
if (!NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex)))
return Relation();
if (parentIndex == -1)
return Relation(mParent);
XULTreeAccessible* treeAcc = mParent->AsXULTree();
return Relation(treeAcc->GetTreeItemAccessible(parentIndex));
}
case RelationType::NODE_PARENT_OF: {
bool isTrue = false;
if (NS_FAILED(mTreeView->IsContainerEmpty(mRow, &isTrue)) || isTrue)
return Relation();
if (NS_FAILED(mTreeView->IsContainerOpen(mRow, &isTrue)) || !isTrue)
return Relation();
XULTreeAccessible* tree = mParent->AsXULTree();
return Relation(new XULTreeItemIterator(tree, mTreeView, mRow));
}
default:
return Relation();
}
}
uint8_t
XULTreeItemAccessibleBase::ActionCount() const
{
// "activate" action is available for all treeitems, "expand/collapse" action
// is avaible for treeitem which is container.
return IsExpandable() ? 2 : 1;
}
void
XULTreeItemAccessibleBase::ActionNameAt(uint8_t aIndex, nsAString& aName)
{
if (aIndex == eAction_Click) {
aName.AssignLiteral("activate");
return;
}
if (aIndex == eAction_Expand && IsExpandable()) {
bool isContainerOpen = false;
mTreeView->IsContainerOpen(mRow, &isContainerOpen);
if (isContainerOpen)
aName.AssignLiteral("collapse");
else
aName.AssignLiteral("expand");
}
}
bool
XULTreeItemAccessibleBase::DoAction(uint8_t aIndex) const
{
if (aIndex != eAction_Click &&
(aIndex != eAction_Expand || !IsExpandable()))
return false;
DoCommand(nullptr, aIndex);
return true;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessibleBase: Accessible implementation
void
XULTreeItemAccessibleBase::Shutdown()
{
mTree = nullptr;
mTreeView = nullptr;
mRow = -1;
mParent = nullptr; // null-out to prevent base class's shutdown ops
AccessibleWrap::Shutdown();
}
GroupPos
XULTreeItemAccessibleBase::GroupPosition()
{
GroupPos groupPos;
int32_t level;
nsresult rv = mTreeView->GetLevel(mRow, &level);
NS_ENSURE_SUCCESS(rv, groupPos);
int32_t topCount = 1;
for (int32_t index = mRow - 1; index >= 0; index--) {
int32_t lvl = -1;
if (NS_SUCCEEDED(mTreeView->GetLevel(index, &lvl))) {
if (lvl < level)
break;
if (lvl == level)
topCount++;
}
}
int32_t rowCount = 0;
rv = mTreeView->GetRowCount(&rowCount);
NS_ENSURE_SUCCESS(rv, groupPos);
int32_t bottomCount = 0;
for (int32_t index = mRow + 1; index < rowCount; index++) {
int32_t lvl = -1;
if (NS_SUCCEEDED(mTreeView->GetLevel(index, &lvl))) {
if (lvl < level)
break;
if (lvl == level)
bottomCount++;
}
}
groupPos.level = level + 1;
groupPos.setSize = topCount + bottomCount;
groupPos.posInSet = topCount;
return groupPos;
}
uint64_t
XULTreeItemAccessibleBase::NativeState() const
{
// focusable and selectable states
uint64_t state = NativeInteractiveState();
// expanded/collapsed state
if (IsExpandable()) {
bool isContainerOpen;
mTreeView->IsContainerOpen(mRow, &isContainerOpen);
state |= isContainerOpen ? states::EXPANDED : states::COLLAPSED;
}
// selected state
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
bool isSelected;
selection->IsSelected(mRow, &isSelected);
if (isSelected)
state |= states::SELECTED;
}
// focused state
if (FocusMgr()->IsFocused(this))
state |= states::FOCUSED;
// invisible state
int32_t firstVisibleRow, lastVisibleRow;
mTree->GetFirstVisibleRow(&firstVisibleRow);
mTree->GetLastVisibleRow(&lastVisibleRow);
if (mRow < firstVisibleRow || mRow > lastVisibleRow)
state |= states::INVISIBLE;
return state;
}
uint64_t
XULTreeItemAccessibleBase::NativeInteractiveState() const
{
return states::FOCUSABLE | states::SELECTABLE;
}
int32_t
XULTreeItemAccessibleBase::IndexInParent() const
{
return mParent ? mParent->ContentChildCount() + mRow : -1;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessibleBase: Widgets
Accessible*
XULTreeItemAccessibleBase::ContainerWidget() const
{
return mParent;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessibleBase: Accessible protected methods
void
XULTreeItemAccessibleBase::DispatchClickEvent(nsIContent* aContent,
uint32_t aActionIndex) const
{
if (IsDefunct())
return;
RefPtr<nsTreeColumns> columns;
mTree->GetColumns(getter_AddRefs(columns));
if (!columns)
return;
// Get column and pseudo element.
RefPtr<nsTreeColumn> column;
nsAutoString pseudoElm;
if (aActionIndex == eAction_Click) {
// Key column is visible and clickable.
column = columns->GetKeyColumn();
} else {
// Primary column contains a twisty we should click on.
column = columns->GetPrimaryColumn();
pseudoElm = NS_LITERAL_STRING("twisty");
}
if (column)
nsCoreUtils::DispatchClickEvent(mTree, mRow, column, pseudoElm);
}
Accessible*
XULTreeItemAccessibleBase::GetSiblingAtOffset(int32_t aOffset,
nsresult* aError) const
{
if (aError)
*aError = NS_OK; // fail peacefully
return mParent->GetChildAt(IndexInParent() + aOffset);
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessibleBase: protected implementation
bool
XULTreeItemAccessibleBase::IsExpandable() const
{
bool isContainer = false;
mTreeView->IsContainer(mRow, &isContainer);
if (isContainer) {
bool isEmpty = false;
mTreeView->IsContainerEmpty(mRow, &isEmpty);
if (!isEmpty) {
RefPtr<nsTreeColumns> columns;
mTree->GetColumns(getter_AddRefs(columns));
if (columns) {
nsTreeColumn* primaryColumn = columns->GetPrimaryColumn();
if (primaryColumn &&
!nsCoreUtils::IsColumnHidden(primaryColumn))
return true;
}
}
}
return false;
}
void
XULTreeItemAccessibleBase::GetCellName(nsTreeColumn* aColumn, nsAString& aName) const
{
mTreeView->GetCellText(mRow, aColumn, 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, aColumn, aName);
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessible
////////////////////////////////////////////////////////////////////////////////
XULTreeItemAccessible::
XULTreeItemAccessible(nsIContent* aContent, DocAccessible* aDoc,
Accessible* aParent, nsITreeBoxObject* aTree,
nsITreeView* aTreeView, int32_t aRow) :
XULTreeItemAccessibleBase(aContent, aDoc, aParent, aTree, aTreeView, aRow)
{
mStateFlags |= eNoKidsFromDOM;
mColumn = nsCoreUtils::GetFirstSensibleColumn(mTree);
GetCellName(mColumn, mCachedName);
}
XULTreeItemAccessible::~XULTreeItemAccessible()
{
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessible: nsISupports implementation
NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessible,
XULTreeItemAccessibleBase,
mColumn)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeItemAccessible)
NS_INTERFACE_MAP_END_INHERITING(XULTreeItemAccessibleBase)
NS_IMPL_ADDREF_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase)
NS_IMPL_RELEASE_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase)
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessible: nsIAccessible implementation
ENameValueFlag
XULTreeItemAccessible::Name(nsString& aName) const
{
aName.Truncate();
GetCellName(mColumn, aName);
return eNameOK;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessible: Accessible implementation
void
XULTreeItemAccessible::Shutdown()
{
mColumn = nullptr;
XULTreeItemAccessibleBase::Shutdown();
}
role
XULTreeItemAccessible::NativeRole() const
{
RefPtr<nsTreeColumns> columns;
mTree->GetColumns(getter_AddRefs(columns));
if (!columns) {
NS_ERROR("No tree columns object in the tree!");
return roles::NOTHING;
}
nsTreeColumn* primaryColumn = columns->GetPrimaryColumn();
return primaryColumn ? roles::OUTLINEITEM : roles::LISTITEM;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemAccessible: XULTreeItemAccessibleBase implementation
void
XULTreeItemAccessible::RowInvalidated(int32_t aStartColIdx, int32_t aEndColIdx)
{
nsAutoString name;
Name(name);
if (name != mCachedName) {
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
mCachedName = name;
}
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeColumAccessible
////////////////////////////////////////////////////////////////////////////////
XULTreeColumAccessible::
XULTreeColumAccessible(nsIContent* aContent, DocAccessible* aDoc) :
XULColumAccessible(aContent, aDoc)
{
}
Accessible*
XULTreeColumAccessible::GetSiblingAtOffset(int32_t aOffset,
nsresult* aError) const
{
if (aOffset < 0)
return XULColumAccessible::GetSiblingAtOffset(aOffset, aError);
if (aError)
*aError = NS_OK; // fail peacefully
nsCOMPtr<nsITreeBoxObject> tree = nsCoreUtils::GetTreeBoxObject(mContent);
if (tree) {
nsCOMPtr<nsITreeView> treeView;
tree->GetView(getter_AddRefs(treeView));
if (treeView) {
int32_t rowCount = 0;
treeView->GetRowCount(&rowCount);
if (rowCount > 0 && aOffset <= rowCount) {
XULTreeAccessible* treeAcc = Parent()->AsXULTree();
if (treeAcc)
return treeAcc->GetTreeItemAccessible(aOffset - 1);
}
}
}
return nullptr;
}