Bug 1833114 - Simplify marking code now |stack| represents the mark stack for the...
[gecko.git] / accessible / base / AccGroupInfo.cpp
blob7d3c2ee9114fdbe93305995aa2c73319f2854173
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"
12 #include "Pivot.h"
13 #include "States.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 {
23 public:
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
31 // accessible.
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
39 // siblings.
40 if (IsGenericContainer(accRole)) {
41 return nsIAccessibleTraversalRule::FILTER_IGNORE;
44 return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
47 private:
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);
54 Update();
57 void AccGroupInfo::Update() {
58 mParent = nullptr;
60 Accessible* parent = GetRelevantParent(mItem);
61 if (!parent) {
62 return;
65 const int32_t level = GetARIAOrDefaultLevel(mItem);
67 // Compute position in set.
68 mPosInSet = 1;
70 // Search backwards through the tree for candidate siblings.
71 Accessible* candidateSibling = const_cast<Accessible*>(mItem);
72 Pivot pivot{parent};
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) {
78 break;
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) {
85 continue;
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;
95 break;
98 // Skip subset.
99 if (siblingLevel > level) {
100 continue;
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;
109 return;
112 mPosInSet++;
115 // Compute set size.
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) {
123 break;
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) {
130 continue;
133 // and check if it's hierarchical flatten structure.
134 const int32_t siblingLevel = GetARIAOrDefaultLevel(candidateSibling);
135 if (siblingLevel < level) {
136 break;
139 // Skip subset.
140 if (siblingLevel > level) {
141 continue;
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;
149 return;
152 mSetSize++;
155 if (mParent) {
156 return;
159 roles::Role parentRole = parent->Role();
160 if (ShouldReportRelations(mRole, parentRole)) {
161 mParent = parent;
164 // ARIA tree and list can be arranged by using ARIA groups to organize levels.
165 if (parentRole != roles::GROUPING) {
166 return;
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
172 // item.
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;
183 return;
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) {
214 return nullptr;
217 AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role));
218 return info;
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();
226 if (item) {
227 if (containerRole == roles::OUTLINEITEM &&
228 item->Role() == roles::GROUPING) {
229 item = item->FirstChild();
232 if (item) {
233 AccGroupInfo* itemGroupInfo = item->GetOrCreateGroupInfo();
234 if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) {
235 return item;
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();
249 if (item) {
250 AccGroupInfo* itemGroupInfo = item->GetOrCreateGroupInfo();
251 if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) {
252 return item;
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;
261 return nullptr;
264 uint32_t AccGroupInfo::TotalItemCount(Accessible* aContainer,
265 bool* aIsHierarchical) {
266 uint32_t itemCount = 0;
267 switch (aContainer->Role()) {
268 case roles::TABLE:
269 if (auto val = aContainer->GetIntARIAAttr(nsGkAtoms::aria_rowcount)) {
270 if (*val >= 0) {
271 return *val;
274 if (TableAccessibleBase* tableAcc = aContainer->AsTableBase()) {
275 return tableAcc->RowCount();
277 break;
278 case roles::ROW:
279 if (Accessible* table = nsAccUtils::TableFor(aContainer)) {
280 if (auto val = table->GetIntARIAAttr(nsGkAtoms::aria_colcount)) {
281 if (*val >= 0) {
282 return *val;
285 if (TableAccessibleBase* tableAcc = table->AsTableBase()) {
286 return tableAcc->ColCount();
289 break;
290 case roles::OUTLINE:
291 case roles::LIST:
292 case roles::MENUBAR:
293 case roles::MENUPOPUP:
294 case roles::COMBOBOX:
295 case roles::GROUPING:
296 case roles::TREE_TABLE:
297 case roles::COMBOBOX_LIST:
298 case roles::LISTBOX:
299 case roles::DEFINITION_LIST:
300 case roles::EDITCOMBOBOX:
301 case roles::RADIO_GROUP:
302 case roles::PAGETABLIST: {
303 Accessible* childItem = AccGroupInfo::FirstItemOf(aContainer);
304 if (!childItem) {
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();
312 if (childItem) {
313 GroupPos groupPos = childItem->GroupPosition();
314 itemCount = groupPos.setSize;
315 if (groupPos.level && aIsHierarchical) {
316 *aIsHierarchical = true;
319 break;
321 default:
322 break;
325 return itemCount;
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();
340 if (nextGroupInfo &&
341 nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
342 return nextItem;
346 MOZ_ASSERT_UNREACHABLE(
347 "Item in the middle of the group but there's no next item!");
348 return nullptr;
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;
364 return false;
367 int32_t AccGroupInfo::GetARIAOrDefaultLevel(const Accessible* aAccessible) {
368 int32_t level = 0;
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) {
382 MOZ_ASSERT(aAcc);
384 // Search through ancestors until we find a relevant parent, skipping generic
385 // accessibles.
386 Accessible* parent = aAcc->Parent();
387 while (parent && IsGenericContainer(parent->Role())) {
388 parent = parent->Parent();
390 return 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;
403 return aRole;