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