accessible/src/generic/Accessible.cpp

branch
TOR_BUG_9701
changeset 3
141e0f1194b1
equal deleted inserted replaced
-1:000000000000 0:d914eb77d8f3
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

mercurial