|
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 "Accessible-inl.h" |
|
7 |
|
8 #include "nsIXBLAccessible.h" |
|
9 |
|
10 #include "AccCollector.h" |
|
11 #include "AccGroupInfo.h" |
|
12 #include "AccIterator.h" |
|
13 #include "nsAccUtils.h" |
|
14 #include "nsAccessibleRelation.h" |
|
15 #include "nsAccessibilityService.h" |
|
16 #include "ApplicationAccessible.h" |
|
17 #include "nsCoreUtils.h" |
|
18 #include "nsIAccessibleRelation.h" |
|
19 #include "nsIAccessibleRole.h" |
|
20 #include "nsEventShell.h" |
|
21 #include "nsTextEquivUtils.h" |
|
22 #include "Relation.h" |
|
23 #include "Role.h" |
|
24 #include "RootAccessible.h" |
|
25 #include "States.h" |
|
26 #include "StyleInfo.h" |
|
27 #include "TableAccessible.h" |
|
28 #include "TableCellAccessible.h" |
|
29 #include "TreeWalker.h" |
|
30 |
|
31 #include "nsIDOMElement.h" |
|
32 #include "nsIDOMNodeFilter.h" |
|
33 #include "nsIDOMHTMLElement.h" |
|
34 #include "nsIDOMKeyEvent.h" |
|
35 #include "nsIDOMTreeWalker.h" |
|
36 #include "nsIDOMXULButtonElement.h" |
|
37 #include "nsIDOMXULDocument.h" |
|
38 #include "nsIDOMXULElement.h" |
|
39 #include "nsIDOMXULLabelElement.h" |
|
40 #include "nsIDOMXULSelectCntrlEl.h" |
|
41 #include "nsIDOMXULSelectCntrlItemEl.h" |
|
42 #include "nsPIDOMWindow.h" |
|
43 |
|
44 #include "nsIDocument.h" |
|
45 #include "nsIContent.h" |
|
46 #include "nsIForm.h" |
|
47 #include "nsIFormControl.h" |
|
48 |
|
49 #include "nsDeckFrame.h" |
|
50 #include "nsLayoutUtils.h" |
|
51 #include "nsIPresShell.h" |
|
52 #include "nsIStringBundle.h" |
|
53 #include "nsPresContext.h" |
|
54 #include "nsIFrame.h" |
|
55 #include "nsView.h" |
|
56 #include "nsIDocShellTreeItem.h" |
|
57 #include "nsIScrollableFrame.h" |
|
58 #include "nsFocusManager.h" |
|
59 |
|
60 #include "nsXPIDLString.h" |
|
61 #include "nsUnicharUtils.h" |
|
62 #include "nsReadableUtils.h" |
|
63 #include "prdtoa.h" |
|
64 #include "nsIAtom.h" |
|
65 #include "nsIURI.h" |
|
66 #include "nsArrayUtils.h" |
|
67 #include "nsIMutableArray.h" |
|
68 #include "nsIObserverService.h" |
|
69 #include "nsIServiceManager.h" |
|
70 #include "nsWhitespaceTokenizer.h" |
|
71 #include "nsAttrName.h" |
|
72 #include "nsNetUtil.h" |
|
73 |
|
74 #ifdef DEBUG |
|
75 #include "nsIDOMCharacterData.h" |
|
76 #endif |
|
77 |
|
78 #include "mozilla/Assertions.h" |
|
79 #include "mozilla/EventStates.h" |
|
80 #include "mozilla/FloatingPoint.h" |
|
81 #include "mozilla/MouseEvents.h" |
|
82 #include "mozilla/unused.h" |
|
83 #include "mozilla/Preferences.h" |
|
84 #include "mozilla/dom/Element.h" |
|
85 #include "mozilla/dom/TreeWalker.h" |
|
86 |
|
87 using namespace mozilla; |
|
88 using namespace mozilla::a11y; |
|
89 |
|
90 |
|
91 //////////////////////////////////////////////////////////////////////////////// |
|
92 // Accessible: nsISupports and cycle collection |
|
93 |
|
94 NS_IMPL_CYCLE_COLLECTION(Accessible, |
|
95 mContent, mParent, mChildren) |
|
96 |
|
97 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Accessible) |
|
98 NS_INTERFACE_MAP_ENTRY(nsIAccessible) |
|
99 if (aIID.Equals(NS_GET_IID(Accessible))) |
|
100 foundInterface = static_cast<nsIAccessible*>(this); |
|
101 else |
|
102 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleSelectable, IsSelect()) |
|
103 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleValue, HasNumericValue()) |
|
104 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleHyperLink, IsLink()) |
|
105 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessible) |
|
106 NS_INTERFACE_MAP_END |
|
107 |
|
108 NS_IMPL_CYCLE_COLLECTING_ADDREF(Accessible) |
|
109 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(Accessible, LastRelease()) |
|
110 |
|
111 Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) : |
|
112 mContent(aContent), mDoc(aDoc), |
|
113 mParent(nullptr), mIndexInParent(-1), mChildrenFlags(eChildrenUninitialized), |
|
114 mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0), |
|
115 mIndexOfEmbeddedChild(-1), mRoleMapEntry(nullptr) |
|
116 { |
|
117 #ifdef NS_DEBUG_X |
|
118 { |
|
119 nsCOMPtr<nsIPresShell> shell(do_QueryReferent(aShell)); |
|
120 printf(">>> %p Created Acc - DOM: %p PS: %p", |
|
121 (void*)static_cast<nsIAccessible*>(this), (void*)aNode, |
|
122 (void*)shell.get()); |
|
123 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
|
124 if (content) { |
|
125 printf(" Con: %s@%p", |
|
126 NS_ConvertUTF16toUTF8(content->NodeInfo()->QualifiedName()).get(), |
|
127 (void *)content.get()); |
|
128 nsAutoString buf; |
|
129 Name(buf); |
|
130 printf(" Name:[%s]", NS_ConvertUTF16toUTF8(buf).get()); |
|
131 } |
|
132 printf("\n"); |
|
133 } |
|
134 #endif |
|
135 } |
|
136 |
|
137 Accessible::~Accessible() |
|
138 { |
|
139 NS_ASSERTION(!mDoc, "LastRelease was never called!?!"); |
|
140 } |
|
141 |
|
142 NS_IMETHODIMP |
|
143 Accessible::GetDocument(nsIAccessibleDocument** aDocument) |
|
144 { |
|
145 NS_ENSURE_ARG_POINTER(aDocument); |
|
146 |
|
147 NS_IF_ADDREF(*aDocument = Document()); |
|
148 return NS_OK; |
|
149 } |
|
150 |
|
151 NS_IMETHODIMP |
|
152 Accessible::GetDOMNode(nsIDOMNode** aDOMNode) |
|
153 { |
|
154 NS_ENSURE_ARG_POINTER(aDOMNode); |
|
155 *aDOMNode = nullptr; |
|
156 |
|
157 nsINode *node = GetNode(); |
|
158 if (node) |
|
159 CallQueryInterface(node, aDOMNode); |
|
160 |
|
161 return NS_OK; |
|
162 } |
|
163 |
|
164 NS_IMETHODIMP |
|
165 Accessible::GetRootDocument(nsIAccessibleDocument** aRootDocument) |
|
166 { |
|
167 NS_ENSURE_ARG_POINTER(aRootDocument); |
|
168 |
|
169 NS_IF_ADDREF(*aRootDocument = RootAccessible()); |
|
170 return NS_OK; |
|
171 } |
|
172 |
|
173 NS_IMETHODIMP |
|
174 Accessible::GetLanguage(nsAString& aLanguage) |
|
175 { |
|
176 Language(aLanguage); |
|
177 return NS_OK; |
|
178 } |
|
179 |
|
180 NS_IMETHODIMP |
|
181 Accessible::GetName(nsAString& aName) |
|
182 { |
|
183 aName.Truncate(); |
|
184 |
|
185 if (IsDefunct()) |
|
186 return NS_ERROR_FAILURE; |
|
187 |
|
188 nsAutoString name; |
|
189 Name(name); |
|
190 aName.Assign(name); |
|
191 |
|
192 return NS_OK; |
|
193 } |
|
194 |
|
195 ENameValueFlag |
|
196 Accessible::Name(nsString& aName) |
|
197 { |
|
198 aName.Truncate(); |
|
199 |
|
200 if (!HasOwnContent()) |
|
201 return eNameOK; |
|
202 |
|
203 ARIAName(aName); |
|
204 if (!aName.IsEmpty()) |
|
205 return eNameOK; |
|
206 |
|
207 nsCOMPtr<nsIXBLAccessible> xblAccessible(do_QueryInterface(mContent)); |
|
208 if (xblAccessible) { |
|
209 xblAccessible->GetAccessibleName(aName); |
|
210 if (!aName.IsEmpty()) |
|
211 return eNameOK; |
|
212 } |
|
213 |
|
214 ENameValueFlag nameFlag = NativeName(aName); |
|
215 if (!aName.IsEmpty()) |
|
216 return nameFlag; |
|
217 |
|
218 // In the end get the name from tooltip. |
|
219 if (mContent->IsHTML()) { |
|
220 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) { |
|
221 aName.CompressWhitespace(); |
|
222 return eNameFromTooltip; |
|
223 } |
|
224 } else if (mContent->IsXUL()) { |
|
225 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) { |
|
226 aName.CompressWhitespace(); |
|
227 return eNameFromTooltip; |
|
228 } |
|
229 } else if (mContent->IsSVG()) { |
|
230 // If user agents need to choose among multiple ‘desc’ or ‘title’ elements |
|
231 // for processing, the user agent shall choose the first one. |
|
232 for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
|
233 childElm = childElm->GetNextSibling()) { |
|
234 if (childElm->IsSVG(nsGkAtoms::desc)) { |
|
235 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName); |
|
236 return eNameFromTooltip; |
|
237 } |
|
238 } |
|
239 } |
|
240 |
|
241 if (nameFlag != eNoNameOnPurpose) |
|
242 aName.SetIsVoid(true); |
|
243 |
|
244 return nameFlag; |
|
245 } |
|
246 |
|
247 NS_IMETHODIMP |
|
248 Accessible::GetDescription(nsAString& aDescription) |
|
249 { |
|
250 if (IsDefunct()) |
|
251 return NS_ERROR_FAILURE; |
|
252 |
|
253 nsAutoString desc; |
|
254 Description(desc); |
|
255 aDescription.Assign(desc); |
|
256 |
|
257 return NS_OK; |
|
258 } |
|
259 |
|
260 void |
|
261 Accessible::Description(nsString& aDescription) |
|
262 { |
|
263 // There are 4 conditions that make an accessible have no accDescription: |
|
264 // 1. it's a text node; or |
|
265 // 2. It has no DHTML describedby property |
|
266 // 3. it doesn't have an accName; or |
|
267 // 4. its title attribute already equals to its accName nsAutoString name; |
|
268 |
|
269 if (!HasOwnContent() || mContent->IsNodeOfType(nsINode::eTEXT)) |
|
270 return; |
|
271 |
|
272 nsTextEquivUtils:: |
|
273 GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby, |
|
274 aDescription); |
|
275 |
|
276 if (aDescription.IsEmpty()) { |
|
277 bool isXUL = mContent->IsXUL(); |
|
278 if (isXUL) { |
|
279 // Try XUL <description control="[id]">description text</description> |
|
280 XULDescriptionIterator iter(Document(), mContent); |
|
281 Accessible* descr = nullptr; |
|
282 while ((descr = iter.Next())) { |
|
283 nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(), |
|
284 &aDescription); |
|
285 } |
|
286 } |
|
287 |
|
288 if (aDescription.IsEmpty()) { |
|
289 // Keep the Name() method logic. |
|
290 if (mContent->IsHTML()) { |
|
291 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aDescription); |
|
292 } else if (mContent->IsXUL()) { |
|
293 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aDescription); |
|
294 } else if (mContent->IsSVG()) { |
|
295 for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
|
296 childElm = childElm->GetNextSibling()) { |
|
297 if (childElm->IsSVG(nsGkAtoms::desc)) { |
|
298 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, |
|
299 &aDescription); |
|
300 break; |
|
301 } |
|
302 } |
|
303 } |
|
304 |
|
305 if (!aDescription.IsEmpty()) { |
|
306 nsAutoString name; |
|
307 ENameValueFlag nameFlag = Name(name); |
|
308 |
|
309 // Don't use tooltip for a description if it was used for a name. |
|
310 if (nameFlag == eNameFromTooltip) |
|
311 aDescription.Truncate(); |
|
312 } |
|
313 } |
|
314 } |
|
315 aDescription.CompressWhitespace(); |
|
316 } |
|
317 |
|
318 NS_IMETHODIMP |
|
319 Accessible::GetAccessKey(nsAString& aAccessKey) |
|
320 { |
|
321 aAccessKey.Truncate(); |
|
322 |
|
323 if (IsDefunct()) |
|
324 return NS_ERROR_FAILURE; |
|
325 |
|
326 AccessKey().ToString(aAccessKey); |
|
327 return NS_OK; |
|
328 } |
|
329 |
|
330 KeyBinding |
|
331 Accessible::AccessKey() const |
|
332 { |
|
333 if (!HasOwnContent()) |
|
334 return KeyBinding(); |
|
335 |
|
336 uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent); |
|
337 if (!key && mContent->IsElement()) { |
|
338 Accessible* label = nullptr; |
|
339 |
|
340 // Copy access key from label node. |
|
341 if (mContent->IsHTML()) { |
|
342 // Unless it is labeled via an ancestor <label>, in which case that would |
|
343 // be redundant. |
|
344 HTMLLabelIterator iter(Document(), this, |
|
345 HTMLLabelIterator::eSkipAncestorLabel); |
|
346 label = iter.Next(); |
|
347 |
|
348 } else if (mContent->IsXUL()) { |
|
349 XULLabelIterator iter(Document(), mContent); |
|
350 label = iter.Next(); |
|
351 } |
|
352 |
|
353 if (label) |
|
354 key = nsCoreUtils::GetAccessKeyFor(label->GetContent()); |
|
355 } |
|
356 |
|
357 if (!key) |
|
358 return KeyBinding(); |
|
359 |
|
360 // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1). |
|
361 switch (Preferences::GetInt("ui.key.generalAccessKey", -1)) { |
|
362 case -1: |
|
363 break; |
|
364 case nsIDOMKeyEvent::DOM_VK_SHIFT: |
|
365 return KeyBinding(key, KeyBinding::kShift); |
|
366 case nsIDOMKeyEvent::DOM_VK_CONTROL: |
|
367 return KeyBinding(key, KeyBinding::kControl); |
|
368 case nsIDOMKeyEvent::DOM_VK_ALT: |
|
369 return KeyBinding(key, KeyBinding::kAlt); |
|
370 case nsIDOMKeyEvent::DOM_VK_META: |
|
371 return KeyBinding(key, KeyBinding::kMeta); |
|
372 default: |
|
373 return KeyBinding(); |
|
374 } |
|
375 |
|
376 // Determine the access modifier used in this context. |
|
377 nsIDocument* document = mContent->GetCurrentDoc(); |
|
378 if (!document) |
|
379 return KeyBinding(); |
|
380 |
|
381 nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell()); |
|
382 if (!treeItem) |
|
383 return KeyBinding(); |
|
384 |
|
385 nsresult rv = NS_ERROR_FAILURE; |
|
386 int32_t modifierMask = 0; |
|
387 switch (treeItem->ItemType()) { |
|
388 case nsIDocShellTreeItem::typeChrome: |
|
389 rv = Preferences::GetInt("ui.key.chromeAccess", &modifierMask); |
|
390 break; |
|
391 case nsIDocShellTreeItem::typeContent: |
|
392 rv = Preferences::GetInt("ui.key.contentAccess", &modifierMask); |
|
393 break; |
|
394 } |
|
395 |
|
396 return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding(); |
|
397 } |
|
398 |
|
399 KeyBinding |
|
400 Accessible::KeyboardShortcut() const |
|
401 { |
|
402 return KeyBinding(); |
|
403 } |
|
404 |
|
405 NS_IMETHODIMP |
|
406 Accessible::GetParent(nsIAccessible** aParent) |
|
407 { |
|
408 NS_ENSURE_ARG_POINTER(aParent); |
|
409 if (IsDefunct()) |
|
410 return NS_ERROR_FAILURE; |
|
411 |
|
412 NS_IF_ADDREF(*aParent = Parent()); |
|
413 return *aParent ? NS_OK : NS_ERROR_FAILURE; |
|
414 } |
|
415 |
|
416 /* readonly attribute nsIAccessible nextSibling; */ |
|
417 NS_IMETHODIMP |
|
418 Accessible::GetNextSibling(nsIAccessible** aNextSibling) |
|
419 { |
|
420 NS_ENSURE_ARG_POINTER(aNextSibling); |
|
421 *aNextSibling = nullptr; |
|
422 |
|
423 if (IsDefunct()) |
|
424 return NS_ERROR_FAILURE; |
|
425 |
|
426 nsresult rv = NS_OK; |
|
427 NS_IF_ADDREF(*aNextSibling = GetSiblingAtOffset(1, &rv)); |
|
428 return rv; |
|
429 } |
|
430 |
|
431 /* readonly attribute nsIAccessible previousSibling; */ |
|
432 NS_IMETHODIMP |
|
433 Accessible::GetPreviousSibling(nsIAccessible ** aPreviousSibling) |
|
434 { |
|
435 NS_ENSURE_ARG_POINTER(aPreviousSibling); |
|
436 *aPreviousSibling = nullptr; |
|
437 |
|
438 if (IsDefunct()) |
|
439 return NS_ERROR_FAILURE; |
|
440 |
|
441 nsresult rv = NS_OK; |
|
442 NS_IF_ADDREF(*aPreviousSibling = GetSiblingAtOffset(-1, &rv)); |
|
443 return rv; |
|
444 } |
|
445 |
|
446 /* readonly attribute nsIAccessible firstChild; */ |
|
447 NS_IMETHODIMP |
|
448 Accessible::GetFirstChild(nsIAccessible** aFirstChild) |
|
449 { |
|
450 NS_ENSURE_ARG_POINTER(aFirstChild); |
|
451 *aFirstChild = nullptr; |
|
452 |
|
453 if (IsDefunct()) |
|
454 return NS_ERROR_FAILURE; |
|
455 |
|
456 NS_IF_ADDREF(*aFirstChild = FirstChild()); |
|
457 return NS_OK; |
|
458 } |
|
459 |
|
460 /* readonly attribute nsIAccessible lastChild; */ |
|
461 NS_IMETHODIMP |
|
462 Accessible::GetLastChild(nsIAccessible** aLastChild) |
|
463 { |
|
464 NS_ENSURE_ARG_POINTER(aLastChild); |
|
465 *aLastChild = nullptr; |
|
466 |
|
467 if (IsDefunct()) |
|
468 return NS_ERROR_FAILURE; |
|
469 |
|
470 NS_IF_ADDREF(*aLastChild = LastChild()); |
|
471 return NS_OK; |
|
472 } |
|
473 |
|
474 NS_IMETHODIMP |
|
475 Accessible::GetChildAt(int32_t aChildIndex, nsIAccessible** aChild) |
|
476 { |
|
477 NS_ENSURE_ARG_POINTER(aChild); |
|
478 *aChild = nullptr; |
|
479 |
|
480 if (IsDefunct()) |
|
481 return NS_ERROR_FAILURE; |
|
482 |
|
483 // If child index is negative, then return last child. |
|
484 // XXX: do we really need this? |
|
485 if (aChildIndex < 0) |
|
486 aChildIndex = ChildCount() - 1; |
|
487 |
|
488 Accessible* child = GetChildAt(aChildIndex); |
|
489 if (!child) |
|
490 return NS_ERROR_INVALID_ARG; |
|
491 |
|
492 NS_ADDREF(*aChild = child); |
|
493 return NS_OK; |
|
494 } |
|
495 |
|
496 // readonly attribute nsIArray children; |
|
497 NS_IMETHODIMP |
|
498 Accessible::GetChildren(nsIArray** aOutChildren) |
|
499 { |
|
500 NS_ENSURE_ARG_POINTER(aOutChildren); |
|
501 *aOutChildren = nullptr; |
|
502 |
|
503 if (IsDefunct()) |
|
504 return NS_ERROR_FAILURE; |
|
505 |
|
506 nsresult rv = NS_OK; |
|
507 nsCOMPtr<nsIMutableArray> children = |
|
508 do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); |
|
509 NS_ENSURE_SUCCESS(rv, rv); |
|
510 |
|
511 uint32_t childCount = ChildCount(); |
|
512 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { |
|
513 nsIAccessible* child = GetChildAt(childIdx); |
|
514 children->AppendElement(child, false); |
|
515 } |
|
516 |
|
517 NS_ADDREF(*aOutChildren = children); |
|
518 return NS_OK; |
|
519 } |
|
520 |
|
521 bool |
|
522 Accessible::CanHaveAnonChildren() |
|
523 { |
|
524 return true; |
|
525 } |
|
526 |
|
527 /* readonly attribute long childCount; */ |
|
528 NS_IMETHODIMP |
|
529 Accessible::GetChildCount(int32_t* aChildCount) |
|
530 { |
|
531 NS_ENSURE_ARG_POINTER(aChildCount); |
|
532 |
|
533 if (IsDefunct()) |
|
534 return NS_ERROR_FAILURE; |
|
535 |
|
536 *aChildCount = ChildCount(); |
|
537 return NS_OK; |
|
538 } |
|
539 |
|
540 /* readonly attribute long indexInParent; */ |
|
541 NS_IMETHODIMP |
|
542 Accessible::GetIndexInParent(int32_t* aIndexInParent) |
|
543 { |
|
544 NS_ENSURE_ARG_POINTER(aIndexInParent); |
|
545 |
|
546 *aIndexInParent = IndexInParent(); |
|
547 return *aIndexInParent != -1 ? NS_OK : NS_ERROR_FAILURE; |
|
548 } |
|
549 |
|
550 void |
|
551 Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut) |
|
552 { |
|
553 nsCOMPtr<nsIStringBundleService> stringBundleService = |
|
554 services::GetStringBundleService(); |
|
555 if (!stringBundleService) |
|
556 return; |
|
557 |
|
558 nsCOMPtr<nsIStringBundle> stringBundle; |
|
559 stringBundleService->CreateBundle( |
|
560 "chrome://global-platform/locale/accessible.properties", |
|
561 getter_AddRefs(stringBundle)); |
|
562 if (!stringBundle) |
|
563 return; |
|
564 |
|
565 nsXPIDLString xsValue; |
|
566 nsresult rv = stringBundle->GetStringFromName(aKey.get(), getter_Copies(xsValue)); |
|
567 if (NS_SUCCEEDED(rv)) |
|
568 aStringOut.Assign(xsValue); |
|
569 } |
|
570 |
|
571 uint64_t |
|
572 Accessible::VisibilityState() |
|
573 { |
|
574 nsIFrame* frame = GetFrame(); |
|
575 if (!frame) |
|
576 return states::INVISIBLE; |
|
577 |
|
578 // Walk the parent frame chain to see if there's invisible parent or the frame |
|
579 // is in background tab. |
|
580 if (!frame->StyleVisibility()->IsVisible()) |
|
581 return states::INVISIBLE; |
|
582 |
|
583 nsIFrame* curFrame = frame; |
|
584 do { |
|
585 nsView* view = curFrame->GetView(); |
|
586 if (view && view->GetVisibility() == nsViewVisibility_kHide) |
|
587 return states::INVISIBLE; |
|
588 |
|
589 if (nsLayoutUtils::IsPopup(curFrame)) |
|
590 return 0; |
|
591 |
|
592 // Offscreen state for background tab content and invisible for not selected |
|
593 // deck panel. |
|
594 nsIFrame* parentFrame = curFrame->GetParent(); |
|
595 nsDeckFrame* deckFrame = do_QueryFrame(parentFrame); |
|
596 if (deckFrame && deckFrame->GetSelectedBox() != curFrame) { |
|
597 if (deckFrame->GetContent()->IsXUL() && |
|
598 deckFrame->GetContent()->Tag() == nsGkAtoms::tabpanels) |
|
599 return states::OFFSCREEN; |
|
600 |
|
601 NS_NOTREACHED("Children of not selected deck panel are not accessible."); |
|
602 return states::INVISIBLE; |
|
603 } |
|
604 |
|
605 // If contained by scrollable frame then check that at least 12 pixels |
|
606 // around the object is visible, otherwise the object is offscreen. |
|
607 nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame); |
|
608 if (scrollableFrame) { |
|
609 nsRect scrollPortRect = scrollableFrame->GetScrollPortRect(); |
|
610 nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor( |
|
611 frame, frame->GetRectRelativeToSelf(), parentFrame); |
|
612 if (!scrollPortRect.Contains(frameRect)) { |
|
613 const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12); |
|
614 scrollPortRect.Deflate(kMinPixels, kMinPixels); |
|
615 if (!scrollPortRect.Intersects(frameRect)) |
|
616 return states::OFFSCREEN; |
|
617 } |
|
618 } |
|
619 |
|
620 if (!parentFrame) { |
|
621 parentFrame = nsLayoutUtils::GetCrossDocParentFrame(curFrame); |
|
622 if (parentFrame && !parentFrame->StyleVisibility()->IsVisible()) |
|
623 return states::INVISIBLE; |
|
624 } |
|
625 |
|
626 curFrame = parentFrame; |
|
627 } while (curFrame); |
|
628 |
|
629 // Zero area rects can occur in the first frame of a multi-frame text flow, |
|
630 // in which case the rendered text is not empty and the frame should not be |
|
631 // marked invisible. |
|
632 // XXX Can we just remove this check? Why do we need to mark empty |
|
633 // text invisible? |
|
634 if (frame->GetType() == nsGkAtoms::textFrame && |
|
635 !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && |
|
636 frame->GetRect().IsEmpty()) { |
|
637 nsAutoString renderedText; |
|
638 frame->GetRenderedText(&renderedText, nullptr, nullptr, 0, 1); |
|
639 if (renderedText.IsEmpty()) |
|
640 return states::INVISIBLE; |
|
641 } |
|
642 |
|
643 return 0; |
|
644 } |
|
645 |
|
646 uint64_t |
|
647 Accessible::NativeState() |
|
648 { |
|
649 uint64_t state = 0; |
|
650 |
|
651 if (!IsInDocument()) |
|
652 state |= states::STALE; |
|
653 |
|
654 if (HasOwnContent() && mContent->IsElement()) { |
|
655 EventStates elementState = mContent->AsElement()->State(); |
|
656 |
|
657 if (elementState.HasState(NS_EVENT_STATE_INVALID)) |
|
658 state |= states::INVALID; |
|
659 |
|
660 if (elementState.HasState(NS_EVENT_STATE_REQUIRED)) |
|
661 state |= states::REQUIRED; |
|
662 |
|
663 state |= NativeInteractiveState(); |
|
664 if (FocusMgr()->IsFocused(this)) |
|
665 state |= states::FOCUSED; |
|
666 } |
|
667 |
|
668 // Gather states::INVISIBLE and states::OFFSCREEN flags for this object. |
|
669 state |= VisibilityState(); |
|
670 |
|
671 nsIFrame *frame = GetFrame(); |
|
672 if (frame) { |
|
673 if (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) |
|
674 state |= states::FLOATING; |
|
675 |
|
676 // XXX we should look at layout for non XUL box frames, but need to decide |
|
677 // how that interacts with ARIA. |
|
678 if (HasOwnContent() && mContent->IsXUL() && frame->IsBoxFrame()) { |
|
679 const nsStyleXUL* xulStyle = frame->StyleXUL(); |
|
680 if (xulStyle && frame->IsBoxFrame()) { |
|
681 // In XUL all boxes are either vertical or horizontal |
|
682 if (xulStyle->mBoxOrient == NS_STYLE_BOX_ORIENT_VERTICAL) |
|
683 state |= states::VERTICAL; |
|
684 else |
|
685 state |= states::HORIZONTAL; |
|
686 } |
|
687 } |
|
688 } |
|
689 |
|
690 // Check if a XUL element has the popup attribute (an attached popup menu). |
|
691 if (HasOwnContent() && mContent->IsXUL() && |
|
692 mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) |
|
693 state |= states::HASPOPUP; |
|
694 |
|
695 // Bypass the link states specialization for non links. |
|
696 if (!mRoleMapEntry || mRoleMapEntry->roleRule == kUseNativeRole || |
|
697 mRoleMapEntry->role == roles::LINK) |
|
698 state |= NativeLinkState(); |
|
699 |
|
700 return state; |
|
701 } |
|
702 |
|
703 uint64_t |
|
704 Accessible::NativeInteractiveState() const |
|
705 { |
|
706 if (!mContent->IsElement()) |
|
707 return 0; |
|
708 |
|
709 if (NativelyUnavailable()) |
|
710 return states::UNAVAILABLE; |
|
711 |
|
712 nsIFrame* frame = GetFrame(); |
|
713 if (frame && frame->IsFocusable()) |
|
714 return states::FOCUSABLE; |
|
715 |
|
716 return 0; |
|
717 } |
|
718 |
|
719 uint64_t |
|
720 Accessible::NativeLinkState() const |
|
721 { |
|
722 return 0; |
|
723 } |
|
724 |
|
725 bool |
|
726 Accessible::NativelyUnavailable() const |
|
727 { |
|
728 if (mContent->IsHTML()) |
|
729 return mContent->AsElement()->State().HasState(NS_EVENT_STATE_DISABLED); |
|
730 |
|
731 return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, |
|
732 nsGkAtoms::_true, eCaseMatters); |
|
733 } |
|
734 |
|
735 /* readonly attribute boolean focusedChild; */ |
|
736 NS_IMETHODIMP |
|
737 Accessible::GetFocusedChild(nsIAccessible** aChild) |
|
738 { |
|
739 NS_ENSURE_ARG_POINTER(aChild); |
|
740 *aChild = nullptr; |
|
741 |
|
742 if (IsDefunct()) |
|
743 return NS_ERROR_FAILURE; |
|
744 |
|
745 NS_IF_ADDREF(*aChild = FocusedChild()); |
|
746 return NS_OK; |
|
747 } |
|
748 |
|
749 Accessible* |
|
750 Accessible::FocusedChild() |
|
751 { |
|
752 Accessible* focus = FocusMgr()->FocusedAccessible(); |
|
753 if (focus && (focus == this || focus->Parent() == this)) |
|
754 return focus; |
|
755 |
|
756 return nullptr; |
|
757 } |
|
758 |
|
759 Accessible* |
|
760 Accessible::ChildAtPoint(int32_t aX, int32_t aY, |
|
761 EWhichChildAtPoint aWhichChild) |
|
762 { |
|
763 // If we can't find the point in a child, we will return the fallback answer: |
|
764 // we return |this| if the point is within it, otherwise nullptr. |
|
765 int32_t x = 0, y = 0, width = 0, height = 0; |
|
766 nsresult rv = GetBounds(&x, &y, &width, &height); |
|
767 NS_ENSURE_SUCCESS(rv, nullptr); |
|
768 |
|
769 Accessible* fallbackAnswer = nullptr; |
|
770 if (aX >= x && aX < x + width && aY >= y && aY < y + height) |
|
771 fallbackAnswer = this; |
|
772 |
|
773 if (nsAccUtils::MustPrune(this)) // Do not dig any further |
|
774 return fallbackAnswer; |
|
775 |
|
776 // Search an accessible at the given point starting from accessible document |
|
777 // because containing block (see CSS2) for out of flow element (for example, |
|
778 // absolutely positioned element) may be different from its DOM parent and |
|
779 // therefore accessible for containing block may be different from accessible |
|
780 // for DOM parent but GetFrameForPoint() should be called for containing block |
|
781 // to get an out of flow element. |
|
782 DocAccessible* accDocument = Document(); |
|
783 NS_ENSURE_TRUE(accDocument, nullptr); |
|
784 |
|
785 nsIFrame* rootFrame = accDocument->GetFrame(); |
|
786 NS_ENSURE_TRUE(rootFrame, nullptr); |
|
787 |
|
788 nsIFrame* startFrame = rootFrame; |
|
789 |
|
790 // Check whether the point is at popup content. |
|
791 nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr); |
|
792 NS_ENSURE_TRUE(rootWidget, nullptr); |
|
793 |
|
794 nsIntRect rootRect; |
|
795 rootWidget->GetScreenBounds(rootRect); |
|
796 |
|
797 WidgetMouseEvent dummyEvent(true, NS_MOUSE_MOVE, rootWidget, |
|
798 WidgetMouseEvent::eSynthesized); |
|
799 dummyEvent.refPoint = LayoutDeviceIntPoint(aX - rootRect.x, aY - rootRect.y); |
|
800 |
|
801 nsIFrame* popupFrame = nsLayoutUtils:: |
|
802 GetPopupFrameForEventCoordinates(accDocument->PresContext()->GetRootPresContext(), |
|
803 &dummyEvent); |
|
804 if (popupFrame) { |
|
805 // If 'this' accessible is not inside the popup then ignore the popup when |
|
806 // searching an accessible at point. |
|
807 DocAccessible* popupDoc = |
|
808 GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc()); |
|
809 Accessible* popupAcc = |
|
810 popupDoc->GetAccessibleOrContainer(popupFrame->GetContent()); |
|
811 Accessible* popupChild = this; |
|
812 while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) |
|
813 popupChild = popupChild->Parent(); |
|
814 |
|
815 if (popupChild == popupAcc) |
|
816 startFrame = popupFrame; |
|
817 } |
|
818 |
|
819 nsPresContext* presContext = startFrame->PresContext(); |
|
820 nsRect screenRect = startFrame->GetScreenRectInAppUnits(); |
|
821 nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.x, |
|
822 presContext->DevPixelsToAppUnits(aY) - screenRect.y); |
|
823 nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(startFrame, offset); |
|
824 |
|
825 nsIContent* content = nullptr; |
|
826 if (!foundFrame || !(content = foundFrame->GetContent())) |
|
827 return fallbackAnswer; |
|
828 |
|
829 // Get accessible for the node with the point or the first accessible in |
|
830 // the DOM parent chain. |
|
831 DocAccessible* contentDocAcc = GetAccService()-> |
|
832 GetDocAccessible(content->OwnerDoc()); |
|
833 |
|
834 // contentDocAcc in some circumstances can be nullptr. See bug 729861 |
|
835 NS_ASSERTION(contentDocAcc, "could not get the document accessible"); |
|
836 if (!contentDocAcc) |
|
837 return fallbackAnswer; |
|
838 |
|
839 Accessible* accessible = contentDocAcc->GetAccessibleOrContainer(content); |
|
840 if (!accessible) |
|
841 return fallbackAnswer; |
|
842 |
|
843 // Hurray! We have an accessible for the frame that layout gave us. |
|
844 // Since DOM node of obtained accessible may be out of flow then we should |
|
845 // ensure obtained accessible is a child of this accessible. |
|
846 Accessible* child = accessible; |
|
847 while (child != this) { |
|
848 Accessible* parent = child->Parent(); |
|
849 if (!parent) { |
|
850 // Reached the top of the hierarchy. These bounds were inside an |
|
851 // accessible that is not a descendant of this one. |
|
852 return fallbackAnswer; |
|
853 } |
|
854 |
|
855 // If we landed on a legitimate child of |this|, and we want the direct |
|
856 // child, return it here. |
|
857 if (parent == this && aWhichChild == eDirectChild) |
|
858 return child; |
|
859 |
|
860 child = parent; |
|
861 } |
|
862 |
|
863 // Manually walk through accessible children and see if the are within this |
|
864 // point. Skip offscreen or invisible accessibles. This takes care of cases |
|
865 // where layout won't walk into things for us, such as image map areas and |
|
866 // sub documents (XXX: subdocuments should be handled by methods of |
|
867 // OuterDocAccessibles). |
|
868 uint32_t childCount = accessible->ChildCount(); |
|
869 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { |
|
870 Accessible* child = accessible->GetChildAt(childIdx); |
|
871 |
|
872 int32_t childX, childY, childWidth, childHeight; |
|
873 child->GetBounds(&childX, &childY, &childWidth, &childHeight); |
|
874 if (aX >= childX && aX < childX + childWidth && |
|
875 aY >= childY && aY < childY + childHeight && |
|
876 (child->State() & states::INVISIBLE) == 0) { |
|
877 |
|
878 if (aWhichChild == eDeepestChild) |
|
879 return child->ChildAtPoint(aX, aY, eDeepestChild); |
|
880 |
|
881 return child; |
|
882 } |
|
883 } |
|
884 |
|
885 return accessible; |
|
886 } |
|
887 |
|
888 // nsIAccessible getChildAtPoint(in long x, in long y) |
|
889 NS_IMETHODIMP |
|
890 Accessible::GetChildAtPoint(int32_t aX, int32_t aY, |
|
891 nsIAccessible** aAccessible) |
|
892 { |
|
893 NS_ENSURE_ARG_POINTER(aAccessible); |
|
894 *aAccessible = nullptr; |
|
895 |
|
896 if (IsDefunct()) |
|
897 return NS_ERROR_FAILURE; |
|
898 |
|
899 NS_IF_ADDREF(*aAccessible = ChildAtPoint(aX, aY, eDirectChild)); |
|
900 return NS_OK; |
|
901 } |
|
902 |
|
903 // nsIAccessible getDeepestChildAtPoint(in long x, in long y) |
|
904 NS_IMETHODIMP |
|
905 Accessible::GetDeepestChildAtPoint(int32_t aX, int32_t aY, |
|
906 nsIAccessible** aAccessible) |
|
907 { |
|
908 NS_ENSURE_ARG_POINTER(aAccessible); |
|
909 *aAccessible = nullptr; |
|
910 |
|
911 if (IsDefunct()) |
|
912 return NS_ERROR_FAILURE; |
|
913 |
|
914 NS_IF_ADDREF(*aAccessible = ChildAtPoint(aX, aY, eDeepestChild)); |
|
915 return NS_OK; |
|
916 } |
|
917 |
|
918 void |
|
919 Accessible::GetBoundsRect(nsRect& aTotalBounds, nsIFrame** aBoundingFrame) |
|
920 { |
|
921 nsIFrame* frame = GetFrame(); |
|
922 if (frame && mContent) { |
|
923 nsRect* hitRegionRect = static_cast<nsRect*>(mContent->GetProperty(nsGkAtoms::hitregion)); |
|
924 |
|
925 if (hitRegionRect) { |
|
926 // This is for canvas fallback content |
|
927 // Find a canvas frame the found hit region is relative to. |
|
928 nsIFrame* canvasFrame = frame->GetParent(); |
|
929 while (canvasFrame && (canvasFrame->GetType() != nsGkAtoms::HTMLCanvasFrame)) |
|
930 canvasFrame = canvasFrame->GetParent(); |
|
931 |
|
932 // make the canvas the bounding frame |
|
933 if (canvasFrame) { |
|
934 *aBoundingFrame = canvasFrame; |
|
935 |
|
936 aTotalBounds = *hitRegionRect; |
|
937 |
|
938 return; |
|
939 } |
|
940 } |
|
941 |
|
942 *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame); |
|
943 aTotalBounds = nsLayoutUtils:: |
|
944 GetAllInFlowRectsUnion(frame, *aBoundingFrame, |
|
945 nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS); |
|
946 } |
|
947 } |
|
948 |
|
949 /* void getBounds (out long x, out long y, out long width, out long height); */ |
|
950 NS_IMETHODIMP |
|
951 Accessible::GetBounds(int32_t* aX, int32_t* aY, |
|
952 int32_t* aWidth, int32_t* aHeight) |
|
953 { |
|
954 NS_ENSURE_ARG_POINTER(aX); |
|
955 *aX = 0; |
|
956 NS_ENSURE_ARG_POINTER(aY); |
|
957 *aY = 0; |
|
958 NS_ENSURE_ARG_POINTER(aWidth); |
|
959 *aWidth = 0; |
|
960 NS_ENSURE_ARG_POINTER(aHeight); |
|
961 *aHeight = 0; |
|
962 |
|
963 if (IsDefunct()) |
|
964 return NS_ERROR_FAILURE; |
|
965 |
|
966 // This routine will get the entire rectangle for all the frames in this node. |
|
967 // ------------------------------------------------------------------------- |
|
968 // Primary Frame for node |
|
969 // Another frame, same node <- Example |
|
970 // Another frame, same node |
|
971 |
|
972 nsRect unionRectTwips; |
|
973 nsIFrame* boundingFrame = nullptr; |
|
974 GetBoundsRect(unionRectTwips, &boundingFrame); // Unions up all primary frames for this node and all siblings after it |
|
975 NS_ENSURE_STATE(boundingFrame); |
|
976 |
|
977 nsPresContext* presContext = mDoc->PresContext(); |
|
978 *aX = presContext->AppUnitsToDevPixels(unionRectTwips.x); |
|
979 *aY = presContext->AppUnitsToDevPixels(unionRectTwips.y); |
|
980 *aWidth = presContext->AppUnitsToDevPixels(unionRectTwips.width); |
|
981 *aHeight = presContext->AppUnitsToDevPixels(unionRectTwips.height); |
|
982 |
|
983 // We have the union of the rectangle, now we need to put it in absolute screen coords |
|
984 nsIntRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits(). |
|
985 ToNearestPixels(presContext->AppUnitsPerDevPixel()); |
|
986 *aX += orgRectPixels.x; |
|
987 *aY += orgRectPixels.y; |
|
988 |
|
989 return NS_OK; |
|
990 } |
|
991 |
|
992 NS_IMETHODIMP |
|
993 Accessible::SetSelected(bool aSelect) |
|
994 { |
|
995 if (IsDefunct()) |
|
996 return NS_ERROR_FAILURE; |
|
997 |
|
998 if (!HasOwnContent()) |
|
999 return NS_OK; |
|
1000 |
|
1001 Accessible* select = nsAccUtils::GetSelectableContainer(this, State()); |
|
1002 if (select) { |
|
1003 if (select->State() & states::MULTISELECTABLE) { |
|
1004 if (mRoleMapEntry) { |
|
1005 if (aSelect) { |
|
1006 return mContent->SetAttr(kNameSpaceID_None, |
|
1007 nsGkAtoms::aria_selected, |
|
1008 NS_LITERAL_STRING("true"), true); |
|
1009 } |
|
1010 return mContent->UnsetAttr(kNameSpaceID_None, |
|
1011 nsGkAtoms::aria_selected, true); |
|
1012 } |
|
1013 |
|
1014 return NS_OK; |
|
1015 } |
|
1016 |
|
1017 return aSelect ? TakeFocus() : NS_ERROR_FAILURE; |
|
1018 } |
|
1019 |
|
1020 return NS_OK; |
|
1021 } |
|
1022 |
|
1023 NS_IMETHODIMP |
|
1024 Accessible::TakeSelection() |
|
1025 { |
|
1026 if (IsDefunct()) |
|
1027 return NS_ERROR_FAILURE; |
|
1028 |
|
1029 Accessible* select = nsAccUtils::GetSelectableContainer(this, State()); |
|
1030 if (select) { |
|
1031 if (select->State() & states::MULTISELECTABLE) |
|
1032 select->UnselectAll(); |
|
1033 return SetSelected(true); |
|
1034 } |
|
1035 |
|
1036 return NS_ERROR_FAILURE; |
|
1037 } |
|
1038 |
|
1039 NS_IMETHODIMP |
|
1040 Accessible::TakeFocus() |
|
1041 { |
|
1042 if (IsDefunct()) |
|
1043 return NS_ERROR_FAILURE; |
|
1044 |
|
1045 nsIFrame *frame = GetFrame(); |
|
1046 NS_ENSURE_STATE(frame); |
|
1047 |
|
1048 nsIContent* focusContent = mContent; |
|
1049 |
|
1050 // If the accessible focus is managed by container widget then focus the |
|
1051 // widget and set the accessible as its current item. |
|
1052 if (!frame->IsFocusable()) { |
|
1053 Accessible* widget = ContainerWidget(); |
|
1054 if (widget && widget->AreItemsOperable()) { |
|
1055 nsIContent* widgetElm = widget->GetContent(); |
|
1056 nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame(); |
|
1057 if (widgetFrame && widgetFrame->IsFocusable()) { |
|
1058 focusContent = widgetElm; |
|
1059 widget->SetCurrentItem(this); |
|
1060 } |
|
1061 } |
|
1062 } |
|
1063 |
|
1064 nsCOMPtr<nsIDOMElement> element(do_QueryInterface(focusContent)); |
|
1065 nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
|
1066 if (fm) |
|
1067 fm->SetFocus(element, 0); |
|
1068 |
|
1069 return NS_OK; |
|
1070 } |
|
1071 |
|
1072 void |
|
1073 Accessible::XULElmName(DocAccessible* aDocument, |
|
1074 nsIContent* aElm, nsString& aName) |
|
1075 { |
|
1076 /** |
|
1077 * 3 main cases for XUL Controls to be labeled |
|
1078 * 1 - control contains label="foo" |
|
1079 * 2 - control has, as a child, a label element |
|
1080 * - label has either value="foo" or children |
|
1081 * 3 - non-child label contains control="controlID" |
|
1082 * - label has either value="foo" or children |
|
1083 * Once a label is found, the search is discontinued, so a control |
|
1084 * that has a label child as well as having a label external to |
|
1085 * the control that uses the control="controlID" syntax will use |
|
1086 * the child label for its Name. |
|
1087 */ |
|
1088 |
|
1089 // CASE #1 (via label attribute) -- great majority of the cases |
|
1090 nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl = do_QueryInterface(aElm); |
|
1091 if (labeledEl) { |
|
1092 labeledEl->GetLabel(aName); |
|
1093 } else { |
|
1094 nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl = do_QueryInterface(aElm); |
|
1095 if (itemEl) { |
|
1096 itemEl->GetLabel(aName); |
|
1097 } else { |
|
1098 nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(aElm); |
|
1099 // Use label if this is not a select control element which |
|
1100 // uses label attribute to indicate which option is selected |
|
1101 if (!select) { |
|
1102 nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aElm)); |
|
1103 if (xulEl) |
|
1104 xulEl->GetAttribute(NS_LITERAL_STRING("label"), aName); |
|
1105 } |
|
1106 } |
|
1107 } |
|
1108 |
|
1109 // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label> |
|
1110 if (aName.IsEmpty()) { |
|
1111 Accessible* labelAcc = nullptr; |
|
1112 XULLabelIterator iter(aDocument, aElm); |
|
1113 while ((labelAcc = iter.Next())) { |
|
1114 nsCOMPtr<nsIDOMXULLabelElement> xulLabel = |
|
1115 do_QueryInterface(labelAcc->GetContent()); |
|
1116 // Check if label's value attribute is used |
|
1117 if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(aName)) && aName.IsEmpty()) { |
|
1118 // If no value attribute, a non-empty label must contain |
|
1119 // children that define its text -- possibly using HTML |
|
1120 nsTextEquivUtils:: |
|
1121 AppendTextEquivFromContent(labelAcc, labelAcc->GetContent(), &aName); |
|
1122 } |
|
1123 } |
|
1124 } |
|
1125 |
|
1126 aName.CompressWhitespace(); |
|
1127 if (!aName.IsEmpty()) |
|
1128 return; |
|
1129 |
|
1130 // Can get text from title of <toolbaritem> if we're a child of a <toolbaritem> |
|
1131 nsIContent *bindingParent = aElm->GetBindingParent(); |
|
1132 nsIContent* parent = |
|
1133 bindingParent? bindingParent->GetParent() : aElm->GetParent(); |
|
1134 while (parent) { |
|
1135 if (parent->Tag() == nsGkAtoms::toolbaritem && |
|
1136 parent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) { |
|
1137 aName.CompressWhitespace(); |
|
1138 return; |
|
1139 } |
|
1140 parent = parent->GetParent(); |
|
1141 } |
|
1142 } |
|
1143 |
|
1144 nsresult |
|
1145 Accessible::HandleAccEvent(AccEvent* aEvent) |
|
1146 { |
|
1147 NS_ENSURE_ARG_POINTER(aEvent); |
|
1148 |
|
1149 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); |
|
1150 NS_ENSURE_TRUE(obsService, NS_ERROR_FAILURE); |
|
1151 |
|
1152 nsCOMPtr<nsISimpleEnumerator> observers; |
|
1153 obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC, |
|
1154 getter_AddRefs(observers)); |
|
1155 |
|
1156 NS_ENSURE_STATE(observers); |
|
1157 |
|
1158 bool hasObservers = false; |
|
1159 observers->HasMoreElements(&hasObservers); |
|
1160 if (hasObservers) { |
|
1161 nsCOMPtr<nsIAccessibleEvent> event = MakeXPCEvent(aEvent); |
|
1162 return obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr); |
|
1163 } |
|
1164 |
|
1165 return NS_OK; |
|
1166 } |
|
1167 |
|
1168 NS_IMETHODIMP |
|
1169 Accessible::GetRole(uint32_t *aRole) |
|
1170 { |
|
1171 NS_ENSURE_ARG_POINTER(aRole); |
|
1172 *aRole = nsIAccessibleRole::ROLE_NOTHING; |
|
1173 |
|
1174 if (IsDefunct()) |
|
1175 return NS_ERROR_FAILURE; |
|
1176 |
|
1177 *aRole = Role(); |
|
1178 return NS_OK; |
|
1179 } |
|
1180 |
|
1181 NS_IMETHODIMP |
|
1182 Accessible::GetAttributes(nsIPersistentProperties** aAttributes) |
|
1183 { |
|
1184 NS_ENSURE_ARG_POINTER(aAttributes); |
|
1185 *aAttributes = nullptr; |
|
1186 |
|
1187 if (IsDefunct()) |
|
1188 return NS_ERROR_FAILURE; |
|
1189 |
|
1190 nsCOMPtr<nsIPersistentProperties> attributes = Attributes(); |
|
1191 attributes.swap(*aAttributes); |
|
1192 |
|
1193 return NS_OK; |
|
1194 } |
|
1195 |
|
1196 already_AddRefed<nsIPersistentProperties> |
|
1197 Accessible::Attributes() |
|
1198 { |
|
1199 nsCOMPtr<nsIPersistentProperties> attributes = NativeAttributes(); |
|
1200 if (!HasOwnContent() || !mContent->IsElement()) |
|
1201 return attributes.forget(); |
|
1202 |
|
1203 // 'xml-roles' attribute coming from ARIA. |
|
1204 nsAutoString xmlRoles, unused; |
|
1205 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::role, xmlRoles)) { |
|
1206 attributes->SetStringProperty(NS_LITERAL_CSTRING("xml-roles"), |
|
1207 xmlRoles, unused); |
|
1208 } |
|
1209 |
|
1210 // Expose object attributes from ARIA attributes. |
|
1211 aria::AttrIterator attribIter(mContent); |
|
1212 nsAutoString name, value; |
|
1213 while(attribIter.Next(name, value)) |
|
1214 attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused); |
|
1215 |
|
1216 // If there is no aria-live attribute then expose default value of 'live' |
|
1217 // object attribute used for ARIA role of this accessible. |
|
1218 if (mRoleMapEntry) { |
|
1219 nsAutoString live; |
|
1220 nsAccUtils::GetAccAttr(attributes, nsGkAtoms::live, live); |
|
1221 if (live.IsEmpty()) { |
|
1222 if (nsAccUtils::GetLiveAttrValue(mRoleMapEntry->liveAttRule, live)) |
|
1223 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::live, live); |
|
1224 } |
|
1225 } |
|
1226 |
|
1227 return attributes.forget(); |
|
1228 } |
|
1229 |
|
1230 already_AddRefed<nsIPersistentProperties> |
|
1231 Accessible::NativeAttributes() |
|
1232 { |
|
1233 nsCOMPtr<nsIPersistentProperties> attributes = |
|
1234 do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); |
|
1235 |
|
1236 nsAutoString unused; |
|
1237 |
|
1238 // We support values, so expose the string value as well, via the valuetext |
|
1239 // object attribute. We test for the value interface because we don't want |
|
1240 // to expose traditional Value() information such as URL's on links and |
|
1241 // documents, or text in an input. |
|
1242 if (HasNumericValue()) { |
|
1243 nsAutoString valuetext; |
|
1244 GetValue(valuetext); |
|
1245 attributes->SetStringProperty(NS_LITERAL_CSTRING("valuetext"), valuetext, |
|
1246 unused); |
|
1247 } |
|
1248 |
|
1249 // Expose checkable object attribute if the accessible has checkable state |
|
1250 if (State() & states::CHECKABLE) { |
|
1251 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::checkable, |
|
1252 NS_LITERAL_STRING("true")); |
|
1253 } |
|
1254 |
|
1255 // Expose 'explicit-name' attribute. |
|
1256 nsAutoString name; |
|
1257 if (Name(name) != eNameFromSubtree && !name.IsVoid()) { |
|
1258 attributes->SetStringProperty(NS_LITERAL_CSTRING("explicit-name"), |
|
1259 NS_LITERAL_STRING("true"), unused); |
|
1260 } |
|
1261 |
|
1262 // Group attributes (level/setsize/posinset) |
|
1263 GroupPos groupPos = GroupPosition(); |
|
1264 nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, |
|
1265 groupPos.setSize, groupPos.posInSet); |
|
1266 |
|
1267 // If the accessible doesn't have own content (such as list item bullet or |
|
1268 // xul tree item) then don't calculate content based attributes. |
|
1269 if (!HasOwnContent()) |
|
1270 return attributes.forget(); |
|
1271 |
|
1272 nsEventShell::GetEventAttributes(GetNode(), attributes); |
|
1273 |
|
1274 // Get container-foo computed live region properties based on the closest |
|
1275 // container with the live region attribute. Inner nodes override outer nodes |
|
1276 // within the same document. The inner nodes can be used to override live |
|
1277 // region behavior on more general outer nodes. However, nodes in outer |
|
1278 // documents override nodes in inner documents: outer doc author may want to |
|
1279 // override properties on a widget they used in an iframe. |
|
1280 nsIContent* startContent = mContent; |
|
1281 while (startContent) { |
|
1282 nsIDocument* doc = startContent->GetDocument(); |
|
1283 if (!doc) |
|
1284 break; |
|
1285 |
|
1286 nsAccUtils::SetLiveContainerAttributes(attributes, startContent, |
|
1287 nsCoreUtils::GetRoleContent(doc)); |
|
1288 |
|
1289 // Allow ARIA live region markup from outer documents to override |
|
1290 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell(); |
|
1291 if (!docShellTreeItem) |
|
1292 break; |
|
1293 |
|
1294 nsCOMPtr<nsIDocShellTreeItem> sameTypeParent; |
|
1295 docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent)); |
|
1296 if (!sameTypeParent || sameTypeParent == docShellTreeItem) |
|
1297 break; |
|
1298 |
|
1299 nsIDocument* parentDoc = doc->GetParentDocument(); |
|
1300 if (!parentDoc) |
|
1301 break; |
|
1302 |
|
1303 startContent = parentDoc->FindContentForSubDocument(doc); |
|
1304 } |
|
1305 |
|
1306 if (!mContent->IsElement()) |
|
1307 return attributes.forget(); |
|
1308 |
|
1309 nsAutoString id; |
|
1310 if (nsCoreUtils::GetID(mContent, id)) |
|
1311 attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, unused); |
|
1312 |
|
1313 // Expose class because it may have useful microformat information. |
|
1314 nsAutoString _class; |
|
1315 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, _class)) |
|
1316 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::_class, _class); |
|
1317 |
|
1318 // Expose tag. |
|
1319 nsAutoString tagName; |
|
1320 mContent->NodeInfo()->GetName(tagName); |
|
1321 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tag, tagName); |
|
1322 |
|
1323 // Expose draggable object attribute. |
|
1324 nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent); |
|
1325 if (htmlElement) { |
|
1326 bool draggable = false; |
|
1327 htmlElement->GetDraggable(&draggable); |
|
1328 if (draggable) { |
|
1329 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::draggable, |
|
1330 NS_LITERAL_STRING("true")); |
|
1331 } |
|
1332 } |
|
1333 |
|
1334 // Don't calculate CSS-based object attributes when no frame (i.e. |
|
1335 // the accessible is unattached from the tree). |
|
1336 if (!mContent->GetPrimaryFrame()) |
|
1337 return attributes.forget(); |
|
1338 |
|
1339 // CSS style based object attributes. |
|
1340 nsAutoString value; |
|
1341 StyleInfo styleInfo(mContent->AsElement(), mDoc->PresShell()); |
|
1342 |
|
1343 // Expose 'display' attribute. |
|
1344 styleInfo.Display(value); |
|
1345 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::display, value); |
|
1346 |
|
1347 // Expose 'text-align' attribute. |
|
1348 styleInfo.TextAlign(value); |
|
1349 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textAlign, value); |
|
1350 |
|
1351 // Expose 'text-indent' attribute. |
|
1352 styleInfo.TextIndent(value); |
|
1353 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textIndent, value); |
|
1354 |
|
1355 // Expose 'margin-left' attribute. |
|
1356 styleInfo.MarginLeft(value); |
|
1357 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginLeft, value); |
|
1358 |
|
1359 // Expose 'margin-right' attribute. |
|
1360 styleInfo.MarginRight(value); |
|
1361 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginRight, value); |
|
1362 |
|
1363 // Expose 'margin-top' attribute. |
|
1364 styleInfo.MarginTop(value); |
|
1365 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginTop, value); |
|
1366 |
|
1367 // Expose 'margin-bottom' attribute. |
|
1368 styleInfo.MarginBottom(value); |
|
1369 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginBottom, value); |
|
1370 |
|
1371 return attributes.forget(); |
|
1372 } |
|
1373 |
|
1374 GroupPos |
|
1375 Accessible::GroupPosition() |
|
1376 { |
|
1377 GroupPos groupPos; |
|
1378 if (!HasOwnContent()) |
|
1379 return groupPos; |
|
1380 |
|
1381 // Get group position from ARIA attributes. |
|
1382 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, &groupPos.level); |
|
1383 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, &groupPos.setSize); |
|
1384 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, &groupPos.posInSet); |
|
1385 |
|
1386 // If ARIA is missed and the accessible is visible then calculate group |
|
1387 // position from hierarchy. |
|
1388 if (State() & states::INVISIBLE) |
|
1389 return groupPos; |
|
1390 |
|
1391 // Calculate group level if ARIA is missed. |
|
1392 if (groupPos.level == 0) { |
|
1393 int32_t level = GetLevelInternal(); |
|
1394 if (level != 0) |
|
1395 groupPos.level = level; |
|
1396 } |
|
1397 |
|
1398 // Calculate position in group and group size if ARIA is missed. |
|
1399 if (groupPos.posInSet == 0 || groupPos.setSize == 0) { |
|
1400 int32_t posInSet = 0, setSize = 0; |
|
1401 GetPositionAndSizeInternal(&posInSet, &setSize); |
|
1402 if (posInSet != 0 && setSize != 0) { |
|
1403 if (groupPos.posInSet == 0) |
|
1404 groupPos.posInSet = posInSet; |
|
1405 |
|
1406 if (groupPos.setSize == 0) |
|
1407 groupPos.setSize = setSize; |
|
1408 } |
|
1409 } |
|
1410 |
|
1411 return groupPos; |
|
1412 } |
|
1413 |
|
1414 NS_IMETHODIMP |
|
1415 Accessible::ScriptableGroupPosition(int32_t* aGroupLevel, |
|
1416 int32_t* aSimilarItemsInGroup, |
|
1417 int32_t* aPositionInGroup) |
|
1418 { |
|
1419 NS_ENSURE_ARG_POINTER(aGroupLevel); |
|
1420 *aGroupLevel = 0; |
|
1421 |
|
1422 NS_ENSURE_ARG_POINTER(aSimilarItemsInGroup); |
|
1423 *aSimilarItemsInGroup = 0; |
|
1424 |
|
1425 NS_ENSURE_ARG_POINTER(aPositionInGroup); |
|
1426 *aPositionInGroup = 0; |
|
1427 |
|
1428 if (IsDefunct()) |
|
1429 return NS_ERROR_FAILURE; |
|
1430 |
|
1431 GroupPos groupPos = GroupPosition(); |
|
1432 |
|
1433 *aGroupLevel = groupPos.level; |
|
1434 *aSimilarItemsInGroup = groupPos.setSize; |
|
1435 *aPositionInGroup = groupPos.posInSet; |
|
1436 |
|
1437 return NS_OK; |
|
1438 } |
|
1439 |
|
1440 NS_IMETHODIMP |
|
1441 Accessible::GetState(uint32_t* aState, uint32_t* aExtraState) |
|
1442 { |
|
1443 NS_ENSURE_ARG_POINTER(aState); |
|
1444 |
|
1445 nsAccUtils::To32States(State(), aState, aExtraState); |
|
1446 return NS_OK; |
|
1447 } |
|
1448 |
|
1449 uint64_t |
|
1450 Accessible::State() |
|
1451 { |
|
1452 if (IsDefunct()) |
|
1453 return states::DEFUNCT; |
|
1454 |
|
1455 uint64_t state = NativeState(); |
|
1456 // Apply ARIA states to be sure accessible states will be overridden. |
|
1457 ApplyARIAState(&state); |
|
1458 |
|
1459 // If this is an ARIA item of the selectable widget and if it's focused and |
|
1460 // not marked unselected explicitly (i.e. aria-selected="false") then expose |
|
1461 // it as selected to make ARIA widget authors life easier. |
|
1462 if (mRoleMapEntry && !(state & states::SELECTED) && |
|
1463 !mContent->AttrValueIs(kNameSpaceID_None, |
|
1464 nsGkAtoms::aria_selected, |
|
1465 nsGkAtoms::_false, eCaseMatters)) { |
|
1466 // Special case for tabs: focused tab or focus inside related tab panel |
|
1467 // implies selected state. |
|
1468 if (mRoleMapEntry->role == roles::PAGETAB) { |
|
1469 if (state & states::FOCUSED) { |
|
1470 state |= states::SELECTED; |
|
1471 } else { |
|
1472 // If focus is in a child of the tab panel surely the tab is selected! |
|
1473 Relation rel = RelationByType(RelationType::LABEL_FOR); |
|
1474 Accessible* relTarget = nullptr; |
|
1475 while ((relTarget = rel.Next())) { |
|
1476 if (relTarget->Role() == roles::PROPERTYPAGE && |
|
1477 FocusMgr()->IsFocusWithin(relTarget)) |
|
1478 state |= states::SELECTED; |
|
1479 } |
|
1480 } |
|
1481 } else if (state & states::FOCUSED) { |
|
1482 Accessible* container = nsAccUtils::GetSelectableContainer(this, state); |
|
1483 if (container && |
|
1484 !nsAccUtils::HasDefinedARIAToken(container->GetContent(), |
|
1485 nsGkAtoms::aria_multiselectable)) { |
|
1486 state |= states::SELECTED; |
|
1487 } |
|
1488 } |
|
1489 } |
|
1490 |
|
1491 const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED; |
|
1492 if ((state & kExpandCollapseStates) == kExpandCollapseStates) { |
|
1493 // Cannot be both expanded and collapsed -- this happens in ARIA expanded |
|
1494 // combobox because of limitation of ARIAMap. |
|
1495 // XXX: Perhaps we will be able to make this less hacky if we support |
|
1496 // extended states in ARIAMap, e.g. derive COLLAPSED from |
|
1497 // EXPANDABLE && !EXPANDED. |
|
1498 state &= ~states::COLLAPSED; |
|
1499 } |
|
1500 |
|
1501 if (!(state & states::UNAVAILABLE)) { |
|
1502 state |= states::ENABLED | states::SENSITIVE; |
|
1503 |
|
1504 // If the object is a current item of container widget then mark it as |
|
1505 // ACTIVE. This allows screen reader virtual buffer modes to know which |
|
1506 // descendant is the current one that would get focus if the user navigates |
|
1507 // to the container widget. |
|
1508 Accessible* widget = ContainerWidget(); |
|
1509 if (widget && widget->CurrentItem() == this) |
|
1510 state |= states::ACTIVE; |
|
1511 } |
|
1512 |
|
1513 if ((state & states::COLLAPSED) || (state & states::EXPANDED)) |
|
1514 state |= states::EXPANDABLE; |
|
1515 |
|
1516 // For some reasons DOM node may have not a frame. We tract such accessibles |
|
1517 // as invisible. |
|
1518 nsIFrame *frame = GetFrame(); |
|
1519 if (!frame) |
|
1520 return state; |
|
1521 |
|
1522 const nsStyleDisplay* display = frame->StyleDisplay(); |
|
1523 if (display && display->mOpacity == 1.0f && |
|
1524 !(state & states::INVISIBLE)) { |
|
1525 state |= states::OPAQUE1; |
|
1526 } |
|
1527 |
|
1528 return state; |
|
1529 } |
|
1530 |
|
1531 void |
|
1532 Accessible::ApplyARIAState(uint64_t* aState) const |
|
1533 { |
|
1534 if (!mContent->IsElement()) |
|
1535 return; |
|
1536 |
|
1537 dom::Element* element = mContent->AsElement(); |
|
1538 |
|
1539 // Test for universal states first |
|
1540 *aState |= aria::UniversalStatesFor(element); |
|
1541 |
|
1542 if (mRoleMapEntry) { |
|
1543 |
|
1544 // We only force the readonly bit off if we have a real mapping for the aria |
|
1545 // role. This preserves the ability for screen readers to use readonly |
|
1546 // (primarily on the document) as the hint for creating a virtual buffer. |
|
1547 if (mRoleMapEntry->role != roles::NOTHING) |
|
1548 *aState &= ~states::READONLY; |
|
1549 |
|
1550 if (mContent->HasAttr(kNameSpaceID_None, mContent->GetIDAttributeName())) { |
|
1551 // If has a role & ID and aria-activedescendant on the container, assume focusable |
|
1552 nsIContent *ancestorContent = mContent; |
|
1553 while ((ancestorContent = ancestorContent->GetParent()) != nullptr) { |
|
1554 if (ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) { |
|
1555 // ancestor has activedescendant property, this content could be active |
|
1556 *aState |= states::FOCUSABLE; |
|
1557 break; |
|
1558 } |
|
1559 } |
|
1560 } |
|
1561 } |
|
1562 |
|
1563 if (*aState & states::FOCUSABLE) { |
|
1564 // Special case: aria-disabled propagates from ancestors down to any focusable descendant |
|
1565 nsIContent *ancestorContent = mContent; |
|
1566 while ((ancestorContent = ancestorContent->GetParent()) != nullptr) { |
|
1567 if (ancestorContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, |
|
1568 nsGkAtoms::_true, eCaseMatters)) { |
|
1569 // ancestor has aria-disabled property, this is disabled |
|
1570 *aState |= states::UNAVAILABLE; |
|
1571 break; |
|
1572 } |
|
1573 } |
|
1574 } |
|
1575 |
|
1576 // special case: A native button element whose role got transformed by ARIA to a toggle button |
|
1577 if (IsButton()) |
|
1578 aria::MapToState(aria::eARIAPressed, element, aState); |
|
1579 |
|
1580 if (!mRoleMapEntry) |
|
1581 return; |
|
1582 |
|
1583 *aState |= mRoleMapEntry->state; |
|
1584 |
|
1585 if (aria::MapToState(mRoleMapEntry->attributeMap1, element, aState) && |
|
1586 aria::MapToState(mRoleMapEntry->attributeMap2, element, aState)) |
|
1587 aria::MapToState(mRoleMapEntry->attributeMap3, element, aState); |
|
1588 |
|
1589 // ARIA gridcell inherits editable/readonly states from the grid until it's |
|
1590 // overridden. |
|
1591 if ((mRoleMapEntry->Is(nsGkAtoms::gridcell) || |
|
1592 mRoleMapEntry->Is(nsGkAtoms::columnheader) || |
|
1593 mRoleMapEntry->Is(nsGkAtoms::rowheader)) && |
|
1594 !(*aState & (states::READONLY | states::EDITABLE))) { |
|
1595 const TableCellAccessible* cell = AsTableCell(); |
|
1596 if (cell) { |
|
1597 TableAccessible* table = cell->Table(); |
|
1598 if (table) { |
|
1599 Accessible* grid = table->AsAccessible(); |
|
1600 uint64_t gridState = 0; |
|
1601 grid->ApplyARIAState(&gridState); |
|
1602 *aState |= (gridState & (states::READONLY | states::EDITABLE)); |
|
1603 } |
|
1604 } |
|
1605 } |
|
1606 } |
|
1607 |
|
1608 NS_IMETHODIMP |
|
1609 Accessible::GetValue(nsAString& aValue) |
|
1610 { |
|
1611 if (IsDefunct()) |
|
1612 return NS_ERROR_FAILURE; |
|
1613 |
|
1614 nsAutoString value; |
|
1615 Value(value); |
|
1616 aValue.Assign(value); |
|
1617 |
|
1618 return NS_OK; |
|
1619 } |
|
1620 |
|
1621 void |
|
1622 Accessible::Value(nsString& aValue) |
|
1623 { |
|
1624 if (!mRoleMapEntry) |
|
1625 return; |
|
1626 |
|
1627 if (mRoleMapEntry->valueRule != eNoValue) { |
|
1628 // aria-valuenow is a number, and aria-valuetext is the optional text |
|
1629 // equivalent. For the string value, we will try the optional text |
|
1630 // equivalent first. |
|
1631 if (!mContent->GetAttr(kNameSpaceID_None, |
|
1632 nsGkAtoms::aria_valuetext, aValue)) { |
|
1633 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, |
|
1634 aValue); |
|
1635 } |
|
1636 return; |
|
1637 } |
|
1638 |
|
1639 // Value of textbox is a textified subtree. |
|
1640 if (mRoleMapEntry->Is(nsGkAtoms::textbox)) { |
|
1641 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); |
|
1642 return; |
|
1643 } |
|
1644 |
|
1645 // Value of combobox is a text of current or selected item. |
|
1646 if (mRoleMapEntry->Is(nsGkAtoms::combobox)) { |
|
1647 Accessible* option = CurrentItem(); |
|
1648 if (!option) { |
|
1649 Accessible* listbox = nullptr; |
|
1650 IDRefsIterator iter(mDoc, mContent, nsGkAtoms::aria_owns); |
|
1651 while ((listbox = iter.Next()) && !listbox->IsListControl()); |
|
1652 |
|
1653 if (!listbox) { |
|
1654 uint32_t childCount = ChildCount(); |
|
1655 for (uint32_t idx = 0; idx < childCount; idx++) { |
|
1656 Accessible* child = mChildren.ElementAt(idx); |
|
1657 if (child->IsListControl()) |
|
1658 listbox = child; |
|
1659 } |
|
1660 } |
|
1661 |
|
1662 if (listbox) |
|
1663 option = listbox->GetSelectedItem(0); |
|
1664 } |
|
1665 |
|
1666 if (option) |
|
1667 nsTextEquivUtils::GetTextEquivFromSubtree(option, aValue); |
|
1668 } |
|
1669 } |
|
1670 |
|
1671 double |
|
1672 Accessible::MaxValue() const |
|
1673 { |
|
1674 return AttrNumericValue(nsGkAtoms::aria_valuemax); |
|
1675 } |
|
1676 |
|
1677 double |
|
1678 Accessible::MinValue() const |
|
1679 { |
|
1680 return AttrNumericValue(nsGkAtoms::aria_valuemin); |
|
1681 } |
|
1682 |
|
1683 double |
|
1684 Accessible::Step() const |
|
1685 { |
|
1686 return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA. |
|
1687 } |
|
1688 |
|
1689 double |
|
1690 Accessible::CurValue() const |
|
1691 { |
|
1692 return AttrNumericValue(nsGkAtoms::aria_valuenow); |
|
1693 } |
|
1694 |
|
1695 bool |
|
1696 Accessible::SetCurValue(double aValue) |
|
1697 { |
|
1698 if (!mRoleMapEntry || mRoleMapEntry->valueRule == eNoValue) |
|
1699 return false; |
|
1700 |
|
1701 const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE; |
|
1702 if (State() & kValueCannotChange) |
|
1703 return false; |
|
1704 |
|
1705 double checkValue = MinValue(); |
|
1706 if (!IsNaN(checkValue) && aValue < checkValue) |
|
1707 return false; |
|
1708 |
|
1709 checkValue = MaxValue(); |
|
1710 if (!IsNaN(checkValue) && aValue > checkValue) |
|
1711 return false; |
|
1712 |
|
1713 nsAutoString strValue; |
|
1714 strValue.AppendFloat(aValue); |
|
1715 |
|
1716 return NS_SUCCEEDED( |
|
1717 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true)); |
|
1718 } |
|
1719 |
|
1720 /* void setName (in DOMString name); */ |
|
1721 NS_IMETHODIMP |
|
1722 Accessible::SetName(const nsAString& aName) |
|
1723 { |
|
1724 return NS_ERROR_NOT_IMPLEMENTED; |
|
1725 } |
|
1726 |
|
1727 NS_IMETHODIMP |
|
1728 Accessible::GetKeyboardShortcut(nsAString& aKeyBinding) |
|
1729 { |
|
1730 aKeyBinding.Truncate(); |
|
1731 if (IsDefunct()) |
|
1732 return NS_ERROR_FAILURE; |
|
1733 |
|
1734 KeyboardShortcut().ToString(aKeyBinding); |
|
1735 return NS_OK; |
|
1736 } |
|
1737 |
|
1738 role |
|
1739 Accessible::ARIATransformRole(role aRole) |
|
1740 { |
|
1741 // XXX: these unfortunate exceptions don't fit into the ARIA table. This is |
|
1742 // where the accessible role depends on both the role and ARIA state. |
|
1743 if (aRole == roles::PUSHBUTTON) { |
|
1744 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) { |
|
1745 // For simplicity, any existing pressed attribute except "" or "undefined" |
|
1746 // indicates a toggle. |
|
1747 return roles::TOGGLE_BUTTON; |
|
1748 } |
|
1749 |
|
1750 if (mContent->AttrValueIs(kNameSpaceID_None, |
|
1751 nsGkAtoms::aria_haspopup, |
|
1752 nsGkAtoms::_true, |
|
1753 eCaseMatters)) { |
|
1754 // For button with aria-haspopup="true". |
|
1755 return roles::BUTTONMENU; |
|
1756 } |
|
1757 |
|
1758 } else if (aRole == roles::LISTBOX) { |
|
1759 // A listbox inside of a combobox needs a special role because of ATK |
|
1760 // mapping to menu. |
|
1761 if (mParent && mParent->Role() == roles::COMBOBOX) { |
|
1762 return roles::COMBOBOX_LIST; |
|
1763 |
|
1764 Relation rel = RelationByType(RelationType::NODE_CHILD_OF); |
|
1765 Accessible* targetAcc = nullptr; |
|
1766 while ((targetAcc = rel.Next())) |
|
1767 if (targetAcc->Role() == roles::COMBOBOX) |
|
1768 return roles::COMBOBOX_LIST; |
|
1769 } |
|
1770 |
|
1771 } else if (aRole == roles::OPTION) { |
|
1772 if (mParent && mParent->Role() == roles::COMBOBOX_LIST) |
|
1773 return roles::COMBOBOX_OPTION; |
|
1774 |
|
1775 } else if (aRole == roles::MENUITEM) { |
|
1776 // Menuitem has a submenu. |
|
1777 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_haspopup, |
|
1778 nsGkAtoms::_true, eCaseMatters)) { |
|
1779 return roles::PARENT_MENUITEM; |
|
1780 } |
|
1781 } |
|
1782 |
|
1783 return aRole; |
|
1784 } |
|
1785 |
|
1786 role |
|
1787 Accessible::NativeRole() |
|
1788 { |
|
1789 return roles::NOTHING; |
|
1790 } |
|
1791 |
|
1792 // readonly attribute uint8_t actionCount |
|
1793 NS_IMETHODIMP |
|
1794 Accessible::GetActionCount(uint8_t* aActionCount) |
|
1795 { |
|
1796 NS_ENSURE_ARG_POINTER(aActionCount); |
|
1797 *aActionCount = 0; |
|
1798 if (IsDefunct()) |
|
1799 return NS_ERROR_FAILURE; |
|
1800 |
|
1801 *aActionCount = ActionCount(); |
|
1802 return NS_OK; |
|
1803 } |
|
1804 |
|
1805 uint8_t |
|
1806 Accessible::ActionCount() |
|
1807 { |
|
1808 return GetActionRule() == eNoAction ? 0 : 1; |
|
1809 } |
|
1810 |
|
1811 /* DOMString getAccActionName (in uint8_t index); */ |
|
1812 NS_IMETHODIMP |
|
1813 Accessible::GetActionName(uint8_t aIndex, nsAString& aName) |
|
1814 { |
|
1815 aName.Truncate(); |
|
1816 |
|
1817 if (aIndex != 0) |
|
1818 return NS_ERROR_INVALID_ARG; |
|
1819 |
|
1820 if (IsDefunct()) |
|
1821 return NS_ERROR_FAILURE; |
|
1822 |
|
1823 uint32_t actionRule = GetActionRule(); |
|
1824 |
|
1825 switch (actionRule) { |
|
1826 case eActivateAction: |
|
1827 aName.AssignLiteral("activate"); |
|
1828 return NS_OK; |
|
1829 |
|
1830 case eClickAction: |
|
1831 aName.AssignLiteral("click"); |
|
1832 return NS_OK; |
|
1833 |
|
1834 case ePressAction: |
|
1835 aName.AssignLiteral("press"); |
|
1836 return NS_OK; |
|
1837 |
|
1838 case eCheckUncheckAction: |
|
1839 { |
|
1840 uint64_t state = State(); |
|
1841 if (state & states::CHECKED) |
|
1842 aName.AssignLiteral("uncheck"); |
|
1843 else if (state & states::MIXED) |
|
1844 aName.AssignLiteral("cycle"); |
|
1845 else |
|
1846 aName.AssignLiteral("check"); |
|
1847 return NS_OK; |
|
1848 } |
|
1849 |
|
1850 case eJumpAction: |
|
1851 aName.AssignLiteral("jump"); |
|
1852 return NS_OK; |
|
1853 |
|
1854 case eOpenCloseAction: |
|
1855 if (State() & states::COLLAPSED) |
|
1856 aName.AssignLiteral("open"); |
|
1857 else |
|
1858 aName.AssignLiteral("close"); |
|
1859 return NS_OK; |
|
1860 |
|
1861 case eSelectAction: |
|
1862 aName.AssignLiteral("select"); |
|
1863 return NS_OK; |
|
1864 |
|
1865 case eSwitchAction: |
|
1866 aName.AssignLiteral("switch"); |
|
1867 return NS_OK; |
|
1868 |
|
1869 case eSortAction: |
|
1870 aName.AssignLiteral("sort"); |
|
1871 return NS_OK; |
|
1872 |
|
1873 case eExpandAction: |
|
1874 if (State() & states::COLLAPSED) |
|
1875 aName.AssignLiteral("expand"); |
|
1876 else |
|
1877 aName.AssignLiteral("collapse"); |
|
1878 return NS_OK; |
|
1879 } |
|
1880 |
|
1881 return NS_ERROR_INVALID_ARG; |
|
1882 } |
|
1883 |
|
1884 // AString getActionDescription(in uint8_t index) |
|
1885 NS_IMETHODIMP |
|
1886 Accessible::GetActionDescription(uint8_t aIndex, nsAString& aDescription) |
|
1887 { |
|
1888 // default to localized action name. |
|
1889 nsAutoString name; |
|
1890 nsresult rv = GetActionName(aIndex, name); |
|
1891 NS_ENSURE_SUCCESS(rv, rv); |
|
1892 |
|
1893 TranslateString(name, aDescription); |
|
1894 return NS_OK; |
|
1895 } |
|
1896 |
|
1897 // void doAction(in uint8_t index) |
|
1898 NS_IMETHODIMP |
|
1899 Accessible::DoAction(uint8_t aIndex) |
|
1900 { |
|
1901 if (aIndex != 0) |
|
1902 return NS_ERROR_INVALID_ARG; |
|
1903 |
|
1904 if (IsDefunct()) |
|
1905 return NS_ERROR_FAILURE; |
|
1906 |
|
1907 if (GetActionRule() != eNoAction) { |
|
1908 DoCommand(); |
|
1909 return NS_OK; |
|
1910 } |
|
1911 |
|
1912 return NS_ERROR_INVALID_ARG; |
|
1913 } |
|
1914 |
|
1915 /* DOMString getHelp (); */ |
|
1916 NS_IMETHODIMP Accessible::GetHelp(nsAString& _retval) |
|
1917 { |
|
1918 return NS_ERROR_NOT_IMPLEMENTED; |
|
1919 } |
|
1920 |
|
1921 nsIContent* |
|
1922 Accessible::GetAtomicRegion() const |
|
1923 { |
|
1924 nsIContent *loopContent = mContent; |
|
1925 nsAutoString atomic; |
|
1926 while (loopContent && !loopContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_atomic, atomic)) |
|
1927 loopContent = loopContent->GetParent(); |
|
1928 |
|
1929 return atomic.EqualsLiteral("true") ? loopContent : nullptr; |
|
1930 } |
|
1931 |
|
1932 // nsIAccessible getRelationByType() |
|
1933 NS_IMETHODIMP |
|
1934 Accessible::GetRelationByType(uint32_t aType, nsIAccessibleRelation** aRelation) |
|
1935 { |
|
1936 NS_ENSURE_ARG_POINTER(aRelation); |
|
1937 *aRelation = nullptr; |
|
1938 |
|
1939 NS_ENSURE_ARG(aType <= static_cast<uint32_t>(RelationType::LAST)); |
|
1940 |
|
1941 if (IsDefunct()) |
|
1942 return NS_ERROR_FAILURE; |
|
1943 |
|
1944 Relation rel = RelationByType(static_cast<RelationType>(aType)); |
|
1945 NS_ADDREF(*aRelation = new nsAccessibleRelation(aType, &rel)); |
|
1946 return *aRelation ? NS_OK : NS_ERROR_FAILURE; |
|
1947 } |
|
1948 |
|
1949 Relation |
|
1950 Accessible::RelationByType(RelationType aType) |
|
1951 { |
|
1952 if (!HasOwnContent()) |
|
1953 return Relation(); |
|
1954 |
|
1955 // Relationships are defined on the same content node that the role would be |
|
1956 // defined on. |
|
1957 switch (aType) { |
|
1958 case RelationType::LABELLED_BY: { |
|
1959 Relation rel(new IDRefsIterator(mDoc, mContent, |
|
1960 nsGkAtoms::aria_labelledby)); |
|
1961 if (mContent->IsHTML()) { |
|
1962 rel.AppendIter(new HTMLLabelIterator(Document(), this)); |
|
1963 } else if (mContent->IsXUL()) { |
|
1964 rel.AppendIter(new XULLabelIterator(Document(), mContent)); |
|
1965 } |
|
1966 |
|
1967 return rel; |
|
1968 } |
|
1969 |
|
1970 case RelationType::LABEL_FOR: { |
|
1971 Relation rel(new RelatedAccIterator(Document(), mContent, |
|
1972 nsGkAtoms::aria_labelledby)); |
|
1973 if (mContent->Tag() == nsGkAtoms::label && mContent->IsXUL()) |
|
1974 rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control)); |
|
1975 |
|
1976 return rel; |
|
1977 } |
|
1978 |
|
1979 case RelationType::DESCRIBED_BY: { |
|
1980 Relation rel(new IDRefsIterator(mDoc, mContent, |
|
1981 nsGkAtoms::aria_describedby)); |
|
1982 if (mContent->IsXUL()) |
|
1983 rel.AppendIter(new XULDescriptionIterator(Document(), mContent)); |
|
1984 |
|
1985 return rel; |
|
1986 } |
|
1987 |
|
1988 case RelationType::DESCRIPTION_FOR: { |
|
1989 Relation rel(new RelatedAccIterator(Document(), mContent, |
|
1990 nsGkAtoms::aria_describedby)); |
|
1991 |
|
1992 // This affectively adds an optional control attribute to xul:description, |
|
1993 // which only affects accessibility, by allowing the description to be |
|
1994 // tied to a control. |
|
1995 if (mContent->Tag() == nsGkAtoms::description && |
|
1996 mContent->IsXUL()) |
|
1997 rel.AppendIter(new IDRefsIterator(mDoc, mContent, |
|
1998 nsGkAtoms::control)); |
|
1999 |
|
2000 return rel; |
|
2001 } |
|
2002 |
|
2003 case RelationType::NODE_CHILD_OF: { |
|
2004 Relation rel(new RelatedAccIterator(Document(), mContent, |
|
2005 nsGkAtoms::aria_owns)); |
|
2006 |
|
2007 // This is an ARIA tree or treegrid that doesn't use owns, so we need to |
|
2008 // get the parent the hard way. |
|
2009 if (mRoleMapEntry && (mRoleMapEntry->role == roles::OUTLINEITEM || |
|
2010 mRoleMapEntry->role == roles::LISTITEM || |
|
2011 mRoleMapEntry->role == roles::ROW)) { |
|
2012 rel.AppendTarget(GetGroupInfo()->ConceptualParent()); |
|
2013 } |
|
2014 |
|
2015 // If accessible is in its own Window, or is the root of a document, |
|
2016 // then we should provide NODE_CHILD_OF relation so that MSAA clients |
|
2017 // can easily get to true parent instead of getting to oleacc's |
|
2018 // ROLE_WINDOW accessible which will prevent us from going up further |
|
2019 // (because it is system generated and has no idea about the hierarchy |
|
2020 // above it). |
|
2021 nsIFrame *frame = GetFrame(); |
|
2022 if (frame) { |
|
2023 nsView *view = frame->GetViewExternal(); |
|
2024 if (view) { |
|
2025 nsIScrollableFrame *scrollFrame = do_QueryFrame(frame); |
|
2026 if (scrollFrame || view->GetWidget() || !frame->GetParent()) |
|
2027 rel.AppendTarget(Parent()); |
|
2028 } |
|
2029 } |
|
2030 |
|
2031 return rel; |
|
2032 } |
|
2033 |
|
2034 case RelationType::NODE_PARENT_OF: { |
|
2035 Relation rel(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_owns)); |
|
2036 |
|
2037 // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees |
|
2038 // also can be organized by groups. |
|
2039 if (mRoleMapEntry && |
|
2040 (mRoleMapEntry->role == roles::OUTLINEITEM || |
|
2041 mRoleMapEntry->role == roles::LISTITEM || |
|
2042 mRoleMapEntry->role == roles::ROW || |
|
2043 mRoleMapEntry->role == roles::OUTLINE || |
|
2044 mRoleMapEntry->role == roles::LIST || |
|
2045 mRoleMapEntry->role == roles::TREE_TABLE)) { |
|
2046 rel.AppendIter(new ItemIterator(this)); |
|
2047 } |
|
2048 |
|
2049 return rel; |
|
2050 } |
|
2051 |
|
2052 case RelationType::CONTROLLED_BY: |
|
2053 return Relation(new RelatedAccIterator(Document(), mContent, |
|
2054 nsGkAtoms::aria_controls)); |
|
2055 |
|
2056 case RelationType::CONTROLLER_FOR: { |
|
2057 Relation rel(new IDRefsIterator(mDoc, mContent, |
|
2058 nsGkAtoms::aria_controls)); |
|
2059 rel.AppendIter(new HTMLOutputIterator(Document(), mContent)); |
|
2060 return rel; |
|
2061 } |
|
2062 |
|
2063 case RelationType::FLOWS_TO: |
|
2064 return Relation(new IDRefsIterator(mDoc, mContent, |
|
2065 nsGkAtoms::aria_flowto)); |
|
2066 |
|
2067 case RelationType::FLOWS_FROM: |
|
2068 return Relation(new RelatedAccIterator(Document(), mContent, |
|
2069 nsGkAtoms::aria_flowto)); |
|
2070 |
|
2071 case RelationType::MEMBER_OF: |
|
2072 return Relation(mDoc, GetAtomicRegion()); |
|
2073 |
|
2074 case RelationType::SUBWINDOW_OF: |
|
2075 case RelationType::EMBEDS: |
|
2076 case RelationType::EMBEDDED_BY: |
|
2077 case RelationType::POPUP_FOR: |
|
2078 case RelationType::PARENT_WINDOW_OF: |
|
2079 return Relation(); |
|
2080 |
|
2081 case RelationType::DEFAULT_BUTTON: { |
|
2082 if (mContent->IsHTML()) { |
|
2083 // HTML form controls implements nsIFormControl interface. |
|
2084 nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent)); |
|
2085 if (control) { |
|
2086 nsCOMPtr<nsIForm> form(do_QueryInterface(control->GetFormElement())); |
|
2087 if (form) { |
|
2088 nsCOMPtr<nsIContent> formContent = |
|
2089 do_QueryInterface(form->GetDefaultSubmitElement()); |
|
2090 return Relation(mDoc, formContent); |
|
2091 } |
|
2092 } |
|
2093 } else { |
|
2094 // In XUL, use first <button default="true" .../> in the document |
|
2095 nsCOMPtr<nsIDOMXULDocument> xulDoc = |
|
2096 do_QueryInterface(mContent->OwnerDoc()); |
|
2097 nsCOMPtr<nsIDOMXULButtonElement> buttonEl; |
|
2098 if (xulDoc) { |
|
2099 nsCOMPtr<nsIDOMNodeList> possibleDefaultButtons; |
|
2100 xulDoc->GetElementsByAttribute(NS_LITERAL_STRING("default"), |
|
2101 NS_LITERAL_STRING("true"), |
|
2102 getter_AddRefs(possibleDefaultButtons)); |
|
2103 if (possibleDefaultButtons) { |
|
2104 uint32_t length; |
|
2105 possibleDefaultButtons->GetLength(&length); |
|
2106 nsCOMPtr<nsIDOMNode> possibleButton; |
|
2107 // Check for button in list of default="true" elements |
|
2108 for (uint32_t count = 0; count < length && !buttonEl; count ++) { |
|
2109 possibleDefaultButtons->Item(count, getter_AddRefs(possibleButton)); |
|
2110 buttonEl = do_QueryInterface(possibleButton); |
|
2111 } |
|
2112 } |
|
2113 if (!buttonEl) { // Check for anonymous accept button in <dialog> |
|
2114 dom::Element* rootElm = mContent->OwnerDoc()->GetRootElement(); |
|
2115 if (rootElm) { |
|
2116 nsIContent* possibleButtonEl = rootElm->OwnerDoc()-> |
|
2117 GetAnonymousElementByAttribute(rootElm, nsGkAtoms::_default, |
|
2118 NS_LITERAL_STRING("true")); |
|
2119 buttonEl = do_QueryInterface(possibleButtonEl); |
|
2120 } |
|
2121 } |
|
2122 nsCOMPtr<nsIContent> relatedContent(do_QueryInterface(buttonEl)); |
|
2123 return Relation(mDoc, relatedContent); |
|
2124 } |
|
2125 } |
|
2126 return Relation(); |
|
2127 } |
|
2128 |
|
2129 case RelationType::CONTAINING_DOCUMENT: |
|
2130 return Relation(mDoc); |
|
2131 |
|
2132 case RelationType::CONTAINING_TAB_PANE: { |
|
2133 nsCOMPtr<nsIDocShell> docShell = |
|
2134 nsCoreUtils::GetDocShellFor(GetNode()); |
|
2135 if (docShell) { |
|
2136 // Walk up the parent chain without crossing the boundary at which item |
|
2137 // types change, preventing us from walking up out of tab content. |
|
2138 nsCOMPtr<nsIDocShellTreeItem> root; |
|
2139 docShell->GetSameTypeRootTreeItem(getter_AddRefs(root)); |
|
2140 if (root) { |
|
2141 // If the item type is typeContent, we assume we are in browser tab |
|
2142 // content. Note, this includes content such as about:addons, |
|
2143 // for consistency. |
|
2144 if (root->ItemType() == nsIDocShellTreeItem::typeContent) { |
|
2145 return Relation(nsAccUtils::GetDocAccessibleFor(root)); |
|
2146 } |
|
2147 } |
|
2148 } |
|
2149 return Relation(); |
|
2150 } |
|
2151 |
|
2152 case RelationType::CONTAINING_APPLICATION: |
|
2153 return Relation(ApplicationAcc()); |
|
2154 |
|
2155 default: |
|
2156 return Relation(); |
|
2157 } |
|
2158 } |
|
2159 |
|
2160 NS_IMETHODIMP |
|
2161 Accessible::GetRelations(nsIArray **aRelations) |
|
2162 { |
|
2163 NS_ENSURE_ARG_POINTER(aRelations); |
|
2164 *aRelations = nullptr; |
|
2165 |
|
2166 if (IsDefunct()) |
|
2167 return NS_ERROR_FAILURE; |
|
2168 |
|
2169 nsCOMPtr<nsIMutableArray> relations = do_CreateInstance(NS_ARRAY_CONTRACTID); |
|
2170 NS_ENSURE_TRUE(relations, NS_ERROR_OUT_OF_MEMORY); |
|
2171 |
|
2172 static const uint32_t relationTypes[] = { |
|
2173 nsIAccessibleRelation::RELATION_LABELLED_BY, |
|
2174 nsIAccessibleRelation::RELATION_LABEL_FOR, |
|
2175 nsIAccessibleRelation::RELATION_DESCRIBED_BY, |
|
2176 nsIAccessibleRelation::RELATION_DESCRIPTION_FOR, |
|
2177 nsIAccessibleRelation::RELATION_NODE_CHILD_OF, |
|
2178 nsIAccessibleRelation::RELATION_NODE_PARENT_OF, |
|
2179 nsIAccessibleRelation::RELATION_CONTROLLED_BY, |
|
2180 nsIAccessibleRelation::RELATION_CONTROLLER_FOR, |
|
2181 nsIAccessibleRelation::RELATION_FLOWS_TO, |
|
2182 nsIAccessibleRelation::RELATION_FLOWS_FROM, |
|
2183 nsIAccessibleRelation::RELATION_MEMBER_OF, |
|
2184 nsIAccessibleRelation::RELATION_SUBWINDOW_OF, |
|
2185 nsIAccessibleRelation::RELATION_EMBEDS, |
|
2186 nsIAccessibleRelation::RELATION_EMBEDDED_BY, |
|
2187 nsIAccessibleRelation::RELATION_POPUP_FOR, |
|
2188 nsIAccessibleRelation::RELATION_PARENT_WINDOW_OF, |
|
2189 nsIAccessibleRelation::RELATION_DEFAULT_BUTTON, |
|
2190 nsIAccessibleRelation::RELATION_CONTAINING_DOCUMENT, |
|
2191 nsIAccessibleRelation::RELATION_CONTAINING_TAB_PANE, |
|
2192 nsIAccessibleRelation::RELATION_CONTAINING_APPLICATION |
|
2193 }; |
|
2194 |
|
2195 for (uint32_t idx = 0; idx < ArrayLength(relationTypes); idx++) { |
|
2196 nsCOMPtr<nsIAccessibleRelation> relation; |
|
2197 nsresult rv = GetRelationByType(relationTypes[idx], getter_AddRefs(relation)); |
|
2198 |
|
2199 if (NS_SUCCEEDED(rv) && relation) { |
|
2200 uint32_t targets = 0; |
|
2201 relation->GetTargetsCount(&targets); |
|
2202 if (targets) |
|
2203 relations->AppendElement(relation, false); |
|
2204 } |
|
2205 } |
|
2206 |
|
2207 NS_ADDREF(*aRelations = relations); |
|
2208 return NS_OK; |
|
2209 } |
|
2210 |
|
2211 /* void extendSelection (); */ |
|
2212 NS_IMETHODIMP Accessible::ExtendSelection() |
|
2213 { |
|
2214 // XXX Should be implemented, but not high priority |
|
2215 return NS_ERROR_NOT_IMPLEMENTED; |
|
2216 } |
|
2217 |
|
2218 /* [noscript] void getNativeInterface(out voidPtr aOutAccessible); */ |
|
2219 NS_IMETHODIMP Accessible::GetNativeInterface(void **aOutAccessible) |
|
2220 { |
|
2221 return NS_ERROR_NOT_IMPLEMENTED; |
|
2222 } |
|
2223 |
|
2224 void |
|
2225 Accessible::DoCommand(nsIContent *aContent, uint32_t aActionIndex) |
|
2226 { |
|
2227 class Runnable MOZ_FINAL : public nsRunnable |
|
2228 { |
|
2229 public: |
|
2230 Runnable(Accessible* aAcc, nsIContent* aContent, uint32_t aIdx) : |
|
2231 mAcc(aAcc), mContent(aContent), mIdx(aIdx) { } |
|
2232 |
|
2233 NS_IMETHOD Run() |
|
2234 { |
|
2235 if (mAcc) |
|
2236 mAcc->DispatchClickEvent(mContent, mIdx); |
|
2237 |
|
2238 return NS_OK; |
|
2239 } |
|
2240 |
|
2241 void Revoke() |
|
2242 { |
|
2243 mAcc = nullptr; |
|
2244 mContent = nullptr; |
|
2245 } |
|
2246 |
|
2247 private: |
|
2248 nsRefPtr<Accessible> mAcc; |
|
2249 nsCOMPtr<nsIContent> mContent; |
|
2250 uint32_t mIdx; |
|
2251 }; |
|
2252 |
|
2253 nsIContent* content = aContent ? aContent : mContent.get(); |
|
2254 nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex); |
|
2255 NS_DispatchToMainThread(runnable); |
|
2256 } |
|
2257 |
|
2258 void |
|
2259 Accessible::DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex) |
|
2260 { |
|
2261 if (IsDefunct()) |
|
2262 return; |
|
2263 |
|
2264 nsCOMPtr<nsIPresShell> presShell = mDoc->PresShell(); |
|
2265 |
|
2266 // Scroll into view. |
|
2267 presShell->ScrollContentIntoView(aContent, |
|
2268 nsIPresShell::ScrollAxis(), |
|
2269 nsIPresShell::ScrollAxis(), |
|
2270 nsIPresShell::SCROLL_OVERFLOW_HIDDEN); |
|
2271 |
|
2272 nsWeakFrame frame = aContent->GetPrimaryFrame(); |
|
2273 if (!frame) |
|
2274 return; |
|
2275 |
|
2276 // Compute x and y coordinates. |
|
2277 nsPoint point; |
|
2278 nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point); |
|
2279 if (!widget) |
|
2280 return; |
|
2281 |
|
2282 nsSize size = frame->GetSize(); |
|
2283 |
|
2284 nsRefPtr<nsPresContext> presContext = presShell->GetPresContext(); |
|
2285 int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2); |
|
2286 int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2); |
|
2287 |
|
2288 // Simulate a touch interaction by dispatching touch events with mouse events. |
|
2289 nsCoreUtils::DispatchTouchEvent(NS_TOUCH_START, x, y, aContent, frame, presShell, widget); |
|
2290 nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_DOWN, x, y, aContent, frame, presShell, widget); |
|
2291 nsCoreUtils::DispatchTouchEvent(NS_TOUCH_END, x, y, aContent, frame, presShell, widget); |
|
2292 nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_UP, x, y, aContent, frame, presShell, widget); |
|
2293 } |
|
2294 |
|
2295 NS_IMETHODIMP |
|
2296 Accessible::ScrollTo(uint32_t aHow) |
|
2297 { |
|
2298 if (IsDefunct()) |
|
2299 return NS_ERROR_FAILURE; |
|
2300 |
|
2301 nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, aHow); |
|
2302 return NS_OK; |
|
2303 } |
|
2304 |
|
2305 NS_IMETHODIMP |
|
2306 Accessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY) |
|
2307 { |
|
2308 nsIFrame *frame = GetFrame(); |
|
2309 if (!frame) |
|
2310 return NS_ERROR_FAILURE; |
|
2311 |
|
2312 nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, |
|
2313 this); |
|
2314 |
|
2315 nsIFrame *parentFrame = frame; |
|
2316 while ((parentFrame = parentFrame->GetParent())) |
|
2317 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords); |
|
2318 |
|
2319 return NS_OK; |
|
2320 } |
|
2321 |
|
2322 // nsIAccessibleHyperLink |
|
2323 // Because of new-atk design, any embedded object in text can implement |
|
2324 // nsIAccessibleHyperLink, which helps determine where it is located |
|
2325 // within containing text |
|
2326 |
|
2327 // readonly attribute long nsIAccessibleHyperLink::anchorCount |
|
2328 NS_IMETHODIMP |
|
2329 Accessible::GetAnchorCount(int32_t *aAnchorCount) |
|
2330 { |
|
2331 NS_ENSURE_ARG_POINTER(aAnchorCount); |
|
2332 *aAnchorCount = 0; |
|
2333 |
|
2334 if (IsDefunct()) |
|
2335 return NS_ERROR_FAILURE; |
|
2336 |
|
2337 *aAnchorCount = AnchorCount(); |
|
2338 return NS_OK; |
|
2339 } |
|
2340 |
|
2341 // readonly attribute long nsIAccessibleHyperLink::startIndex |
|
2342 NS_IMETHODIMP |
|
2343 Accessible::GetStartIndex(int32_t *aStartIndex) |
|
2344 { |
|
2345 NS_ENSURE_ARG_POINTER(aStartIndex); |
|
2346 *aStartIndex = 0; |
|
2347 |
|
2348 if (IsDefunct()) |
|
2349 return NS_ERROR_FAILURE; |
|
2350 |
|
2351 *aStartIndex = StartOffset(); |
|
2352 return NS_OK; |
|
2353 } |
|
2354 |
|
2355 // readonly attribute long nsIAccessibleHyperLink::endIndex |
|
2356 NS_IMETHODIMP |
|
2357 Accessible::GetEndIndex(int32_t *aEndIndex) |
|
2358 { |
|
2359 NS_ENSURE_ARG_POINTER(aEndIndex); |
|
2360 *aEndIndex = 0; |
|
2361 |
|
2362 if (IsDefunct()) |
|
2363 return NS_ERROR_FAILURE; |
|
2364 |
|
2365 *aEndIndex = EndOffset(); |
|
2366 return NS_OK; |
|
2367 } |
|
2368 |
|
2369 NS_IMETHODIMP |
|
2370 Accessible::GetURI(int32_t aIndex, nsIURI **aURI) |
|
2371 { |
|
2372 NS_ENSURE_ARG_POINTER(aURI); |
|
2373 |
|
2374 if (IsDefunct()) |
|
2375 return NS_ERROR_FAILURE; |
|
2376 |
|
2377 if (aIndex < 0 || aIndex >= static_cast<int32_t>(AnchorCount())) |
|
2378 return NS_ERROR_INVALID_ARG; |
|
2379 |
|
2380 nsRefPtr<nsIURI>(AnchorURIAt(aIndex)).forget(aURI); |
|
2381 return NS_OK; |
|
2382 } |
|
2383 |
|
2384 |
|
2385 NS_IMETHODIMP |
|
2386 Accessible::GetAnchor(int32_t aIndex, nsIAccessible** aAccessible) |
|
2387 { |
|
2388 NS_ENSURE_ARG_POINTER(aAccessible); |
|
2389 *aAccessible = nullptr; |
|
2390 |
|
2391 if (IsDefunct()) |
|
2392 return NS_ERROR_FAILURE; |
|
2393 |
|
2394 if (aIndex < 0 || aIndex >= static_cast<int32_t>(AnchorCount())) |
|
2395 return NS_ERROR_INVALID_ARG; |
|
2396 |
|
2397 NS_IF_ADDREF(*aAccessible = AnchorAt(aIndex)); |
|
2398 return NS_OK; |
|
2399 } |
|
2400 |
|
2401 // readonly attribute boolean nsIAccessibleHyperLink::valid |
|
2402 NS_IMETHODIMP |
|
2403 Accessible::GetValid(bool *aValid) |
|
2404 { |
|
2405 NS_ENSURE_ARG_POINTER(aValid); |
|
2406 *aValid = false; |
|
2407 |
|
2408 if (IsDefunct()) |
|
2409 return NS_ERROR_FAILURE; |
|
2410 |
|
2411 *aValid = IsLinkValid(); |
|
2412 return NS_OK; |
|
2413 } |
|
2414 |
|
2415 void |
|
2416 Accessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset, |
|
2417 uint32_t aLength) |
|
2418 { |
|
2419 // Return text representation of non-text accessible within hypertext |
|
2420 // accessible. Text accessible overrides this method to return enclosed text. |
|
2421 if (aStartOffset != 0 || aLength == 0) |
|
2422 return; |
|
2423 |
|
2424 nsIFrame *frame = GetFrame(); |
|
2425 if (!frame) |
|
2426 return; |
|
2427 |
|
2428 NS_ASSERTION(mParent, |
|
2429 "Called on accessible unbound from tree. Result can be wrong."); |
|
2430 |
|
2431 if (frame->GetType() == nsGkAtoms::brFrame) { |
|
2432 aText += kForcedNewLineChar; |
|
2433 } else if (mParent && nsAccUtils::MustPrune(mParent)) { |
|
2434 // Expose the embedded object accessible as imaginary embedded object |
|
2435 // character if its parent hypertext accessible doesn't expose children to |
|
2436 // AT. |
|
2437 aText += kImaginaryEmbeddedObjectChar; |
|
2438 } else { |
|
2439 aText += kEmbeddedObjectChar; |
|
2440 } |
|
2441 } |
|
2442 |
|
2443 void |
|
2444 Accessible::Shutdown() |
|
2445 { |
|
2446 // Mark the accessible as defunct, invalidate the child count and pointers to |
|
2447 // other accessibles, also make sure none of its children point to this parent |
|
2448 mStateFlags |= eIsDefunct; |
|
2449 |
|
2450 InvalidateChildren(); |
|
2451 if (mParent) |
|
2452 mParent->RemoveChild(this); |
|
2453 |
|
2454 mContent = nullptr; |
|
2455 mDoc = nullptr; |
|
2456 } |
|
2457 |
|
2458 // Accessible protected |
|
2459 void |
|
2460 Accessible::ARIAName(nsString& aName) |
|
2461 { |
|
2462 // aria-labelledby now takes precedence over aria-label |
|
2463 nsresult rv = nsTextEquivUtils:: |
|
2464 GetTextEquivFromIDRefs(this, nsGkAtoms::aria_labelledby, aName); |
|
2465 if (NS_SUCCEEDED(rv)) { |
|
2466 aName.CompressWhitespace(); |
|
2467 } |
|
2468 |
|
2469 if (aName.IsEmpty() && |
|
2470 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_label, aName)) { |
|
2471 aName.CompressWhitespace(); |
|
2472 } |
|
2473 } |
|
2474 |
|
2475 // Accessible protected |
|
2476 ENameValueFlag |
|
2477 Accessible::NativeName(nsString& aName) |
|
2478 { |
|
2479 if (mContent->IsHTML()) { |
|
2480 Accessible* label = nullptr; |
|
2481 HTMLLabelIterator iter(Document(), this); |
|
2482 while ((label = iter.Next())) { |
|
2483 nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(), |
|
2484 &aName); |
|
2485 aName.CompressWhitespace(); |
|
2486 } |
|
2487 |
|
2488 if (!aName.IsEmpty()) |
|
2489 return eNameOK; |
|
2490 |
|
2491 nsTextEquivUtils::GetNameFromSubtree(this, aName); |
|
2492 return aName.IsEmpty() ? eNameOK : eNameFromSubtree; |
|
2493 } |
|
2494 |
|
2495 if (mContent->IsXUL()) { |
|
2496 XULElmName(mDoc, mContent, aName); |
|
2497 if (!aName.IsEmpty()) |
|
2498 return eNameOK; |
|
2499 |
|
2500 nsTextEquivUtils::GetNameFromSubtree(this, aName); |
|
2501 return aName.IsEmpty() ? eNameOK : eNameFromSubtree; |
|
2502 } |
|
2503 |
|
2504 if (mContent->IsSVG()) { |
|
2505 // If user agents need to choose among multiple ‘desc’ or ‘title’ elements |
|
2506 // for processing, the user agent shall choose the first one. |
|
2507 for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
|
2508 childElm = childElm->GetNextSibling()) { |
|
2509 if (childElm->IsSVG(nsGkAtoms::title)) { |
|
2510 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName); |
|
2511 return eNameOK; |
|
2512 } |
|
2513 } |
|
2514 } |
|
2515 |
|
2516 return eNameOK; |
|
2517 } |
|
2518 |
|
2519 // Accessible protected |
|
2520 void |
|
2521 Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) |
|
2522 { |
|
2523 NS_PRECONDITION(aParent, "This method isn't used to set null parent!"); |
|
2524 |
|
2525 if (mParent) { |
|
2526 if (mParent != aParent) { |
|
2527 NS_ERROR("Adopting child!"); |
|
2528 mParent->RemoveChild(this); |
|
2529 mParent->InvalidateChildrenGroupInfo(); |
|
2530 } else { |
|
2531 NS_ERROR("Binding to the same parent!"); |
|
2532 return; |
|
2533 } |
|
2534 } |
|
2535 |
|
2536 mParent = aParent; |
|
2537 mIndexInParent = aIndexInParent; |
|
2538 |
|
2539 mParent->InvalidateChildrenGroupInfo(); |
|
2540 |
|
2541 // Note: this is currently only used for richlistitems and their children. |
|
2542 if (mParent->HasNameDependentParent() || mParent->IsXULListItem()) |
|
2543 mContextFlags |= eHasNameDependentParent; |
|
2544 else |
|
2545 mContextFlags &= ~eHasNameDependentParent; |
|
2546 } |
|
2547 |
|
2548 // Accessible protected |
|
2549 void |
|
2550 Accessible::UnbindFromParent() |
|
2551 { |
|
2552 mParent->InvalidateChildrenGroupInfo(); |
|
2553 mParent = nullptr; |
|
2554 mIndexInParent = -1; |
|
2555 mIndexOfEmbeddedChild = -1; |
|
2556 mGroupInfo = nullptr; |
|
2557 mContextFlags &= ~eHasNameDependentParent; |
|
2558 } |
|
2559 |
|
2560 //////////////////////////////////////////////////////////////////////////////// |
|
2561 // Accessible public methods |
|
2562 |
|
2563 RootAccessible* |
|
2564 Accessible::RootAccessible() const |
|
2565 { |
|
2566 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode()); |
|
2567 NS_ASSERTION(docShell, "No docshell for mContent"); |
|
2568 if (!docShell) { |
|
2569 return nullptr; |
|
2570 } |
|
2571 |
|
2572 nsCOMPtr<nsIDocShellTreeItem> root; |
|
2573 docShell->GetRootTreeItem(getter_AddRefs(root)); |
|
2574 NS_ASSERTION(root, "No root content tree item"); |
|
2575 if (!root) { |
|
2576 return nullptr; |
|
2577 } |
|
2578 |
|
2579 DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root); |
|
2580 return docAcc ? docAcc->AsRoot() : nullptr; |
|
2581 } |
|
2582 |
|
2583 nsIFrame* |
|
2584 Accessible::GetFrame() const |
|
2585 { |
|
2586 return mContent ? mContent->GetPrimaryFrame() : nullptr; |
|
2587 } |
|
2588 |
|
2589 nsINode* |
|
2590 Accessible::GetNode() const |
|
2591 { |
|
2592 return mContent; |
|
2593 } |
|
2594 |
|
2595 void |
|
2596 Accessible::Language(nsAString& aLanguage) |
|
2597 { |
|
2598 aLanguage.Truncate(); |
|
2599 |
|
2600 if (!mDoc) |
|
2601 return; |
|
2602 |
|
2603 nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage); |
|
2604 if (aLanguage.IsEmpty()) { // Nothing found, so use document's language |
|
2605 mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage, |
|
2606 aLanguage); |
|
2607 } |
|
2608 } |
|
2609 |
|
2610 void |
|
2611 Accessible::InvalidateChildren() |
|
2612 { |
|
2613 int32_t childCount = mChildren.Length(); |
|
2614 for (int32_t childIdx = 0; childIdx < childCount; childIdx++) { |
|
2615 Accessible* child = mChildren.ElementAt(childIdx); |
|
2616 child->UnbindFromParent(); |
|
2617 } |
|
2618 |
|
2619 mEmbeddedObjCollector = nullptr; |
|
2620 mChildren.Clear(); |
|
2621 SetChildrenFlag(eChildrenUninitialized); |
|
2622 } |
|
2623 |
|
2624 bool |
|
2625 Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) |
|
2626 { |
|
2627 if (!aChild) |
|
2628 return false; |
|
2629 |
|
2630 if (aIndex == mChildren.Length()) { |
|
2631 if (!mChildren.AppendElement(aChild)) |
|
2632 return false; |
|
2633 |
|
2634 } else { |
|
2635 if (!mChildren.InsertElementAt(aIndex, aChild)) |
|
2636 return false; |
|
2637 |
|
2638 for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) { |
|
2639 NS_ASSERTION(static_cast<uint32_t>(mChildren[idx]->mIndexInParent) == idx - 1, |
|
2640 "Accessible child index doesn't match"); |
|
2641 mChildren[idx]->mIndexInParent = idx; |
|
2642 } |
|
2643 |
|
2644 mEmbeddedObjCollector = nullptr; |
|
2645 } |
|
2646 |
|
2647 if (!nsAccUtils::IsEmbeddedObject(aChild)) |
|
2648 SetChildrenFlag(eMixedChildren); |
|
2649 |
|
2650 aChild->BindToParent(this, aIndex); |
|
2651 return true; |
|
2652 } |
|
2653 |
|
2654 bool |
|
2655 Accessible::RemoveChild(Accessible* aChild) |
|
2656 { |
|
2657 if (!aChild) |
|
2658 return false; |
|
2659 |
|
2660 if (aChild->mParent != this || aChild->mIndexInParent == -1) |
|
2661 return false; |
|
2662 |
|
2663 uint32_t index = static_cast<uint32_t>(aChild->mIndexInParent); |
|
2664 if (index >= mChildren.Length() || mChildren[index] != aChild) { |
|
2665 NS_ERROR("Child is bound to parent but parent hasn't this child at its index!"); |
|
2666 aChild->UnbindFromParent(); |
|
2667 return false; |
|
2668 } |
|
2669 |
|
2670 for (uint32_t idx = index + 1; idx < mChildren.Length(); idx++) { |
|
2671 NS_ASSERTION(static_cast<uint32_t>(mChildren[idx]->mIndexInParent) == idx, |
|
2672 "Accessible child index doesn't match"); |
|
2673 mChildren[idx]->mIndexInParent = idx - 1; |
|
2674 } |
|
2675 |
|
2676 aChild->UnbindFromParent(); |
|
2677 mChildren.RemoveElementAt(index); |
|
2678 mEmbeddedObjCollector = nullptr; |
|
2679 |
|
2680 return true; |
|
2681 } |
|
2682 |
|
2683 Accessible* |
|
2684 Accessible::GetChildAt(uint32_t aIndex) const |
|
2685 { |
|
2686 Accessible* child = mChildren.SafeElementAt(aIndex, nullptr); |
|
2687 if (!child) |
|
2688 return nullptr; |
|
2689 |
|
2690 #ifdef DEBUG |
|
2691 Accessible* realParent = child->mParent; |
|
2692 NS_ASSERTION(!realParent || realParent == this, |
|
2693 "Two accessibles have the same first child accessible!"); |
|
2694 #endif |
|
2695 |
|
2696 return child; |
|
2697 } |
|
2698 |
|
2699 uint32_t |
|
2700 Accessible::ChildCount() const |
|
2701 { |
|
2702 return mChildren.Length(); |
|
2703 } |
|
2704 |
|
2705 int32_t |
|
2706 Accessible::IndexInParent() const |
|
2707 { |
|
2708 return mIndexInParent; |
|
2709 } |
|
2710 |
|
2711 uint32_t |
|
2712 Accessible::EmbeddedChildCount() |
|
2713 { |
|
2714 if (IsChildrenFlag(eMixedChildren)) { |
|
2715 if (!mEmbeddedObjCollector) |
|
2716 mEmbeddedObjCollector = new EmbeddedObjCollector(this); |
|
2717 return mEmbeddedObjCollector->Count(); |
|
2718 } |
|
2719 |
|
2720 return ChildCount(); |
|
2721 } |
|
2722 |
|
2723 Accessible* |
|
2724 Accessible::GetEmbeddedChildAt(uint32_t aIndex) |
|
2725 { |
|
2726 if (IsChildrenFlag(eMixedChildren)) { |
|
2727 if (!mEmbeddedObjCollector) |
|
2728 mEmbeddedObjCollector = new EmbeddedObjCollector(this); |
|
2729 return mEmbeddedObjCollector ? |
|
2730 mEmbeddedObjCollector->GetAccessibleAt(aIndex) : nullptr; |
|
2731 } |
|
2732 |
|
2733 return GetChildAt(aIndex); |
|
2734 } |
|
2735 |
|
2736 int32_t |
|
2737 Accessible::GetIndexOfEmbeddedChild(Accessible* aChild) |
|
2738 { |
|
2739 if (IsChildrenFlag(eMixedChildren)) { |
|
2740 if (!mEmbeddedObjCollector) |
|
2741 mEmbeddedObjCollector = new EmbeddedObjCollector(this); |
|
2742 return mEmbeddedObjCollector ? |
|
2743 mEmbeddedObjCollector->GetIndexAt(aChild) : -1; |
|
2744 } |
|
2745 |
|
2746 return GetIndexOf(aChild); |
|
2747 } |
|
2748 |
|
2749 //////////////////////////////////////////////////////////////////////////////// |
|
2750 // HyperLinkAccessible methods |
|
2751 |
|
2752 bool |
|
2753 Accessible::IsLink() |
|
2754 { |
|
2755 // Every embedded accessible within hypertext accessible implements |
|
2756 // hyperlink interface. |
|
2757 return mParent && mParent->IsHyperText() && nsAccUtils::IsEmbeddedObject(this); |
|
2758 } |
|
2759 |
|
2760 uint32_t |
|
2761 Accessible::StartOffset() |
|
2762 { |
|
2763 NS_PRECONDITION(IsLink(), "StartOffset is called not on hyper link!"); |
|
2764 |
|
2765 HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr; |
|
2766 return hyperText ? hyperText->GetChildOffset(this) : 0; |
|
2767 } |
|
2768 |
|
2769 uint32_t |
|
2770 Accessible::EndOffset() |
|
2771 { |
|
2772 NS_PRECONDITION(IsLink(), "EndOffset is called on not hyper link!"); |
|
2773 |
|
2774 HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr; |
|
2775 return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0; |
|
2776 } |
|
2777 |
|
2778 uint32_t |
|
2779 Accessible::AnchorCount() |
|
2780 { |
|
2781 NS_PRECONDITION(IsLink(), "AnchorCount is called on not hyper link!"); |
|
2782 return 1; |
|
2783 } |
|
2784 |
|
2785 Accessible* |
|
2786 Accessible::AnchorAt(uint32_t aAnchorIndex) |
|
2787 { |
|
2788 NS_PRECONDITION(IsLink(), "GetAnchor is called on not hyper link!"); |
|
2789 return aAnchorIndex == 0 ? this : nullptr; |
|
2790 } |
|
2791 |
|
2792 already_AddRefed<nsIURI> |
|
2793 Accessible::AnchorURIAt(uint32_t aAnchorIndex) |
|
2794 { |
|
2795 NS_PRECONDITION(IsLink(), "AnchorURIAt is called on not hyper link!"); |
|
2796 return nullptr; |
|
2797 } |
|
2798 |
|
2799 |
|
2800 //////////////////////////////////////////////////////////////////////////////// |
|
2801 // SelectAccessible |
|
2802 |
|
2803 already_AddRefed<nsIArray> |
|
2804 Accessible::SelectedItems() |
|
2805 { |
|
2806 nsCOMPtr<nsIMutableArray> selectedItems = do_CreateInstance(NS_ARRAY_CONTRACTID); |
|
2807 if (!selectedItems) |
|
2808 return nullptr; |
|
2809 |
|
2810 AccIterator iter(this, filters::GetSelected); |
|
2811 nsIAccessible* selected = nullptr; |
|
2812 while ((selected = iter.Next())) |
|
2813 selectedItems->AppendElement(selected, false); |
|
2814 |
|
2815 return selectedItems.forget(); |
|
2816 } |
|
2817 |
|
2818 uint32_t |
|
2819 Accessible::SelectedItemCount() |
|
2820 { |
|
2821 uint32_t count = 0; |
|
2822 AccIterator iter(this, filters::GetSelected); |
|
2823 Accessible* selected = nullptr; |
|
2824 while ((selected = iter.Next())) |
|
2825 ++count; |
|
2826 |
|
2827 return count; |
|
2828 } |
|
2829 |
|
2830 Accessible* |
|
2831 Accessible::GetSelectedItem(uint32_t aIndex) |
|
2832 { |
|
2833 AccIterator iter(this, filters::GetSelected); |
|
2834 Accessible* selected = nullptr; |
|
2835 |
|
2836 uint32_t index = 0; |
|
2837 while ((selected = iter.Next()) && index < aIndex) |
|
2838 index++; |
|
2839 |
|
2840 return selected; |
|
2841 } |
|
2842 |
|
2843 bool |
|
2844 Accessible::IsItemSelected(uint32_t aIndex) |
|
2845 { |
|
2846 uint32_t index = 0; |
|
2847 AccIterator iter(this, filters::GetSelectable); |
|
2848 Accessible* selected = nullptr; |
|
2849 while ((selected = iter.Next()) && index < aIndex) |
|
2850 index++; |
|
2851 |
|
2852 return selected && |
|
2853 selected->State() & states::SELECTED; |
|
2854 } |
|
2855 |
|
2856 bool |
|
2857 Accessible::AddItemToSelection(uint32_t aIndex) |
|
2858 { |
|
2859 uint32_t index = 0; |
|
2860 AccIterator iter(this, filters::GetSelectable); |
|
2861 Accessible* selected = nullptr; |
|
2862 while ((selected = iter.Next()) && index < aIndex) |
|
2863 index++; |
|
2864 |
|
2865 if (selected) |
|
2866 selected->SetSelected(true); |
|
2867 |
|
2868 return static_cast<bool>(selected); |
|
2869 } |
|
2870 |
|
2871 bool |
|
2872 Accessible::RemoveItemFromSelection(uint32_t aIndex) |
|
2873 { |
|
2874 uint32_t index = 0; |
|
2875 AccIterator iter(this, filters::GetSelectable); |
|
2876 Accessible* selected = nullptr; |
|
2877 while ((selected = iter.Next()) && index < aIndex) |
|
2878 index++; |
|
2879 |
|
2880 if (selected) |
|
2881 selected->SetSelected(false); |
|
2882 |
|
2883 return static_cast<bool>(selected); |
|
2884 } |
|
2885 |
|
2886 bool |
|
2887 Accessible::SelectAll() |
|
2888 { |
|
2889 bool success = false; |
|
2890 Accessible* selectable = nullptr; |
|
2891 |
|
2892 AccIterator iter(this, filters::GetSelectable); |
|
2893 while((selectable = iter.Next())) { |
|
2894 success = true; |
|
2895 selectable->SetSelected(true); |
|
2896 } |
|
2897 return success; |
|
2898 } |
|
2899 |
|
2900 bool |
|
2901 Accessible::UnselectAll() |
|
2902 { |
|
2903 bool success = false; |
|
2904 Accessible* selected = nullptr; |
|
2905 |
|
2906 AccIterator iter(this, filters::GetSelected); |
|
2907 while ((selected = iter.Next())) { |
|
2908 success = true; |
|
2909 selected->SetSelected(false); |
|
2910 } |
|
2911 return success; |
|
2912 } |
|
2913 |
|
2914 //////////////////////////////////////////////////////////////////////////////// |
|
2915 // Widgets |
|
2916 |
|
2917 bool |
|
2918 Accessible::IsWidget() const |
|
2919 { |
|
2920 return false; |
|
2921 } |
|
2922 |
|
2923 bool |
|
2924 Accessible::IsActiveWidget() const |
|
2925 { |
|
2926 if (FocusMgr()->HasDOMFocus(mContent)) |
|
2927 return true; |
|
2928 |
|
2929 // If text entry of combobox widget has a focus then the combobox widget is |
|
2930 // active. |
|
2931 if (mRoleMapEntry && mRoleMapEntry->Is(nsGkAtoms::combobox)) { |
|
2932 uint32_t childCount = ChildCount(); |
|
2933 for (uint32_t idx = 0; idx < childCount; idx++) { |
|
2934 Accessible* child = mChildren.ElementAt(idx); |
|
2935 if (child->Role() == roles::ENTRY) |
|
2936 return FocusMgr()->HasDOMFocus(child->GetContent()); |
|
2937 } |
|
2938 } |
|
2939 |
|
2940 return false; |
|
2941 } |
|
2942 |
|
2943 bool |
|
2944 Accessible::AreItemsOperable() const |
|
2945 { |
|
2946 return HasOwnContent() && |
|
2947 mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant); |
|
2948 } |
|
2949 |
|
2950 Accessible* |
|
2951 Accessible::CurrentItem() |
|
2952 { |
|
2953 // Check for aria-activedescendant, which changes which element has focus. |
|
2954 // For activedescendant, the ARIA spec does not require that the user agent |
|
2955 // checks whether pointed node is actually a DOM descendant of the element |
|
2956 // with the aria-activedescendant attribute. |
|
2957 nsAutoString id; |
|
2958 if (HasOwnContent() && |
|
2959 mContent->GetAttr(kNameSpaceID_None, |
|
2960 nsGkAtoms::aria_activedescendant, id)) { |
|
2961 nsIDocument* DOMDoc = mContent->OwnerDoc(); |
|
2962 dom::Element* activeDescendantElm = DOMDoc->GetElementById(id); |
|
2963 if (activeDescendantElm) { |
|
2964 DocAccessible* document = Document(); |
|
2965 if (document) |
|
2966 return document->GetAccessible(activeDescendantElm); |
|
2967 } |
|
2968 } |
|
2969 return nullptr; |
|
2970 } |
|
2971 |
|
2972 void |
|
2973 Accessible::SetCurrentItem(Accessible* aItem) |
|
2974 { |
|
2975 nsIAtom* id = aItem->GetContent()->GetID(); |
|
2976 if (id) { |
|
2977 nsAutoString idStr; |
|
2978 id->ToString(idStr); |
|
2979 mContent->SetAttr(kNameSpaceID_None, |
|
2980 nsGkAtoms::aria_activedescendant, idStr, true); |
|
2981 } |
|
2982 } |
|
2983 |
|
2984 Accessible* |
|
2985 Accessible::ContainerWidget() const |
|
2986 { |
|
2987 if (HasARIARole() && mContent->HasID()) { |
|
2988 for (Accessible* parent = Parent(); parent; parent = parent->Parent()) { |
|
2989 nsIContent* parentContent = parent->GetContent(); |
|
2990 if (parentContent && |
|
2991 parentContent->HasAttr(kNameSpaceID_None, |
|
2992 nsGkAtoms::aria_activedescendant)) { |
|
2993 return parent; |
|
2994 } |
|
2995 |
|
2996 // Don't cross DOM document boundaries. |
|
2997 if (parent->IsDoc()) |
|
2998 break; |
|
2999 } |
|
3000 } |
|
3001 return nullptr; |
|
3002 } |
|
3003 |
|
3004 //////////////////////////////////////////////////////////////////////////////// |
|
3005 // Accessible protected methods |
|
3006 |
|
3007 void |
|
3008 Accessible::LastRelease() |
|
3009 { |
|
3010 // First cleanup if needed... |
|
3011 if (mDoc) { |
|
3012 Shutdown(); |
|
3013 NS_ASSERTION(!mDoc, |
|
3014 "A Shutdown() impl forgot to call its parent's Shutdown?"); |
|
3015 } |
|
3016 // ... then die. |
|
3017 delete this; |
|
3018 } |
|
3019 |
|
3020 void |
|
3021 Accessible::CacheChildren() |
|
3022 { |
|
3023 DocAccessible* doc = Document(); |
|
3024 NS_ENSURE_TRUE_VOID(doc); |
|
3025 |
|
3026 TreeWalker walker(this, mContent); |
|
3027 |
|
3028 Accessible* child = nullptr; |
|
3029 while ((child = walker.NextChild()) && AppendChild(child)); |
|
3030 } |
|
3031 |
|
3032 void |
|
3033 Accessible::TestChildCache(Accessible* aCachedChild) const |
|
3034 { |
|
3035 #ifdef DEBUG |
|
3036 int32_t childCount = mChildren.Length(); |
|
3037 if (childCount == 0) { |
|
3038 NS_ASSERTION(IsChildrenFlag(eChildrenUninitialized), |
|
3039 "No children but initialized!"); |
|
3040 return; |
|
3041 } |
|
3042 |
|
3043 Accessible* child = nullptr; |
|
3044 for (int32_t childIdx = 0; childIdx < childCount; childIdx++) { |
|
3045 child = mChildren[childIdx]; |
|
3046 if (child == aCachedChild) |
|
3047 break; |
|
3048 } |
|
3049 |
|
3050 NS_ASSERTION(child == aCachedChild, |
|
3051 "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!"); |
|
3052 #endif |
|
3053 } |
|
3054 |
|
3055 // Accessible public |
|
3056 bool |
|
3057 Accessible::EnsureChildren() |
|
3058 { |
|
3059 if (IsDefunct()) { |
|
3060 SetChildrenFlag(eChildrenUninitialized); |
|
3061 return true; |
|
3062 } |
|
3063 |
|
3064 if (!IsChildrenFlag(eChildrenUninitialized)) |
|
3065 return false; |
|
3066 |
|
3067 // State is embedded children until text leaf accessible is appended. |
|
3068 SetChildrenFlag(eEmbeddedChildren); // Prevent reentry |
|
3069 |
|
3070 CacheChildren(); |
|
3071 return false; |
|
3072 } |
|
3073 |
|
3074 Accessible* |
|
3075 Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const |
|
3076 { |
|
3077 if (!mParent || mIndexInParent == -1) { |
|
3078 if (aError) |
|
3079 *aError = NS_ERROR_UNEXPECTED; |
|
3080 |
|
3081 return nullptr; |
|
3082 } |
|
3083 |
|
3084 if (aError && |
|
3085 mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) { |
|
3086 *aError = NS_OK; // fail peacefully |
|
3087 return nullptr; |
|
3088 } |
|
3089 |
|
3090 Accessible* child = mParent->GetChildAt(mIndexInParent + aOffset); |
|
3091 if (aError && !child) |
|
3092 *aError = NS_ERROR_UNEXPECTED; |
|
3093 |
|
3094 return child; |
|
3095 } |
|
3096 |
|
3097 double |
|
3098 Accessible::AttrNumericValue(nsIAtom* aAttr) const |
|
3099 { |
|
3100 if (!mRoleMapEntry || mRoleMapEntry->valueRule == eNoValue) |
|
3101 return UnspecifiedNaN<double>(); |
|
3102 |
|
3103 nsAutoString attrValue; |
|
3104 if (!mContent->GetAttr(kNameSpaceID_None, aAttr, attrValue)) |
|
3105 return UnspecifiedNaN<double>(); |
|
3106 |
|
3107 nsresult error = NS_OK; |
|
3108 double value = attrValue.ToDouble(&error); |
|
3109 return NS_FAILED(error) ? UnspecifiedNaN<double>() : value; |
|
3110 } |
|
3111 |
|
3112 uint32_t |
|
3113 Accessible::GetActionRule() |
|
3114 { |
|
3115 if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) |
|
3116 return eNoAction; |
|
3117 |
|
3118 // Return "click" action on elements that have an attached popup menu. |
|
3119 if (mContent->IsXUL()) |
|
3120 if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) |
|
3121 return eClickAction; |
|
3122 |
|
3123 // Has registered 'click' event handler. |
|
3124 bool isOnclick = nsCoreUtils::HasClickListener(mContent); |
|
3125 |
|
3126 if (isOnclick) |
|
3127 return eClickAction; |
|
3128 |
|
3129 // Get an action based on ARIA role. |
|
3130 if (mRoleMapEntry && |
|
3131 mRoleMapEntry->actionRule != eNoAction) |
|
3132 return mRoleMapEntry->actionRule; |
|
3133 |
|
3134 // Get an action based on ARIA attribute. |
|
3135 if (nsAccUtils::HasDefinedARIAToken(mContent, |
|
3136 nsGkAtoms::aria_expanded)) |
|
3137 return eExpandAction; |
|
3138 |
|
3139 return eNoAction; |
|
3140 } |
|
3141 |
|
3142 AccGroupInfo* |
|
3143 Accessible::GetGroupInfo() |
|
3144 { |
|
3145 if (mGroupInfo){ |
|
3146 if (HasDirtyGroupInfo()) { |
|
3147 mGroupInfo->Update(); |
|
3148 SetDirtyGroupInfo(false); |
|
3149 } |
|
3150 |
|
3151 return mGroupInfo; |
|
3152 } |
|
3153 |
|
3154 mGroupInfo = AccGroupInfo::CreateGroupInfo(this); |
|
3155 return mGroupInfo; |
|
3156 } |
|
3157 |
|
3158 void |
|
3159 Accessible::InvalidateChildrenGroupInfo() |
|
3160 { |
|
3161 uint32_t length = mChildren.Length(); |
|
3162 for (uint32_t i = 0; i < length; i++) { |
|
3163 Accessible* child = mChildren[i]; |
|
3164 child->SetDirtyGroupInfo(true); |
|
3165 } |
|
3166 } |
|
3167 |
|
3168 void |
|
3169 Accessible::GetPositionAndSizeInternal(int32_t *aPosInSet, int32_t *aSetSize) |
|
3170 { |
|
3171 AccGroupInfo* groupInfo = GetGroupInfo(); |
|
3172 if (groupInfo) { |
|
3173 *aPosInSet = groupInfo->PosInSet(); |
|
3174 *aSetSize = groupInfo->SetSize(); |
|
3175 } |
|
3176 } |
|
3177 |
|
3178 int32_t |
|
3179 Accessible::GetLevelInternal() |
|
3180 { |
|
3181 int32_t level = nsAccUtils::GetDefaultLevel(this); |
|
3182 |
|
3183 if (!IsBoundToParent()) |
|
3184 return level; |
|
3185 |
|
3186 roles::Role role = Role(); |
|
3187 if (role == roles::OUTLINEITEM) { |
|
3188 // Always expose 'level' attribute for 'outlineitem' accessible. The number |
|
3189 // of nested 'grouping' accessibles containing 'outlineitem' accessible is |
|
3190 // its level. |
|
3191 level = 1; |
|
3192 |
|
3193 Accessible* parent = this; |
|
3194 while ((parent = parent->Parent())) { |
|
3195 roles::Role parentRole = parent->Role(); |
|
3196 |
|
3197 if (parentRole == roles::OUTLINE) |
|
3198 break; |
|
3199 if (parentRole == roles::GROUPING) |
|
3200 ++ level; |
|
3201 |
|
3202 } |
|
3203 |
|
3204 } else if (role == roles::LISTITEM) { |
|
3205 // Expose 'level' attribute on nested lists. We support two hierarchies: |
|
3206 // a) list -> listitem -> list -> listitem (nested list is a last child |
|
3207 // of listitem of the parent list); |
|
3208 // b) list -> listitem -> group -> listitem (nested listitems are contained |
|
3209 // by group that is a last child of the parent listitem). |
|
3210 |
|
3211 // Calculate 'level' attribute based on number of parent listitems. |
|
3212 level = 0; |
|
3213 Accessible* parent = this; |
|
3214 while ((parent = parent->Parent())) { |
|
3215 roles::Role parentRole = parent->Role(); |
|
3216 |
|
3217 if (parentRole == roles::LISTITEM) |
|
3218 ++ level; |
|
3219 else if (parentRole != roles::LIST && parentRole != roles::GROUPING) |
|
3220 break; |
|
3221 } |
|
3222 |
|
3223 if (level == 0) { |
|
3224 // If this listitem is on top of nested lists then expose 'level' |
|
3225 // attribute. |
|
3226 parent = Parent(); |
|
3227 uint32_t siblingCount = parent->ChildCount(); |
|
3228 for (uint32_t siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) { |
|
3229 Accessible* sibling = parent->GetChildAt(siblingIdx); |
|
3230 |
|
3231 Accessible* siblingChild = sibling->LastChild(); |
|
3232 if (siblingChild) { |
|
3233 roles::Role lastChildRole = siblingChild->Role(); |
|
3234 if (lastChildRole == roles::LIST || lastChildRole == roles::GROUPING) |
|
3235 return 1; |
|
3236 } |
|
3237 } |
|
3238 } else { |
|
3239 ++ level; // level is 1-index based |
|
3240 } |
|
3241 } |
|
3242 |
|
3243 return level; |
|
3244 } |
|
3245 |
|
3246 void |
|
3247 Accessible::StaticAsserts() const |
|
3248 { |
|
3249 static_assert(eLastChildrenFlag <= (1 << kChildrenFlagsBits) - 1, |
|
3250 "Accessible::mChildrenFlags was oversized by eLastChildrenFlag!"); |
|
3251 static_assert(eLastStateFlag <= (1 << kStateFlagsBits) - 1, |
|
3252 "Accessible::mStateFlags was oversized by eLastStateFlag!"); |
|
3253 static_assert(eLastAccType <= (1 << kTypeBits) - 1, |
|
3254 "Accessible::mType was oversized by eLastAccType!"); |
|
3255 static_assert(eLastContextFlag <= (1 << kContextFlagsBits) - 1, |
|
3256 "Accessible::mContextFlags was oversized by eLastContextFlag!"); |
|
3257 static_assert(eLastAccGenericType <= (1 << kGenericTypesBits) - 1, |
|
3258 "Accessible::mGenericType was oversized by eLastAccGenericType!"); |
|
3259 } |
|
3260 |
|
3261 |
|
3262 //////////////////////////////////////////////////////////////////////////////// |
|
3263 // KeyBinding class |
|
3264 |
|
3265 void |
|
3266 KeyBinding::ToPlatformFormat(nsAString& aValue) const |
|
3267 { |
|
3268 nsCOMPtr<nsIStringBundle> keyStringBundle; |
|
3269 nsCOMPtr<nsIStringBundleService> stringBundleService = |
|
3270 mozilla::services::GetStringBundleService(); |
|
3271 if (stringBundleService) |
|
3272 stringBundleService->CreateBundle( |
|
3273 "chrome://global-platform/locale/platformKeys.properties", |
|
3274 getter_AddRefs(keyStringBundle)); |
|
3275 |
|
3276 if (!keyStringBundle) |
|
3277 return; |
|
3278 |
|
3279 nsAutoString separator; |
|
3280 keyStringBundle->GetStringFromName(MOZ_UTF16("MODIFIER_SEPARATOR"), |
|
3281 getter_Copies(separator)); |
|
3282 |
|
3283 nsAutoString modifierName; |
|
3284 if (mModifierMask & kControl) { |
|
3285 keyStringBundle->GetStringFromName(MOZ_UTF16("VK_CONTROL"), |
|
3286 getter_Copies(modifierName)); |
|
3287 |
|
3288 aValue.Append(modifierName); |
|
3289 aValue.Append(separator); |
|
3290 } |
|
3291 |
|
3292 if (mModifierMask & kAlt) { |
|
3293 keyStringBundle->GetStringFromName(MOZ_UTF16("VK_ALT"), |
|
3294 getter_Copies(modifierName)); |
|
3295 |
|
3296 aValue.Append(modifierName); |
|
3297 aValue.Append(separator); |
|
3298 } |
|
3299 |
|
3300 if (mModifierMask & kShift) { |
|
3301 keyStringBundle->GetStringFromName(MOZ_UTF16("VK_SHIFT"), |
|
3302 getter_Copies(modifierName)); |
|
3303 |
|
3304 aValue.Append(modifierName); |
|
3305 aValue.Append(separator); |
|
3306 } |
|
3307 |
|
3308 if (mModifierMask & kMeta) { |
|
3309 keyStringBundle->GetStringFromName(MOZ_UTF16("VK_META"), |
|
3310 getter_Copies(modifierName)); |
|
3311 |
|
3312 aValue.Append(modifierName); |
|
3313 aValue.Append(separator); |
|
3314 } |
|
3315 |
|
3316 aValue.Append(mKey); |
|
3317 } |
|
3318 |
|
3319 void |
|
3320 KeyBinding::ToAtkFormat(nsAString& aValue) const |
|
3321 { |
|
3322 nsAutoString modifierName; |
|
3323 if (mModifierMask & kControl) |
|
3324 aValue.Append(NS_LITERAL_STRING("<Control>")); |
|
3325 |
|
3326 if (mModifierMask & kAlt) |
|
3327 aValue.Append(NS_LITERAL_STRING("<Alt>")); |
|
3328 |
|
3329 if (mModifierMask & kShift) |
|
3330 aValue.Append(NS_LITERAL_STRING("<Shift>")); |
|
3331 |
|
3332 if (mModifierMask & kMeta) |
|
3333 aValue.Append(NS_LITERAL_STRING("<Meta>")); |
|
3334 |
|
3335 aValue.Append(mKey); |
|
3336 } |
|
3337 |