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 "LocalAccessible-inl.h"
7
8 #include "EmbeddedObjCollector.h"
9 #include "AccAttributes.h"
10 #include "AccGroupInfo.h"
11 #include "AccIterator.h"
12 #include "CacheConstants.h"
13 #include "DocAccessible-inl.h"
14 #include "nsAccUtils.h"
15 #include "nsAccessibilityService.h"
16 #include "ApplicationAccessible.h"
17 #include "nsAccessiblePivot.h"
18 #include "nsGenericHTMLElement.h"
19 #include "NotificationController.h"
20 #include "nsEventShell.h"
21 #include "nsTextEquivUtils.h"
22 #include "DocAccessibleChild.h"
23 #include "EventTree.h"
24 #include "Pivot.h"
25 #include "Relation.h"
26 #include "Role.h"
27 #include "RootAccessible.h"
28 #include "States.h"
29 #include "StyleInfo.h"
30 #include "TextLeafRange.h"
31 #include "TextRange.h"
32 #include "TableAccessible.h"
33 #include "TableCellAccessible.h"
34 #include "TreeWalker.h"
35 #include "HTMLElementAccessibles.h"
36 #include "ImageAccessible.h"
37
38 #include "nsIDOMXULButtonElement.h"
39 #include "nsIDOMXULSelectCntrlEl.h"
40 #include "nsIDOMXULSelectCntrlItemEl.h"
41 #include "nsINodeList.h"
42 #include "nsPIDOMWindow.h"
43
44 #include "mozilla/dom/Document.h"
45 #include "mozilla/dom/HTMLFormElement.h"
46 #include "mozilla/dom/HTMLAnchorElement.h"
47 #include "mozilla/gfx/Matrix.h"
48 #include "nsIContent.h"
49 #include "nsIFormControl.h"
50
51 #include "nsDeckFrame.h"
52 #include "nsLayoutUtils.h"
53 #include "nsIStringBundle.h"
54 #include "nsPresContext.h"
55 #include "nsIFrame.h"
56 #include "nsView.h"
57 #include "nsIDocShellTreeItem.h"
58 #include "nsIScrollableFrame.h"
59 #include "nsFocusManager.h"
60
61 #include "nsString.h"
62 #include "nsUnicharUtils.h"
63 #include "nsReadableUtils.h"
64 #include "prdtoa.h"
65 #include "nsAtom.h"
66 #include "nsIURI.h"
67 #include "nsArrayUtils.h"
68 #include "nsWhitespaceTokenizer.h"
69 #include "nsAttrName.h"
70
71 #include "mozilla/Assertions.h"
72 #include "mozilla/BasicEvents.h"
73 #include "mozilla/Components.h"
74 #include "mozilla/ErrorResult.h"
75 #include "mozilla/EventStates.h"
76 #include "mozilla/FloatingPoint.h"
77 #include "mozilla/MouseEvents.h"
78 #include "mozilla/PresShell.h"
79 #include "mozilla/Unused.h"
80 #include "mozilla/Preferences.h"
81 #include "mozilla/ProfilerMarkers.h"
82 #include "mozilla/StaticPrefs_accessibility.h"
83 #include "mozilla/StaticPrefs_ui.h"
84 #include "mozilla/dom/CanvasRenderingContext2D.h"
85 #include "mozilla/dom/Element.h"
86 #include "mozilla/dom/HTMLCanvasElement.h"
87 #include "mozilla/dom/HTMLBodyElement.h"
88 #include "mozilla/dom/KeyboardEventBinding.h"
89 #include "mozilla/dom/TreeWalker.h"
90 #include "mozilla/dom/UserActivation.h"
91 #include "mozilla/dom/MutationEventBinding.h"
92
93 using namespace mozilla;
94 using namespace mozilla::a11y;
95
96 ////////////////////////////////////////////////////////////////////////////////
97 // LocalAccessible: nsISupports and cycle collection
98
99 NS_IMPL_CYCLE_COLLECTION_CLASS(LocalAccessible)
100 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalAccessible)
101 tmp->Shutdown();
102 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalAccessible)103 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalAccessible)
104 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)
105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
106
107 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalAccessible)
108 NS_INTERFACE_MAP_ENTRY_CONCRETE(LocalAccessible)
109 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LocalAccessible)
110 NS_INTERFACE_MAP_END
111
112 NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalAccessible)
113 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(LocalAccessible, LastRelease())
114
115 LocalAccessible::LocalAccessible(nsIContent* aContent, DocAccessible* aDoc)
116 : Accessible(),
117 mContent(aContent),
118 mDoc(aDoc),
119 mParent(nullptr),
120 mIndexInParent(-1),
121 mBounds(),
122 mStateFlags(0),
123 mContextFlags(0),
124 mReorderEventTarget(false),
125 mShowEventTarget(false),
126 mHideEventTarget(false) {
127 mBits.groupInfo = nullptr;
128 mIndexOfEmbeddedChild = -1;
129 }
130
~LocalAccessible()131 LocalAccessible::~LocalAccessible() {
132 NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
133 }
134
Name(nsString & aName) const135 ENameValueFlag LocalAccessible::Name(nsString& aName) const {
136 aName.Truncate();
137
138 if (!HasOwnContent()) return eNameOK;
139
140 ARIAName(aName);
141 if (!aName.IsEmpty()) return eNameOK;
142
143 ENameValueFlag nameFlag = NativeName(aName);
144 if (!aName.IsEmpty()) return nameFlag;
145
146 // In the end get the name from tooltip.
147 if (mContent->IsHTMLElement()) {
148 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title,
149 aName)) {
150 aName.CompressWhitespace();
151 return eNameFromTooltip;
152 }
153 } else if (mContent->IsXULElement()) {
154 if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
155 nsGkAtoms::tooltiptext, aName)) {
156 aName.CompressWhitespace();
157 return eNameFromTooltip;
158 }
159 } else if (mContent->IsSVGElement()) {
160 // If user agents need to choose among multiple 'desc' or 'title'
161 // elements for processing, the user agent shall choose the first one.
162 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
163 childElm = childElm->GetNextSibling()) {
164 if (childElm->IsSVGElement(nsGkAtoms::desc)) {
165 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
166 return eNameFromTooltip;
167 }
168 }
169 }
170
171 if (nameFlag != eNoNameOnPurpose) aName.SetIsVoid(true);
172
173 return nameFlag;
174 }
175
Description(nsString & aDescription) const176 void LocalAccessible::Description(nsString& aDescription) const {
177 // There are 4 conditions that make an accessible have no accDescription:
178 // 1. it's a text node; or
179 // 2. It has no ARIA describedby or description property
180 // 3. it doesn't have an accName; or
181 // 4. its title attribute already equals to its accName nsAutoString name;
182
183 if (!HasOwnContent() || mContent->IsText()) return;
184
185 ARIADescription(aDescription);
186
187 if (aDescription.IsEmpty()) {
188 NativeDescription(aDescription);
189
190 if (aDescription.IsEmpty()) {
191 // Keep the Name() method logic.
192 if (mContent->IsHTMLElement()) {
193 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title,
194 aDescription);
195 } else if (mContent->IsXULElement()) {
196 mContent->AsElement()->GetAttr(kNameSpaceID_None,
197 nsGkAtoms::tooltiptext, aDescription);
198 } else if (mContent->IsSVGElement()) {
199 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
200 childElm = childElm->GetNextSibling()) {
201 if (childElm->IsSVGElement(nsGkAtoms::desc)) {
202 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm,
203 &aDescription);
204 break;
205 }
206 }
207 }
208 }
209 }
210
211 if (!aDescription.IsEmpty()) {
212 aDescription.CompressWhitespace();
213 nsAutoString name;
214 Name(name);
215 // Don't expose a description if it is the same as the name.
216 if (aDescription.Equals(name)) aDescription.Truncate();
217 }
218 }
219
AccessKey() const220 KeyBinding LocalAccessible::AccessKey() const {
221 if (!HasOwnContent()) return KeyBinding();
222
223 uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
224 if (!key && mContent->IsElement()) {
225 LocalAccessible* label = nullptr;
226
227 // Copy access key from label node.
228 if (mContent->IsHTMLElement()) {
229 // Unless it is labeled via an ancestor <label>, in which case that would
230 // be redundant.
231 HTMLLabelIterator iter(Document(), this,
232 HTMLLabelIterator::eSkipAncestorLabel);
233 label = iter.Next();
234 }
235 if (!label) {
236 XULLabelIterator iter(Document(), mContent);
237 label = iter.Next();
238 }
239
240 if (label) key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
241 }
242
243 if (!key) return KeyBinding();
244
245 // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
246 switch (StaticPrefs::ui_key_generalAccessKey()) {
247 case -1:
248 break;
249 case dom::KeyboardEvent_Binding::DOM_VK_SHIFT:
250 return KeyBinding(key, KeyBinding::kShift);
251 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
252 return KeyBinding(key, KeyBinding::kControl);
253 case dom::KeyboardEvent_Binding::DOM_VK_ALT:
254 return KeyBinding(key, KeyBinding::kAlt);
255 case dom::KeyboardEvent_Binding::DOM_VK_META:
256 return KeyBinding(key, KeyBinding::kMeta);
257 default:
258 return KeyBinding();
259 }
260
261 // Determine the access modifier used in this context.
262 dom::Document* document = mContent->GetComposedDoc();
263 if (!document) return KeyBinding();
264
265 nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
266 if (!treeItem) return KeyBinding();
267
268 nsresult rv = NS_ERROR_FAILURE;
269 int32_t modifierMask = 0;
270 switch (treeItem->ItemType()) {
271 case nsIDocShellTreeItem::typeChrome:
272 modifierMask = StaticPrefs::ui_key_chromeAccess();
273 rv = NS_OK;
274 break;
275 case nsIDocShellTreeItem::typeContent:
276 modifierMask = StaticPrefs::ui_key_contentAccess();
277 rv = NS_OK;
278 break;
279 }
280
281 return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
282 }
283
KeyboardShortcut() const284 KeyBinding LocalAccessible::KeyboardShortcut() const { return KeyBinding(); }
285
VisibilityState() const286 uint64_t LocalAccessible::VisibilityState() const {
287 if (IPCAccessibilityActive() &&
288 StaticPrefs::accessibility_cache_enabled_AtStartup()) {
289 // Visibility states must be calculated by RemoteAccessible, so there's no
290 // point calculating them here.
291 return 0;
292 }
293 nsIFrame* frame = GetFrame();
294 if (!frame) {
295 // Element having display:contents is considered visible semantically,
296 // despite it doesn't have a visually visible box.
297 if (nsCoreUtils::IsDisplayContents(mContent)) {
298 return states::OFFSCREEN;
299 }
300 return states::INVISIBLE;
301 }
302
303 if (!frame->StyleVisibility()->IsVisible()) return states::INVISIBLE;
304
305 // It's invisible if the presshell is hidden by a visibility:hidden element in
306 // an ancestor document.
307 if (frame->PresShell()->IsUnderHiddenEmbedderElement()) {
308 return states::INVISIBLE;
309 }
310
311 // Offscreen state if the document's visibility state is not visible.
312 if (Document()->IsHidden()) return states::OFFSCREEN;
313
314 // Walk the parent frame chain to see if the frame is in background tab or
315 // scrolled out.
316 nsIFrame* curFrame = frame;
317 do {
318 nsView* view = curFrame->GetView();
319 if (view && view->GetVisibility() == nsViewVisibility_kHide) {
320 return states::INVISIBLE;
321 }
322
323 if (nsLayoutUtils::IsPopup(curFrame)) return 0;
324
325 // Offscreen state for background tab content and invisible for not selected
326 // deck panel.
327 nsIFrame* parentFrame = curFrame->GetParent();
328 nsDeckFrame* deckFrame = do_QueryFrame(parentFrame);
329 if (deckFrame && deckFrame->GetSelectedBox() != curFrame) {
330 if (deckFrame->GetContent()->IsXULElement(nsGkAtoms::tabpanels)) {
331 return states::OFFSCREEN;
332 }
333
334 MOZ_ASSERT_UNREACHABLE(
335 "Children of not selected deck panel are not accessible.");
336 return states::INVISIBLE;
337 }
338
339 // If contained by scrollable frame then check that at least 12 pixels
340 // around the object is visible, otherwise the object is offscreen.
341 nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame);
342 const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
343 if (scrollableFrame) {
344 nsRect scrollPortRect = scrollableFrame->GetScrollPortRect();
345 nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
346 frame, frame->GetRectRelativeToSelf(), parentFrame);
347 if (!scrollPortRect.Contains(frameRect)) {
348 scrollPortRect.Deflate(kMinPixels, kMinPixels);
349 if (!scrollPortRect.Intersects(frameRect)) return states::OFFSCREEN;
350 }
351 }
352
353 if (!parentFrame) {
354 parentFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(curFrame);
355 // Even if we couldn't find the parent frame, it might mean we are in an
356 // out-of-process iframe, try to see if |frame| is scrolled out in an
357 // scrollable frame in a cross-process ancestor document.
358 if (!parentFrame &&
359 nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
360 frame, kMinPixels)) {
361 return states::OFFSCREEN;
362 }
363 }
364
365 curFrame = parentFrame;
366 } while (curFrame);
367
368 // Zero area rects can occur in the first frame of a multi-frame text flow,
369 // in which case the rendered text is not empty and the frame should not be
370 // marked invisible.
371 // XXX Can we just remove this check? Why do we need to mark empty
372 // text invisible?
373 if (frame->IsTextFrame() && !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
374 frame->GetRect().IsEmpty()) {
375 nsIFrame::RenderedText text = frame->GetRenderedText(
376 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
377 nsIFrame::TrailingWhitespace::DontTrim);
378 if (text.mString.IsEmpty()) {
379 return states::INVISIBLE;
380 }
381 }
382
383 return 0;
384 }
385
NativeState() const386 uint64_t LocalAccessible::NativeState() const {
387 uint64_t state = 0;
388
389 if (!IsInDocument()) state |= states::STALE;
390
391 if (HasOwnContent() && mContent->IsElement()) {
392 EventStates elementState = mContent->AsElement()->State();
393
394 if (elementState.HasState(NS_EVENT_STATE_INVALID)) state |= states::INVALID;
395
396 if (elementState.HasState(NS_EVENT_STATE_REQUIRED)) {
397 state |= states::REQUIRED;
398 }
399
400 state |= NativeInteractiveState();
401 if (FocusMgr()->IsFocused(this)) state |= states::FOCUSED;
402 }
403
404 // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
405 state |= VisibilityState();
406
407 nsIFrame* frame = GetFrame();
408 if (frame) {
409 if (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) state |= states::FLOATING;
410
411 // XXX we should look at layout for non XUL box frames, but need to decide
412 // how that interacts with ARIA.
413 if (HasOwnContent() && mContent->IsXULElement() && frame->IsXULBoxFrame()) {
414 const nsStyleXUL* xulStyle = frame->StyleXUL();
415 if (xulStyle && frame->IsXULBoxFrame()) {
416 // In XUL all boxes are either vertical or horizontal
417 if (xulStyle->mBoxOrient == StyleBoxOrient::Vertical) {
418 state |= states::VERTICAL;
419 } else {
420 state |= states::HORIZONTAL;
421 }
422 }
423 }
424 }
425
426 // Check if a XUL element has the popup attribute (an attached popup menu).
427 if (HasOwnContent() && mContent->IsXULElement() &&
428 mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) {
429 state |= states::HASPOPUP;
430 }
431
432 // Bypass the link states specialization for non links.
433 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
434 if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
435 roleMapEntry->role == roles::LINK) {
436 state |= NativeLinkState();
437 }
438
439 return state;
440 }
441
NativeInteractiveState() const442 uint64_t LocalAccessible::NativeInteractiveState() const {
443 if (!mContent->IsElement()) return 0;
444
445 if (NativelyUnavailable()) return states::UNAVAILABLE;
446
447 nsIFrame* frame = GetFrame();
448 if (frame && frame->IsFocusable()) return states::FOCUSABLE;
449
450 return 0;
451 }
452
NativeLinkState() const453 uint64_t LocalAccessible::NativeLinkState() const { return 0; }
454
NativelyUnavailable() const455 bool LocalAccessible::NativelyUnavailable() const {
456 if (mContent->IsHTMLElement()) return mContent->AsElement()->IsDisabled();
457
458 return mContent->IsElement() && mContent->AsElement()->AttrValueIs(
459 kNameSpaceID_None, nsGkAtoms::disabled,
460 nsGkAtoms::_true, eCaseMatters);
461 }
462
FocusedChild()463 LocalAccessible* LocalAccessible::FocusedChild() {
464 LocalAccessible* focus = FocusMgr()->FocusedAccessible();
465 if (focus && (focus == this || focus->LocalParent() == this)) {
466 return focus;
467 }
468
469 return nullptr;
470 }
471
ChildAtPoint(int32_t aX,int32_t aY,EWhichChildAtPoint aWhichChild)472 Accessible* LocalAccessible::ChildAtPoint(int32_t aX, int32_t aY,
473 EWhichChildAtPoint aWhichChild) {
474 Accessible* child = LocalChildAtPoint(aX, aY, aWhichChild);
475 if (aWhichChild != EWhichChildAtPoint::DirectChild && child &&
476 child->IsOuterDoc()) {
477 child = child->ChildAtPoint(aX, aY, aWhichChild);
478 }
479
480 return child;
481 }
482
LocalChildAtPoint(int32_t aX,int32_t aY,EWhichChildAtPoint aWhichChild)483 LocalAccessible* LocalAccessible::LocalChildAtPoint(
484 int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) {
485 // If we can't find the point in a child, we will return the fallback answer:
486 // we return |this| if the point is within it, otherwise nullptr.
487 LocalAccessible* fallbackAnswer = nullptr;
488 LayoutDeviceIntRect rect = Bounds();
489 if (rect.Contains(aX, aY)) fallbackAnswer = this;
490
491 if (nsAccUtils::MustPrune(this)) { // Do not dig any further
492 return fallbackAnswer;
493 }
494
495 // Search an accessible at the given point starting from accessible document
496 // because containing block (see CSS2) for out of flow element (for example,
497 // absolutely positioned element) may be different from its DOM parent and
498 // therefore accessible for containing block may be different from accessible
499 // for DOM parent but GetFrameForPoint() should be called for containing block
500 // to get an out of flow element.
501 DocAccessible* accDocument = Document();
502 NS_ENSURE_TRUE(accDocument, nullptr);
503
504 nsIFrame* rootFrame = accDocument->GetFrame();
505 NS_ENSURE_TRUE(rootFrame, nullptr);
506
507 nsIFrame* startFrame = rootFrame;
508
509 // Check whether the point is at popup content.
510 nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
511 NS_ENSURE_TRUE(rootWidget, nullptr);
512
513 LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
514
515 auto point = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y());
516
517 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
518 accDocument->PresContext()->GetRootPresContext(), rootWidget, point);
519 if (popupFrame) {
520 // If 'this' accessible is not inside the popup then ignore the popup when
521 // searching an accessible at point.
522 DocAccessible* popupDoc =
523 GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
524 LocalAccessible* popupAcc =
525 popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
526 LocalAccessible* popupChild = this;
527 while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) {
528 popupChild = popupChild->LocalParent();
529 }
530
531 if (popupChild == popupAcc) startFrame = popupFrame;
532 }
533
534 nsPresContext* presContext = startFrame->PresContext();
535 nsRect screenRect = startFrame->GetScreenRectInAppUnits();
536 nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(),
537 presContext->DevPixelsToAppUnits(aY) - screenRect.Y());
538
539 nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(
540 RelativeTo{startFrame, ViewportType::Visual}, offset);
541
542 nsIContent* content = nullptr;
543 if (!foundFrame || !(content = foundFrame->GetContent())) {
544 return fallbackAnswer;
545 }
546
547 // Get accessible for the node with the point or the first accessible in
548 // the DOM parent chain.
549 DocAccessible* contentDocAcc =
550 GetAccService()->GetDocAccessible(content->OwnerDoc());
551
552 // contentDocAcc in some circumstances can be nullptr. See bug 729861
553 NS_ASSERTION(contentDocAcc, "could not get the document accessible");
554 if (!contentDocAcc) return fallbackAnswer;
555
556 LocalAccessible* accessible =
557 contentDocAcc->GetAccessibleOrContainer(content);
558 if (!accessible) return fallbackAnswer;
559
560 // Hurray! We have an accessible for the frame that layout gave us.
561 // Since DOM node of obtained accessible may be out of flow then we should
562 // ensure obtained accessible is a child of this accessible.
563 LocalAccessible* child = accessible;
564 while (child != this) {
565 LocalAccessible* parent = child->LocalParent();
566 if (!parent) {
567 // Reached the top of the hierarchy. These bounds were inside an
568 // accessible that is not a descendant of this one.
569 return fallbackAnswer;
570 }
571
572 // If we landed on a legitimate child of |this|, and we want the direct
573 // child, return it here.
574 if (parent == this && aWhichChild == EWhichChildAtPoint::DirectChild) {
575 return child;
576 }
577
578 child = parent;
579 }
580
581 // Manually walk through accessible children and see if the are within this
582 // point. Skip offscreen or invisible accessibles. This takes care of cases
583 // where layout won't walk into things for us, such as image map areas and
584 // sub documents (XXX: subdocuments should be handled by methods of
585 // OuterDocAccessibles).
586 uint32_t childCount = accessible->ChildCount();
587 if (childCount == 1 && accessible->IsOuterDoc() &&
588 accessible->FirstChild()->IsRemote()) {
589 // No local children.
590 return accessible;
591 }
592 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
593 LocalAccessible* child = accessible->LocalChildAt(childIdx);
594
595 LayoutDeviceIntRect childRect = child->Bounds();
596 if (childRect.Contains(aX, aY) &&
597 (child->State() & states::INVISIBLE) == 0) {
598 if (aWhichChild == EWhichChildAtPoint::DeepestChild) {
599 return child->LocalChildAtPoint(aX, aY,
600 EWhichChildAtPoint::DeepestChild);
601 }
602
603 return child;
604 }
605 }
606
607 return accessible;
608 }
609
FindNearestAccessibleAncestorFrame()610 nsIFrame* LocalAccessible::FindNearestAccessibleAncestorFrame() {
611 nsIFrame* frame = GetFrame();
612 nsIFrame* boundingFrame = nullptr;
613
614 if (IsDoc() &&
615 nsCoreUtils::IsTopLevelContentDocInProcess(AsDoc()->DocumentNode())) {
616 // Tab documents and OOP iframe docs won't have ancestor accessibles
617 // with frames. Instead, bound by their presshell's root frame.
618 boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
619 }
620
621 // Iterate through accessible's ancestors to find one with a frame.
622 LocalAccessible* ancestor = mParent;
623 while (ancestor && !boundingFrame) {
624 if (ancestor->IsDoc()) {
625 // If we find a doc accessible, use its presshell's root frame
626 // (since doc accessibles themselves don't have frames).
627 boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
628 break;
629 }
630
631 if ((boundingFrame = ancestor->GetFrame())) {
632 // Otherwise, if the ancestor has a frame, use that
633 break;
634 }
635
636 ancestor = ancestor->LocalParent();
637 }
638
639 if (!boundingFrame) {
640 MOZ_ASSERT_UNREACHABLE("No ancestor with frame?");
641 boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
642 }
643
644 return boundingFrame;
645 }
646
ParentRelativeBounds()647 nsRect LocalAccessible::ParentRelativeBounds() {
648 nsIFrame* frame = GetFrame();
649 if (frame && mContent) {
650 if (mContent->GetProperty(nsGkAtoms::hitregion) && mContent->IsElement()) {
651 // This is for canvas fallback content
652 // Find a canvas frame the found hit region is relative to.
653 nsIFrame* canvasFrame = frame->GetParent();
654 if (canvasFrame) {
655 canvasFrame = nsLayoutUtils::GetClosestFrameOfType(
656 canvasFrame, LayoutFrameType::HTMLCanvas);
657 }
658
659 if (canvasFrame) {
660 if (auto* canvas =
661 dom::HTMLCanvasElement::FromNode(canvasFrame->GetContent())) {
662 if (auto* context = canvas->GetCurrentContext()) {
663 nsRect bounds;
664 if (context->GetHitRegionRect(mContent->AsElement(), bounds)) {
665 return bounds;
666 }
667 }
668 }
669 }
670 }
671
672 nsIFrame* boundingFrame = FindNearestAccessibleAncestorFrame();
673 nsRect unionRect =
674 nsLayoutUtils::GetAllInFlowRectsUnion(frame, boundingFrame);
675
676 if (unionRect.IsEmpty()) {
677 // If we end up with a 0x0 rect from above (or one with negative
678 // height/width) we should try using the ink overflow rect instead. If we
679 // use this rect, our relative bounds will match the bounds of what
680 // appears visually. We do this because some web authors (icloud.com for
681 // example) employ things like 0x0 buttons with visual overflow. Without
682 // this, such frames aren't navigable by screen readers.
683 nsRect overflow = frame->InkOverflowRectRelativeToSelf();
684 nsLayoutUtils::TransformRect(frame, boundingFrame, overflow);
685 return overflow;
686 }
687
688 return unionRect;
689 }
690
691 return nsRect();
692 }
693
RelativeBounds(nsIFrame ** aBoundingFrame) const694 nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const {
695 nsIFrame* frame = GetFrame();
696 if (frame && mContent) {
697 if (mContent->GetProperty(nsGkAtoms::hitregion) && mContent->IsElement()) {
698 // This is for canvas fallback content
699 // Find a canvas frame the found hit region is relative to.
700 nsIFrame* canvasFrame = frame->GetParent();
701 if (canvasFrame) {
702 canvasFrame = nsLayoutUtils::GetClosestFrameOfType(
703 canvasFrame, LayoutFrameType::HTMLCanvas);
704 }
705
706 // make the canvas the bounding frame
707 if (canvasFrame) {
708 *aBoundingFrame = canvasFrame;
709 if (auto* canvas =
710 dom::HTMLCanvasElement::FromNode(canvasFrame->GetContent())) {
711 if (auto* context = canvas->GetCurrentContext()) {
712 nsRect bounds;
713 if (context->GetHitRegionRect(mContent->AsElement(), bounds)) {
714 return bounds;
715 }
716 }
717 }
718 }
719 }
720
721 *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
722 nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
723 frame, *aBoundingFrame, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
724
725 if (unionRect.IsEmpty()) {
726 // If we end up with a 0x0 rect from above (or one with negative
727 // height/width) we should try using the ink overflow rect instead. If we
728 // use this rect, our relative bounds will match the bounds of what
729 // appears visually. We do this because some web authors (icloud.com for
730 // example) employ things like 0x0 buttons with visual overflow. Without
731 // this, such frames aren't navigable by screen readers.
732 nsRect overflow = frame->InkOverflowRectRelativeToSelf();
733 nsLayoutUtils::TransformRect(frame, *aBoundingFrame, overflow);
734 return overflow;
735 }
736
737 return unionRect;
738 }
739
740 return nsRect();
741 }
742
BoundsInAppUnits() const743 nsRect LocalAccessible::BoundsInAppUnits() const {
744 nsIFrame* boundingFrame = nullptr;
745 nsRect unionRectTwips = RelativeBounds(&boundingFrame);
746 if (!boundingFrame) {
747 return nsRect();
748 }
749
750 PresShell* presShell = mDoc->PresContext()->PresShell();
751
752 // We need to inverse translate with the offset of the edge of the visual
753 // viewport from top edge of the layout viewport.
754 nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
755 presShell->GetLayoutViewportOffset();
756 unionRectTwips.MoveBy(-viewportOffset);
757
758 // We need to take into account a non-1 resolution set on the presshell.
759 // This happens with async pinch zooming. Here we scale the bounds before
760 // adding the screen-relative offset.
761 unionRectTwips.ScaleRoundOut(presShell->GetResolution());
762 // We have the union of the rectangle, now we need to put it in absolute
763 // screen coords.
764 nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits();
765 unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y());
766
767 return unionRectTwips;
768 }
769
Bounds() const770 LayoutDeviceIntRect LocalAccessible::Bounds() const {
771 return LayoutDeviceIntRect::FromAppUnitsToNearest(
772 BoundsInAppUnits(), mDoc->PresContext()->AppUnitsPerDevPixel());
773 }
774
BoundsInCSSPixels() const775 nsIntRect LocalAccessible::BoundsInCSSPixels() const {
776 return BoundsInAppUnits().ToNearestPixels(AppUnitsPerCSSPixel());
777 }
778
SetSelected(bool aSelect)779 void LocalAccessible::SetSelected(bool aSelect) {
780 if (!HasOwnContent()) return;
781
782 LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State());
783 if (select) {
784 if (select->State() & states::MULTISELECTABLE) {
785 if (mContent->IsElement() && ARIARoleMap()) {
786 if (aSelect) {
787 mContent->AsElement()->SetAttr(
788 kNameSpaceID_None, nsGkAtoms::aria_selected, u"true"_ns, true);
789 } else {
790 mContent->AsElement()->UnsetAttr(kNameSpaceID_None,
791 nsGkAtoms::aria_selected, true);
792 }
793 }
794 return;
795 }
796
797 if (aSelect) TakeFocus();
798 }
799 }
800
TakeSelection()801 void LocalAccessible::TakeSelection() {
802 LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State());
803 if (select) {
804 if (select->State() & states::MULTISELECTABLE) select->UnselectAll();
805 SetSelected(true);
806 }
807 }
808
TakeFocus() const809 void LocalAccessible::TakeFocus() const {
810 nsIFrame* frame = GetFrame();
811 if (!frame) return;
812
813 nsIContent* focusContent = mContent;
814
815 // If the accessible focus is managed by container widget then focus the
816 // widget and set the accessible as its current item.
817 if (!frame->IsFocusable()) {
818 LocalAccessible* widget = ContainerWidget();
819 if (widget && widget->AreItemsOperable()) {
820 nsIContent* widgetElm = widget->GetContent();
821 nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
822 if (widgetFrame && widgetFrame->IsFocusable()) {
823 focusContent = widgetElm;
824 widget->SetCurrentItem(this);
825 }
826 }
827 }
828
829 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
830 dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
831 // XXXbz: Can we actually have a non-element content here?
832 RefPtr<dom::Element> element = dom::Element::FromNodeOrNull(focusContent);
833 fm->SetFocus(element, 0);
834 }
835 }
836
NameFromAssociatedXULLabel(DocAccessible * aDocument,nsIContent * aElm,nsString & aName)837 void LocalAccessible::NameFromAssociatedXULLabel(DocAccessible* aDocument,
838 nsIContent* aElm,
839 nsString& aName) {
840 LocalAccessible* label = nullptr;
841 XULLabelIterator iter(aDocument, aElm);
842 while ((label = iter.Next())) {
843 // Check if label's value attribute is used
844 label->Elm()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName);
845 if (aName.IsEmpty()) {
846 // If no value attribute, a non-empty label must contain
847 // children that define its text -- possibly using HTML
848 nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName);
849 }
850 }
851 aName.CompressWhitespace();
852 }
853
XULElmName(DocAccessible * aDocument,nsIContent * aElm,nsString & aName)854 void LocalAccessible::XULElmName(DocAccessible* aDocument, nsIContent* aElm,
855 nsString& aName) {
856 /**
857 * 3 main cases for XUL Controls to be labeled
858 * 1 - control contains label="foo"
859 * 2 - non-child label contains control="controlID"
860 * - label has either value="foo" or children
861 * 3 - name from subtree; e.g. a child label element
862 * Cases 1 and 2 are handled here.
863 * Case 3 is handled by GetNameFromSubtree called in NativeName.
864 * Once a label is found, the search is discontinued, so a control
865 * that has a label attribute as well as having a label external to
866 * the control that uses the control="controlID" syntax will use
867 * the label attribute for its Name.
868 */
869
870 // CASE #1 (via label attribute) -- great majority of the cases
871 // Only do this if this is not a select control element, which uses label
872 // attribute to indicate, which option is selected.
873 nsCOMPtr<nsIDOMXULSelectControlElement> select =
874 aElm->AsElement()->AsXULSelectControl();
875 if (!select) {
876 aElm->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
877 }
878
879 // CASE #2 -- label as <label control="id" ... ></label>
880 if (aName.IsEmpty()) {
881 NameFromAssociatedXULLabel(aDocument, aElm, aName);
882 }
883
884 aName.CompressWhitespace();
885 }
886
HandleAccEvent(AccEvent * aEvent)887 nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) {
888 NS_ENSURE_ARG_POINTER(aEvent);
889
890 if (profiler_thread_is_being_profiled_for_markers()) {
891 nsAutoCString strEventType;
892 GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
893 nsAutoCString strMarker;
894 strMarker.AppendLiteral("A11y Event - ");
895 strMarker.Append(strEventType);
896 PROFILER_MARKER_UNTYPED(strMarker, OTHER);
897 }
898
899 if (IPCAccessibilityActive() && Document()) {
900 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
901 // If ipcDoc is null, we can't fire the event to the client. We shouldn't
902 // have fired the event in the first place, since this makes events
903 // inconsistent for local and remote documents. To avoid this, don't call
904 // nsEventShell::FireEvent on a DocAccessible for which
905 // HasLoadState(eTreeConstructed) is false.
906 MOZ_ASSERT(ipcDoc);
907 if (ipcDoc) {
908 uint64_t id = aEvent->GetAccessible()->IsDoc()
909 ? 0
910 : reinterpret_cast<uintptr_t>(
911 aEvent->GetAccessible()->UniqueID());
912
913 switch (aEvent->GetEventType()) {
914 case nsIAccessibleEvent::EVENT_SHOW:
915 ipcDoc->ShowEvent(downcast_accEvent(aEvent));
916 break;
917
918 case nsIAccessibleEvent::EVENT_HIDE:
919 ipcDoc->SendHideEvent(id, aEvent->IsFromUserInput());
920 break;
921
922 case nsIAccessibleEvent::EVENT_REORDER:
923 // reorder events on the application acc aren't necessary to tell the
924 // parent about new top level documents.
925 if (!aEvent->GetAccessible()->IsApplication()) {
926 ipcDoc->SendEvent(id, aEvent->GetEventType());
927 }
928 break;
929 case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
930 AccStateChangeEvent* event = downcast_accEvent(aEvent);
931 ipcDoc->SendStateChangeEvent(id, event->GetState(),
932 event->IsStateEnabled());
933 break;
934 }
935 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
936 AccCaretMoveEvent* event = downcast_accEvent(aEvent);
937 ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset(),
938 event->IsSelectionCollapsed(),
939 event->IsAtEndOfLine());
940 break;
941 }
942 case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
943 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
944 AccTextChangeEvent* event = downcast_accEvent(aEvent);
945 const nsString& text = event->ModifiedText();
946 #if defined(XP_WIN)
947 // On Windows, events for live region updates containing embedded
948 // objects require us to dispatch synchronous events.
949 bool sync = text.Contains(L'\xfffc') &&
950 nsAccUtils::IsARIALive(aEvent->GetAccessible());
951 #endif
952 ipcDoc->SendTextChangeEvent(id, text, event->GetStartOffset(),
953 event->GetLength(),
954 event->IsTextInserted(),
955 event->IsFromUserInput()
956 #if defined(XP_WIN)
957 // This parameter only exists on Windows.
958 ,
959 sync
960 #endif
961 );
962 break;
963 }
964 case nsIAccessibleEvent::EVENT_SELECTION:
965 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
966 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
967 AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
968 uint64_t widgetID =
969 selEvent->Widget()->IsDoc()
970 ? 0
971 : reinterpret_cast<uintptr_t>(selEvent->Widget()->UniqueID());
972 ipcDoc->SendSelectionEvent(id, widgetID, aEvent->GetEventType());
973 break;
974 }
975 case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
976 AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
977 LocalAccessible* position = vcEvent->NewAccessible();
978 LocalAccessible* oldPosition = vcEvent->OldAccessible();
979 ipcDoc->SendVirtualCursorChangeEvent(
980 id,
981 oldPosition ? reinterpret_cast<uintptr_t>(oldPosition->UniqueID())
982 : 0,
983 vcEvent->OldStartOffset(), vcEvent->OldEndOffset(),
984 position ? reinterpret_cast<uintptr_t>(position->UniqueID()) : 0,
985 vcEvent->NewStartOffset(), vcEvent->NewEndOffset(),
986 vcEvent->Reason(), vcEvent->BoundaryType(),
987 vcEvent->IsFromUserInput());
988 break;
989 }
990 #if defined(XP_WIN)
991 case nsIAccessibleEvent::EVENT_FOCUS: {
992 ipcDoc->SendFocusEvent(id);
993 break;
994 }
995 #endif
996 case nsIAccessibleEvent::EVENT_SCROLLING_END:
997 case nsIAccessibleEvent::EVENT_SCROLLING: {
998 AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
999 ipcDoc->SendScrollingEvent(
1000 id, aEvent->GetEventType(), scrollingEvent->ScrollX(),
1001 scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
1002 scrollingEvent->MaxScrollY());
1003 break;
1004 }
1005 #if !defined(XP_WIN)
1006 case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
1007 AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
1008 ipcDoc->SendAnnouncementEvent(id, announcementEvent->Announcement(),
1009 announcementEvent->Priority());
1010 break;
1011 }
1012 #endif // !defined(XP_WIN)
1013 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
1014 #if defined(XP_WIN)
1015 if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
1016 // On Windows, when the cache is disabled, we have to defer events
1017 // until we are notified that the DocAccessibleParent has been
1018 // constructed, which needs specific code for each event payload.
1019 // Since we don't need a special event payload for text selection in
1020 // this case anyway, just send it as a generic event.
1021 ipcDoc->SendEvent(id, aEvent->GetEventType());
1022 break;
1023 }
1024 #endif // defined(XP_WIN)
1025 AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
1026 AutoTArray<TextRange, 1> ranges;
1027 textSelChangeEvent->SelectionRanges(&ranges);
1028 nsTArray<TextRangeData> textRangeData(ranges.Length());
1029 for (size_t i = 0; i < ranges.Length(); i++) {
1030 const TextRange& range = ranges.ElementAt(i);
1031 LocalAccessible* start = range.StartContainer()->AsLocal();
1032 LocalAccessible* end = range.EndContainer()->AsLocal();
1033 textRangeData.AppendElement(TextRangeData(
1034 start->IsDoc() && start->AsDoc()->IPCDoc()
1035 ? 0
1036 : reinterpret_cast<uint64_t>(start->UniqueID()),
1037 end->IsDoc() && end->AsDoc()->IPCDoc()
1038 ? 0
1039 : reinterpret_cast<uint64_t>(end->UniqueID()),
1040 range.StartOffset(), range.EndOffset()));
1041 }
1042 ipcDoc->SendTextSelectionChangeEvent(id, textRangeData);
1043 break;
1044 }
1045 case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
1046 case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
1047 SendCache(CacheDomain::NameAndDescription, CacheUpdateType::Update);
1048 ipcDoc->SendEvent(id, aEvent->GetEventType());
1049 break;
1050 }
1051 case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
1052 case nsIAccessibleEvent::EVENT_VALUE_CHANGE: {
1053 SendCache(CacheDomain::Value, CacheUpdateType::Update);
1054 ipcDoc->SendEvent(id, aEvent->GetEventType());
1055 break;
1056 }
1057 default:
1058 ipcDoc->SendEvent(id, aEvent->GetEventType());
1059 }
1060 }
1061 }
1062
1063 if (nsCoreUtils::AccEventObserversExist()) {
1064 nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
1065 }
1066
1067 return NS_OK;
1068 }
1069
Attributes()1070 already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
1071 RefPtr<AccAttributes> attributes = NativeAttributes();
1072 if (!HasOwnContent() || !mContent->IsElement()) return attributes.forget();
1073
1074 // 'xml-roles' attribute coming from ARIA.
1075 nsString xmlRoles;
1076 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::role,
1077 xmlRoles)) {
1078 attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(xmlRoles));
1079 } else if (nsAtom* landmark = LandmarkRole()) {
1080 // 'xml-roles' attribute for landmark.
1081 attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
1082 }
1083
1084 // Expose object attributes from ARIA attributes.
1085 aria::AttrIterator attribIter(mContent);
1086 while (attribIter.Next()) {
1087 nsString value;
1088 attribIter.AttrValue(value);
1089 attributes->SetAttribute(attribIter.AttrName(), std::move(value));
1090 }
1091
1092 // If there is no aria-live attribute then expose default value of 'live'
1093 // object attribute used for ARIA role of this accessible.
1094 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1095 if (roleMapEntry) {
1096 if (roleMapEntry->Is(nsGkAtoms::searchbox)) {
1097 attributes->SetAttribute(nsGkAtoms::textInputType, nsGkAtoms::search);
1098 }
1099
1100 if (!attributes->HasAttribute(nsGkAtoms::aria_live)) {
1101 nsString live;
1102 if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live)) {
1103 attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
1104 }
1105 }
1106 }
1107
1108 return attributes.forget();
1109 }
1110
NativeAttributes()1111 already_AddRefed<AccAttributes> LocalAccessible::NativeAttributes() {
1112 RefPtr<AccAttributes> attributes = new AccAttributes();
1113
1114 // We support values, so expose the string value as well, via the valuetext
1115 // object attribute. We test for the value interface because we don't want
1116 // to expose traditional Value() information such as URL's on links and
1117 // documents, or text in an input.
1118 if (HasNumericValue()) {
1119 nsString valuetext;
1120 Value(valuetext);
1121 attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
1122 }
1123
1124 // Expose checkable object attribute if the accessible has checkable state
1125 if (State() & states::CHECKABLE) {
1126 attributes->SetAttribute(nsGkAtoms::checkable, true);
1127 }
1128
1129 // Expose 'explicit-name' attribute.
1130 nsAutoString name;
1131 if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
1132 attributes->SetAttribute(nsGkAtoms::explicit_name, true);
1133 }
1134
1135 // Group attributes (level/setsize/posinset)
1136 GroupPos groupPos = GroupPosition();
1137 nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, groupPos.setSize,
1138 groupPos.posInSet);
1139
1140 bool hierarchical = false;
1141 uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
1142 if (itemCount) {
1143 attributes->SetAttribute(nsGkAtoms::child_item_count,
1144 static_cast<int32_t>(itemCount));
1145 }
1146
1147 if (hierarchical) {
1148 attributes->SetAttribute(nsGkAtoms::tree, true);
1149 }
1150
1151 // If the accessible doesn't have own content (such as list item bullet or
1152 // xul tree item) then don't calculate content based attributes.
1153 if (!HasOwnContent()) return attributes.forget();
1154
1155 nsEventShell::GetEventAttributes(GetNode(), attributes);
1156
1157 // Get container-foo computed live region properties based on the closest
1158 // container with the live region attribute. Inner nodes override outer nodes
1159 // within the same document. The inner nodes can be used to override live
1160 // region behavior on more general outer nodes.
1161 nsAccUtils::SetLiveContainerAttributes(attributes, mContent);
1162
1163 if (!mContent->IsElement()) return attributes.forget();
1164
1165 nsString id;
1166 if (nsCoreUtils::GetID(mContent, id)) {
1167 attributes->SetAttribute(nsGkAtoms::id, std::move(id));
1168 }
1169
1170 // Expose class because it may have useful microformat information.
1171 nsString _class;
1172 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_class,
1173 _class)) {
1174 attributes->SetAttribute(nsGkAtoms::_class, std::move(_class));
1175 }
1176
1177 // Expose tag.
1178 attributes->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom());
1179
1180 // Expose draggable object attribute.
1181 if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
1182 if (htmlElement->Draggable()) {
1183 attributes->SetAttribute(nsGkAtoms::draggable, true);
1184 }
1185 }
1186
1187 // Don't calculate CSS-based object attributes when no frame (i.e.
1188 // the accessible is unattached from the tree).
1189 if (!mContent->GetPrimaryFrame()) return attributes.forget();
1190
1191 // CSS style based object attributes.
1192 nsAutoString value;
1193 StyleInfo styleInfo(mContent->AsElement());
1194
1195 // Expose 'display' attribute.
1196 RefPtr<nsAtom> displayValue = styleInfo.Display();
1197 attributes->SetAttribute(nsGkAtoms::display, displayValue);
1198
1199 // Expose 'text-align' attribute.
1200 RefPtr<nsAtom> textAlignValue = styleInfo.TextAlign();
1201 attributes->SetAttribute(nsGkAtoms::textAlign, textAlignValue);
1202
1203 // Expose 'text-indent' attribute.
1204 mozilla::LengthPercentage textIndent = styleInfo.TextIndent();
1205 if (textIndent.ConvertsToLength()) {
1206 attributes->SetAttribute(nsGkAtoms::textIndent,
1207 textIndent.ToLengthInCSSPixels());
1208 } else if (textIndent.ConvertsToPercentage()) {
1209 attributes->SetAttribute(nsGkAtoms::textIndent, textIndent.ToPercentage());
1210 }
1211
1212 // Expose 'margin-left' attribute.
1213 attributes->SetAttribute(nsGkAtoms::marginLeft, styleInfo.MarginLeft());
1214
1215 // Expose 'margin-right' attribute.
1216 attributes->SetAttribute(nsGkAtoms::marginRight, styleInfo.MarginRight());
1217
1218 // Expose 'margin-top' attribute.
1219 attributes->SetAttribute(nsGkAtoms::marginTop, styleInfo.MarginTop());
1220
1221 // Expose 'margin-bottom' attribute.
1222 attributes->SetAttribute(nsGkAtoms::marginBottom, styleInfo.MarginBottom());
1223
1224 // Expose data-at-shortcutkeys attribute for web applications and virtual
1225 // cursors. Currently mostly used by JAWS.
1226 nsString atShortcutKeys;
1227 if (mContent->AsElement()->GetAttr(
1228 kNameSpaceID_None, nsGkAtoms::dataAtShortcutkeys, atShortcutKeys)) {
1229 attributes->SetAttribute(nsGkAtoms::dataAtShortcutkeys,
1230 std::move(atShortcutKeys));
1231 }
1232
1233 return attributes.forget();
1234 }
1235
AttributeChangesState(nsAtom * aAttribute)1236 bool LocalAccessible::AttributeChangesState(nsAtom* aAttribute) {
1237 return aAttribute == nsGkAtoms::aria_disabled ||
1238 aAttribute == nsGkAtoms::disabled ||
1239 aAttribute == nsGkAtoms::tabindex ||
1240 aAttribute == nsGkAtoms::aria_required ||
1241 aAttribute == nsGkAtoms::aria_invalid ||
1242 aAttribute == nsGkAtoms::aria_expanded ||
1243 aAttribute == nsGkAtoms::aria_checked ||
1244 (aAttribute == nsGkAtoms::aria_pressed && IsButton()) ||
1245 aAttribute == nsGkAtoms::aria_readonly ||
1246 aAttribute == nsGkAtoms::aria_current ||
1247 aAttribute == nsGkAtoms::aria_haspopup ||
1248 aAttribute == nsGkAtoms::aria_busy ||
1249 aAttribute == nsGkAtoms::aria_multiline ||
1250 aAttribute == nsGkAtoms::aria_multiselectable ||
1251 aAttribute == nsGkAtoms::contenteditable;
1252 }
1253
DOMAttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue,uint64_t aOldState)1254 void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
1255 nsAtom* aAttribute, int32_t aModType,
1256 const nsAttrValue* aOldValue,
1257 uint64_t aOldState) {
1258 // Fire accessible event after short timer, because we need to wait for
1259 // DOM attribute & resulting layout to actually change. Otherwise,
1260 // assistive technology will retrieve the wrong state/value/selection info.
1261
1262 // XXX todo
1263 // We still need to handle special HTML cases here
1264 // For example, if an <img>'s usemap attribute is modified
1265 // Otherwise it may just be a state change, for example an object changing
1266 // its visibility
1267 //
1268 // XXX todo: report aria state changes for "undefined" literal value changes
1269 // filed as bug 472142
1270 //
1271 // XXX todo: invalidate accessible when aria state changes affect exposed
1272 // role filed as bug 472143
1273
1274 if (AttributeChangesState(aAttribute)) {
1275 uint64_t currState = State();
1276 uint64_t diffState = currState ^ aOldState;
1277 if (diffState) {
1278 for (uint64_t state = 1; state <= states::LAST_ENTRY; state <<= 1) {
1279 if (diffState & state) {
1280 RefPtr<AccEvent> stateChangeEvent =
1281 new AccStateChangeEvent(this, state, (currState & state));
1282 mDoc->FireDelayedEvent(stateChangeEvent);
1283 }
1284 }
1285 }
1286 }
1287
1288 // When a details object has its open attribute changed
1289 // we should fire a state-change event on the accessible of
1290 // its main summary
1291 if (aAttribute == nsGkAtoms::open) {
1292 // FromDetails checks if the given accessible belongs to
1293 // a details frame and also locates the accessible of its
1294 // main summary.
1295 if (HTMLSummaryAccessible* summaryAccessible =
1296 HTMLSummaryAccessible::FromDetails(this)) {
1297 RefPtr<AccEvent> expandedChangeEvent =
1298 new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
1299 mDoc->FireDelayedEvent(expandedChangeEvent);
1300 return;
1301 }
1302 }
1303
1304 // Check for namespaced ARIA attribute
1305 if (aNameSpaceID == kNameSpaceID_None) {
1306 // Check for hyphenated aria-foo property?
1307 if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
1308 uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
1309 if (!(attrFlags & ATTR_BYPASSOBJ)) {
1310 // For aria attributes like drag and drop changes we fire a generic
1311 // attribute change event; at least until native API comes up with a
1312 // more meaningful event.
1313 RefPtr<AccEvent> event =
1314 new AccObjectAttrChangedEvent(this, aAttribute);
1315 mDoc->FireDelayedEvent(event);
1316 }
1317 }
1318 }
1319
1320 dom::Element* elm = Elm();
1321
1322 if (HasNumericValue() &&
1323 (aAttribute == nsGkAtoms::aria_valuemax ||
1324 aAttribute == nsGkAtoms::aria_valuemin || aAttribute == nsGkAtoms::min ||
1325 aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step)) {
1326 SendCache(CacheDomain::Value, CacheUpdateType::Update);
1327 return;
1328 }
1329
1330 // Fire text value change event whenever aria-valuetext is changed.
1331 if (aAttribute == nsGkAtoms::aria_valuetext) {
1332 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, this);
1333 return;
1334 }
1335
1336 if (aAttribute == nsGkAtoms::aria_valuenow) {
1337 if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
1338 elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
1339 nsGkAtoms::_empty, eCaseMatters)) {
1340 // Fire numeric value change event when aria-valuenow is changed and
1341 // aria-valuetext is empty
1342 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
1343 } else {
1344 // We need to update the cache here since we won't get an event if
1345 // aria-valuenow is shadowed by aria-valuetext.
1346 SendCache(CacheDomain::Value, CacheUpdateType::Update);
1347 }
1348 return;
1349 }
1350
1351 if (aAttribute == nsGkAtoms::aria_owns) {
1352 mDoc->Controller()->ScheduleRelocation(this);
1353 }
1354
1355 // Fire name change and description change events.
1356 if (aAttribute == nsGkAtoms::aria_label) {
1357 // A valid aria-labelledby would take precedence so an aria-label change
1358 // won't change the name.
1359 IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
1360 if (!iter.NextElem()) {
1361 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1362 }
1363 return;
1364 }
1365
1366 if (aAttribute == nsGkAtoms::aria_describedby) {
1367 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, this);
1368 if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
1369 aModType == dom::MutationEvent_Binding::ADDITION) {
1370 // The subtrees of the new aria-describedby targets might be used to
1371 // compute the description for this. Therefore, we need to set
1372 // the eHasDescriptionDependent flag on all Accessibles in these subtrees.
1373 IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
1374 while (LocalAccessible* target = iter.Next()) {
1375 target->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
1376 }
1377 }
1378 return;
1379 }
1380
1381 if (aAttribute == nsGkAtoms::aria_labelledby) {
1382 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1383 if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
1384 aModType == dom::MutationEvent_Binding::ADDITION) {
1385 // The subtrees of the new aria-labelledby targets might be used to
1386 // compute the name for this. Therefore, we need to set
1387 // the eHasNameDependent flag on all Accessibles in these subtrees.
1388 IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
1389 while (LocalAccessible* target = iter.Next()) {
1390 target->ModifySubtreeContextFlags(eHasNameDependent, true);
1391 }
1392 }
1393 return;
1394 }
1395
1396 if ((aAttribute == nsGkAtoms::aria_expanded ||
1397 aAttribute == nsGkAtoms::href) &&
1398 (aModType == dom::MutationEvent_Binding::ADDITION ||
1399 aModType == dom::MutationEvent_Binding::REMOVAL)) {
1400 // The presence of aria-expanded adds an expand/collapse action.
1401 SendCache(CacheDomain::Actions, CacheUpdateType::Update);
1402 }
1403
1404 if (aAttribute == nsGkAtoms::alt &&
1405 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
1406 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
1407 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1408 return;
1409 }
1410
1411 if (aAttribute == nsGkAtoms::title) {
1412 nsAutoString name;
1413 ARIAName(name);
1414 if (name.IsEmpty()) {
1415 NativeName(name);
1416 if (name.IsEmpty()) {
1417 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1418 return;
1419 }
1420 }
1421
1422 if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby)) {
1423 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
1424 this);
1425 }
1426
1427 return;
1428 }
1429
1430 // ARIA or XUL selection
1431 if ((mContent->IsXULElement() && aAttribute == nsGkAtoms::selected) ||
1432 aAttribute == nsGkAtoms::aria_selected) {
1433 LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State());
1434 if (widget) {
1435 AccSelChangeEvent::SelChangeType selChangeType =
1436 elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
1437 eCaseMatters)
1438 ? AccSelChangeEvent::eSelectionAdd
1439 : AccSelChangeEvent::eSelectionRemove;
1440
1441 RefPtr<AccEvent> event =
1442 new AccSelChangeEvent(widget, this, selChangeType);
1443 mDoc->FireDelayedEvent(event);
1444 }
1445
1446 return;
1447 }
1448
1449 if (aAttribute == nsGkAtoms::aria_level ||
1450 aAttribute == nsGkAtoms::aria_setsize ||
1451 aAttribute == nsGkAtoms::aria_posinset) {
1452 SendCache(CacheDomain::GroupInfo, CacheUpdateType::Update);
1453 return;
1454 }
1455 }
1456
ARIAGroupPosition(int32_t * aLevel,int32_t * aSetSize,int32_t * aPosInSet) const1457 void LocalAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
1458 int32_t* aPosInSet) const {
1459 if (!mContent) {
1460 return;
1461 }
1462
1463 if (aLevel) {
1464 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, aLevel);
1465 }
1466 if (aSetSize) {
1467 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, aSetSize);
1468 }
1469 if (aPosInSet) {
1470 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, aPosInSet);
1471 }
1472 }
1473
State()1474 uint64_t LocalAccessible::State() {
1475 if (IsDefunct()) return states::DEFUNCT;
1476
1477 uint64_t state = NativeState();
1478 // Apply ARIA states to be sure accessible states will be overridden.
1479 ApplyARIAState(&state);
1480
1481 // If this is an ARIA item of the selectable widget and if it's focused and
1482 // not marked unselected explicitly (i.e. aria-selected="false") then expose
1483 // it as selected to make ARIA widget authors life easier.
1484 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1485 if (roleMapEntry && !(state & states::SELECTED) &&
1486 (!mContent->IsElement() ||
1487 !mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1488 nsGkAtoms::aria_selected,
1489 nsGkAtoms::_false, eCaseMatters))) {
1490 // Special case for tabs: focused tab or focus inside related tab panel
1491 // implies selected state.
1492 if (roleMapEntry->role == roles::PAGETAB) {
1493 if (state & states::FOCUSED) {
1494 state |= states::SELECTED;
1495 } else {
1496 // If focus is in a child of the tab panel surely the tab is selected!
1497 Relation rel = RelationByType(RelationType::LABEL_FOR);
1498 LocalAccessible* relTarget = nullptr;
1499 while ((relTarget = rel.Next())) {
1500 if (relTarget->Role() == roles::PROPERTYPAGE &&
1501 FocusMgr()->IsFocusWithin(relTarget)) {
1502 state |= states::SELECTED;
1503 }
1504 }
1505 }
1506 } else if (state & states::FOCUSED) {
1507 LocalAccessible* container =
1508 nsAccUtils::GetSelectableContainer(this, state);
1509 if (container &&
1510 !nsAccUtils::HasDefinedARIAToken(container->GetContent(),
1511 nsGkAtoms::aria_multiselectable)) {
1512 state |= states::SELECTED;
1513 }
1514 }
1515 }
1516
1517 const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED;
1518 if ((state & kExpandCollapseStates) == kExpandCollapseStates) {
1519 // Cannot be both expanded and collapsed -- this happens in ARIA expanded
1520 // combobox because of limitation of ARIAMap.
1521 // XXX: Perhaps we will be able to make this less hacky if we support
1522 // extended states in ARIAMap, e.g. derive COLLAPSED from
1523 // EXPANDABLE && !EXPANDED.
1524 state &= ~states::COLLAPSED;
1525 }
1526
1527 if (!(state & states::UNAVAILABLE)) {
1528 state |= states::ENABLED | states::SENSITIVE;
1529
1530 // If the object is a current item of container widget then mark it as
1531 // ACTIVE. This allows screen reader virtual buffer modes to know which
1532 // descendant is the current one that would get focus if the user navigates
1533 // to the container widget.
1534 LocalAccessible* widget = ContainerWidget();
1535 if (widget && widget->CurrentItem() == this) state |= states::ACTIVE;
1536 }
1537
1538 if ((state & states::COLLAPSED) || (state & states::EXPANDED)) {
1539 state |= states::EXPANDABLE;
1540 }
1541
1542 // For some reasons DOM node may have not a frame. We tract such accessibles
1543 // as invisible.
1544 nsIFrame* frame = GetFrame();
1545 if (!frame) return state;
1546
1547 if (frame->StyleEffects()->mOpacity == 1.0f && !(state & states::INVISIBLE)) {
1548 state |= states::OPAQUE1;
1549 }
1550
1551 return state;
1552 }
1553
ApplyARIAState(uint64_t * aState) const1554 void LocalAccessible::ApplyARIAState(uint64_t* aState) const {
1555 if (!mContent->IsElement()) return;
1556
1557 dom::Element* element = mContent->AsElement();
1558
1559 // Test for universal states first
1560 *aState |= aria::UniversalStatesFor(element);
1561
1562 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1563 if (roleMapEntry) {
1564 // We only force the readonly bit off if we have a real mapping for the aria
1565 // role. This preserves the ability for screen readers to use readonly
1566 // (primarily on the document) as the hint for creating a virtual buffer.
1567 if (roleMapEntry->role != roles::NOTHING) *aState &= ~states::READONLY;
1568
1569 if (mContent->HasID()) {
1570 // If has a role & ID and aria-activedescendant on the container, assume
1571 // focusable.
1572 const LocalAccessible* ancestor = this;
1573 while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
1574 dom::Element* el = ancestor->Elm();
1575 if (el &&
1576 el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
1577 *aState |= states::FOCUSABLE;
1578 break;
1579 }
1580 }
1581 }
1582 }
1583
1584 if (*aState & states::FOCUSABLE) {
1585 // Propogate aria-disabled from ancestors down to any focusable descendant.
1586 const LocalAccessible* ancestor = this;
1587 while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
1588 dom::Element* el = ancestor->Elm();
1589 if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
1590 nsGkAtoms::_true, eCaseMatters)) {
1591 *aState |= states::UNAVAILABLE;
1592 break;
1593 }
1594 }
1595 } else {
1596 // Sometimes, we use aria-activedescendant targeting something which isn't
1597 // actually a descendant. This is technically a spec violation, but it's a
1598 // useful hack which makes certain things much easier. For example, we use
1599 // this for "fake focus" for multi select browser tabs and Quantumbar
1600 // autocomplete suggestions.
1601 // In these cases, the aria-activedescendant code above won't make the
1602 // active item focusable. It doesn't make sense for something to have
1603 // focus when it isn't focusable, so fix that here.
1604 if (FocusMgr()->IsActiveItem(this)) {
1605 *aState |= states::FOCUSABLE;
1606 }
1607 }
1608
1609 // special case: A native button element whose role got transformed by ARIA to
1610 // a toggle button Also applies to togglable button menus, like in the Dev
1611 // Tools Web Console.
1612 if (IsButton() || IsMenuButton()) {
1613 aria::MapToState(aria::eARIAPressed, element, aState);
1614 }
1615
1616 if (!roleMapEntry) return;
1617
1618 *aState |= roleMapEntry->state;
1619
1620 if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
1621 aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
1622 aria::MapToState(roleMapEntry->attributeMap3, element, aState)) {
1623 aria::MapToState(roleMapEntry->attributeMap4, element, aState);
1624 }
1625
1626 // ARIA gridcell inherits readonly state from the grid until it's overridden.
1627 if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
1628 roleMapEntry->Is(nsGkAtoms::columnheader) ||
1629 roleMapEntry->Is(nsGkAtoms::rowheader)) &&
1630 !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) {
1631 const TableCellAccessible* cell = AsTableCell();
1632 if (cell) {
1633 TableAccessible* table = cell->Table();
1634 if (table) {
1635 LocalAccessible* grid = table->AsAccessible();
1636 uint64_t gridState = 0;
1637 grid->ApplyARIAState(&gridState);
1638 *aState |= gridState & states::READONLY;
1639 }
1640 }
1641 }
1642 }
1643
Value(nsString & aValue) const1644 void LocalAccessible::Value(nsString& aValue) const {
1645 if (HasNumericValue()) {
1646 // aria-valuenow is a number, and aria-valuetext is the optional text
1647 // equivalent. For the string value, we will try the optional text
1648 // equivalent first.
1649 if (!mContent->IsElement()) {
1650 return;
1651 }
1652
1653 if (!mContent->AsElement()->GetAttr(kNameSpaceID_None,
1654 nsGkAtoms::aria_valuetext, aValue)) {
1655 if (!NativeHasNumericValue()) {
1656 double checkValue = CurValue();
1657 if (!IsNaN(checkValue)) {
1658 aValue.AppendFloat(checkValue);
1659 }
1660 }
1661 }
1662 return;
1663 }
1664
1665 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1666 if (!roleMapEntry) {
1667 return;
1668 }
1669
1670 // Value of textbox is a textified subtree.
1671 if (roleMapEntry->Is(nsGkAtoms::textbox)) {
1672 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
1673 return;
1674 }
1675
1676 // Value of combobox is a text of current or selected item.
1677 if (roleMapEntry->Is(nsGkAtoms::combobox)) {
1678 LocalAccessible* option = CurrentItem();
1679 if (!option) {
1680 uint32_t childCount = ChildCount();
1681 for (uint32_t idx = 0; idx < childCount; idx++) {
1682 LocalAccessible* child = mChildren.ElementAt(idx);
1683 if (child->IsListControl()) {
1684 Accessible* acc = child->GetSelectedItem(0);
1685 option = acc ? acc->AsLocal() : nullptr;
1686 break;
1687 }
1688 }
1689 }
1690
1691 if (option) nsTextEquivUtils::GetTextEquivFromSubtree(option, aValue);
1692 }
1693 }
1694
MaxValue() const1695 double LocalAccessible::MaxValue() const {
1696 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemax);
1697 return IsNaN(checkValue) && !NativeHasNumericValue() ? 100 : checkValue;
1698 }
1699
MinValue() const1700 double LocalAccessible::MinValue() const {
1701 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemin);
1702 return IsNaN(checkValue) && !NativeHasNumericValue() ? 0 : checkValue;
1703 }
1704
Step() const1705 double LocalAccessible::Step() const {
1706 return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
1707 }
1708
CurValue() const1709 double LocalAccessible::CurValue() const {
1710 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuenow);
1711 if (IsNaN(checkValue) && !NativeHasNumericValue()) {
1712 double minValue = MinValue();
1713 return minValue + ((MaxValue() - minValue) / 2);
1714 }
1715
1716 return checkValue;
1717 }
1718
SetCurValue(double aValue)1719 bool LocalAccessible::SetCurValue(double aValue) {
1720 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1721 if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) return false;
1722
1723 const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
1724 if (State() & kValueCannotChange) return false;
1725
1726 double checkValue = MinValue();
1727 if (!IsNaN(checkValue) && aValue < checkValue) return false;
1728
1729 checkValue = MaxValue();
1730 if (!IsNaN(checkValue) && aValue > checkValue) return false;
1731
1732 nsAutoString strValue;
1733 strValue.AppendFloat(aValue);
1734
1735 if (!mContent->IsElement()) return true;
1736
1737 return NS_SUCCEEDED(mContent->AsElement()->SetAttr(
1738 kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
1739 }
1740
ARIATransformRole(role aRole) const1741 role LocalAccessible::ARIATransformRole(role aRole) const {
1742 // Beginning with ARIA 1.1, user agents are expected to use the native host
1743 // language role of the element when the region role is used without a name.
1744 // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-region
1745 //
1746 // XXX: While the name computation algorithm can be non-trivial in the general
1747 // case, it should not be especially bad here: If the author hasn't used the
1748 // region role, this calculation won't occur. And the region role's name
1749 // calculation rule excludes name from content. That said, this use case is
1750 // another example of why we should consider caching the accessible name. See:
1751 // https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
1752 if (aRole == roles::REGION) {
1753 nsAutoString name;
1754 Name(name);
1755 return name.IsEmpty() ? NativeRole() : aRole;
1756 }
1757
1758 // XXX: these unfortunate exceptions don't fit into the ARIA table. This is
1759 // where the accessible role depends on both the role and ARIA state.
1760 if (aRole == roles::PUSHBUTTON) {
1761 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) {
1762 // For simplicity, any existing pressed attribute except "" or "undefined"
1763 // indicates a toggle.
1764 return roles::TOGGLE_BUTTON;
1765 }
1766
1767 if (mContent->IsElement() &&
1768 mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1769 nsGkAtoms::aria_haspopup,
1770 nsGkAtoms::_true, eCaseMatters)) {
1771 // For button with aria-haspopup="true".
1772 return roles::BUTTONMENU;
1773 }
1774
1775 } else if (aRole == roles::LISTBOX) {
1776 // A listbox inside of a combobox needs a special role because of ATK
1777 // mapping to menu.
1778 if (mParent && mParent->IsCombobox()) {
1779 return roles::COMBOBOX_LIST;
1780 } else {
1781 // Listbox is owned by a combobox
1782 Relation rel = RelationByType(RelationType::NODE_CHILD_OF);
1783 LocalAccessible* targetAcc = nullptr;
1784 while ((targetAcc = rel.Next())) {
1785 if (targetAcc->IsCombobox()) return roles::COMBOBOX_LIST;
1786 }
1787 }
1788
1789 } else if (aRole == roles::OPTION) {
1790 if (mParent && mParent->Role() == roles::COMBOBOX_LIST) {
1791 return roles::COMBOBOX_OPTION;
1792 }
1793
1794 } else if (aRole == roles::MENUITEM) {
1795 // Menuitem has a submenu.
1796 if (mContent->IsElement() &&
1797 mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1798 nsGkAtoms::aria_haspopup,
1799 nsGkAtoms::_true, eCaseMatters)) {
1800 return roles::PARENT_MENUITEM;
1801 }
1802
1803 } else if (aRole == roles::CELL) {
1804 // A cell inside an ancestor table element that has a grid role needs a
1805 // gridcell role
1806 // (https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings).
1807 const TableCellAccessible* cell = AsTableCell();
1808 if (cell) {
1809 TableAccessible* table = cell->Table();
1810 if (table && table->AsAccessible()->IsARIARole(nsGkAtoms::grid)) {
1811 return roles::GRID_CELL;
1812 }
1813 }
1814 }
1815
1816 return aRole;
1817 }
1818
NativeRole() const1819 role LocalAccessible::NativeRole() const { return roles::NOTHING; }
1820
ActionCount() const1821 uint8_t LocalAccessible::ActionCount() const {
1822 return HasPrimaryAction() ? 1 : 0;
1823 }
1824
ActionNameAt(uint8_t aIndex,nsAString & aName)1825 void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
1826 aName.Truncate();
1827
1828 if (aIndex != 0) return;
1829
1830 uint32_t actionRule = GetActionRule();
1831
1832 switch (actionRule) {
1833 case eActivateAction:
1834 aName.AssignLiteral("activate");
1835 return;
1836
1837 case eClickAction:
1838 aName.AssignLiteral("click");
1839 return;
1840
1841 case ePressAction:
1842 aName.AssignLiteral("press");
1843 return;
1844
1845 case eCheckUncheckAction: {
1846 uint64_t state = State();
1847 if (state & states::CHECKED) {
1848 aName.AssignLiteral("uncheck");
1849 } else if (state & states::MIXED) {
1850 aName.AssignLiteral("cycle");
1851 } else {
1852 aName.AssignLiteral("check");
1853 }
1854 return;
1855 }
1856
1857 case eJumpAction:
1858 aName.AssignLiteral("jump");
1859 return;
1860
1861 case eOpenCloseAction:
1862 if (State() & states::COLLAPSED) {
1863 aName.AssignLiteral("open");
1864 } else {
1865 aName.AssignLiteral("close");
1866 }
1867 return;
1868
1869 case eSelectAction:
1870 aName.AssignLiteral("select");
1871 return;
1872
1873 case eSwitchAction:
1874 aName.AssignLiteral("switch");
1875 return;
1876
1877 case eSortAction:
1878 aName.AssignLiteral("sort");
1879 return;
1880
1881 case eExpandAction:
1882 if (State() & states::COLLAPSED) {
1883 aName.AssignLiteral("expand");
1884 } else {
1885 aName.AssignLiteral("collapse");
1886 }
1887 return;
1888 }
1889 }
1890
DoAction(uint8_t aIndex) const1891 bool LocalAccessible::DoAction(uint8_t aIndex) const {
1892 if (aIndex != 0) return false;
1893
1894 if (HasPrimaryAction()) {
1895 DoCommand();
1896 return true;
1897 }
1898
1899 return false;
1900 }
1901
HasPrimaryAction() const1902 bool LocalAccessible::HasPrimaryAction() const {
1903 return GetActionRule() != eNoAction;
1904 }
1905
GetAtomicRegion() const1906 nsIContent* LocalAccessible::GetAtomicRegion() const {
1907 nsIContent* loopContent = mContent;
1908 nsAutoString atomic;
1909 while (loopContent &&
1910 (!loopContent->IsElement() ||
1911 !loopContent->AsElement()->GetAttr(kNameSpaceID_None,
1912 nsGkAtoms::aria_atomic, atomic))) {
1913 loopContent = loopContent->GetParent();
1914 }
1915
1916 return atomic.EqualsLiteral("true") ? loopContent : nullptr;
1917 }
1918
RelationByType(RelationType aType) const1919 Relation LocalAccessible::RelationByType(RelationType aType) const {
1920 if (!HasOwnContent()) return Relation();
1921
1922 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1923
1924 // Relationships are defined on the same content node that the role would be
1925 // defined on.
1926 switch (aType) {
1927 case RelationType::LABELLED_BY: {
1928 Relation rel(
1929 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_labelledby));
1930 if (mContent->IsHTMLElement()) {
1931 rel.AppendIter(new HTMLLabelIterator(Document(), this));
1932 }
1933 rel.AppendIter(new XULLabelIterator(Document(), mContent));
1934
1935 return rel;
1936 }
1937
1938 case RelationType::LABEL_FOR: {
1939 Relation rel(new RelatedAccIterator(Document(), mContent,
1940 nsGkAtoms::aria_labelledby));
1941 if (mContent->IsXULElement(nsGkAtoms::label)) {
1942 rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
1943 }
1944
1945 return rel;
1946 }
1947
1948 case RelationType::DESCRIBED_BY: {
1949 Relation rel(
1950 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_describedby));
1951 if (mContent->IsXULElement()) {
1952 rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
1953 }
1954
1955 return rel;
1956 }
1957
1958 case RelationType::DESCRIPTION_FOR: {
1959 Relation rel(new RelatedAccIterator(Document(), mContent,
1960 nsGkAtoms::aria_describedby));
1961
1962 // This affectively adds an optional control attribute to xul:description,
1963 // which only affects accessibility, by allowing the description to be
1964 // tied to a control.
1965 if (mContent->IsXULElement(nsGkAtoms::description)) {
1966 rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
1967 }
1968
1969 return rel;
1970 }
1971
1972 case RelationType::NODE_CHILD_OF: {
1973 Relation rel;
1974 // This is an ARIA tree or treegrid that doesn't use owns, so we need to
1975 // get the parent the hard way.
1976 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
1977 roleMapEntry->role == roles::LISTITEM ||
1978 roleMapEntry->role == roles::ROW)) {
1979 Accessible* parent = const_cast<LocalAccessible*>(this)
1980 ->GetOrCreateGroupInfo()
1981 ->ConceptualParent();
1982 if (parent) {
1983 MOZ_ASSERT(parent->IsLocal());
1984 rel.AppendTarget(parent->AsLocal());
1985 }
1986 }
1987
1988 // If this is an OOP iframe document, we can't support NODE_CHILD_OF
1989 // here, since the iframe resides in a different process. This is fine
1990 // because the client will then request the parent instead, which will be
1991 // correctly handled by platform code.
1992 if (XRE_IsContentProcess() && IsRoot()) {
1993 dom::Document* doc =
1994 const_cast<LocalAccessible*>(this)->AsDoc()->DocumentNode();
1995 dom::BrowsingContext* bc = doc->GetBrowsingContext();
1996 MOZ_ASSERT(bc);
1997 if (!bc->Top()->IsInProcess()) {
1998 return rel;
1999 }
2000 }
2001
2002 // If accessible is in its own Window, or is the root of a document,
2003 // then we should provide NODE_CHILD_OF relation so that MSAA clients
2004 // can easily get to true parent instead of getting to oleacc's
2005 // ROLE_WINDOW accessible which will prevent us from going up further
2006 // (because it is system generated and has no idea about the hierarchy
2007 // above it).
2008 nsIFrame* frame = GetFrame();
2009 if (frame) {
2010 nsView* view = frame->GetView();
2011 if (view) {
2012 nsIScrollableFrame* scrollFrame = do_QueryFrame(frame);
2013 if (scrollFrame || view->GetWidget() || !frame->GetParent()) {
2014 rel.AppendTarget(LocalParent());
2015 }
2016 }
2017 }
2018
2019 return rel;
2020 }
2021
2022 case RelationType::NODE_PARENT_OF: {
2023 // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
2024 // also can be organized by groups.
2025 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2026 roleMapEntry->role == roles::LISTITEM ||
2027 roleMapEntry->role == roles::ROW ||
2028 roleMapEntry->role == roles::OUTLINE ||
2029 roleMapEntry->role == roles::LIST ||
2030 roleMapEntry->role == roles::TREE_TABLE)) {
2031 return Relation(new ItemIterator(this));
2032 }
2033
2034 return Relation();
2035 }
2036
2037 case RelationType::CONTROLLED_BY:
2038 return Relation(new RelatedAccIterator(Document(), mContent,
2039 nsGkAtoms::aria_controls));
2040
2041 case RelationType::CONTROLLER_FOR: {
2042 Relation rel(
2043 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_controls));
2044 rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
2045 return rel;
2046 }
2047
2048 case RelationType::FLOWS_TO:
2049 return Relation(
2050 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_flowto));
2051
2052 case RelationType::FLOWS_FROM:
2053 return Relation(
2054 new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_flowto));
2055
2056 case RelationType::MEMBER_OF: {
2057 if (Role() == roles::RADIOBUTTON) {
2058 /* If we see a radio button role here, we're dealing with an aria
2059 * radio button (because input=radio buttons are
2060 * HTMLRadioButtonAccessibles) */
2061 Relation rel = Relation();
2062 LocalAccessible* currParent = LocalParent();
2063 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
2064 currParent = currParent->LocalParent();
2065 }
2066
2067 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
2068 /* If we found a radiogroup parent, search for all
2069 * roles::RADIOBUTTON children and add them to our relation.
2070 * This search will include the radio button this method
2071 * was called from, which is expected. */
2072 Pivot p = Pivot(currParent);
2073 PivotRoleRule rule(roles::RADIOBUTTON);
2074 Accessible* match = p.Next(currParent, rule);
2075 while (match) {
2076 MOZ_ASSERT(match->IsLocal(),
2077 "We shouldn't find any remote accs while building our "
2078 "relation!");
2079 rel.AppendTarget(match->AsLocal());
2080 match = p.Next(match, rule);
2081 }
2082 }
2083
2084 /* By webkit's standard, aria radio buttons do not get grouped
2085 * if they lack a group parent, so we return an empty
2086 * relation here if the above check fails. */
2087
2088 return rel;
2089 }
2090
2091 return Relation(mDoc, GetAtomicRegion());
2092 }
2093
2094 case RelationType::LINKS_TO: {
2095 Relation rel = Relation();
2096 if (Role() == roles::LINK) {
2097 dom::HTMLAnchorElement* anchor =
2098 dom::HTMLAnchorElement::FromNode(mContent);
2099 if (!anchor) {
2100 return rel;
2101 }
2102 // If this node is an anchor element, query its hash to find the
2103 // target.
2104 nsAutoString hash;
2105 anchor->GetHash(hash);
2106 if (hash.IsEmpty()) {
2107 return rel;
2108 }
2109
2110 // GetHash returns an ID or name with a leading '#', trim it so we can
2111 // search the doc by ID or name alone.
2112 hash.Trim("#");
2113 if (dom::Element* elm = mContent->OwnerDoc()->GetElementById(hash)) {
2114 rel.AppendTarget(mDoc->GetAccessibleOrContainer(elm));
2115 } else if (nsCOMPtr<nsINodeList> list =
2116 mContent->OwnerDoc()->GetElementsByName(hash)) {
2117 // Loop through the named nodes looking for the first anchor
2118 uint32_t length = list->Length();
2119 for (uint32_t i = 0; i < length; i++) {
2120 nsIContent* node = list->Item(i);
2121 if (node->IsHTMLElement(nsGkAtoms::a)) {
2122 rel.AppendTarget(mDoc->GetAccessibleOrContainer(node));
2123 break;
2124 }
2125 }
2126 }
2127 }
2128
2129 return rel;
2130 }
2131
2132 case RelationType::SUBWINDOW_OF:
2133 case RelationType::EMBEDS:
2134 case RelationType::EMBEDDED_BY:
2135 case RelationType::POPUP_FOR:
2136 case RelationType::PARENT_WINDOW_OF:
2137 return Relation();
2138
2139 case RelationType::DEFAULT_BUTTON: {
2140 if (mContent->IsHTMLElement()) {
2141 // HTML form controls implements nsIFormControl interface.
2142 nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent));
2143 if (control) {
2144 if (dom::HTMLFormElement* form = control->GetForm()) {
2145 return Relation(mDoc, form->GetDefaultSubmitElement());
2146 }
2147 }
2148 } else {
2149 // In XUL, use first <button default="true" .../> in the document
2150 dom::Document* doc = mContent->OwnerDoc();
2151 nsIContent* buttonEl = nullptr;
2152 if (doc->AllowXULXBL()) {
2153 nsCOMPtr<nsIHTMLCollection> possibleDefaultButtons =
2154 doc->GetElementsByAttribute(u"default"_ns, u"true"_ns);
2155 if (possibleDefaultButtons) {
2156 uint32_t length = possibleDefaultButtons->Length();
2157 // Check for button in list of default="true" elements
2158 for (uint32_t count = 0; count < length && !buttonEl; count++) {
2159 nsIContent* item = possibleDefaultButtons->Item(count);
2160 RefPtr<nsIDOMXULButtonElement> button =
2161 item->IsElement() ? item->AsElement()->AsXULButton()
2162 : nullptr;
2163 if (button) {
2164 buttonEl = item;
2165 }
2166 }
2167 }
2168 return Relation(mDoc, buttonEl);
2169 }
2170 }
2171 return Relation();
2172 }
2173
2174 case RelationType::CONTAINING_DOCUMENT:
2175 return Relation(mDoc);
2176
2177 case RelationType::CONTAINING_TAB_PANE: {
2178 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2179 if (docShell) {
2180 // Walk up the parent chain without crossing the boundary at which item
2181 // types change, preventing us from walking up out of tab content.
2182 nsCOMPtr<nsIDocShellTreeItem> root;
2183 docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
2184 if (root) {
2185 // If the item type is typeContent, we assume we are in browser tab
2186 // content. Note, this includes content such as about:addons,
2187 // for consistency.
2188 if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
2189 return Relation(nsAccUtils::GetDocAccessibleFor(root));
2190 }
2191 }
2192 }
2193 return Relation();
2194 }
2195
2196 case RelationType::CONTAINING_APPLICATION:
2197 return Relation(ApplicationAcc());
2198
2199 case RelationType::DETAILS:
2200 return Relation(
2201 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_details));
2202
2203 case RelationType::DETAILS_FOR:
2204 return Relation(
2205 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
2206
2207 case RelationType::ERRORMSG:
2208 return Relation(
2209 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2210
2211 case RelationType::ERRORMSG_FOR:
2212 return Relation(
2213 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2214
2215 default:
2216 return Relation();
2217 }
2218 }
2219
GetNativeInterface(void ** aNativeAccessible)2220 void LocalAccessible::GetNativeInterface(void** aNativeAccessible) {}
2221
DoCommand(nsIContent * aContent,uint32_t aActionIndex) const2222 void LocalAccessible::DoCommand(nsIContent* aContent,
2223 uint32_t aActionIndex) const {
2224 class Runnable final : public mozilla::Runnable {
2225 public:
2226 Runnable(const LocalAccessible* aAcc, nsIContent* aContent, uint32_t aIdx)
2227 : mozilla::Runnable("Runnable"),
2228 mAcc(aAcc),
2229 mContent(aContent),
2230 mIdx(aIdx) {}
2231
2232 // XXX Cannot mark as MOZ_CAN_RUN_SCRIPT because the base class change
2233 // requires too big changes across a lot of modules.
2234 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
2235 if (mAcc) {
2236 MOZ_KnownLive(mAcc)->DispatchClickEvent(MOZ_KnownLive(mContent), mIdx);
2237 }
2238 return NS_OK;
2239 }
2240
2241 void Revoke() {
2242 mAcc = nullptr;
2243 mContent = nullptr;
2244 }
2245
2246 private:
2247 RefPtr<const LocalAccessible> mAcc;
2248 nsCOMPtr<nsIContent> mContent;
2249 uint32_t mIdx;
2250 };
2251
2252 nsIContent* content = aContent ? aContent : mContent.get();
2253 nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex);
2254 NS_DispatchToMainThread(runnable);
2255 }
2256
DispatchClickEvent(nsIContent * aContent,uint32_t aActionIndex) const2257 void LocalAccessible::DispatchClickEvent(nsIContent* aContent,
2258 uint32_t aActionIndex) const {
2259 if (IsDefunct()) return;
2260
2261 RefPtr<PresShell> presShell = mDoc->PresShellPtr();
2262
2263 // Scroll into view.
2264 presShell->ScrollContentIntoView(aContent, ScrollAxis(), ScrollAxis(),
2265 ScrollFlags::ScrollOverflowHidden);
2266
2267 AutoWeakFrame frame = aContent->GetPrimaryFrame();
2268 if (!frame) return;
2269
2270 // Compute x and y coordinates.
2271 nsPoint point;
2272 nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
2273 if (!widget) return;
2274
2275 nsSize size = frame->GetSize();
2276
2277 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2278 int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
2279 int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
2280
2281 // Simulate a touch interaction by dispatching touch events with mouse events.
2282 nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, aContent, frame, presShell,
2283 widget);
2284 nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, aContent, frame, presShell,
2285 widget);
2286 nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, aContent, frame, presShell,
2287 widget);
2288 nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, aContent, frame, presShell,
2289 widget);
2290 }
2291
ScrollToPoint(uint32_t aCoordinateType,int32_t aX,int32_t aY)2292 void LocalAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX,
2293 int32_t aY) {
2294 nsIFrame* frame = GetFrame();
2295 if (!frame) return;
2296
2297 LayoutDeviceIntPoint coords =
2298 nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
2299
2300 nsIFrame* parentFrame = frame;
2301 while ((parentFrame = parentFrame->GetParent())) {
2302 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2303 }
2304 }
2305
AppendTextTo(nsAString & aText,uint32_t aStartOffset,uint32_t aLength)2306 void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
2307 uint32_t aLength) {
2308 // Return text representation of non-text accessible within hypertext
2309 // accessible. Text accessible overrides this method to return enclosed text.
2310 if (aStartOffset != 0 || aLength == 0) return;
2311
2312 nsIFrame* frame = GetFrame();
2313 if (!frame) {
2314 if (nsCoreUtils::IsDisplayContents(mContent)) {
2315 aText += kEmbeddedObjectChar;
2316 }
2317 return;
2318 }
2319
2320 MOZ_ASSERT(mParent,
2321 "Called on accessible unbound from tree. Result can be wrong.");
2322
2323 if (frame->IsBrFrame()) {
2324 aText += kForcedNewLineChar;
2325 } else if (mParent && nsAccUtils::MustPrune(mParent)) {
2326 // Expose the embedded object accessible as imaginary embedded object
2327 // character if its parent hypertext accessible doesn't expose children to
2328 // AT.
2329 aText += kImaginaryEmbeddedObjectChar;
2330 } else {
2331 aText += kEmbeddedObjectChar;
2332 }
2333 }
2334
Shutdown()2335 void LocalAccessible::Shutdown() {
2336 // Mark the accessible as defunct, invalidate the child count and pointers to
2337 // other accessibles, also make sure none of its children point to this
2338 // parent
2339 mStateFlags |= eIsDefunct;
2340
2341 int32_t childCount = mChildren.Length();
2342 for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
2343 mChildren.ElementAt(childIdx)->UnbindFromParent();
2344 }
2345 mChildren.Clear();
2346
2347 mEmbeddedObjCollector = nullptr;
2348
2349 if (mParent) mParent->RemoveChild(this);
2350
2351 mContent = nullptr;
2352 mDoc = nullptr;
2353 if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) {
2354 SelectionMgr()->ResetCaretOffset();
2355 }
2356 }
2357
2358 // LocalAccessible protected
ARIAName(nsString & aName) const2359 void LocalAccessible::ARIAName(nsString& aName) const {
2360 // aria-labelledby now takes precedence over aria-label
2361 nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs(
2362 this, nsGkAtoms::aria_labelledby, aName);
2363 if (NS_SUCCEEDED(rv)) {
2364 aName.CompressWhitespace();
2365 }
2366
2367 if (aName.IsEmpty() && mContent->IsElement() &&
2368 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_label,
2369 aName)) {
2370 aName.CompressWhitespace();
2371 }
2372 }
2373
2374 // LocalAccessible protected
ARIADescription(nsString & aDescription) const2375 void LocalAccessible::ARIADescription(nsString& aDescription) const {
2376 // aria-describedby takes precedence over aria-description
2377 nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs(
2378 this, nsGkAtoms::aria_describedby, aDescription);
2379 if (NS_SUCCEEDED(rv)) {
2380 aDescription.CompressWhitespace();
2381 }
2382
2383 if (aDescription.IsEmpty() && mContent->IsElement() &&
2384 mContent->AsElement()->GetAttr(
2385 kNameSpaceID_None, nsGkAtoms::aria_description, aDescription)) {
2386 aDescription.CompressWhitespace();
2387 }
2388 }
2389
2390 // LocalAccessible protected
NativeName(nsString & aName) const2391 ENameValueFlag LocalAccessible::NativeName(nsString& aName) const {
2392 if (mContent->IsHTMLElement()) {
2393 LocalAccessible* label = nullptr;
2394 HTMLLabelIterator iter(Document(), this);
2395 while ((label = iter.Next())) {
2396 nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(),
2397 &aName);
2398 aName.CompressWhitespace();
2399 }
2400
2401 if (!aName.IsEmpty()) return eNameOK;
2402
2403 NameFromAssociatedXULLabel(mDoc, mContent, aName);
2404 if (!aName.IsEmpty()) {
2405 return eNameOK;
2406 }
2407
2408 nsTextEquivUtils::GetNameFromSubtree(this, aName);
2409 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2410 }
2411
2412 if (mContent->IsXULElement()) {
2413 XULElmName(mDoc, mContent, aName);
2414 if (!aName.IsEmpty()) return eNameOK;
2415
2416 nsTextEquivUtils::GetNameFromSubtree(this, aName);
2417 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2418 }
2419
2420 if (mContent->IsSVGElement()) {
2421 // If user agents need to choose among multiple 'desc' or 'title'
2422 // elements for processing, the user agent shall choose the first one.
2423 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2424 childElm = childElm->GetNextSibling()) {
2425 if (childElm->IsSVGElement(nsGkAtoms::title)) {
2426 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
2427 return eNameOK;
2428 }
2429 }
2430 }
2431
2432 return eNameOK;
2433 }
2434
2435 // LocalAccessible protected
NativeDescription(nsString & aDescription) const2436 void LocalAccessible::NativeDescription(nsString& aDescription) const {
2437 bool isXUL = mContent->IsXULElement();
2438 if (isXUL) {
2439 // Try XUL <description control="[id]">description text</description>
2440 XULDescriptionIterator iter(Document(), mContent);
2441 LocalAccessible* descr = nullptr;
2442 while ((descr = iter.Next())) {
2443 nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
2444 &aDescription);
2445 }
2446 }
2447 }
2448
2449 // LocalAccessible protected
BindToParent(LocalAccessible * aParent,uint32_t aIndexInParent)2450 void LocalAccessible::BindToParent(LocalAccessible* aParent,
2451 uint32_t aIndexInParent) {
2452 MOZ_ASSERT(aParent, "This method isn't used to set null parent");
2453 MOZ_ASSERT(!mParent, "The child was expected to be moved");
2454
2455 #ifdef A11Y_LOG
2456 if (mParent) {
2457 logging::TreeInfo("BindToParent: stealing accessible", 0, "old parent",
2458 mParent, "new parent", aParent, "child", this, nullptr);
2459 }
2460 #endif
2461
2462 mParent = aParent;
2463 mIndexInParent = aIndexInParent;
2464
2465 if (mParent->HasNameDependent() || mParent->IsXULListItem() ||
2466 RelationByType(RelationType::LABEL_FOR).Next() ||
2467 nsTextEquivUtils::HasNameRule(mParent, eNameFromSubtreeRule)) {
2468 mContextFlags |= eHasNameDependent;
2469 } else {
2470 mContextFlags &= ~eHasNameDependent;
2471 }
2472 if (mParent->HasDescriptionDependent() ||
2473 RelationByType(RelationType::DESCRIPTION_FOR).Next()) {
2474 mContextFlags |= eHasDescriptionDependent;
2475 } else {
2476 mContextFlags &= ~eHasDescriptionDependent;
2477 }
2478
2479 // Add name/description dependent flags for dependent content once
2480 // a name/description provider is added to doc.
2481 Relation rel = RelationByType(RelationType::LABELLED_BY);
2482 LocalAccessible* relTarget = nullptr;
2483 while ((relTarget = rel.Next())) {
2484 if (!relTarget->HasNameDependent()) {
2485 relTarget->ModifySubtreeContextFlags(eHasNameDependent, true);
2486 }
2487 }
2488
2489 rel = RelationByType(RelationType::DESCRIBED_BY);
2490 while ((relTarget = rel.Next())) {
2491 if (!relTarget->HasDescriptionDependent()) {
2492 relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
2493 }
2494 }
2495
2496 mContextFlags |=
2497 static_cast<uint32_t>((mParent->IsAlert() || mParent->IsInsideAlert())) &
2498 eInsideAlert;
2499
2500 // if a new column header is being added, invalidate the table's header cache.
2501 TableCellAccessible* cell = AsTableCell();
2502 if (cell && Role() == roles::COLUMNHEADER) {
2503 TableAccessible* table = cell->Table();
2504 if (table) {
2505 table->GetHeaderCache().Clear();
2506 }
2507 }
2508 }
2509
2510 // LocalAccessible protected
UnbindFromParent()2511 void LocalAccessible::UnbindFromParent() {
2512 mParent = nullptr;
2513 mIndexInParent = -1;
2514 mIndexOfEmbeddedChild = -1;
2515 if (IsProxy()) MOZ_CRASH("this should never be called on proxy wrappers");
2516
2517 delete mBits.groupInfo;
2518 mBits.groupInfo = nullptr;
2519 mContextFlags &= ~eHasNameDependent & ~eInsideAlert;
2520 }
2521
2522 ////////////////////////////////////////////////////////////////////////////////
2523 // LocalAccessible public methods
2524
RootAccessible() const2525 RootAccessible* LocalAccessible::RootAccessible() const {
2526 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2527 NS_ASSERTION(docShell, "No docshell for mContent");
2528 if (!docShell) {
2529 return nullptr;
2530 }
2531
2532 nsCOMPtr<nsIDocShellTreeItem> root;
2533 docShell->GetInProcessRootTreeItem(getter_AddRefs(root));
2534 NS_ASSERTION(root, "No root content tree item");
2535 if (!root) {
2536 return nullptr;
2537 }
2538
2539 DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
2540 return docAcc ? docAcc->AsRoot() : nullptr;
2541 }
2542
GetFrame() const2543 nsIFrame* LocalAccessible::GetFrame() const {
2544 return mContent ? mContent->GetPrimaryFrame() : nullptr;
2545 }
2546
GetNode() const2547 nsINode* LocalAccessible::GetNode() const { return mContent; }
2548
Elm() const2549 dom::Element* LocalAccessible::Elm() const {
2550 return dom::Element::FromNodeOrNull(mContent);
2551 }
2552
Language(nsAString & aLanguage)2553 void LocalAccessible::Language(nsAString& aLanguage) {
2554 aLanguage.Truncate();
2555
2556 if (!mDoc) return;
2557
2558 nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
2559 if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
2560 mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
2561 aLanguage);
2562 }
2563 }
2564
InsertChildAt(uint32_t aIndex,LocalAccessible * aChild)2565 bool LocalAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) {
2566 if (!aChild) return false;
2567
2568 if (aIndex == mChildren.Length()) {
2569 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2570 // pretended earlier.
2571 mChildren.AppendElement(aChild);
2572 } else {
2573 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2574 // pretended earlier.
2575 mChildren.InsertElementAt(aIndex, aChild);
2576
2577 MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change");
2578
2579 for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
2580 mChildren[idx]->mIndexInParent = idx;
2581 }
2582 }
2583
2584 if (aChild->IsText()) {
2585 mStateFlags |= eHasTextKids;
2586 }
2587
2588 aChild->BindToParent(this, aIndex);
2589 return true;
2590 }
2591
RemoveChild(LocalAccessible * aChild)2592 bool LocalAccessible::RemoveChild(LocalAccessible* aChild) {
2593 MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
2594 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent");
2595 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent");
2596 MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,
2597 "Unbound child was given");
2598 MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() ||
2599 aChild->IsDoc() || IsApplication(),
2600 "Illicit children change");
2601
2602 int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
2603 if (mChildren.SafeElementAt(index) != aChild) {
2604 MOZ_ASSERT_UNREACHABLE("A wrong child index");
2605 index = mChildren.IndexOf(aChild);
2606 if (index == -1) {
2607 MOZ_ASSERT_UNREACHABLE("No child was found");
2608 return false;
2609 }
2610 }
2611
2612 aChild->UnbindFromParent();
2613 mChildren.RemoveElementAt(index);
2614
2615 for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
2616 mChildren[idx]->mIndexInParent = idx;
2617 }
2618
2619 return true;
2620 }
2621
RelocateChild(uint32_t aNewIndex,LocalAccessible * aChild)2622 void LocalAccessible::RelocateChild(uint32_t aNewIndex,
2623 LocalAccessible* aChild) {
2624 MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
2625 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this,
2626 "A child from different subtree was given");
2627 MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,
2628 "Unbound child was given");
2629 MOZ_DIAGNOSTIC_ASSERT(
2630 aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild,
2631 "Wrong index in parent");
2632 MOZ_DIAGNOSTIC_ASSERT(
2633 static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,
2634 "No move, same index");
2635 MOZ_DIAGNOSTIC_ASSERT(aNewIndex <= mChildren.Length(),
2636 "Wrong new index was given");
2637
2638 RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false);
2639 if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
2640 aChild->SetHideEventTarget(true);
2641 }
2642
2643 mEmbeddedObjCollector = nullptr;
2644 mChildren.RemoveElementAt(aChild->mIndexInParent);
2645
2646 uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
2647
2648 // If the child is moved after its current position.
2649 if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
2650 startIdx = aChild->mIndexInParent;
2651 if (aNewIndex == mChildren.Length() + 1) {
2652 // The child is moved to the end.
2653 mChildren.AppendElement(aChild);
2654 endIdx = mChildren.Length() - 1;
2655 } else {
2656 mChildren.InsertElementAt(aNewIndex - 1, aChild);
2657 endIdx = aNewIndex;
2658 }
2659 } else {
2660 // The child is moved prior its current position.
2661 mChildren.InsertElementAt(aNewIndex, aChild);
2662 }
2663
2664 for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
2665 mChildren[idx]->mIndexInParent = idx;
2666 mChildren[idx]->mIndexOfEmbeddedChild = -1;
2667 }
2668
2669 for (uint32_t idx = 0; idx < mChildren.Length(); idx++) {
2670 mChildren[idx]->mStateFlags |= eGroupInfoDirty;
2671 }
2672
2673 RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild);
2674 DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
2675 MOZ_ASSERT(added);
2676 aChild->SetShowEventTarget(true);
2677 }
2678
LocalChildAt(uint32_t aIndex) const2679 LocalAccessible* LocalAccessible::LocalChildAt(uint32_t aIndex) const {
2680 LocalAccessible* child = mChildren.SafeElementAt(aIndex, nullptr);
2681 if (!child) return nullptr;
2682
2683 #ifdef DEBUG
2684 LocalAccessible* realParent = child->mParent;
2685 NS_ASSERTION(!realParent || realParent == this,
2686 "Two accessibles have the same first child accessible!");
2687 #endif
2688
2689 return child;
2690 }
2691
ChildCount() const2692 uint32_t LocalAccessible::ChildCount() const { return mChildren.Length(); }
2693
IndexInParent() const2694 int32_t LocalAccessible::IndexInParent() const { return mIndexInParent; }
2695
EmbeddedChildCount()2696 uint32_t LocalAccessible::EmbeddedChildCount() {
2697 if (mStateFlags & eHasTextKids) {
2698 if (!mEmbeddedObjCollector) {
2699 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2700 }
2701 return mEmbeddedObjCollector->Count();
2702 }
2703
2704 return ChildCount();
2705 }
2706
EmbeddedChildAt(uint32_t aIndex)2707 LocalAccessible* LocalAccessible::EmbeddedChildAt(uint32_t aIndex) {
2708 if (mStateFlags & eHasTextKids) {
2709 if (!mEmbeddedObjCollector) {
2710 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2711 }
2712 return mEmbeddedObjCollector.get()
2713 ? mEmbeddedObjCollector->GetAccessibleAt(aIndex)
2714 : nullptr;
2715 }
2716
2717 return LocalChildAt(aIndex);
2718 }
2719
IndexOfEmbeddedChild(Accessible * aChild)2720 int32_t LocalAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
2721 MOZ_ASSERT(aChild->IsLocal());
2722 if (mStateFlags & eHasTextKids) {
2723 if (!mEmbeddedObjCollector) {
2724 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2725 }
2726 return mEmbeddedObjCollector.get()
2727 ? mEmbeddedObjCollector->GetIndexAt(aChild->AsLocal())
2728 : -1;
2729 }
2730
2731 return GetIndexOf(aChild->AsLocal());
2732 }
2733
2734 ////////////////////////////////////////////////////////////////////////////////
2735 // HyperLinkAccessible methods
2736
IsLink() const2737 bool LocalAccessible::IsLink() const {
2738 // Every embedded accessible within hypertext accessible implements
2739 // hyperlink interface.
2740 return mParent && mParent->IsHyperText() && !IsText();
2741 }
2742
AnchorCount()2743 uint32_t LocalAccessible::AnchorCount() {
2744 MOZ_ASSERT(IsLink(), "AnchorCount is called on not hyper link!");
2745 return 1;
2746 }
2747
AnchorAt(uint32_t aAnchorIndex)2748 LocalAccessible* LocalAccessible::AnchorAt(uint32_t aAnchorIndex) {
2749 MOZ_ASSERT(IsLink(), "GetAnchor is called on not hyper link!");
2750 return aAnchorIndex == 0 ? this : nullptr;
2751 }
2752
AnchorURIAt(uint32_t aAnchorIndex) const2753 already_AddRefed<nsIURI> LocalAccessible::AnchorURIAt(
2754 uint32_t aAnchorIndex) const {
2755 MOZ_ASSERT(IsLink(), "AnchorURIAt is called on not hyper link!");
2756 return nullptr;
2757 }
2758
2759 ////////////////////////////////////////////////////////////////////////////////
2760 // SelectAccessible
2761
SelectedItems(nsTArray<Accessible * > * aItems)2762 void LocalAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
2763 AccIterator iter(this, filters::GetSelected);
2764 LocalAccessible* selected = nullptr;
2765 while ((selected = iter.Next())) aItems->AppendElement(selected);
2766 }
2767
SelectedItemCount()2768 uint32_t LocalAccessible::SelectedItemCount() {
2769 uint32_t count = 0;
2770 AccIterator iter(this, filters::GetSelected);
2771 LocalAccessible* selected = nullptr;
2772 while ((selected = iter.Next())) ++count;
2773
2774 return count;
2775 }
2776
GetSelectedItem(uint32_t aIndex)2777 Accessible* LocalAccessible::GetSelectedItem(uint32_t aIndex) {
2778 AccIterator iter(this, filters::GetSelected);
2779 LocalAccessible* selected = nullptr;
2780
2781 uint32_t index = 0;
2782 while ((selected = iter.Next()) && index < aIndex) index++;
2783
2784 return selected;
2785 }
2786
IsItemSelected(uint32_t aIndex)2787 bool LocalAccessible::IsItemSelected(uint32_t aIndex) {
2788 uint32_t index = 0;
2789 AccIterator iter(this, filters::GetSelectable);
2790 LocalAccessible* selected = nullptr;
2791 while ((selected = iter.Next()) && index < aIndex) index++;
2792
2793 return selected && selected->State() & states::SELECTED;
2794 }
2795
AddItemToSelection(uint32_t aIndex)2796 bool LocalAccessible::AddItemToSelection(uint32_t aIndex) {
2797 uint32_t index = 0;
2798 AccIterator iter(this, filters::GetSelectable);
2799 LocalAccessible* selected = nullptr;
2800 while ((selected = iter.Next()) && index < aIndex) index++;
2801
2802 if (selected) selected->SetSelected(true);
2803
2804 return static_cast<bool>(selected);
2805 }
2806
RemoveItemFromSelection(uint32_t aIndex)2807 bool LocalAccessible::RemoveItemFromSelection(uint32_t aIndex) {
2808 uint32_t index = 0;
2809 AccIterator iter(this, filters::GetSelectable);
2810 LocalAccessible* selected = nullptr;
2811 while ((selected = iter.Next()) && index < aIndex) index++;
2812
2813 if (selected) selected->SetSelected(false);
2814
2815 return static_cast<bool>(selected);
2816 }
2817
SelectAll()2818 bool LocalAccessible::SelectAll() {
2819 bool success = false;
2820 LocalAccessible* selectable = nullptr;
2821
2822 AccIterator iter(this, filters::GetSelectable);
2823 while ((selectable = iter.Next())) {
2824 success = true;
2825 selectable->SetSelected(true);
2826 }
2827 return success;
2828 }
2829
UnselectAll()2830 bool LocalAccessible::UnselectAll() {
2831 bool success = false;
2832 LocalAccessible* selected = nullptr;
2833
2834 AccIterator iter(this, filters::GetSelected);
2835 while ((selected = iter.Next())) {
2836 success = true;
2837 selected->SetSelected(false);
2838 }
2839 return success;
2840 }
2841
2842 ////////////////////////////////////////////////////////////////////////////////
2843 // Widgets
2844
IsWidget() const2845 bool LocalAccessible::IsWidget() const { return false; }
2846
IsActiveWidget() const2847 bool LocalAccessible::IsActiveWidget() const {
2848 if (FocusMgr()->HasDOMFocus(mContent)) return true;
2849
2850 // If text entry of combobox widget has a focus then the combobox widget is
2851 // active.
2852 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
2853 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
2854 uint32_t childCount = ChildCount();
2855 for (uint32_t idx = 0; idx < childCount; idx++) {
2856 LocalAccessible* child = mChildren.ElementAt(idx);
2857 if (child->Role() == roles::ENTRY) {
2858 return FocusMgr()->HasDOMFocus(child->GetContent());
2859 }
2860 }
2861 }
2862
2863 return false;
2864 }
2865
AreItemsOperable() const2866 bool LocalAccessible::AreItemsOperable() const {
2867 return HasOwnContent() && mContent->IsElement() &&
2868 mContent->AsElement()->HasAttr(kNameSpaceID_None,
2869 nsGkAtoms::aria_activedescendant);
2870 }
2871
CurrentItem() const2872 LocalAccessible* LocalAccessible::CurrentItem() const {
2873 // Check for aria-activedescendant, which changes which element has focus.
2874 // For activedescendant, the ARIA spec does not require that the user agent
2875 // checks whether pointed node is actually a DOM descendant of the element
2876 // with the aria-activedescendant attribute.
2877 nsAutoString id;
2878 if (HasOwnContent() && mContent->IsElement() &&
2879 mContent->AsElement()->GetAttr(kNameSpaceID_None,
2880 nsGkAtoms::aria_activedescendant, id)) {
2881 dom::Element* activeDescendantElm = IDRefsIterator::GetElem(mContent, id);
2882 if (activeDescendantElm) {
2883 if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) {
2884 // Don't want a cyclical descendant relationship. That would be bad.
2885 return nullptr;
2886 }
2887
2888 DocAccessible* document = Document();
2889 if (document) return document->GetAccessible(activeDescendantElm);
2890 }
2891 }
2892 return nullptr;
2893 }
2894
SetCurrentItem(const LocalAccessible * aItem)2895 void LocalAccessible::SetCurrentItem(const LocalAccessible* aItem) {
2896 nsAtom* id = aItem->GetContent()->GetID();
2897 if (id) {
2898 nsAutoString idStr;
2899 id->ToString(idStr);
2900 mContent->AsElement()->SetAttr(
2901 kNameSpaceID_None, nsGkAtoms::aria_activedescendant, idStr, true);
2902 }
2903 }
2904
ContainerWidget() const2905 LocalAccessible* LocalAccessible::ContainerWidget() const {
2906 if (HasARIARole() && mContent->HasID()) {
2907 for (LocalAccessible* parent = LocalParent(); parent;
2908 parent = parent->LocalParent()) {
2909 nsIContent* parentContent = parent->GetContent();
2910 if (parentContent && parentContent->IsElement() &&
2911 parentContent->AsElement()->HasAttr(
2912 kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
2913 return parent;
2914 }
2915
2916 // Don't cross DOM document boundaries.
2917 if (parent->IsDoc()) break;
2918 }
2919 }
2920 return nullptr;
2921 }
2922
IsActiveDescendant(LocalAccessible ** aWidget) const2923 bool LocalAccessible::IsActiveDescendant(LocalAccessible** aWidget) const {
2924 if (!HasOwnContent() || !mContent->HasID()) {
2925 return false;
2926 }
2927
2928 dom::DocumentOrShadowRoot* docOrShadowRoot =
2929 mContent->GetUncomposedDocOrConnectedShadowRoot();
2930 if (!docOrShadowRoot) {
2931 return false;
2932 }
2933
2934 nsAutoCString selector;
2935 selector.AppendPrintf(
2936 "[aria-activedescendant=\"%s\"]",
2937 NS_ConvertUTF16toUTF8(mContent->GetID()->GetUTF16String()).get());
2938 ErrorResult er;
2939
2940 dom::Element* widgetElm =
2941 docOrShadowRoot->AsNode().QuerySelector(selector, er);
2942
2943 if (!widgetElm || er.Failed()) {
2944 return false;
2945 }
2946
2947 if (widgetElm->IsInclusiveDescendantOf(mContent)) {
2948 // Don't want a cyclical descendant relationship. That would be bad.
2949 return false;
2950 }
2951
2952 LocalAccessible* widget = mDoc->GetAccessible(widgetElm);
2953
2954 if (aWidget) {
2955 *aWidget = widget;
2956 }
2957
2958 return !!widget;
2959 }
2960
Announce(const nsAString & aAnnouncement,uint16_t aPriority)2961 void LocalAccessible::Announce(const nsAString& aAnnouncement,
2962 uint16_t aPriority) {
2963 RefPtr<AccAnnouncementEvent> event =
2964 new AccAnnouncementEvent(this, aAnnouncement, aPriority);
2965 nsEventShell::FireEvent(event);
2966 }
2967
2968 ////////////////////////////////////////////////////////////////////////////////
2969 // LocalAccessible protected methods
2970
LastRelease()2971 void LocalAccessible::LastRelease() {
2972 // First cleanup if needed...
2973 if (mDoc) {
2974 Shutdown();
2975 NS_ASSERTION(!mDoc,
2976 "A Shutdown() impl forgot to call its parent's Shutdown?");
2977 }
2978 // ... then die.
2979 delete this;
2980 }
2981
GetSiblingAtOffset(int32_t aOffset,nsresult * aError) const2982 LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset,
2983 nsresult* aError) const {
2984 if (!mParent || mIndexInParent == -1) {
2985 if (aError) *aError = NS_ERROR_UNEXPECTED;
2986
2987 return nullptr;
2988 }
2989
2990 if (aError &&
2991 mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
2992 *aError = NS_OK; // fail peacefully
2993 return nullptr;
2994 }
2995
2996 LocalAccessible* child = mParent->LocalChildAt(mIndexInParent + aOffset);
2997 if (aError && !child) *aError = NS_ERROR_UNEXPECTED;
2998
2999 return child;
3000 }
3001
ModifySubtreeContextFlags(uint32_t aContextFlags,bool aAdd)3002 void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags,
3003 bool aAdd) {
3004 Pivot pivot(this);
3005 LocalAccInSameDocRule rule;
3006 for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) {
3007 MOZ_ASSERT(anchor->IsLocal());
3008 LocalAccessible* acc = anchor->AsLocal();
3009 if (aAdd) {
3010 acc->mContextFlags |= aContextFlags;
3011 } else {
3012 acc->mContextFlags &= ~aContextFlags;
3013 }
3014 }
3015 }
3016
AttrNumericValue(nsAtom * aAttr) const3017 double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const {
3018 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3019 if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) {
3020 return UnspecifiedNaN<double>();
3021 }
3022
3023 nsAutoString attrValue;
3024 if (!mContent->IsElement() ||
3025 !mContent->AsElement()->GetAttr(kNameSpaceID_None, aAttr, attrValue)) {
3026 return UnspecifiedNaN<double>();
3027 }
3028
3029 nsresult error = NS_OK;
3030 double value = attrValue.ToDouble(&error);
3031 return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
3032 }
3033
GetActionRule() const3034 uint32_t LocalAccessible::GetActionRule() const {
3035 if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) {
3036 return eNoAction;
3037 }
3038
3039 // Return "click" action on elements that have an attached popup menu.
3040 if (mContent->IsXULElement()) {
3041 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) {
3042 return eClickAction;
3043 }
3044 }
3045
3046 // Has registered 'click' event handler.
3047 bool isOnclick = nsCoreUtils::HasClickListener(mContent);
3048
3049 if (isOnclick) return eClickAction;
3050
3051 // Get an action based on ARIA role.
3052 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3053 if (roleMapEntry && roleMapEntry->actionRule != eNoAction) {
3054 return roleMapEntry->actionRule;
3055 }
3056
3057 // Get an action based on ARIA attribute.
3058 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) {
3059 return eExpandAction;
3060 }
3061
3062 return eNoAction;
3063 }
3064
GetGroupInfo() const3065 AccGroupInfo* LocalAccessible::GetGroupInfo() const {
3066 if (mBits.groupInfo && !(mStateFlags & eGroupInfoDirty)) {
3067 return mBits.groupInfo;
3068 }
3069
3070 return nullptr;
3071 }
3072
GetOrCreateGroupInfo()3073 AccGroupInfo* LocalAccessible::GetOrCreateGroupInfo() {
3074 if (IsProxy()) MOZ_CRASH("This should never be called on proxy wrappers");
3075
3076 if (mBits.groupInfo) {
3077 if (mStateFlags & eGroupInfoDirty) {
3078 mBits.groupInfo->Update();
3079 mStateFlags &= ~eGroupInfoDirty;
3080 }
3081
3082 return mBits.groupInfo;
3083 }
3084
3085 mBits.groupInfo = AccGroupInfo::CreateGroupInfo(this);
3086 mStateFlags &= ~eGroupInfoDirty;
3087 return mBits.groupInfo;
3088 }
3089
SendCache(uint64_t aCacheDomain,CacheUpdateType aUpdateType)3090 void LocalAccessible::SendCache(uint64_t aCacheDomain,
3091 CacheUpdateType aUpdateType) {
3092 if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
3093 return;
3094 }
3095
3096 if (!IPCAccessibilityActive() || !Document()) {
3097 return;
3098 }
3099
3100 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
3101 if (!ipcDoc) {
3102 // This means DocAccessible::DoInitialUpdate hasn't been called yet, which
3103 // means the a11y tree hasn't been built yet. Therefore, this should only
3104 // be possible if this is a DocAccessible.
3105 MOZ_ASSERT(IsDoc(), "Called on a non-DocAccessible but IPCDoc is null");
3106 return;
3107 }
3108
3109 RefPtr<AccAttributes> fields =
3110 BundleFieldsForCache(aCacheDomain, aUpdateType);
3111 nsTArray<CacheData> data;
3112 data.AppendElement(
3113 CacheData(IsDoc() ? 0 : reinterpret_cast<uint64_t>(UniqueID()), fields));
3114 ipcDoc->SendCache(aUpdateType, data, true);
3115 }
3116
BundleFieldsForCache(uint64_t aCacheDomain,CacheUpdateType aUpdateType)3117 already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
3118 uint64_t aCacheDomain, CacheUpdateType aUpdateType) {
3119 RefPtr<AccAttributes> fields = new AccAttributes();
3120
3121 // Caching name for text leaf Accessibles is redundant, since their name is
3122 // always their text. Text gets handled below.
3123 if (aCacheDomain & CacheDomain::NameAndDescription && !IsText()) {
3124 nsString name;
3125 int32_t nameFlag = Name(name);
3126 if (nameFlag != eNameOK) {
3127 fields->SetAttribute(nsGkAtoms::explicit_name, nameFlag);
3128 } else if (aUpdateType == CacheUpdateType::Update) {
3129 fields->SetAttribute(nsGkAtoms::explicit_name, DeleteEntry());
3130 }
3131
3132 if (!name.IsEmpty()) {
3133 fields->SetAttribute(nsGkAtoms::name, std::move(name));
3134 } else if (aUpdateType == CacheUpdateType::Update) {
3135 fields->SetAttribute(nsGkAtoms::name, DeleteEntry());
3136 }
3137
3138 nsString description;
3139 Description(description);
3140 if (!description.IsEmpty()) {
3141 fields->SetAttribute(nsGkAtoms::description, std::move(description));
3142 } else if (aUpdateType == CacheUpdateType::Update) {
3143 fields->SetAttribute(nsGkAtoms::description, DeleteEntry());
3144 }
3145 }
3146
3147 if (aCacheDomain & CacheDomain::Value) {
3148 // We cache the text value in 3 cases:
3149 // 1. Accessible is an HTML input type that holds a number.
3150 // 2. Accessible has a numeric value and an aria-valuetext.
3151 // 3. Accessible is an HTML input type that holds text.
3152 // ... for all other cases we divine the value remotely.
3153 bool cacheValueText = false;
3154 if (HasNumericValue()) {
3155 fields->SetAttribute(nsGkAtoms::value, CurValue());
3156 fields->SetAttribute(nsGkAtoms::max, MaxValue());
3157 fields->SetAttribute(nsGkAtoms::min, MinValue());
3158 fields->SetAttribute(nsGkAtoms::step, Step());
3159 cacheValueText = NativeHasNumericValue() ||
3160 (mContent->IsElement() &&
3161 mContent->AsElement()->HasAttr(
3162 kNameSpaceID_None, nsGkAtoms::aria_valuetext));
3163 } else {
3164 cacheValueText = IsTextField();
3165 }
3166
3167 if (cacheValueText) {
3168 nsString value;
3169 Value(value);
3170 if (!value.IsEmpty()) {
3171 fields->SetAttribute(nsGkAtoms::aria_valuetext, std::move(value));
3172 } else if (aUpdateType == CacheUpdateType::Update) {
3173 fields->SetAttribute(nsGkAtoms::aria_valuetext, DeleteEntry());
3174 }
3175 }
3176 }
3177
3178 if (aCacheDomain & CacheDomain::Bounds) {
3179 nsRect newBoundsRect = ParentRelativeBounds();
3180
3181 // 1. Layout might notify us of a possible bounds change when the bounds
3182 // haven't really changed. Therefore, we cache the last bounds we sent
3183 // and don't send an update if they haven't changed.
3184 // 2. For an initial cache push, we ignore 1) and always send the bounds.
3185 // This handles the case where this LocalAccessible was moved (but not
3186 // re-created). In that case, we will have cached bounds, but we currently
3187 // do an initial cache push.
3188 MOZ_ASSERT(aUpdateType == CacheUpdateType::Initial || mBounds.isSome(),
3189 "Incremental cache push but mBounds is not set!");
3190 if (aUpdateType == CacheUpdateType::Initial ||
3191 !newBoundsRect.IsEqualEdges(mBounds.value())) {
3192 mBounds = Some(newBoundsRect);
3193
3194 nsTArray<int32_t> boundsArray(4);
3195
3196 boundsArray.AppendElement(newBoundsRect.x);
3197 boundsArray.AppendElement(newBoundsRect.y);
3198 boundsArray.AppendElement(newBoundsRect.width);
3199 boundsArray.AppendElement(newBoundsRect.height);
3200
3201 fields->SetAttribute(nsGkAtoms::relativeBounds, std::move(boundsArray));
3202 }
3203 }
3204
3205 if (aCacheDomain & CacheDomain::Text) {
3206 if (!HasChildren()) {
3207 // We only cache text and line offsets on leaf Accessibles.
3208 // Only text Accessibles can have actual text.
3209 if (IsText()) {
3210 nsString text;
3211 AppendTextTo(text);
3212 fields->SetAttribute(nsGkAtoms::text, std::move(text));
3213 TextLeafPoint point(this, 0);
3214 RefPtr<AccAttributes> attrs = point.GetTextAttributesLocalAcc(
3215 /* aIncludeDefaults */ false);
3216 fields->SetAttribute(nsGkAtoms::style, std::move(attrs));
3217 }
3218 // We cache line start offsets for both text and non-text leaf Accessibles
3219 // because non-text leaf Accessibles can still start a line.
3220 nsTArray<int32_t> lineStarts;
3221 for (TextLeafPoint lineStart =
3222 TextLeafPoint(this, 0).FindNextLineStartSameLocalAcc(
3223 /* aIncludeOrigin */ true);
3224 lineStart;
3225 lineStart = lineStart.FindNextLineStartSameLocalAcc(false)) {
3226 lineStarts.AppendElement(lineStart.mOffset);
3227 }
3228 if (!lineStarts.IsEmpty()) {
3229 fields->SetAttribute(nsGkAtoms::line, std::move(lineStarts));
3230 }
3231 }
3232 if (HyperTextAccessible* ht = AsHyperText()) {
3233 RefPtr<AccAttributes> attrs = ht->DefaultTextAttributes();
3234 fields->SetAttribute(nsGkAtoms::style, std::move(attrs));
3235 }
3236 }
3237
3238 if (aCacheDomain & CacheDomain::TransformMatrix) {
3239 nsIFrame* frame = GetFrame();
3240
3241 if (frame && frame->IsTransformed()) {
3242 // We need to find a frame to make our transform relative to.
3243 // It's important this frame have a corresponding accessible,
3244 // because this transform is applied while walking the accessibility
3245 // tree (in the parent process), not the frame tree.
3246 nsIFrame* boundingFrame = FindNearestAccessibleAncestorFrame();
3247 // This matrix is only valid when applied to CSSPixel points/rects
3248 // in the coordinate space of `frame`. It also includes the translation
3249 // to the parent space.
3250 gfx::Matrix4x4 mtx = nsLayoutUtils::GetTransformToAncestor(
3251 RelativeTo{frame}, RelativeTo{boundingFrame},
3252 nsIFrame::IN_CSS_UNITS)
3253 .GetMatrix();
3254
3255 UniquePtr<gfx::Matrix4x4> ptr = MakeUnique<gfx::Matrix4x4>(mtx);
3256 fields->SetAttribute(nsGkAtoms::transform, std::move(ptr));
3257 } else {
3258 // Otherwise, if we're bundling a transform update but this
3259 // frame isn't transformed (or doesn't exist), we need
3260 // to send a DeleteEntry() to remove any
3261 // transform that was previously cached for this frame.
3262 fields->SetAttribute(nsGkAtoms::transform, DeleteEntry());
3263 }
3264 }
3265
3266 if (aCacheDomain & CacheDomain::DOMNodeID && mContent) {
3267 nsAtom* id = mContent->GetID();
3268 if (id) {
3269 fields->SetAttribute(nsGkAtoms::id, id);
3270 } else if (aUpdateType == CacheUpdateType::Update) {
3271 fields->SetAttribute(nsGkAtoms::id, DeleteEntry());
3272 }
3273 }
3274
3275 // State is only included in the initial push. Thereafter, cached state is
3276 // updated via events.
3277 if (aCacheDomain & CacheDomain::State &&
3278 aUpdateType == CacheUpdateType::Initial) {
3279 uint64_t state = State();
3280 // Exclude states which must be calculated by RemoteAccessible.
3281 state &= ~kRemoteCalculatedStates;
3282 fields->SetAttribute(nsGkAtoms::state, state);
3283 }
3284
3285 if (aCacheDomain & CacheDomain::GroupInfo && mContent) {
3286 for (nsAtom* attr : {nsGkAtoms::aria_level, nsGkAtoms::aria_setsize,
3287 nsGkAtoms::aria_posinset}) {
3288 int32_t value = 0;
3289 if (nsCoreUtils::GetUIntAttr(mContent, attr, &value)) {
3290 fields->SetAttribute(attr, value);
3291 } else if (aUpdateType == CacheUpdateType::Update) {
3292 fields->SetAttribute(attr, DeleteEntry());
3293 }
3294 }
3295 }
3296
3297 if (aCacheDomain & CacheDomain::Actions) {
3298 if (HasPrimaryAction()) {
3299 // Here we cache the primary action.
3300 nsAutoString actionName;
3301 ActionNameAt(0, actionName);
3302 RefPtr<nsAtom> actionAtom = NS_Atomize(actionName);
3303 fields->SetAttribute(nsGkAtoms::action, actionAtom);
3304 } else if (aUpdateType == CacheUpdateType::Update) {
3305 fields->SetAttribute(nsGkAtoms::action, DeleteEntry());
3306 }
3307
3308 if (ImageAccessible* imgAcc = AsImage()) {
3309 // Here we cache the showlongdesc action.
3310 if (imgAcc->HasLongDesc()) {
3311 fields->SetAttribute(nsGkAtoms::longdesc, true);
3312 } else if (aUpdateType == CacheUpdateType::Update) {
3313 fields->SetAttribute(nsGkAtoms::longdesc, DeleteEntry());
3314 }
3315 }
3316 }
3317
3318 if (aCacheDomain & CacheDomain::Style) {
3319 if (RefPtr<nsAtom> display = DisplayStyle()) {
3320 fields->SetAttribute(nsGkAtoms::display, display);
3321 }
3322 }
3323
3324 if (aUpdateType == CacheUpdateType::Initial) {
3325 // Add fields which never change and thus only need to be included in the
3326 // initial cache push.
3327 if (mContent && mContent->IsElement()) {
3328 fields->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom());
3329
3330 if (IsTextField() || IsDateTimeField()) {
3331 // Cache text input types. Accessible is recreated if this changes,
3332 // so it is considered immutable.
3333 if (const nsAttrValue* attr =
3334 mContent->AsElement()->GetParsedAttr(nsGkAtoms::type)) {
3335 RefPtr<nsAtom> inputType = attr->GetAsAtom();
3336 if (inputType) {
3337 fields->SetAttribute(nsGkAtoms::textInputType, inputType);
3338 }
3339 }
3340 }
3341 }
3342
3343 if (nsIFrame* frame = GetFrame()) {
3344 // Note our frame's current computed style so we can track style changes
3345 // later on.
3346 mOldComputedStyle = frame->Style();
3347 if (frame->IsTransformed()) {
3348 mStateFlags |= eOldFrameHasValidTransformStyle;
3349 } else {
3350 mStateFlags &= ~eOldFrameHasValidTransformStyle;
3351 }
3352 }
3353 }
3354
3355 return fields.forget();
3356 }
3357
MaybeQueueCacheUpdateForStyleChanges()3358 void LocalAccessible::MaybeQueueCacheUpdateForStyleChanges() {
3359 // mOldComputedStyle might be null if the initial cache hasn't been sent yet.
3360 // In that case, there is nothing to do here.
3361 if (!StaticPrefs::accessibility_cache_enabled_AtStartup() ||
3362 !mOldComputedStyle) {
3363 return;
3364 }
3365
3366 if (nsIFrame* frame = GetFrame()) {
3367 const ComputedStyle* newStyle = frame->Style();
3368
3369 nsAutoCString oldVal, newVal;
3370 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_display, oldVal);
3371 newStyle->GetComputedPropertyValue(eCSSProperty_display, newVal);
3372 if (oldVal != newVal) {
3373 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
3374 }
3375
3376 bool newHasValidTransformStyle = frame->IsTransformed();
3377 bool oldHasValidTransformStyle =
3378 (mStateFlags & eOldFrameHasValidTransformStyle) != 0;
3379
3380 // We should send a transform update if we're adding or
3381 // removing transform styling altogether.
3382 bool sendTransformUpdate =
3383 newHasValidTransformStyle || oldHasValidTransformStyle;
3384
3385 if (newHasValidTransformStyle && oldHasValidTransformStyle) {
3386 // If we continue to have transform styling, verify
3387 // our transform has actually changed.
3388 nsChangeHint transformHint =
3389 newStyle->StyleDisplay()->CalcTransformPropertyDifference(
3390 *mOldComputedStyle->StyleDisplay());
3391 // If this hint exists, it implies we found a property difference
3392 sendTransformUpdate = !!transformHint;
3393 }
3394
3395 if (sendTransformUpdate) {
3396 // Queuing a cache update for the TransformMatrix domain doesn't
3397 // necessarily mean we'll send the matrix itself, we may
3398 // send a DeleteEntry() instead. See BundleFieldsForCache for
3399 // more information.
3400 mDoc->QueueCacheUpdate(this, CacheDomain::TransformMatrix);
3401 }
3402
3403 mOldComputedStyle = newStyle;
3404 if (newHasValidTransformStyle) {
3405 mStateFlags |= eOldFrameHasValidTransformStyle;
3406 } else {
3407 mStateFlags &= ~eOldFrameHasValidTransformStyle;
3408 }
3409 }
3410 }
3411
TagName() const3412 nsAtom* LocalAccessible::TagName() const {
3413 return mContent && mContent->IsElement() ? mContent->NodeInfo()->NameAtom()
3414 : nullptr;
3415 }
3416
DisplayStyle() const3417 already_AddRefed<nsAtom> LocalAccessible::DisplayStyle() const {
3418 if (dom::Element* elm = Elm()) {
3419 StyleInfo info(elm);
3420 return info.Display();
3421 }
3422 return nullptr;
3423 }
3424
StaticAsserts() const3425 void LocalAccessible::StaticAsserts() const {
3426 static_assert(
3427 eLastStateFlag <= (1 << kStateFlagsBits) - 1,
3428 "LocalAccessible::mStateFlags was oversized by eLastStateFlag!");
3429 static_assert(
3430 eLastContextFlag <= (1 << kContextFlagsBits) - 1,
3431 "LocalAccessible::mContextFlags was oversized by eLastContextFlag!");
3432 }
3433
3434 ////////////////////////////////////////////////////////////////////////////////
3435 // KeyBinding class
3436
3437 // static
AccelModifier()3438 uint32_t KeyBinding::AccelModifier() {
3439 switch (WidgetInputEvent::AccelModifier()) {
3440 case MODIFIER_ALT:
3441 return kAlt;
3442 case MODIFIER_CONTROL:
3443 return kControl;
3444 case MODIFIER_META:
3445 return kMeta;
3446 case MODIFIER_OS:
3447 return kOS;
3448 default:
3449 MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
3450 return 0;
3451 }
3452 }
3453
ToPlatformFormat(nsAString & aValue) const3454 void KeyBinding::ToPlatformFormat(nsAString& aValue) const {
3455 nsCOMPtr<nsIStringBundle> keyStringBundle;
3456 nsCOMPtr<nsIStringBundleService> stringBundleService =
3457 mozilla::components::StringBundle::Service();
3458 if (stringBundleService) {
3459 stringBundleService->CreateBundle(
3460 "chrome://global-platform/locale/platformKeys.properties",
3461 getter_AddRefs(keyStringBundle));
3462 }
3463
3464 if (!keyStringBundle) return;
3465
3466 nsAutoString separator;
3467 keyStringBundle->GetStringFromName("MODIFIER_SEPARATOR", separator);
3468
3469 nsAutoString modifierName;
3470 if (mModifierMask & kControl) {
3471 keyStringBundle->GetStringFromName("VK_CONTROL", modifierName);
3472
3473 aValue.Append(modifierName);
3474 aValue.Append(separator);
3475 }
3476
3477 if (mModifierMask & kAlt) {
3478 keyStringBundle->GetStringFromName("VK_ALT", modifierName);
3479
3480 aValue.Append(modifierName);
3481 aValue.Append(separator);
3482 }
3483
3484 if (mModifierMask & kShift) {
3485 keyStringBundle->GetStringFromName("VK_SHIFT", modifierName);
3486
3487 aValue.Append(modifierName);
3488 aValue.Append(separator);
3489 }
3490
3491 if (mModifierMask & kMeta) {
3492 keyStringBundle->GetStringFromName("VK_META", modifierName);
3493
3494 aValue.Append(modifierName);
3495 aValue.Append(separator);
3496 }
3497
3498 aValue.Append(mKey);
3499 }
3500
ToAtkFormat(nsAString & aValue) const3501 void KeyBinding::ToAtkFormat(nsAString& aValue) const {
3502 nsAutoString modifierName;
3503 if (mModifierMask & kControl) aValue.AppendLiteral("<Control>");
3504
3505 if (mModifierMask & kAlt) aValue.AppendLiteral("<Alt>");
3506
3507 if (mModifierMask & kShift) aValue.AppendLiteral("<Shift>");
3508
3509 if (mModifierMask & kMeta) aValue.AppendLiteral("<Meta>");
3510
3511 aValue.Append(mKey);
3512 }
3513