diff options
Diffstat (limited to 'accessible/base/AccGroupInfo.cpp')
-rw-r--r-- | accessible/base/AccGroupInfo.cpp | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/accessible/base/AccGroupInfo.cpp b/accessible/base/AccGroupInfo.cpp new file mode 100644 index 000000000..bef707887 --- /dev/null +++ b/accessible/base/AccGroupInfo.cpp @@ -0,0 +1,227 @@ +/* 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 "AccGroupInfo.h" +#include "nsAccUtils.h" + +#include "Role.h" +#include "States.h" + +using namespace mozilla::a11y; + +AccGroupInfo::AccGroupInfo(Accessible* aItem, role aRole) : + mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem), mRole(aRole) +{ + MOZ_COUNT_CTOR(AccGroupInfo); + Update(); +} + +void +AccGroupInfo::Update() +{ + Accessible* parent = mItem->Parent(); + if (!parent) + return; + + int32_t indexInParent = mItem->IndexInParent(); + uint32_t siblingCount = parent->ChildCount(); + if (indexInParent == -1 || + indexInParent >= static_cast<int32_t>(siblingCount)) { + NS_ERROR("Wrong index in parent! Tree invalidation problem."); + return; + } + + int32_t level = nsAccUtils::GetARIAOrDefaultLevel(mItem); + + // Compute position in set. + mPosInSet = 1; + for (int32_t idx = indexInParent - 1; idx >= 0 ; idx--) { + Accessible* sibling = parent->GetChildAt(idx); + roles::Role siblingRole = sibling->Role(); + + // If the sibling is separator then the group is ended. + if (siblingRole == roles::SEPARATOR) + break; + + // If sibling is not visible and hasn't the same base role. + if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) + continue; + + // Check if it's hierarchical flatten structure, i.e. if the sibling + // level is lesser than this one then group is ended, if the sibling level + // is greater than this one then the group is split by some child elements + // (group will be continued). + int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) { + mParent = sibling; + break; + } + + // Skip subset. + if (siblingLevel > level) + continue; + + // If the previous item in the group has calculated group information then + // build group information for this item based on found one. + if (sibling->mBits.groupInfo) { + mPosInSet += sibling->mBits.groupInfo->mPosInSet; + mParent = sibling->mBits.groupInfo->mParent; + mSetSize = sibling->mBits.groupInfo->mSetSize; + return; + } + + mPosInSet++; + } + + // Compute set size. + mSetSize = mPosInSet; + + for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) { + Accessible* sibling = parent->GetChildAt(idx); + + roles::Role siblingRole = sibling->Role(); + + // If the sibling is separator then the group is ended. + if (siblingRole == roles::SEPARATOR) + break; + + // If sibling is visible and has the same base role + if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) + continue; + + // and check if it's hierarchical flatten structure. + int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) + break; + + // Skip subset. + if (siblingLevel > level) + continue; + + // If the next item in the group has calculated group information then + // build group information for this item based on found one. + if (sibling->mBits.groupInfo) { + mParent = sibling->mBits.groupInfo->mParent; + mSetSize = sibling->mBits.groupInfo->mSetSize; + return; + } + + mSetSize++; + } + + if (mParent) + return; + + roles::Role parentRole = parent->Role(); + if (ShouldReportRelations(mRole, parentRole)) + mParent = parent; + + // ARIA tree and list can be arranged by using ARIA groups to organize levels. + if (parentRole != roles::GROUPING) + return; + + // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a + // parent. In other words the parent of the tree item will be a group and + // the previous tree item of the group is a conceptual parent of the tree + // item. + if (mRole == roles::OUTLINEITEM) { + Accessible* parentPrevSibling = parent->PrevSibling(); + if (parentPrevSibling && parentPrevSibling->Role() == mRole) { + mParent = parentPrevSibling; + return; + } + } + + // Way #2 for ARIA list and tree: group is a child of an item. In other words + // the parent of the item will be a group and containing item of the group is + // a conceptual parent of the item. + if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) { + Accessible* grandParent = parent->Parent(); + if (grandParent && grandParent->Role() == mRole) + mParent = grandParent; + } +} + +Accessible* +AccGroupInfo::FirstItemOf(Accessible* aContainer) +{ + // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a + // group is a parent) or by aria-level. + a11y::role containerRole = aContainer->Role(); + Accessible* item = aContainer->NextSibling(); + if (item) { + if (containerRole == roles::OUTLINEITEM && item->Role() == roles::GROUPING) + item = item->FirstChild(); + + if (item) { + AccGroupInfo* itemGroupInfo = item->GetGroupInfo(); + if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) + return item; + } + } + + // ARIA list and tree can be arranged by ARIA groups case #2 (group is + // a child of an item). + item = aContainer->LastChild(); + if (!item) + return nullptr; + + if (item->Role() == roles::GROUPING && + (containerRole == roles::LISTITEM || containerRole == roles::OUTLINEITEM)) { + item = item->FirstChild(); + if (item) { + AccGroupInfo* itemGroupInfo = item->GetGroupInfo(); + if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) + return item; + } + } + + // Otherwise, it can be a direct child if the container is a list or tree. + item = aContainer->FirstChild(); + if (ShouldReportRelations(item->Role(), containerRole)) + return item; + + return nullptr; +} + +Accessible* +AccGroupInfo::NextItemTo(Accessible* aItem) +{ + AccGroupInfo* groupInfo = aItem->GetGroupInfo(); + if (!groupInfo) + return nullptr; + + // If the item in middle of the group then search next item in siblings. + if (groupInfo->PosInSet() >= groupInfo->SetSize()) + return nullptr; + + Accessible* parent = aItem->Parent(); + uint32_t childCount = parent->ChildCount(); + for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) { + Accessible* nextItem = parent->GetChildAt(idx); + AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo(); + if (nextGroupInfo && + nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) { + return nextItem; + } + } + + NS_NOTREACHED("Item in the middle of the group but there's no next item!"); + return nullptr; +} + +bool +AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole) +{ + // We only want to report hierarchy-based node relations for items in tree or + // list form. ARIA level/owns relations are always reported. + if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM) + return true; + if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW) + return true; + if (aParentRole == roles::LIST && aRole == roles::LISTITEM) + return true; + + return false; +} |