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"
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
{
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
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
38 if (aAcc
->IsGeneric()) {
39 return nsIAccessibleTraversalRule::FILTER_IGNORE
;
42 return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
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
);
55 void AccGroupInfo::Update() {
58 Accessible
* parent
= mItem
->GetNonGenericParent();
63 const int32_t level
= GetARIAOrDefaultLevel(mItem
);
65 // Compute position in set.
68 // Search backwards through the tree for candidate siblings.
69 Accessible
* candidateSibling
= const_cast<Accessible
*>(mItem
);
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
) {
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
) {
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();
97 if (siblingLevel
> level
) {
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
;
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
) {
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
) {
131 // and check if it's hierarchical flatten structure.
132 const int32_t siblingLevel
= GetARIAOrDefaultLevel(candidateSibling
);
133 if (siblingLevel
< level
) {
138 if (siblingLevel
> level
) {
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
;
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
) {
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
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();
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
) {
215 AccGroupInfo
* info
= new AccGroupInfo(aAccessible
, BaseRole(role
));
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();
225 if (containerRole
== roles::OUTLINEITEM
&&
226 item
->Role() == roles::GROUPING
) {
227 item
= item
->FirstChild();
231 AccGroupInfo
* itemGroupInfo
= item
->GetOrCreateGroupInfo();
232 if (itemGroupInfo
&& itemGroupInfo
->ConceptualParent() == aContainer
) {
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();
248 AccGroupInfo
* itemGroupInfo
= item
->GetOrCreateGroupInfo();
249 if (itemGroupInfo
&& itemGroupInfo
->ConceptualParent() == aContainer
) {
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
;
262 uint32_t AccGroupInfo::TotalItemCount(Accessible
* aContainer
,
263 bool* aIsHierarchical
) {
264 uint32_t itemCount
= 0;
265 switch (aContainer
->Role()) {
268 if (auto val
= aContainer
->GetIntARIAAttr(nsGkAtoms::aria_rowcount
)) {
273 if (TableAccessible
* tableAcc
= aContainer
->AsTable()) {
274 return tableAcc
->RowCount();
278 if (Accessible
* table
= nsAccUtils::TableFor(aContainer
)) {
279 if (auto val
= table
->GetIntARIAAttr(nsGkAtoms::aria_colcount
)) {
284 if (TableAccessible
* tableAcc
= table
->AsTable()) {
285 return tableAcc
->ColCount();
292 case roles::MENUPOPUP
:
293 case roles::COMBOBOX
:
294 case roles::GROUPING
:
295 case roles::TREE_TABLE
:
296 case roles::COMBOBOX_LIST
:
298 case roles::DEFINITION_LIST
:
299 case roles::EDITCOMBOBOX
:
300 case roles::RADIO_GROUP
:
301 case roles::PAGETABLIST
: {
302 Accessible
* childItem
= AccGroupInfo::FirstItemOf(aContainer
);
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();
312 GroupPos groupPos
= childItem
->GroupPosition();
313 itemCount
= groupPos
.setSize
;
314 if (groupPos
.level
&& aIsHierarchical
) {
315 *aIsHierarchical
= true;
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();
340 nextGroupInfo
->ConceptualParent() == groupInfo
->ConceptualParent()) {
345 MOZ_ASSERT_UNREACHABLE(
346 "Item in the middle of the group but there's no next item!");
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;
366 int32_t AccGroupInfo::GetARIAOrDefaultLevel(const Accessible
* aAccessible
) {
368 aAccessible
->ARIAGroupPosition(&level
, nullptr, nullptr);
370 if (level
!= 0) return level
;
372 return aAccessible
->GetLevel(true);
375 Accessible
* AccGroupInfo::ConceptualParent() const {
377 // The conceptual parent can never be the document, so id 0 means none.
380 if (Accessible
* doc
=
381 nsAccUtils::DocumentFor(const_cast<Accessible
*>(mItem
))) {
382 return nsAccUtils::GetAccessibleByID(doc
, mParentId
);
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
;