|
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/. */ |
|
4 |
|
5 #include "AccGroupInfo.h" |
|
6 #include "nsAccUtils.h" |
|
7 |
|
8 #include "Role.h" |
|
9 #include "States.h" |
|
10 |
|
11 using namespace mozilla::a11y; |
|
12 |
|
13 AccGroupInfo::AccGroupInfo(Accessible* aItem, role aRole) : |
|
14 mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem), mRole(aRole) |
|
15 { |
|
16 MOZ_COUNT_CTOR(AccGroupInfo); |
|
17 Update(); |
|
18 } |
|
19 |
|
20 void |
|
21 AccGroupInfo::Update() |
|
22 { |
|
23 Accessible* parent = mItem->Parent(); |
|
24 if (!parent) |
|
25 return; |
|
26 |
|
27 int32_t indexInParent = mItem->IndexInParent(); |
|
28 uint32_t siblingCount = parent->ChildCount(); |
|
29 if (indexInParent == -1 || |
|
30 indexInParent >= static_cast<int32_t>(siblingCount)) { |
|
31 NS_ERROR("Wrong index in parent! Tree invalidation problem."); |
|
32 return; |
|
33 } |
|
34 |
|
35 int32_t level = nsAccUtils::GetARIAOrDefaultLevel(mItem); |
|
36 |
|
37 // Compute position in set. |
|
38 mPosInSet = 1; |
|
39 for (int32_t idx = indexInParent - 1; idx >= 0 ; idx--) { |
|
40 Accessible* sibling = parent->GetChildAt(idx); |
|
41 roles::Role siblingRole = sibling->Role(); |
|
42 |
|
43 // If the sibling is separator then the group is ended. |
|
44 if (siblingRole == roles::SEPARATOR) |
|
45 break; |
|
46 |
|
47 // If sibling is not visible and hasn't the same base role. |
|
48 if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) |
|
49 continue; |
|
50 |
|
51 // Check if it's hierarchical flatten structure, i.e. if the sibling |
|
52 // level is lesser than this one then group is ended, if the sibling level |
|
53 // is greater than this one then the group is split by some child elements |
|
54 // (group will be continued). |
|
55 int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); |
|
56 if (siblingLevel < level) { |
|
57 mParent = sibling; |
|
58 break; |
|
59 } |
|
60 |
|
61 // Skip subset. |
|
62 if (siblingLevel > level) |
|
63 continue; |
|
64 |
|
65 // If the previous item in the group has calculated group information then |
|
66 // build group information for this item based on found one. |
|
67 if (sibling->mGroupInfo) { |
|
68 mPosInSet += sibling->mGroupInfo->mPosInSet; |
|
69 mParent = sibling->mGroupInfo->mParent; |
|
70 mSetSize = sibling->mGroupInfo->mSetSize; |
|
71 return; |
|
72 } |
|
73 |
|
74 mPosInSet++; |
|
75 } |
|
76 |
|
77 // Compute set size. |
|
78 mSetSize = mPosInSet; |
|
79 |
|
80 for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) { |
|
81 Accessible* sibling = parent->GetChildAt(idx); |
|
82 |
|
83 roles::Role siblingRole = sibling->Role(); |
|
84 |
|
85 // If the sibling is separator then the group is ended. |
|
86 if (siblingRole == roles::SEPARATOR) |
|
87 break; |
|
88 |
|
89 // If sibling is visible and has the same base role |
|
90 if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) |
|
91 continue; |
|
92 |
|
93 // and check if it's hierarchical flatten structure. |
|
94 int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); |
|
95 if (siblingLevel < level) |
|
96 break; |
|
97 |
|
98 // Skip subset. |
|
99 if (siblingLevel > level) |
|
100 continue; |
|
101 |
|
102 // If the next item in the group has calculated group information then |
|
103 // build group information for this item based on found one. |
|
104 if (sibling->mGroupInfo) { |
|
105 mParent = sibling->mGroupInfo->mParent; |
|
106 mSetSize = sibling->mGroupInfo->mSetSize; |
|
107 return; |
|
108 } |
|
109 |
|
110 mSetSize++; |
|
111 } |
|
112 |
|
113 if (mParent) |
|
114 return; |
|
115 |
|
116 roles::Role parentRole = parent->Role(); |
|
117 if (ShouldReportRelations(mRole, parentRole)) |
|
118 mParent = parent; |
|
119 |
|
120 // ARIA tree and list can be arranged by using ARIA groups to organize levels. |
|
121 if (parentRole != roles::GROUPING) |
|
122 return; |
|
123 |
|
124 // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a |
|
125 // parent. In other words the parent of the tree item will be a group and |
|
126 // the previous tree item of the group is a conceptual parent of the tree |
|
127 // item. |
|
128 if (mRole == roles::OUTLINEITEM) { |
|
129 Accessible* parentPrevSibling = parent->PrevSibling(); |
|
130 if (parentPrevSibling && parentPrevSibling->Role() == mRole) { |
|
131 mParent = parentPrevSibling; |
|
132 return; |
|
133 } |
|
134 } |
|
135 |
|
136 // Way #2 for ARIA list and tree: group is a child of an item. In other words |
|
137 // the parent of the item will be a group and containing item of the group is |
|
138 // a conceptual parent of the item. |
|
139 if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) { |
|
140 Accessible* grandParent = parent->Parent(); |
|
141 if (grandParent && grandParent->Role() == mRole) |
|
142 mParent = grandParent; |
|
143 } |
|
144 } |
|
145 |
|
146 Accessible* |
|
147 AccGroupInfo::FirstItemOf(Accessible* aContainer) |
|
148 { |
|
149 // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a |
|
150 // group is a parent) or by aria-level. |
|
151 a11y::role containerRole = aContainer->Role(); |
|
152 Accessible* item = aContainer->NextSibling(); |
|
153 if (item) { |
|
154 if (containerRole == roles::OUTLINEITEM && item->Role() == roles::GROUPING) |
|
155 item = item->FirstChild(); |
|
156 |
|
157 if (item) { |
|
158 AccGroupInfo* itemGroupInfo = item->GetGroupInfo(); |
|
159 if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) |
|
160 return item; |
|
161 } |
|
162 } |
|
163 |
|
164 // ARIA list and tree can be arranged by ARIA groups case #2 (group is |
|
165 // a child of an item). |
|
166 item = aContainer->LastChild(); |
|
167 if (!item) |
|
168 return nullptr; |
|
169 |
|
170 if (item->Role() == roles::GROUPING && |
|
171 (containerRole == roles::LISTITEM || containerRole == roles::OUTLINEITEM)) { |
|
172 item = item->FirstChild(); |
|
173 if (item) { |
|
174 AccGroupInfo* itemGroupInfo = item->GetGroupInfo(); |
|
175 if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) |
|
176 return item; |
|
177 } |
|
178 } |
|
179 |
|
180 // Otherwise, it can be a direct child if the container is a list or tree. |
|
181 item = aContainer->FirstChild(); |
|
182 if (ShouldReportRelations(item->Role(), containerRole)) |
|
183 return item; |
|
184 |
|
185 return nullptr; |
|
186 } |
|
187 |
|
188 Accessible* |
|
189 AccGroupInfo::NextItemTo(Accessible* aItem) |
|
190 { |
|
191 AccGroupInfo* groupInfo = aItem->GetGroupInfo(); |
|
192 if (!groupInfo) |
|
193 return nullptr; |
|
194 |
|
195 // If the item in middle of the group then search next item in siblings. |
|
196 if (groupInfo->PosInSet() >= groupInfo->SetSize()) |
|
197 return nullptr; |
|
198 |
|
199 Accessible* parent = aItem->Parent(); |
|
200 uint32_t childCount = parent->ChildCount(); |
|
201 for (int32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) { |
|
202 Accessible* nextItem = parent->GetChildAt(idx); |
|
203 AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo(); |
|
204 if (nextGroupInfo && |
|
205 nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) { |
|
206 return nextItem; |
|
207 } |
|
208 } |
|
209 |
|
210 NS_NOTREACHED("Item in the middle of the group but there's no next item!"); |
|
211 return nullptr; |
|
212 } |
|
213 |
|
214 bool |
|
215 AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole) |
|
216 { |
|
217 // We only want to report hierarchy-based node relations for items in tree or |
|
218 // list form. ARIA level/owns relations are always reported. |
|
219 if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM) |
|
220 return true; |
|
221 if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW) |
|
222 return true; |
|
223 if (aParentRole == roles::LIST && aRole == roles::LISTITEM) |
|
224 return true; |
|
225 |
|
226 return false; |
|
227 } |