|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsAccUtils.h" |
|
7 |
|
8 #include "Accessible-inl.h" |
|
9 #include "ARIAMap.h" |
|
10 #include "nsAccessibilityService.h" |
|
11 #include "nsCoreUtils.h" |
|
12 #include "DocAccessible.h" |
|
13 #include "HyperTextAccessible.h" |
|
14 #include "nsIAccessibleTypes.h" |
|
15 #include "Role.h" |
|
16 #include "States.h" |
|
17 #include "TextLeafAccessible.h" |
|
18 |
|
19 #include "nsIDOMXULContainerElement.h" |
|
20 #include "nsIPersistentProperties2.h" |
|
21 #include "mozilla/dom/Element.h" |
|
22 |
|
23 using namespace mozilla; |
|
24 using namespace mozilla::a11y; |
|
25 |
|
26 void |
|
27 nsAccUtils::GetAccAttr(nsIPersistentProperties *aAttributes, |
|
28 nsIAtom *aAttrName, nsAString& aAttrValue) |
|
29 { |
|
30 aAttrValue.Truncate(); |
|
31 |
|
32 aAttributes->GetStringProperty(nsAtomCString(aAttrName), aAttrValue); |
|
33 } |
|
34 |
|
35 void |
|
36 nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes, |
|
37 nsIAtom *aAttrName, const nsAString& aAttrValue) |
|
38 { |
|
39 nsAutoString oldValue; |
|
40 nsAutoCString attrName; |
|
41 |
|
42 aAttributes->SetStringProperty(nsAtomCString(aAttrName), aAttrValue, oldValue); |
|
43 } |
|
44 |
|
45 void |
|
46 nsAccUtils::SetAccGroupAttrs(nsIPersistentProperties *aAttributes, |
|
47 int32_t aLevel, int32_t aSetSize, |
|
48 int32_t aPosInSet) |
|
49 { |
|
50 nsAutoString value; |
|
51 |
|
52 if (aLevel) { |
|
53 value.AppendInt(aLevel); |
|
54 SetAccAttr(aAttributes, nsGkAtoms::level, value); |
|
55 } |
|
56 |
|
57 if (aSetSize && aPosInSet) { |
|
58 value.Truncate(); |
|
59 value.AppendInt(aPosInSet); |
|
60 SetAccAttr(aAttributes, nsGkAtoms::posinset, value); |
|
61 |
|
62 value.Truncate(); |
|
63 value.AppendInt(aSetSize); |
|
64 SetAccAttr(aAttributes, nsGkAtoms::setsize, value); |
|
65 } |
|
66 } |
|
67 |
|
68 int32_t |
|
69 nsAccUtils::GetDefaultLevel(Accessible* aAccessible) |
|
70 { |
|
71 roles::Role role = aAccessible->Role(); |
|
72 |
|
73 if (role == roles::OUTLINEITEM) |
|
74 return 1; |
|
75 |
|
76 if (role == roles::ROW) { |
|
77 Accessible* parent = aAccessible->Parent(); |
|
78 // It is a row inside flatten treegrid. Group level is always 1 until it |
|
79 // is overriden by aria-level attribute. |
|
80 if (parent && parent->Role() == roles::TREE_TABLE) |
|
81 return 1; |
|
82 } |
|
83 |
|
84 return 0; |
|
85 } |
|
86 |
|
87 int32_t |
|
88 nsAccUtils::GetARIAOrDefaultLevel(Accessible* aAccessible) |
|
89 { |
|
90 int32_t level = 0; |
|
91 nsCoreUtils::GetUIntAttr(aAccessible->GetContent(), |
|
92 nsGkAtoms::aria_level, &level); |
|
93 |
|
94 if (level != 0) |
|
95 return level; |
|
96 |
|
97 return GetDefaultLevel(aAccessible); |
|
98 } |
|
99 |
|
100 int32_t |
|
101 nsAccUtils::GetLevelForXULContainerItem(nsIContent *aContent) |
|
102 { |
|
103 nsCOMPtr<nsIDOMXULContainerItemElement> item(do_QueryInterface(aContent)); |
|
104 if (!item) |
|
105 return 0; |
|
106 |
|
107 nsCOMPtr<nsIDOMXULContainerElement> container; |
|
108 item->GetParentContainer(getter_AddRefs(container)); |
|
109 if (!container) |
|
110 return 0; |
|
111 |
|
112 // Get level of the item. |
|
113 int32_t level = -1; |
|
114 while (container) { |
|
115 level++; |
|
116 |
|
117 nsCOMPtr<nsIDOMXULContainerElement> parentContainer; |
|
118 container->GetParentContainer(getter_AddRefs(parentContainer)); |
|
119 parentContainer.swap(container); |
|
120 } |
|
121 |
|
122 return level; |
|
123 } |
|
124 |
|
125 void |
|
126 nsAccUtils::SetLiveContainerAttributes(nsIPersistentProperties *aAttributes, |
|
127 nsIContent *aStartContent, |
|
128 nsIContent *aTopContent) |
|
129 { |
|
130 nsAutoString live, relevant, busy; |
|
131 nsIContent *ancestor = aStartContent; |
|
132 while (ancestor) { |
|
133 |
|
134 // container-relevant attribute |
|
135 if (relevant.IsEmpty() && |
|
136 HasDefinedARIAToken(ancestor, nsGkAtoms::aria_relevant) && |
|
137 ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_relevant, relevant)) |
|
138 SetAccAttr(aAttributes, nsGkAtoms::containerRelevant, relevant); |
|
139 |
|
140 // container-live, and container-live-role attributes |
|
141 if (live.IsEmpty()) { |
|
142 nsRoleMapEntry* role = aria::GetRoleMap(ancestor); |
|
143 if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) { |
|
144 ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live, |
|
145 live); |
|
146 } else if (role) { |
|
147 GetLiveAttrValue(role->liveAttRule, live); |
|
148 } |
|
149 if (!live.IsEmpty()) { |
|
150 SetAccAttr(aAttributes, nsGkAtoms::containerLive, live); |
|
151 if (role) { |
|
152 SetAccAttr(aAttributes, nsGkAtoms::containerLiveRole, |
|
153 role->ARIARoleString()); |
|
154 } |
|
155 } |
|
156 } |
|
157 |
|
158 // container-atomic attribute |
|
159 if (ancestor->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_atomic, |
|
160 nsGkAtoms::_true, eCaseMatters)) { |
|
161 SetAccAttr(aAttributes, nsGkAtoms::containerAtomic, |
|
162 NS_LITERAL_STRING("true")); |
|
163 } |
|
164 |
|
165 // container-busy attribute |
|
166 if (busy.IsEmpty() && |
|
167 HasDefinedARIAToken(ancestor, nsGkAtoms::aria_busy) && |
|
168 ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_busy, busy)) |
|
169 SetAccAttr(aAttributes, nsGkAtoms::containerBusy, busy); |
|
170 |
|
171 if (ancestor == aTopContent) |
|
172 break; |
|
173 |
|
174 ancestor = ancestor->GetParent(); |
|
175 if (!ancestor) |
|
176 ancestor = aTopContent; // Use <body>/<frameset> |
|
177 } |
|
178 } |
|
179 |
|
180 bool |
|
181 nsAccUtils::HasDefinedARIAToken(nsIContent *aContent, nsIAtom *aAtom) |
|
182 { |
|
183 NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!"); |
|
184 |
|
185 if (!aContent->HasAttr(kNameSpaceID_None, aAtom) || |
|
186 aContent->AttrValueIs(kNameSpaceID_None, aAtom, |
|
187 nsGkAtoms::_empty, eCaseMatters) || |
|
188 aContent->AttrValueIs(kNameSpaceID_None, aAtom, |
|
189 nsGkAtoms::_undefined, eCaseMatters)) { |
|
190 return false; |
|
191 } |
|
192 return true; |
|
193 } |
|
194 |
|
195 nsIAtom* |
|
196 nsAccUtils::GetARIAToken(dom::Element* aElement, nsIAtom* aAttr) |
|
197 { |
|
198 if (!HasDefinedARIAToken(aElement, aAttr)) |
|
199 return nsGkAtoms::_empty; |
|
200 |
|
201 static nsIContent::AttrValuesArray tokens[] = |
|
202 { &nsGkAtoms::_false, &nsGkAtoms::_true, |
|
203 &nsGkAtoms::mixed, nullptr}; |
|
204 |
|
205 int32_t idx = aElement->FindAttrValueIn(kNameSpaceID_None, |
|
206 aAttr, tokens, eCaseMatters); |
|
207 if (idx >= 0) |
|
208 return *(tokens[idx]); |
|
209 |
|
210 return nullptr; |
|
211 } |
|
212 |
|
213 Accessible* |
|
214 nsAccUtils::GetSelectableContainer(Accessible* aAccessible, uint64_t aState) |
|
215 { |
|
216 if (!aAccessible) |
|
217 return nullptr; |
|
218 |
|
219 if (!(aState & states::SELECTABLE)) |
|
220 return nullptr; |
|
221 |
|
222 Accessible* parent = aAccessible; |
|
223 while ((parent = parent->Parent()) && !parent->IsSelect()) { |
|
224 if (parent->Role() == roles::PANE) |
|
225 return nullptr; |
|
226 } |
|
227 return parent; |
|
228 } |
|
229 |
|
230 bool |
|
231 nsAccUtils::IsARIASelected(Accessible* aAccessible) |
|
232 { |
|
233 return aAccessible->GetContent()-> |
|
234 AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_selected, |
|
235 nsGkAtoms::_true, eCaseMatters); |
|
236 } |
|
237 |
|
238 HyperTextAccessible* |
|
239 nsAccUtils::GetTextContainer(nsINode* aNode) |
|
240 { |
|
241 // Get text accessible containing the result node. |
|
242 DocAccessible* doc = |
|
243 GetAccService()->GetDocAccessible(aNode->OwnerDoc()); |
|
244 Accessible* accessible = |
|
245 doc ? doc->GetAccessibleOrContainer(aNode) : nullptr; |
|
246 if (!accessible) |
|
247 return nullptr; |
|
248 |
|
249 do { |
|
250 HyperTextAccessible* textAcc = accessible->AsHyperText(); |
|
251 if (textAcc) |
|
252 return textAcc; |
|
253 |
|
254 accessible = accessible->Parent(); |
|
255 } while (accessible); |
|
256 |
|
257 return nullptr; |
|
258 } |
|
259 |
|
260 nsIntPoint |
|
261 nsAccUtils::ConvertToScreenCoords(int32_t aX, int32_t aY, |
|
262 uint32_t aCoordinateType, |
|
263 Accessible* aAccessible) |
|
264 { |
|
265 nsIntPoint coords(aX, aY); |
|
266 |
|
267 switch (aCoordinateType) { |
|
268 case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE: |
|
269 break; |
|
270 |
|
271 case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: |
|
272 { |
|
273 coords += nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode()); |
|
274 break; |
|
275 } |
|
276 |
|
277 case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: |
|
278 { |
|
279 coords += GetScreenCoordsForParent(aAccessible); |
|
280 break; |
|
281 } |
|
282 |
|
283 default: |
|
284 NS_NOTREACHED("invalid coord type!"); |
|
285 } |
|
286 |
|
287 return coords; |
|
288 } |
|
289 |
|
290 void |
|
291 nsAccUtils::ConvertScreenCoordsTo(int32_t *aX, int32_t *aY, |
|
292 uint32_t aCoordinateType, |
|
293 Accessible* aAccessible) |
|
294 { |
|
295 switch (aCoordinateType) { |
|
296 case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE: |
|
297 break; |
|
298 |
|
299 case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: |
|
300 { |
|
301 nsIntPoint coords = nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode()); |
|
302 *aX -= coords.x; |
|
303 *aY -= coords.y; |
|
304 break; |
|
305 } |
|
306 |
|
307 case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: |
|
308 { |
|
309 nsIntPoint coords = GetScreenCoordsForParent(aAccessible); |
|
310 *aX -= coords.x; |
|
311 *aY -= coords.y; |
|
312 break; |
|
313 } |
|
314 |
|
315 default: |
|
316 NS_NOTREACHED("invalid coord type!"); |
|
317 } |
|
318 } |
|
319 |
|
320 nsIntPoint |
|
321 nsAccUtils::GetScreenCoordsForParent(Accessible* aAccessible) |
|
322 { |
|
323 Accessible* parent = aAccessible->Parent(); |
|
324 if (!parent) |
|
325 return nsIntPoint(0, 0); |
|
326 |
|
327 nsIFrame *parentFrame = parent->GetFrame(); |
|
328 if (!parentFrame) |
|
329 return nsIntPoint(0, 0); |
|
330 |
|
331 nsRect rect = parentFrame->GetScreenRectInAppUnits(); |
|
332 return nsPoint(rect.x, rect.y). |
|
333 ToNearestPixels(parentFrame->PresContext()->AppUnitsPerDevPixel()); |
|
334 } |
|
335 |
|
336 bool |
|
337 nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue) |
|
338 { |
|
339 switch (aRule) { |
|
340 case eOffLiveAttr: |
|
341 aValue = NS_LITERAL_STRING("off"); |
|
342 return true; |
|
343 case ePoliteLiveAttr: |
|
344 aValue = NS_LITERAL_STRING("polite"); |
|
345 return true; |
|
346 } |
|
347 |
|
348 return false; |
|
349 } |
|
350 |
|
351 #ifdef DEBUG |
|
352 |
|
353 bool |
|
354 nsAccUtils::IsTextInterfaceSupportCorrect(Accessible* aAccessible) |
|
355 { |
|
356 // Don't test for accessible docs, it makes us create accessibles too |
|
357 // early and fire mutation events before we need to |
|
358 if (aAccessible->IsDoc()) |
|
359 return true; |
|
360 |
|
361 bool foundText = false; |
|
362 uint32_t childCount = aAccessible->ChildCount(); |
|
363 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { |
|
364 Accessible* child = aAccessible->GetChildAt(childIdx); |
|
365 if (!IsEmbeddedObject(child)) { |
|
366 foundText = true; |
|
367 break; |
|
368 } |
|
369 } |
|
370 |
|
371 if (foundText) { |
|
372 // found text child node |
|
373 nsCOMPtr<nsIAccessibleText> text = do_QueryObject(aAccessible); |
|
374 if (!text) |
|
375 return false; |
|
376 } |
|
377 |
|
378 return true; |
|
379 } |
|
380 #endif |
|
381 |
|
382 uint32_t |
|
383 nsAccUtils::TextLength(Accessible* aAccessible) |
|
384 { |
|
385 if (IsEmbeddedObject(aAccessible)) |
|
386 return 1; |
|
387 |
|
388 TextLeafAccessible* textLeaf = aAccessible->AsTextLeaf(); |
|
389 if (textLeaf) |
|
390 return textLeaf->Text().Length(); |
|
391 |
|
392 // For list bullets (or anything other accessible which would compute its own |
|
393 // text. They don't have their own frame. |
|
394 // XXX In the future, list bullets may have frame and anon content, so |
|
395 // we should be able to remove this at that point |
|
396 nsAutoString text; |
|
397 aAccessible->AppendTextTo(text); // Get all the text |
|
398 return text.Length(); |
|
399 } |
|
400 |
|
401 bool |
|
402 nsAccUtils::MustPrune(Accessible* aAccessible) |
|
403 { |
|
404 roles::Role role = aAccessible->Role(); |
|
405 |
|
406 // Don't prune the tree for certain roles if the tree is more complex than |
|
407 // a single text leaf. |
|
408 return |
|
409 (role == roles::MENUITEM || |
|
410 role == roles::COMBOBOX_OPTION || |
|
411 role == roles::OPTION || |
|
412 role == roles::ENTRY || |
|
413 role == roles::FLAT_EQUATION || |
|
414 role == roles::PASSWORD_TEXT || |
|
415 role == roles::PUSHBUTTON || |
|
416 role == roles::TOGGLE_BUTTON || |
|
417 role == roles::GRAPHIC || |
|
418 role == roles::SLIDER || |
|
419 role == roles::PROGRESSBAR || |
|
420 role == roles::SEPARATOR) && |
|
421 aAccessible->ContentChildCount() == 1 && |
|
422 aAccessible->ContentChildAt(0)->IsTextLeaf(); |
|
423 } |