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