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