1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "AccGroupInfo.h"
6 #include "mozilla/a11y/Accessible.h"
7 #include "mozilla/a11y/TableAccessibleBase.h"
9 #include "nsAccUtils.h"
10 #include "nsIAccessiblePivot.h"
15 using namespace mozilla::a11y
;
17 static bool IsGenericContainer(role aRole
);
18 static Accessible
* GetRelevantParent(const Accessible
* aAcc
);
19 static role
BaseRole(role aRole
);
21 // This rule finds candidate siblings for compound widget children.
22 class CompoundWidgetSiblingRule
: public PivotRule
{
24 CompoundWidgetSiblingRule() = delete;
25 explicit CompoundWidgetSiblingRule(role aRole
) : mRole(aRole
) {}
27 uint16_t Match(Accessible
* aAcc
) override
{
28 // If the acc has a matching role, that's a valid sibling. If the acc is
29 // separator then the group is ended. Return a match for separators with
30 // the assumption that the caller will check for the role of the returned
32 const role accRole
= aAcc
->Role();
33 if (BaseRole(accRole
) == mRole
|| accRole
== role::SEPARATOR
) {
34 return nsIAccessibleTraversalRule::FILTER_MATCH
|
35 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
38 // Ignore generic accessibles, but keep searching through the subtree for
40 if (IsGenericContainer(accRole
)) {
41 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
44 return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
48 role mRole
= role::NOTHING
;
51 AccGroupInfo::AccGroupInfo(const Accessible
* aItem
, role aRole
)
52 : mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem
), mRole(aRole
) {
53 MOZ_COUNT_CTOR(AccGroupInfo
);
57 void AccGroupInfo::Update() {
60 Accessible
* parent
= GetRelevantParent(mItem
);
65 const int32_t level
= GetARIAOrDefaultLevel(mItem
);
67 // Compute position in set.
70 // Search backwards through the tree for candidate siblings.
71 Accessible
* candidateSibling
= const_cast<Accessible
*>(mItem
);
73 CompoundWidgetSiblingRule widgetSiblingRule
{mRole
};
74 while ((candidateSibling
= pivot
.Prev(candidateSibling
, widgetSiblingRule
)) &&
75 candidateSibling
!= parent
) {
76 // If the sibling is separator then the group is ended.
77 if (candidateSibling
->Role() == roles::SEPARATOR
) {
81 const AccGroupInfo
* siblingGroupInfo
= candidateSibling
->GetGroupInfo();
82 // Skip invisible siblings.
83 // If the sibling has calculated group info, that means it's visible.
84 if (!siblingGroupInfo
&& candidateSibling
->State() & states::INVISIBLE
) {
88 // Check if it's hierarchical flatten structure, i.e. if the sibling
89 // level is lesser than this one then group is ended, if the sibling level
90 // is greater than this one then the group is split by some child elements
91 // (group will be continued).
92 const int32_t siblingLevel
= GetARIAOrDefaultLevel(candidateSibling
);
93 if (siblingLevel
< level
) {
94 mParent
= candidateSibling
;
99 if (siblingLevel
> level
) {
103 // If the previous item in the group has calculated group information then
104 // build group information for this item based on found one.
105 if (siblingGroupInfo
) {
106 mPosInSet
+= siblingGroupInfo
->mPosInSet
;
107 mParent
= siblingGroupInfo
->mParent
;
108 mSetSize
= siblingGroupInfo
->mSetSize
;
116 mSetSize
= mPosInSet
;
118 candidateSibling
= const_cast<Accessible
*>(mItem
);
119 while ((candidateSibling
= pivot
.Next(candidateSibling
, widgetSiblingRule
)) &&
120 candidateSibling
!= parent
) {
121 // If the sibling is separator then the group is ended.
122 if (candidateSibling
->Role() == roles::SEPARATOR
) {
126 const AccGroupInfo
* siblingGroupInfo
= candidateSibling
->GetGroupInfo();
127 // Skip invisible siblings.
128 // If the sibling has calculated group info, that means it's visible.
129 if (!siblingGroupInfo
&& candidateSibling
->State() & states::INVISIBLE
) {
133 // and check if it's hierarchical flatten structure.
134 const int32_t siblingLevel
= GetARIAOrDefaultLevel(candidateSibling
);
135 if (siblingLevel
< level
) {
140 if (siblingLevel
> level
) {
144 // If the next item in the group has calculated group information then
145 // build group information for this item based on found one.
146 if (siblingGroupInfo
) {
147 mParent
= siblingGroupInfo
->mParent
;
148 mSetSize
= siblingGroupInfo
->mSetSize
;
159 roles::Role parentRole
= parent
->Role();
160 if (ShouldReportRelations(mRole
, parentRole
)) {
164 // ARIA tree and list can be arranged by using ARIA groups to organize levels.
165 if (parentRole
!= roles::GROUPING
) {
169 // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a
170 // parent. In other words the parent of the tree item will be a group and
171 // the previous tree item of the group is a conceptual parent of the tree
173 if (mRole
== roles::OUTLINEITEM
) {
174 // Find the relevant grandparent of the item. Use that parent as the root
175 // and find the previous outline item sibling within that root.
176 Accessible
* grandParent
= GetRelevantParent(parent
);
177 MOZ_ASSERT(grandParent
);
178 Pivot pivot
{grandParent
};
179 CompoundWidgetSiblingRule parentSiblingRule
{mRole
};
180 Accessible
* parentPrevSibling
= pivot
.Prev(parent
, widgetSiblingRule
);
181 if (parentPrevSibling
&& parentPrevSibling
->Role() == mRole
) {
182 mParent
= parentPrevSibling
;
187 // Way #2 for ARIA list and tree: group is a child of an item. In other words
188 // the parent of the item will be a group and containing item of the group is
189 // a conceptual parent of the item.
190 if (mRole
== roles::LISTITEM
|| mRole
== roles::OUTLINEITEM
) {
191 Accessible
* grandParent
= GetRelevantParent(parent
);
192 if (grandParent
&& grandParent
->Role() == mRole
) {
193 mParent
= grandParent
;
198 AccGroupInfo
* AccGroupInfo::CreateGroupInfo(const Accessible
* aAccessible
) {
199 mozilla::a11y::role role
= aAccessible
->Role();
200 if (role
!= mozilla::a11y::roles::ROW
&&
201 role
!= mozilla::a11y::roles::OUTLINEITEM
&&
202 role
!= mozilla::a11y::roles::OPTION
&&
203 role
!= mozilla::a11y::roles::LISTITEM
&&
204 role
!= mozilla::a11y::roles::MENUITEM
&&
205 role
!= mozilla::a11y::roles::COMBOBOX_OPTION
&&
206 role
!= mozilla::a11y::roles::RICH_OPTION
&&
207 role
!= mozilla::a11y::roles::CHECK_RICH_OPTION
&&
208 role
!= mozilla::a11y::roles::PARENT_MENUITEM
&&
209 role
!= mozilla::a11y::roles::CHECK_MENU_ITEM
&&
210 role
!= mozilla::a11y::roles::RADIO_MENU_ITEM
&&
211 role
!= mozilla::a11y::roles::RADIOBUTTON
&&
212 role
!= mozilla::a11y::roles::PAGETAB
&&
213 role
!= mozilla::a11y::roles::COMMENT
) {
217 AccGroupInfo
* info
= new AccGroupInfo(aAccessible
, BaseRole(role
));
221 Accessible
* AccGroupInfo::FirstItemOf(const Accessible
* aContainer
) {
222 // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a
223 // group is a parent) or by aria-level.
224 a11y::role containerRole
= aContainer
->Role();
225 Accessible
* item
= aContainer
->NextSibling();
227 if (containerRole
== roles::OUTLINEITEM
&&
228 item
->Role() == roles::GROUPING
) {
229 item
= item
->FirstChild();
233 AccGroupInfo
* itemGroupInfo
= item
->GetOrCreateGroupInfo();
234 if (itemGroupInfo
&& itemGroupInfo
->ConceptualParent() == aContainer
) {
240 // ARIA list and tree can be arranged by ARIA groups case #2 (group is
241 // a child of an item).
242 item
= aContainer
->LastChild();
243 if (!item
) return nullptr;
245 if (item
->Role() == roles::GROUPING
&&
246 (containerRole
== roles::LISTITEM
||
247 containerRole
== roles::OUTLINEITEM
)) {
248 item
= item
->FirstChild();
250 AccGroupInfo
* itemGroupInfo
= item
->GetOrCreateGroupInfo();
251 if (itemGroupInfo
&& itemGroupInfo
->ConceptualParent() == aContainer
) {
257 // Otherwise, it can be a direct child if the container is a list or tree.
258 item
= aContainer
->FirstChild();
259 if (ShouldReportRelations(item
->Role(), containerRole
)) return item
;
264 uint32_t AccGroupInfo::TotalItemCount(Accessible
* aContainer
,
265 bool* aIsHierarchical
) {
266 uint32_t itemCount
= 0;
267 switch (aContainer
->Role()) {
269 if (auto val
= aContainer
->GetIntARIAAttr(nsGkAtoms::aria_rowcount
)) {
274 if (TableAccessibleBase
* tableAcc
= aContainer
->AsTableBase()) {
275 return tableAcc
->RowCount();
279 if (Accessible
* table
= nsAccUtils::TableFor(aContainer
)) {
280 if (auto val
= table
->GetIntARIAAttr(nsGkAtoms::aria_colcount
)) {
285 if (TableAccessibleBase
* tableAcc
= table
->AsTableBase()) {
286 return tableAcc
->ColCount();
293 case roles::MENUPOPUP
:
294 case roles::COMBOBOX
:
295 case roles::GROUPING
:
296 case roles::TREE_TABLE
:
297 case roles::COMBOBOX_LIST
:
299 case roles::DEFINITION_LIST
:
300 case roles::EDITCOMBOBOX
:
301 case roles::RADIO_GROUP
:
302 case roles::PAGETABLIST
: {
303 Accessible
* childItem
= AccGroupInfo::FirstItemOf(aContainer
);
305 childItem
= aContainer
->FirstChild();
306 if (childItem
&& childItem
->IsTextLeaf()) {
307 // First child can be a text leaf, check its sibling for an item.
308 childItem
= childItem
->NextSibling();
313 GroupPos groupPos
= childItem
->GroupPosition();
314 itemCount
= groupPos
.setSize
;
315 if (groupPos
.level
&& aIsHierarchical
) {
316 *aIsHierarchical
= true;
328 Accessible
* AccGroupInfo::NextItemTo(Accessible
* aItem
) {
329 AccGroupInfo
* groupInfo
= aItem
->GetOrCreateGroupInfo();
330 if (!groupInfo
) return nullptr;
332 // If the item in middle of the group then search next item in siblings.
333 if (groupInfo
->PosInSet() >= groupInfo
->SetSize()) return nullptr;
335 Accessible
* parent
= aItem
->Parent();
336 uint32_t childCount
= parent
->ChildCount();
337 for (uint32_t idx
= aItem
->IndexInParent() + 1; idx
< childCount
; idx
++) {
338 Accessible
* nextItem
= parent
->ChildAt(idx
);
339 AccGroupInfo
* nextGroupInfo
= nextItem
->GetOrCreateGroupInfo();
341 nextGroupInfo
->ConceptualParent() == groupInfo
->ConceptualParent()) {
346 MOZ_ASSERT_UNREACHABLE(
347 "Item in the middle of the group but there's no next item!");
351 size_t AccGroupInfo::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) {
352 // We don't count mParent or mItem since they (should be) counted
353 // as part of the document.
354 return aMallocSizeOf(this);
357 bool AccGroupInfo::ShouldReportRelations(role aRole
, role aParentRole
) {
358 // We only want to report hierarchy-based node relations for items in tree or
359 // list form. ARIA level/owns relations are always reported.
360 if (aParentRole
== roles::OUTLINE
&& aRole
== roles::OUTLINEITEM
) return true;
361 if (aParentRole
== roles::TREE_TABLE
&& aRole
== roles::ROW
) return true;
362 if (aParentRole
== roles::LIST
&& aRole
== roles::LISTITEM
) return true;
367 int32_t AccGroupInfo::GetARIAOrDefaultLevel(const Accessible
* aAccessible
) {
369 aAccessible
->ARIAGroupPosition(&level
, nullptr, nullptr);
371 if (level
!= 0) return level
;
373 return aAccessible
->GetLevel(true);
376 static bool IsGenericContainer(role aRole
) {
377 return aRole
== roles::TEXT
|| aRole
== roles::TEXT_CONTAINER
||
378 aRole
== roles::SECTION
;
381 static Accessible
* GetRelevantParent(const Accessible
* aAcc
) {
384 // Search through ancestors until we find a relevant parent, skipping generic
386 Accessible
* parent
= aAcc
->Parent();
387 while (parent
&& IsGenericContainer(parent
->Role())) {
388 parent
= parent
->Parent();
393 static role
BaseRole(role aRole
) {
394 if (aRole
== roles::CHECK_MENU_ITEM
|| aRole
== roles::PARENT_MENUITEM
||
395 aRole
== roles::RADIO_MENU_ITEM
) {
396 return roles::MENUITEM
;
399 if (aRole
== roles::CHECK_RICH_OPTION
) {
400 return roles::RICH_OPTION
;