no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / accessible / base / AccGroupInfo.cpp
blobc3501dc36e76c4530885ee9961e04f59d94600d2
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/TableAccessible.h"
9 #include "nsAccUtils.h"
10 #include "nsIAccessiblePivot.h"
12 #include "Pivot.h"
13 #include "States.h"
15 using namespace mozilla::a11y;
17 static role BaseRole(role aRole);
19 // This rule finds candidate siblings for compound widget children.
20 class CompoundWidgetSiblingRule : public PivotRule {
21 public:
22 CompoundWidgetSiblingRule() = delete;
23 explicit CompoundWidgetSiblingRule(role aRole) : mRole(aRole) {}
25 uint16_t Match(Accessible* aAcc) override {
26 // If the acc has a matching role, that's a valid sibling. If the acc is
27 // separator then the group is ended. Return a match for separators with
28 // the assumption that the caller will check for the role of the returned
29 // accessible.
30 const role accRole = aAcc->Role();
31 if (BaseRole(accRole) == mRole || accRole == role::SEPARATOR) {
32 return nsIAccessibleTraversalRule::FILTER_MATCH |
33 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
36 // Ignore generic accessibles, but keep searching through the subtree for
37 // siblings.
38 if (aAcc->IsGeneric()) {
39 return nsIAccessibleTraversalRule::FILTER_IGNORE;
42 return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
45 private:
46 role mRole = role::NOTHING;
49 AccGroupInfo::AccGroupInfo(const Accessible* aItem, role aRole)
50 : mPosInSet(0), mSetSize(0), mParentId(0), mItem(aItem), mRole(aRole) {
51 MOZ_COUNT_CTOR(AccGroupInfo);
52 Update();
55 void AccGroupInfo::Update() {
56 mParentId = 0;
58 Accessible* parent = mItem->GetNonGenericParent();
59 if (!parent) {
60 return;
63 const int32_t level = GetARIAOrDefaultLevel(mItem);
65 // Compute position in set.
66 mPosInSet = 1;
68 // Search backwards through the tree for candidate siblings.
69 Accessible* candidateSibling = const_cast<Accessible*>(mItem);
70 Pivot pivot{parent};
71 CompoundWidgetSiblingRule widgetSiblingRule{mRole};
72 while ((candidateSibling = pivot.Prev(candidateSibling, widgetSiblingRule)) &&
73 candidateSibling != parent) {
74 // If the sibling is separator then the group is ended.
75 if (candidateSibling->Role() == roles::SEPARATOR) {
76 break;
79 const AccGroupInfo* siblingGroupInfo = candidateSibling->GetGroupInfo();
80 // Skip invisible siblings.
81 // If the sibling has calculated group info, that means it's visible.
82 if (!siblingGroupInfo && candidateSibling->State() & states::INVISIBLE) {
83 continue;
86 // Check if it's hierarchical flatten structure, i.e. if the sibling
87 // level is lesser than this one then group is ended, if the sibling level
88 // is greater than this one then the group is split by some child elements
89 // (group will be continued).
90 const int32_t siblingLevel = GetARIAOrDefaultLevel(candidateSibling);
91 if (siblingLevel < level) {
92 mParentId = candidateSibling->ID();
93 break;
96 // Skip subset.
97 if (siblingLevel > level) {
98 continue;
101 // If the previous item in the group has calculated group information then
102 // build group information for this item based on found one.
103 if (siblingGroupInfo) {
104 mPosInSet += siblingGroupInfo->mPosInSet;
105 mParentId = siblingGroupInfo->mParentId;
106 mSetSize = siblingGroupInfo->mSetSize;
107 return;
110 mPosInSet++;
113 // Compute set size.
114 mSetSize = mPosInSet;
116 candidateSibling = const_cast<Accessible*>(mItem);
117 while ((candidateSibling = pivot.Next(candidateSibling, widgetSiblingRule)) &&
118 candidateSibling != parent) {
119 // If the sibling is separator then the group is ended.
120 if (candidateSibling->Role() == roles::SEPARATOR) {
121 break;
124 const AccGroupInfo* siblingGroupInfo = candidateSibling->GetGroupInfo();
125 // Skip invisible siblings.
126 // If the sibling has calculated group info, that means it's visible.
127 if (!siblingGroupInfo && candidateSibling->State() & states::INVISIBLE) {
128 continue;
131 // and check if it's hierarchical flatten structure.
132 const int32_t siblingLevel = GetARIAOrDefaultLevel(candidateSibling);
133 if (siblingLevel < level) {
134 break;
137 // Skip subset.
138 if (siblingLevel > level) {
139 continue;
142 // If the next item in the group has calculated group information then
143 // build group information for this item based on found one.
144 if (siblingGroupInfo) {
145 mParentId = siblingGroupInfo->mParentId;
146 mSetSize = siblingGroupInfo->mSetSize;
147 return;
150 mSetSize++;
153 if (mParentId) {
154 return;
157 roles::Role parentRole = parent->Role();
158 if (ShouldReportRelations(mRole, parentRole)) {
159 mParentId = parent->ID();
162 // ARIA tree and list can be arranged by using ARIA groups to organize levels.
163 if (parentRole != roles::GROUPING) {
164 return;
167 // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a
168 // parent. In other words the parent of the tree item will be a group and
169 // the previous tree item of the group is a conceptual parent of the tree
170 // item.
171 if (mRole == roles::OUTLINEITEM) {
172 // Find the relevant grandparent of the item. Use that parent as the root
173 // and find the previous outline item sibling within that root.
174 Accessible* grandParent = parent->GetNonGenericParent();
175 MOZ_ASSERT(grandParent);
176 Pivot pivot{grandParent};
177 CompoundWidgetSiblingRule parentSiblingRule{mRole};
178 Accessible* parentPrevSibling = pivot.Prev(parent, widgetSiblingRule);
179 if (parentPrevSibling && parentPrevSibling->Role() == mRole) {
180 mParentId = parentPrevSibling->ID();
181 return;
185 // Way #2 for ARIA list and tree: group is a child of an item. In other words
186 // the parent of the item will be a group and containing item of the group is
187 // a conceptual parent of the item.
188 if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) {
189 Accessible* grandParent = parent->GetNonGenericParent();
190 if (grandParent && grandParent->Role() == mRole) {
191 mParentId = grandParent->ID();
196 AccGroupInfo* AccGroupInfo::CreateGroupInfo(const Accessible* aAccessible) {
197 mozilla::a11y::role role = aAccessible->Role();
198 if (role != mozilla::a11y::roles::ROW &&
199 role != mozilla::a11y::roles::OUTLINEITEM &&
200 role != mozilla::a11y::roles::OPTION &&
201 role != mozilla::a11y::roles::LISTITEM &&
202 role != mozilla::a11y::roles::MENUITEM &&
203 role != mozilla::a11y::roles::COMBOBOX_OPTION &&
204 role != mozilla::a11y::roles::RICH_OPTION &&
205 role != mozilla::a11y::roles::CHECK_RICH_OPTION &&
206 role != mozilla::a11y::roles::PARENT_MENUITEM &&
207 role != mozilla::a11y::roles::CHECK_MENU_ITEM &&
208 role != mozilla::a11y::roles::RADIO_MENU_ITEM &&
209 role != mozilla::a11y::roles::RADIOBUTTON &&
210 role != mozilla::a11y::roles::PAGETAB &&
211 role != mozilla::a11y::roles::COMMENT) {
212 return nullptr;
215 AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role));
216 return info;
219 Accessible* AccGroupInfo::FirstItemOf(const Accessible* aContainer) {
220 // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a
221 // group is a parent) or by aria-level.
222 a11y::role containerRole = aContainer->Role();
223 Accessible* item = aContainer->NextSibling();
224 if (item) {
225 if (containerRole == roles::OUTLINEITEM &&
226 item->Role() == roles::GROUPING) {
227 item = item->FirstChild();
230 if (item) {
231 AccGroupInfo* itemGroupInfo = item->GetOrCreateGroupInfo();
232 if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) {
233 return item;
238 // ARIA list and tree can be arranged by ARIA groups case #2 (group is
239 // a child of an item).
240 item = aContainer->LastChild();
241 if (!item) return nullptr;
243 if (item->Role() == roles::GROUPING &&
244 (containerRole == roles::LISTITEM ||
245 containerRole == roles::OUTLINEITEM)) {
246 item = item->FirstChild();
247 if (item) {
248 AccGroupInfo* itemGroupInfo = item->GetOrCreateGroupInfo();
249 if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) {
250 return item;
255 // Otherwise, it can be a direct child if the container is a list or tree.
256 item = aContainer->FirstChild();
257 if (ShouldReportRelations(item->Role(), containerRole)) return item;
259 return nullptr;
262 uint32_t AccGroupInfo::TotalItemCount(Accessible* aContainer,
263 bool* aIsHierarchical) {
264 uint32_t itemCount = 0;
265 switch (aContainer->Role()) {
266 case roles::GRID:
267 case roles::TABLE:
268 if (auto val = aContainer->GetIntARIAAttr(nsGkAtoms::aria_rowcount)) {
269 if (*val >= 0) {
270 return *val;
273 if (TableAccessible* tableAcc = aContainer->AsTable()) {
274 return tableAcc->RowCount();
276 break;
277 case roles::ROW:
278 if (Accessible* table = nsAccUtils::TableFor(aContainer)) {
279 if (auto val = table->GetIntARIAAttr(nsGkAtoms::aria_colcount)) {
280 if (*val >= 0) {
281 return *val;
284 if (TableAccessible* tableAcc = table->AsTable()) {
285 return tableAcc->ColCount();
288 break;
289 case roles::OUTLINE:
290 case roles::LIST:
291 case roles::MENUBAR:
292 case roles::MENUPOPUP:
293 case roles::COMBOBOX:
294 case roles::GROUPING:
295 case roles::TREE_TABLE:
296 case roles::COMBOBOX_LIST:
297 case roles::LISTBOX:
298 case roles::DEFINITION_LIST:
299 case roles::EDITCOMBOBOX:
300 case roles::RADIO_GROUP:
301 case roles::PAGETABLIST: {
302 Accessible* childItem = AccGroupInfo::FirstItemOf(aContainer);
303 if (!childItem) {
304 childItem = aContainer->FirstChild();
305 if (childItem && childItem->IsTextLeaf()) {
306 // First child can be a text leaf, check its sibling for an item.
307 childItem = childItem->NextSibling();
311 if (childItem) {
312 GroupPos groupPos = childItem->GroupPosition();
313 itemCount = groupPos.setSize;
314 if (groupPos.level && aIsHierarchical) {
315 *aIsHierarchical = true;
318 break;
320 default:
321 break;
324 return itemCount;
327 Accessible* AccGroupInfo::NextItemTo(Accessible* aItem) {
328 AccGroupInfo* groupInfo = aItem->GetOrCreateGroupInfo();
329 if (!groupInfo) return nullptr;
331 // If the item in middle of the group then search next item in siblings.
332 if (groupInfo->PosInSet() >= groupInfo->SetSize()) return nullptr;
334 Accessible* parent = aItem->Parent();
335 uint32_t childCount = parent->ChildCount();
336 for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) {
337 Accessible* nextItem = parent->ChildAt(idx);
338 AccGroupInfo* nextGroupInfo = nextItem->GetOrCreateGroupInfo();
339 if (nextGroupInfo &&
340 nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
341 return nextItem;
345 MOZ_ASSERT_UNREACHABLE(
346 "Item in the middle of the group but there's no next item!");
347 return nullptr;
350 size_t AccGroupInfo::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
351 // We don't count mParentId or mItem since they (should be) counted
352 // as part of the document.
353 return aMallocSizeOf(this);
356 bool AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole) {
357 // We only want to report hierarchy-based node relations for items in tree or
358 // list form. ARIA level/owns relations are always reported.
359 if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM) return true;
360 if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW) return true;
361 if (aParentRole == roles::LIST && aRole == roles::LISTITEM) return true;
363 return false;
366 int32_t AccGroupInfo::GetARIAOrDefaultLevel(const Accessible* aAccessible) {
367 int32_t level = 0;
368 aAccessible->ARIAGroupPosition(&level, nullptr, nullptr);
370 if (level != 0) return level;
372 return aAccessible->GetLevel(true);
375 Accessible* AccGroupInfo::ConceptualParent() const {
376 if (!mParentId) {
377 // The conceptual parent can never be the document, so id 0 means none.
378 return nullptr;
380 if (Accessible* doc =
381 nsAccUtils::DocumentFor(const_cast<Accessible*>(mItem))) {
382 return nsAccUtils::GetAccessibleByID(doc, mParentId);
384 return nullptr;
387 static role BaseRole(role aRole) {
388 if (aRole == roles::CHECK_MENU_ITEM || aRole == roles::PARENT_MENUITEM ||
389 aRole == roles::RADIO_MENU_ITEM) {
390 return roles::MENUITEM;
393 if (aRole == roles::CHECK_RICH_OPTION) {
394 return roles::RICH_OPTION;
397 return aRole;