Bug 1732219 - Add API for fetching the preview image. r=geckoview-reviewers,agi,mconley
[gecko.git] / accessible / generic / LocalAccessible.cpp
blob38be6d1929ebdeadf3ef44a76e830b431748f609
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/. */
6 #include "LocalAccessible-inl.h"
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"
37 #include "nsIDOMXULButtonElement.h"
38 #include "nsIDOMXULSelectCntrlEl.h"
39 #include "nsIDOMXULSelectCntrlItemEl.h"
40 #include "nsINodeList.h"
41 #include "nsPIDOMWindow.h"
43 #include "mozilla/dom/Document.h"
44 #include "mozilla/dom/HTMLFormElement.h"
45 #include "mozilla/dom/HTMLAnchorElement.h"
46 #include "nsIContent.h"
47 #include "nsIFormControl.h"
49 #include "nsDeckFrame.h"
50 #include "nsLayoutUtils.h"
51 #include "nsIStringBundle.h"
52 #include "nsPresContext.h"
53 #include "nsIFrame.h"
54 #include "nsView.h"
55 #include "nsIDocShellTreeItem.h"
56 #include "nsIScrollableFrame.h"
57 #include "nsFocusManager.h"
59 #include "nsString.h"
60 #include "nsUnicharUtils.h"
61 #include "nsReadableUtils.h"
62 #include "prdtoa.h"
63 #include "nsAtom.h"
64 #include "nsIURI.h"
65 #include "nsArrayUtils.h"
66 #include "nsWhitespaceTokenizer.h"
67 #include "nsAttrName.h"
69 #include "mozilla/Assertions.h"
70 #include "mozilla/BasicEvents.h"
71 #include "mozilla/Components.h"
72 #include "mozilla/ErrorResult.h"
73 #include "mozilla/EventStates.h"
74 #include "mozilla/FloatingPoint.h"
75 #include "mozilla/MouseEvents.h"
76 #include "mozilla/PresShell.h"
77 #include "mozilla/Unused.h"
78 #include "mozilla/Preferences.h"
79 #include "mozilla/ProfilerMarkers.h"
80 #include "mozilla/StaticPrefs_accessibility.h"
81 #include "mozilla/StaticPrefs_ui.h"
82 #include "mozilla/dom/CanvasRenderingContext2D.h"
83 #include "mozilla/dom/Element.h"
84 #include "mozilla/dom/HTMLCanvasElement.h"
85 #include "mozilla/dom/HTMLBodyElement.h"
86 #include "mozilla/dom/KeyboardEventBinding.h"
87 #include "mozilla/dom/TreeWalker.h"
88 #include "mozilla/dom/UserActivation.h"
89 #include "mozilla/dom/MutationEventBinding.h"
91 using namespace mozilla;
92 using namespace mozilla::a11y;
94 ////////////////////////////////////////////////////////////////////////////////
95 // LocalAccessible: nsISupports and cycle collection
97 NS_IMPL_CYCLE_COLLECTION_CLASS(LocalAccessible)
98 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalAccessible)
99 tmp->Shutdown();
100 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalAccessible)
102 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)
103 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
105 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalAccessible)
106 NS_INTERFACE_MAP_ENTRY_CONCRETE(LocalAccessible)
107 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LocalAccessible)
108 NS_INTERFACE_MAP_END
110 NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalAccessible)
111 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(LocalAccessible, LastRelease())
113 LocalAccessible::LocalAccessible(nsIContent* aContent, DocAccessible* aDoc)
114 : Accessible(),
115 mContent(aContent),
116 mDoc(aDoc),
117 mParent(nullptr),
118 mIndexInParent(-1),
119 mBounds(),
120 mStateFlags(0),
121 mContextFlags(0),
122 mReorderEventTarget(false),
123 mShowEventTarget(false),
124 mHideEventTarget(false) {
125 mBits.groupInfo = nullptr;
126 mIndexOfEmbeddedChild = -1;
129 LocalAccessible::~LocalAccessible() {
130 NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
133 ENameValueFlag LocalAccessible::Name(nsString& aName) const {
134 aName.Truncate();
136 if (!HasOwnContent()) return eNameOK;
138 ARIAName(aName);
139 if (!aName.IsEmpty()) return eNameOK;
141 ENameValueFlag nameFlag = NativeName(aName);
142 if (!aName.IsEmpty()) return nameFlag;
144 // In the end get the name from tooltip.
145 if (mContent->IsHTMLElement()) {
146 if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title,
147 aName)) {
148 aName.CompressWhitespace();
149 return eNameFromTooltip;
151 } else if (mContent->IsXULElement()) {
152 if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
153 nsGkAtoms::tooltiptext, aName)) {
154 aName.CompressWhitespace();
155 return eNameFromTooltip;
157 } else if (mContent->IsSVGElement()) {
158 // If user agents need to choose among multiple 'desc' or 'title'
159 // elements for processing, the user agent shall choose the first one.
160 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
161 childElm = childElm->GetNextSibling()) {
162 if (childElm->IsSVGElement(nsGkAtoms::desc)) {
163 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
164 return eNameFromTooltip;
169 if (nameFlag != eNoNameOnPurpose) aName.SetIsVoid(true);
171 return nameFlag;
174 void LocalAccessible::Description(nsString& aDescription) const {
175 // There are 4 conditions that make an accessible have no accDescription:
176 // 1. it's a text node; or
177 // 2. It has no ARIA describedby or description property
178 // 3. it doesn't have an accName; or
179 // 4. its title attribute already equals to its accName nsAutoString name;
181 if (!HasOwnContent() || mContent->IsText()) return;
183 ARIADescription(aDescription);
185 if (aDescription.IsEmpty()) {
186 NativeDescription(aDescription);
188 if (aDescription.IsEmpty()) {
189 // Keep the Name() method logic.
190 if (mContent->IsHTMLElement()) {
191 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title,
192 aDescription);
193 } else if (mContent->IsXULElement()) {
194 mContent->AsElement()->GetAttr(kNameSpaceID_None,
195 nsGkAtoms::tooltiptext, aDescription);
196 } else if (mContent->IsSVGElement()) {
197 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
198 childElm = childElm->GetNextSibling()) {
199 if (childElm->IsSVGElement(nsGkAtoms::desc)) {
200 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm,
201 &aDescription);
202 break;
209 if (!aDescription.IsEmpty()) {
210 aDescription.CompressWhitespace();
211 nsAutoString name;
212 Name(name);
213 // Don't expose a description if it is the same as the name.
214 if (aDescription.Equals(name)) aDescription.Truncate();
218 KeyBinding LocalAccessible::AccessKey() const {
219 if (!HasOwnContent()) return KeyBinding();
221 uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
222 if (!key && mContent->IsElement()) {
223 LocalAccessible* label = nullptr;
225 // Copy access key from label node.
226 if (mContent->IsHTMLElement()) {
227 // Unless it is labeled via an ancestor <label>, in which case that would
228 // be redundant.
229 HTMLLabelIterator iter(Document(), this,
230 HTMLLabelIterator::eSkipAncestorLabel);
231 label = iter.Next();
233 if (!label) {
234 XULLabelIterator iter(Document(), mContent);
235 label = iter.Next();
238 if (label) key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
241 if (!key) return KeyBinding();
243 // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
244 switch (StaticPrefs::ui_key_generalAccessKey()) {
245 case -1:
246 break;
247 case dom::KeyboardEvent_Binding::DOM_VK_SHIFT:
248 return KeyBinding(key, KeyBinding::kShift);
249 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
250 return KeyBinding(key, KeyBinding::kControl);
251 case dom::KeyboardEvent_Binding::DOM_VK_ALT:
252 return KeyBinding(key, KeyBinding::kAlt);
253 case dom::KeyboardEvent_Binding::DOM_VK_META:
254 return KeyBinding(key, KeyBinding::kMeta);
255 default:
256 return KeyBinding();
259 // Determine the access modifier used in this context.
260 dom::Document* document = mContent->GetComposedDoc();
261 if (!document) return KeyBinding();
263 nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
264 if (!treeItem) return KeyBinding();
266 nsresult rv = NS_ERROR_FAILURE;
267 int32_t modifierMask = 0;
268 switch (treeItem->ItemType()) {
269 case nsIDocShellTreeItem::typeChrome:
270 modifierMask = StaticPrefs::ui_key_chromeAccess();
271 rv = NS_OK;
272 break;
273 case nsIDocShellTreeItem::typeContent:
274 modifierMask = StaticPrefs::ui_key_contentAccess();
275 rv = NS_OK;
276 break;
279 return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
282 KeyBinding LocalAccessible::KeyboardShortcut() const { return KeyBinding(); }
284 void LocalAccessible::TranslateString(const nsString& aKey,
285 nsAString& aStringOut) {
286 nsCOMPtr<nsIStringBundleService> stringBundleService =
287 components::StringBundle::Service();
288 if (!stringBundleService) return;
290 nsCOMPtr<nsIStringBundle> stringBundle;
291 stringBundleService->CreateBundle(
292 "chrome://global-platform/locale/accessible.properties",
293 getter_AddRefs(stringBundle));
294 if (!stringBundle) return;
296 nsAutoString xsValue;
297 nsresult rv = stringBundle->GetStringFromName(
298 NS_ConvertUTF16toUTF8(aKey).get(), xsValue);
299 if (NS_SUCCEEDED(rv)) aStringOut.Assign(xsValue);
302 uint64_t LocalAccessible::VisibilityState() const {
303 nsIFrame* frame = GetFrame();
304 if (!frame) {
305 // Element having display:contents is considered visible semantically,
306 // despite it doesn't have a visually visible box.
307 if (nsCoreUtils::IsDisplayContents(mContent)) {
308 return states::OFFSCREEN;
310 return states::INVISIBLE;
313 if (!frame->StyleVisibility()->IsVisible()) return states::INVISIBLE;
315 // It's invisible if the presshell is hidden by a visibility:hidden element in
316 // an ancestor document.
317 if (frame->PresShell()->IsUnderHiddenEmbedderElement()) {
318 return states::INVISIBLE;
321 // Offscreen state if the document's visibility state is not visible.
322 if (Document()->IsHidden()) return states::OFFSCREEN;
324 // Walk the parent frame chain to see if the frame is in background tab or
325 // scrolled out.
326 nsIFrame* curFrame = frame;
327 do {
328 nsView* view = curFrame->GetView();
329 if (view && view->GetVisibility() == nsViewVisibility_kHide) {
330 return states::INVISIBLE;
333 if (nsLayoutUtils::IsPopup(curFrame)) return 0;
335 // Offscreen state for background tab content and invisible for not selected
336 // deck panel.
337 nsIFrame* parentFrame = curFrame->GetParent();
338 nsDeckFrame* deckFrame = do_QueryFrame(parentFrame);
339 if (deckFrame && deckFrame->GetSelectedBox() != curFrame) {
340 if (deckFrame->GetContent()->IsXULElement(nsGkAtoms::tabpanels)) {
341 return states::OFFSCREEN;
344 MOZ_ASSERT_UNREACHABLE(
345 "Children of not selected deck panel are not accessible.");
346 return states::INVISIBLE;
349 // If contained by scrollable frame then check that at least 12 pixels
350 // around the object is visible, otherwise the object is offscreen.
351 nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame);
352 const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
353 if (scrollableFrame) {
354 nsRect scrollPortRect = scrollableFrame->GetScrollPortRect();
355 nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
356 frame, frame->GetRectRelativeToSelf(), parentFrame);
357 if (!scrollPortRect.Contains(frameRect)) {
358 scrollPortRect.Deflate(kMinPixels, kMinPixels);
359 if (!scrollPortRect.Intersects(frameRect)) return states::OFFSCREEN;
363 if (!parentFrame) {
364 parentFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(curFrame);
365 // Even if we couldn't find the parent frame, it might mean we are in an
366 // out-of-process iframe, try to see if |frame| is scrolled out in an
367 // scrollable frame in a cross-process ancestor document.
368 if (!parentFrame &&
369 nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
370 frame, kMinPixels)) {
371 return states::OFFSCREEN;
375 curFrame = parentFrame;
376 } while (curFrame);
378 // Zero area rects can occur in the first frame of a multi-frame text flow,
379 // in which case the rendered text is not empty and the frame should not be
380 // marked invisible.
381 // XXX Can we just remove this check? Why do we need to mark empty
382 // text invisible?
383 if (frame->IsTextFrame() && !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
384 frame->GetRect().IsEmpty()) {
385 nsIFrame::RenderedText text = frame->GetRenderedText(
386 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
387 nsIFrame::TrailingWhitespace::DontTrim);
388 if (text.mString.IsEmpty()) {
389 return states::INVISIBLE;
393 return 0;
396 uint64_t LocalAccessible::NativeState() const {
397 uint64_t state = 0;
399 if (!IsInDocument()) state |= states::STALE;
401 if (HasOwnContent() && mContent->IsElement()) {
402 EventStates elementState = mContent->AsElement()->State();
404 if (elementState.HasState(NS_EVENT_STATE_INVALID)) state |= states::INVALID;
406 if (elementState.HasState(NS_EVENT_STATE_REQUIRED)) {
407 state |= states::REQUIRED;
410 state |= NativeInteractiveState();
411 if (FocusMgr()->IsFocused(this)) state |= states::FOCUSED;
414 // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
415 state |= VisibilityState();
417 nsIFrame* frame = GetFrame();
418 if (frame) {
419 if (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) state |= states::FLOATING;
421 // XXX we should look at layout for non XUL box frames, but need to decide
422 // how that interacts with ARIA.
423 if (HasOwnContent() && mContent->IsXULElement() && frame->IsXULBoxFrame()) {
424 const nsStyleXUL* xulStyle = frame->StyleXUL();
425 if (xulStyle && frame->IsXULBoxFrame()) {
426 // In XUL all boxes are either vertical or horizontal
427 if (xulStyle->mBoxOrient == StyleBoxOrient::Vertical) {
428 state |= states::VERTICAL;
429 } else {
430 state |= states::HORIZONTAL;
436 // Check if a XUL element has the popup attribute (an attached popup menu).
437 if (HasOwnContent() && mContent->IsXULElement() &&
438 mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) {
439 state |= states::HASPOPUP;
442 // Bypass the link states specialization for non links.
443 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
444 if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
445 roleMapEntry->role == roles::LINK) {
446 state |= NativeLinkState();
449 return state;
452 uint64_t LocalAccessible::NativeInteractiveState() const {
453 if (!mContent->IsElement()) return 0;
455 if (NativelyUnavailable()) return states::UNAVAILABLE;
457 nsIFrame* frame = GetFrame();
458 if (frame && frame->IsFocusable()) return states::FOCUSABLE;
460 return 0;
463 uint64_t LocalAccessible::NativeLinkState() const { return 0; }
465 bool LocalAccessible::NativelyUnavailable() const {
466 if (mContent->IsHTMLElement()) return mContent->AsElement()->IsDisabled();
468 return mContent->IsElement() && mContent->AsElement()->AttrValueIs(
469 kNameSpaceID_None, nsGkAtoms::disabled,
470 nsGkAtoms::_true, eCaseMatters);
473 LocalAccessible* LocalAccessible::FocusedChild() {
474 LocalAccessible* focus = FocusMgr()->FocusedAccessible();
475 if (focus && (focus == this || focus->LocalParent() == this)) {
476 return focus;
479 return nullptr;
482 Accessible* LocalAccessible::ChildAtPoint(int32_t aX, int32_t aY,
483 EWhichChildAtPoint aWhichChild) {
484 Accessible* child = LocalChildAtPoint(aX, aY, aWhichChild);
485 if (aWhichChild != EWhichChildAtPoint::DirectChild && child &&
486 child->IsOuterDoc()) {
487 child = child->ChildAtPoint(aX, aY, aWhichChild);
490 return child;
493 LocalAccessible* LocalAccessible::LocalChildAtPoint(
494 int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) {
495 // If we can't find the point in a child, we will return the fallback answer:
496 // we return |this| if the point is within it, otherwise nullptr.
497 LocalAccessible* fallbackAnswer = nullptr;
498 nsIntRect rect = Bounds();
499 if (rect.Contains(aX, aY)) fallbackAnswer = this;
501 if (nsAccUtils::MustPrune(this)) { // Do not dig any further
502 return fallbackAnswer;
505 // Search an accessible at the given point starting from accessible document
506 // because containing block (see CSS2) for out of flow element (for example,
507 // absolutely positioned element) may be different from its DOM parent and
508 // therefore accessible for containing block may be different from accessible
509 // for DOM parent but GetFrameForPoint() should be called for containing block
510 // to get an out of flow element.
511 DocAccessible* accDocument = Document();
512 NS_ENSURE_TRUE(accDocument, nullptr);
514 nsIFrame* rootFrame = accDocument->GetFrame();
515 NS_ENSURE_TRUE(rootFrame, nullptr);
517 nsIFrame* startFrame = rootFrame;
519 // Check whether the point is at popup content.
520 nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
521 NS_ENSURE_TRUE(rootWidget, nullptr);
523 LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
525 WidgetMouseEvent dummyEvent(true, eMouseMove, rootWidget,
526 WidgetMouseEvent::eSynthesized);
527 dummyEvent.mRefPoint =
528 LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y());
530 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates(
531 accDocument->PresContext()->GetRootPresContext(), &dummyEvent);
532 if (popupFrame) {
533 // If 'this' accessible is not inside the popup then ignore the popup when
534 // searching an accessible at point.
535 DocAccessible* popupDoc =
536 GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
537 LocalAccessible* popupAcc =
538 popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
539 LocalAccessible* popupChild = this;
540 while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) {
541 popupChild = popupChild->LocalParent();
544 if (popupChild == popupAcc) startFrame = popupFrame;
547 nsPresContext* presContext = startFrame->PresContext();
548 nsRect screenRect = startFrame->GetScreenRectInAppUnits();
549 nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(),
550 presContext->DevPixelsToAppUnits(aY) - screenRect.Y());
552 nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(
553 RelativeTo{startFrame, ViewportType::Visual}, offset);
555 nsIContent* content = nullptr;
556 if (!foundFrame || !(content = foundFrame->GetContent())) {
557 return fallbackAnswer;
560 // Get accessible for the node with the point or the first accessible in
561 // the DOM parent chain.
562 DocAccessible* contentDocAcc =
563 GetAccService()->GetDocAccessible(content->OwnerDoc());
565 // contentDocAcc in some circumstances can be nullptr. See bug 729861
566 NS_ASSERTION(contentDocAcc, "could not get the document accessible");
567 if (!contentDocAcc) return fallbackAnswer;
569 LocalAccessible* accessible =
570 contentDocAcc->GetAccessibleOrContainer(content);
571 if (!accessible) return fallbackAnswer;
573 // Hurray! We have an accessible for the frame that layout gave us.
574 // Since DOM node of obtained accessible may be out of flow then we should
575 // ensure obtained accessible is a child of this accessible.
576 LocalAccessible* child = accessible;
577 while (child != this) {
578 LocalAccessible* parent = child->LocalParent();
579 if (!parent) {
580 // Reached the top of the hierarchy. These bounds were inside an
581 // accessible that is not a descendant of this one.
582 return fallbackAnswer;
585 // If we landed on a legitimate child of |this|, and we want the direct
586 // child, return it here.
587 if (parent == this && aWhichChild == EWhichChildAtPoint::DirectChild) {
588 return child;
591 child = parent;
594 // Manually walk through accessible children and see if the are within this
595 // point. Skip offscreen or invisible accessibles. This takes care of cases
596 // where layout won't walk into things for us, such as image map areas and
597 // sub documents (XXX: subdocuments should be handled by methods of
598 // OuterDocAccessibles).
599 uint32_t childCount = accessible->ChildCount();
600 if (childCount == 1 && accessible->IsOuterDoc() &&
601 accessible->FirstChild()->IsRemote()) {
602 // No local children.
603 return accessible;
605 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
606 LocalAccessible* child = accessible->LocalChildAt(childIdx);
608 nsIntRect childRect = child->Bounds();
609 if (childRect.Contains(aX, aY) &&
610 (child->State() & states::INVISIBLE) == 0) {
611 if (aWhichChild == EWhichChildAtPoint::DeepestChild) {
612 return child->LocalChildAtPoint(aX, aY,
613 EWhichChildAtPoint::DeepestChild);
616 return child;
620 return accessible;
623 nsRect LocalAccessible::ParentRelativeBounds() {
624 nsIFrame* boundingFrame = nullptr;
625 nsIFrame* frame = GetFrame();
626 if (frame && mContent) {
627 if (mContent->GetProperty(nsGkAtoms::hitregion) && mContent->IsElement()) {
628 // This is for canvas fallback content
629 // Find a canvas frame the found hit region is relative to.
630 nsIFrame* canvasFrame = frame->GetParent();
631 if (canvasFrame) {
632 canvasFrame = nsLayoutUtils::GetClosestFrameOfType(
633 canvasFrame, LayoutFrameType::HTMLCanvas);
636 if (canvasFrame) {
637 if (auto* canvas =
638 dom::HTMLCanvasElement::FromNode(canvasFrame->GetContent())) {
639 if (auto* context = canvas->GetCurrentContext()) {
640 nsRect bounds;
641 if (context->GetHitRegionRect(mContent->AsElement(), bounds)) {
642 return bounds;
649 // We need to find a frame to make our bounds relative to. We'll store this
650 // in `boundingFrame`. Ultimately, we'll create screen-relative coordinates
651 // by summing the x, y offsets of our ancestors' bounds in
652 // RemoteAccessibleBase::Bounds(), so it is important that our bounding
653 // frame have a corresponding accessible.
654 if (IsDoc() &&
655 nsCoreUtils::IsTopLevelContentDocInProcess(AsDoc()->DocumentNode())) {
656 // Tab documents and OOP iframe docs won't have ancestor accessibles with
657 // frames. We'll use their presshell root frame instead.
658 // XXX bug 1736635: Should DocAccessibles return their presShell frame on
659 // GetFrame()?
660 boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
663 // Iterate through ancestors to find one with a frame.
664 LocalAccessible* parent = mParent;
665 while (parent && !boundingFrame) {
666 if (parent->IsDoc()) {
667 // If we find a doc accessible, use its presshell's root frame
668 // (since doc accessibles themselves don't have frames).
669 boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
670 break;
673 if ((boundingFrame = parent->GetFrame())) {
674 // Otherwise, if the parent has a frame, use that
675 break;
678 parent = parent->LocalParent();
681 if (!boundingFrame) {
682 MOZ_ASSERT_UNREACHABLE("No ancestor with frame?");
683 boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
686 nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
687 frame, boundingFrame, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
689 if (unionRect.IsEmpty()) {
690 // If we end up with a 0x0 rect from above (or one with negative
691 // height/width) we should try using the ink overflow rect instead. If we
692 // use this rect, our relative bounds will match the bounds of what
693 // appears visually. We do this because some web authors (icloud.com for
694 // example) employ things like 0x0 buttons with visual overflow. Without
695 // this, such frames aren't navigable by screen readers.
696 nsRect overflow = frame->InkOverflowRectRelativeToSelf();
697 nsLayoutUtils::TransformRect(frame, boundingFrame, overflow);
698 return overflow;
701 return unionRect;
704 return nsRect();
707 nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const {
708 nsIFrame* frame = GetFrame();
709 if (frame && mContent) {
710 if (mContent->GetProperty(nsGkAtoms::hitregion) && mContent->IsElement()) {
711 // This is for canvas fallback content
712 // Find a canvas frame the found hit region is relative to.
713 nsIFrame* canvasFrame = frame->GetParent();
714 if (canvasFrame) {
715 canvasFrame = nsLayoutUtils::GetClosestFrameOfType(
716 canvasFrame, LayoutFrameType::HTMLCanvas);
719 // make the canvas the bounding frame
720 if (canvasFrame) {
721 *aBoundingFrame = canvasFrame;
722 if (auto* canvas =
723 dom::HTMLCanvasElement::FromNode(canvasFrame->GetContent())) {
724 if (auto* context = canvas->GetCurrentContext()) {
725 nsRect bounds;
726 if (context->GetHitRegionRect(mContent->AsElement(), bounds)) {
727 return bounds;
734 *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
735 nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
736 frame, *aBoundingFrame, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
738 if (unionRect.IsEmpty()) {
739 // If we end up with a 0x0 rect from above (or one with negative
740 // height/width) we should try using the ink overflow rect instead. If we
741 // use this rect, our relative bounds will match the bounds of what
742 // appears visually. We do this because some web authors (icloud.com for
743 // example) employ things like 0x0 buttons with visual overflow. Without
744 // this, such frames aren't navigable by screen readers.
745 nsRect overflow = frame->InkOverflowRectRelativeToSelf();
746 nsLayoutUtils::TransformRect(frame, *aBoundingFrame, overflow);
747 return overflow;
750 return unionRect;
753 return nsRect();
756 nsRect LocalAccessible::BoundsInAppUnits() const {
757 nsIFrame* boundingFrame = nullptr;
758 nsRect unionRectTwips = RelativeBounds(&boundingFrame);
759 if (!boundingFrame) {
760 return nsRect();
763 PresShell* presShell = mDoc->PresContext()->PresShell();
765 // We need to inverse translate with the offset of the edge of the visual
766 // viewport from top edge of the layout viewport.
767 nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
768 presShell->GetLayoutViewportOffset();
769 unionRectTwips.MoveBy(-viewportOffset);
771 // We need to take into account a non-1 resolution set on the presshell.
772 // This happens with async pinch zooming. Here we scale the bounds before
773 // adding the screen-relative offset.
774 unionRectTwips.ScaleRoundOut(presShell->GetResolution());
775 // We have the union of the rectangle, now we need to put it in absolute
776 // screen coords.
777 nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits();
778 unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y());
780 return unionRectTwips;
783 nsIntRect LocalAccessible::Bounds() const {
784 return BoundsInAppUnits().ToNearestPixels(
785 mDoc->PresContext()->AppUnitsPerDevPixel());
788 nsIntRect LocalAccessible::BoundsInCSSPixels() const {
789 return BoundsInAppUnits().ToNearestPixels(AppUnitsPerCSSPixel());
792 void LocalAccessible::SetSelected(bool aSelect) {
793 if (!HasOwnContent()) return;
795 LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State());
796 if (select) {
797 if (select->State() & states::MULTISELECTABLE) {
798 if (mContent->IsElement() && ARIARoleMap()) {
799 if (aSelect) {
800 mContent->AsElement()->SetAttr(
801 kNameSpaceID_None, nsGkAtoms::aria_selected, u"true"_ns, true);
802 } else {
803 mContent->AsElement()->UnsetAttr(kNameSpaceID_None,
804 nsGkAtoms::aria_selected, true);
807 return;
810 if (aSelect) TakeFocus();
814 void LocalAccessible::TakeSelection() {
815 LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State());
816 if (select) {
817 if (select->State() & states::MULTISELECTABLE) select->UnselectAll();
818 SetSelected(true);
822 void LocalAccessible::TakeFocus() const {
823 nsIFrame* frame = GetFrame();
824 if (!frame) return;
826 nsIContent* focusContent = mContent;
828 // If the accessible focus is managed by container widget then focus the
829 // widget and set the accessible as its current item.
830 if (!frame->IsFocusable()) {
831 LocalAccessible* widget = ContainerWidget();
832 if (widget && widget->AreItemsOperable()) {
833 nsIContent* widgetElm = widget->GetContent();
834 nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
835 if (widgetFrame && widgetFrame->IsFocusable()) {
836 focusContent = widgetElm;
837 widget->SetCurrentItem(this);
842 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
843 dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
844 // XXXbz: Can we actually have a non-element content here?
845 RefPtr<dom::Element> element = dom::Element::FromNodeOrNull(focusContent);
846 fm->SetFocus(element, 0);
850 void LocalAccessible::NameFromAssociatedXULLabel(DocAccessible* aDocument,
851 nsIContent* aElm,
852 nsString& aName) {
853 LocalAccessible* label = nullptr;
854 XULLabelIterator iter(aDocument, aElm);
855 while ((label = iter.Next())) {
856 // Check if label's value attribute is used
857 label->Elm()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName);
858 if (aName.IsEmpty()) {
859 // If no value attribute, a non-empty label must contain
860 // children that define its text -- possibly using HTML
861 nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName);
864 aName.CompressWhitespace();
867 void LocalAccessible::XULElmName(DocAccessible* aDocument, nsIContent* aElm,
868 nsString& aName) {
870 * 3 main cases for XUL Controls to be labeled
871 * 1 - control contains label="foo"
872 * 2 - non-child label contains control="controlID"
873 * - label has either value="foo" or children
874 * 3 - name from subtree; e.g. a child label element
875 * Cases 1 and 2 are handled here.
876 * Case 3 is handled by GetNameFromSubtree called in NativeName.
877 * Once a label is found, the search is discontinued, so a control
878 * that has a label attribute as well as having a label external to
879 * the control that uses the control="controlID" syntax will use
880 * the label attribute for its Name.
883 // CASE #1 (via label attribute) -- great majority of the cases
884 // Only do this if this is not a select control element, which uses label
885 // attribute to indicate, which option is selected.
886 nsCOMPtr<nsIDOMXULSelectControlElement> select =
887 aElm->AsElement()->AsXULSelectControl();
888 if (!select) {
889 aElm->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
892 // CASE #2 -- label as <label control="id" ... ></label>
893 if (aName.IsEmpty()) {
894 NameFromAssociatedXULLabel(aDocument, aElm, aName);
897 aName.CompressWhitespace();
900 nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) {
901 NS_ENSURE_ARG_POINTER(aEvent);
903 if (profiler_thread_is_being_profiled()) {
904 nsAutoCString strEventType;
905 GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
906 nsAutoCString strMarker;
907 strMarker.AppendLiteral("A11y Event - ");
908 strMarker.Append(strEventType);
909 PROFILER_MARKER_UNTYPED(strMarker, OTHER);
912 if (IPCAccessibilityActive() && Document()) {
913 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
914 // If ipcDoc is null, we can't fire the event to the client. We shouldn't
915 // have fired the event in the first place, since this makes events
916 // inconsistent for local and remote documents. To avoid this, don't call
917 // nsEventShell::FireEvent on a DocAccessible for which
918 // HasLoadState(eTreeConstructed) is false.
919 MOZ_ASSERT(ipcDoc);
920 if (ipcDoc) {
921 uint64_t id = aEvent->GetAccessible()->IsDoc()
923 : reinterpret_cast<uintptr_t>(
924 aEvent->GetAccessible()->UniqueID());
926 switch (aEvent->GetEventType()) {
927 case nsIAccessibleEvent::EVENT_SHOW:
928 ipcDoc->ShowEvent(downcast_accEvent(aEvent));
929 break;
931 case nsIAccessibleEvent::EVENT_HIDE:
932 ipcDoc->SendHideEvent(id, aEvent->IsFromUserInput());
933 break;
935 case nsIAccessibleEvent::EVENT_REORDER:
936 // reorder events on the application acc aren't necessary to tell the
937 // parent about new top level documents.
938 if (!aEvent->GetAccessible()->IsApplication()) {
939 ipcDoc->SendEvent(id, aEvent->GetEventType());
941 break;
942 case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
943 AccStateChangeEvent* event = downcast_accEvent(aEvent);
944 ipcDoc->SendStateChangeEvent(id, event->GetState(),
945 event->IsStateEnabled());
946 break;
948 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
949 AccCaretMoveEvent* event = downcast_accEvent(aEvent);
950 ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset(),
951 event->IsSelectionCollapsed());
952 break;
954 case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
955 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
956 AccTextChangeEvent* event = downcast_accEvent(aEvent);
957 const nsString& text = event->ModifiedText();
958 #if defined(XP_WIN)
959 // On Windows, events for live region updates containing embedded
960 // objects require us to dispatch synchronous events.
961 bool sync = text.Contains(L'\xfffc') &&
962 nsAccUtils::IsARIALive(aEvent->GetAccessible());
963 #endif
964 ipcDoc->SendTextChangeEvent(id, text, event->GetStartOffset(),
965 event->GetLength(),
966 event->IsTextInserted(),
967 event->IsFromUserInput()
968 #if defined(XP_WIN)
969 // This parameter only exists on Windows.
971 sync
972 #endif
974 break;
976 case nsIAccessibleEvent::EVENT_SELECTION:
977 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
978 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
979 AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
980 uint64_t widgetID =
981 selEvent->Widget()->IsDoc()
983 : reinterpret_cast<uintptr_t>(selEvent->Widget()->UniqueID());
984 ipcDoc->SendSelectionEvent(id, widgetID, aEvent->GetEventType());
985 break;
987 case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
988 AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
989 LocalAccessible* position = vcEvent->NewAccessible();
990 LocalAccessible* oldPosition = vcEvent->OldAccessible();
991 ipcDoc->SendVirtualCursorChangeEvent(
993 oldPosition ? reinterpret_cast<uintptr_t>(oldPosition->UniqueID())
994 : 0,
995 vcEvent->OldStartOffset(), vcEvent->OldEndOffset(),
996 position ? reinterpret_cast<uintptr_t>(position->UniqueID()) : 0,
997 vcEvent->NewStartOffset(), vcEvent->NewEndOffset(),
998 vcEvent->Reason(), vcEvent->BoundaryType(),
999 vcEvent->IsFromUserInput());
1000 break;
1002 #if defined(XP_WIN)
1003 case nsIAccessibleEvent::EVENT_FOCUS: {
1004 ipcDoc->SendFocusEvent(id);
1005 break;
1007 #endif
1008 case nsIAccessibleEvent::EVENT_SCROLLING_END:
1009 case nsIAccessibleEvent::EVENT_SCROLLING: {
1010 AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
1011 ipcDoc->SendScrollingEvent(
1012 id, aEvent->GetEventType(), scrollingEvent->ScrollX(),
1013 scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
1014 scrollingEvent->MaxScrollY());
1015 break;
1017 #if !defined(XP_WIN)
1018 case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
1019 AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
1020 ipcDoc->SendAnnouncementEvent(id, announcementEvent->Announcement(),
1021 announcementEvent->Priority());
1022 break;
1024 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
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();
1032 LocalAccessible* end = range.EndContainer();
1033 textRangeData.AppendElement(TextRangeData(
1034 start->IsDoc() && start->AsDoc()->IPCDoc()
1036 : reinterpret_cast<uint64_t>(start->UniqueID()),
1037 end->IsDoc() && end->AsDoc()->IPCDoc()
1039 : reinterpret_cast<uint64_t>(end->UniqueID()),
1040 range.StartOffset(), range.EndOffset()));
1042 ipcDoc->SendTextSelectionChangeEvent(id, textRangeData);
1043 break;
1045 #endif
1046 case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
1047 case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
1048 SendCache(CacheDomain::NameAndDescription, CacheUpdateType::Update);
1049 ipcDoc->SendEvent(id, aEvent->GetEventType());
1050 break;
1052 case nsIAccessibleEvent::EVENT_VALUE_CHANGE: {
1053 SendCache(CacheDomain::Value, CacheUpdateType::Update);
1054 ipcDoc->SendEvent(id, aEvent->GetEventType());
1055 break;
1057 default:
1058 ipcDoc->SendEvent(id, aEvent->GetEventType());
1063 if (nsCoreUtils::AccEventObserversExist()) {
1064 nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
1067 return NS_OK;
1070 already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
1071 RefPtr<AccAttributes> attributes = NativeAttributes();
1072 if (!HasOwnContent() || !mContent->IsElement()) return attributes.forget();
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);
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));
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);
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));
1108 return attributes.forget();
1111 already_AddRefed<AccAttributes> LocalAccessible::NativeAttributes() {
1112 RefPtr<AccAttributes> attributes = new AccAttributes();
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));
1124 // Expose checkable object attribute if the accessible has checkable state
1125 if (State() & states::CHECKABLE) {
1126 attributes->SetAttribute(nsGkAtoms::checkable, true);
1129 // Expose 'explicit-name' attribute.
1130 nsAutoString name;
1131 if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
1132 attributes->SetAttribute(nsGkAtoms::explicit_name, true);
1135 // Group attributes (level/setsize/posinset)
1136 GroupPos groupPos = GroupPosition();
1137 nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, groupPos.setSize,
1138 groupPos.posInSet);
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));
1147 if (hierarchical) {
1148 attributes->SetAttribute(nsGkAtoms::tree, true);
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();
1155 nsEventShell::GetEventAttributes(GetNode(), attributes);
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);
1163 if (!mContent->IsElement()) return attributes.forget();
1165 nsString id;
1166 if (nsCoreUtils::GetID(mContent, id)) {
1167 attributes->SetAttribute(nsGkAtoms::id, std::move(id));
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));
1177 // Expose tag.
1178 attributes->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom());
1180 // Expose draggable object attribute.
1181 if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
1182 if (htmlElement->Draggable()) {
1183 attributes->SetAttribute(nsGkAtoms::draggable, true);
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();
1191 // CSS style based object attributes.
1192 nsAutoString value;
1193 StyleInfo styleInfo(mContent->AsElement());
1195 // Expose 'display' attribute.
1196 RefPtr<nsAtom> displayValue = styleInfo.Display();
1197 attributes->SetAttribute(nsGkAtoms::display, displayValue);
1199 // Expose 'text-align' attribute.
1200 RefPtr<nsAtom> textAlignValue = styleInfo.TextAlign();
1201 attributes->SetAttribute(nsGkAtoms::textAlign, textAlignValue);
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());
1212 // Expose 'margin-left' attribute.
1213 attributes->SetAttribute(nsGkAtoms::marginLeft, styleInfo.MarginLeft());
1215 // Expose 'margin-right' attribute.
1216 attributes->SetAttribute(nsGkAtoms::marginRight, styleInfo.MarginRight());
1218 // Expose 'margin-top' attribute.
1219 attributes->SetAttribute(nsGkAtoms::marginTop, styleInfo.MarginTop());
1221 // Expose 'margin-bottom' attribute.
1222 attributes->SetAttribute(nsGkAtoms::marginBottom, styleInfo.MarginBottom());
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));
1233 return attributes.forget();
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 (aAttribute == nsGkAtoms::href && IsHTMLLink());
1255 void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
1256 nsAtom* aAttribute, int32_t aModType,
1257 const nsAttrValue* aOldValue,
1258 uint64_t aOldState) {
1259 // Fire accessible event after short timer, because we need to wait for
1260 // DOM attribute & resulting layout to actually change. Otherwise,
1261 // assistive technology will retrieve the wrong state/value/selection info.
1263 // XXX todo
1264 // We still need to handle special HTML cases here
1265 // For example, if an <img>'s usemap attribute is modified
1266 // Otherwise it may just be a state change, for example an object changing
1267 // its visibility
1269 // XXX todo: report aria state changes for "undefined" literal value changes
1270 // filed as bug 472142
1272 // XXX todo: invalidate accessible when aria state changes affect exposed
1273 // role filed as bug 472143
1275 if (AttributeChangesState(aAttribute)) {
1276 uint64_t currState = State();
1277 uint64_t diffState = currState ^ aOldState;
1278 if (diffState) {
1279 for (uint64_t state = 1; state <= states::LAST_ENTRY; state <<= 1) {
1280 if (diffState & state) {
1281 RefPtr<AccEvent> stateChangeEvent =
1282 new AccStateChangeEvent(this, state, (currState & state));
1283 mDoc->FireDelayedEvent(stateChangeEvent);
1289 // When a details object has its open attribute changed
1290 // we should fire a state-change event on the accessible of
1291 // its main summary
1292 if (aAttribute == nsGkAtoms::open) {
1293 // FromDetails checks if the given accessible belongs to
1294 // a details frame and also locates the accessible of its
1295 // main summary.
1296 if (HTMLSummaryAccessible* summaryAccessible =
1297 HTMLSummaryAccessible::FromDetails(this)) {
1298 RefPtr<AccEvent> expandedChangeEvent =
1299 new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
1300 mDoc->FireDelayedEvent(expandedChangeEvent);
1301 return;
1305 // Check for namespaced ARIA attribute
1306 if (aNameSpaceID == kNameSpaceID_None) {
1307 // Check for hyphenated aria-foo property?
1308 if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
1309 uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
1310 if (!(attrFlags & ATTR_BYPASSOBJ)) {
1311 // For aria attributes like drag and drop changes we fire a generic
1312 // attribute change event; at least until native API comes up with a
1313 // more meaningful event.
1314 RefPtr<AccEvent> event =
1315 new AccObjectAttrChangedEvent(this, aAttribute);
1316 mDoc->FireDelayedEvent(event);
1321 dom::Element* elm = Elm();
1323 if (HasNumericValue() &&
1324 (aAttribute == nsGkAtoms::aria_valuemax ||
1325 aAttribute == nsGkAtoms::aria_valuemin || aAttribute == nsGkAtoms::min ||
1326 aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step)) {
1327 SendCache(CacheDomain::Value, CacheUpdateType::Update);
1328 return;
1331 // Fire text value change event whenever aria-valuetext is changed.
1332 if (aAttribute == nsGkAtoms::aria_valuetext) {
1333 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, this);
1334 return;
1337 if (aAttribute == nsGkAtoms::aria_valuenow) {
1338 if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
1339 elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
1340 nsGkAtoms::_empty, eCaseMatters)) {
1341 // Fire numeric value change event when aria-valuenow is changed and
1342 // aria-valuetext is empty
1343 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
1344 } else {
1345 // We need to update the cache here since we won't get an event if
1346 // aria-valuenow is shadowed by aria-valuetext.
1347 SendCache(CacheDomain::Value, CacheUpdateType::Update);
1349 return;
1352 if (aAttribute == nsGkAtoms::aria_owns) {
1353 mDoc->Controller()->ScheduleRelocation(this);
1356 // Fire name change and description change events.
1357 if (aAttribute == nsGkAtoms::aria_label) {
1358 // A valid aria-labelledby would take precedence so an aria-label change
1359 // won't change the name.
1360 IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
1361 if (!iter.NextElem()) {
1362 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1364 return;
1367 if (aAttribute == nsGkAtoms::aria_describedby) {
1368 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, this);
1369 if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
1370 aModType == dom::MutationEvent_Binding::ADDITION) {
1371 // The subtrees of the new aria-describedby targets might be used to
1372 // compute the description for this. Therefore, we need to set
1373 // the eHasDescriptionDependent flag on all Accessibles in these subtrees.
1374 IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
1375 while (LocalAccessible* target = iter.Next()) {
1376 target->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
1379 return;
1382 if (aAttribute == nsGkAtoms::aria_labelledby) {
1383 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1384 if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
1385 aModType == dom::MutationEvent_Binding::ADDITION) {
1386 // The subtrees of the new aria-labelledby targets might be used to
1387 // compute the name for this. Therefore, we need to set
1388 // the eHasNameDependent flag on all Accessibles in these subtrees.
1389 IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
1390 while (LocalAccessible* target = iter.Next()) {
1391 target->ModifySubtreeContextFlags(eHasNameDependent, true);
1394 return;
1397 if (aAttribute == nsGkAtoms::alt &&
1398 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
1399 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
1400 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1401 return;
1404 if (aAttribute == nsGkAtoms::title) {
1405 nsAutoString name;
1406 ARIAName(name);
1407 if (name.IsEmpty()) {
1408 NativeName(name);
1409 if (name.IsEmpty()) {
1410 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1411 return;
1415 if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby)) {
1416 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
1417 this);
1420 return;
1423 // ARIA or XUL selection
1424 if ((mContent->IsXULElement() && aAttribute == nsGkAtoms::selected) ||
1425 aAttribute == nsGkAtoms::aria_selected) {
1426 LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State());
1427 if (widget) {
1428 AccSelChangeEvent::SelChangeType selChangeType =
1429 elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
1430 eCaseMatters)
1431 ? AccSelChangeEvent::eSelectionAdd
1432 : AccSelChangeEvent::eSelectionRemove;
1434 RefPtr<AccEvent> event =
1435 new AccSelChangeEvent(widget, this, selChangeType);
1436 mDoc->FireDelayedEvent(event);
1439 return;
1443 GroupPos LocalAccessible::GroupPosition() {
1444 GroupPos groupPos;
1445 if (!HasOwnContent()) return groupPos;
1447 // Get group position from ARIA attributes.
1448 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, &groupPos.level);
1449 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize,
1450 &groupPos.setSize);
1451 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset,
1452 &groupPos.posInSet);
1454 // If ARIA is missed and the accessible is visible then calculate group
1455 // position from hierarchy.
1456 if (State() & states::INVISIBLE) return groupPos;
1458 // Calculate group level if ARIA is missed.
1459 if (groupPos.level == 0) {
1460 int32_t level = GetLevelInternal();
1461 if (level != 0) {
1462 groupPos.level = level;
1463 } else {
1464 const nsRoleMapEntry* role = this->ARIARoleMap();
1465 if (role && role->Is(nsGkAtoms::heading)) {
1466 groupPos.level = 2;
1471 // Calculate position in group and group size if ARIA is missed.
1472 if (groupPos.posInSet == 0 || groupPos.setSize == 0) {
1473 int32_t posInSet = 0, setSize = 0;
1474 GetPositionAndSizeInternal(&posInSet, &setSize);
1475 if (posInSet != 0 && setSize != 0) {
1476 if (groupPos.posInSet == 0) groupPos.posInSet = posInSet;
1478 if (groupPos.setSize == 0) groupPos.setSize = setSize;
1482 return groupPos;
1485 uint64_t LocalAccessible::State() {
1486 if (IsDefunct()) return states::DEFUNCT;
1488 uint64_t state = NativeState();
1489 // Apply ARIA states to be sure accessible states will be overridden.
1490 ApplyARIAState(&state);
1492 // If this is an ARIA item of the selectable widget and if it's focused and
1493 // not marked unselected explicitly (i.e. aria-selected="false") then expose
1494 // it as selected to make ARIA widget authors life easier.
1495 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1496 if (roleMapEntry && !(state & states::SELECTED) &&
1497 (!mContent->IsElement() ||
1498 !mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1499 nsGkAtoms::aria_selected,
1500 nsGkAtoms::_false, eCaseMatters))) {
1501 // Special case for tabs: focused tab or focus inside related tab panel
1502 // implies selected state.
1503 if (roleMapEntry->role == roles::PAGETAB) {
1504 if (state & states::FOCUSED) {
1505 state |= states::SELECTED;
1506 } else {
1507 // If focus is in a child of the tab panel surely the tab is selected!
1508 Relation rel = RelationByType(RelationType::LABEL_FOR);
1509 LocalAccessible* relTarget = nullptr;
1510 while ((relTarget = rel.Next())) {
1511 if (relTarget->Role() == roles::PROPERTYPAGE &&
1512 FocusMgr()->IsFocusWithin(relTarget)) {
1513 state |= states::SELECTED;
1517 } else if (state & states::FOCUSED) {
1518 LocalAccessible* container =
1519 nsAccUtils::GetSelectableContainer(this, state);
1520 if (container &&
1521 !nsAccUtils::HasDefinedARIAToken(container->GetContent(),
1522 nsGkAtoms::aria_multiselectable)) {
1523 state |= states::SELECTED;
1528 const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED;
1529 if ((state & kExpandCollapseStates) == kExpandCollapseStates) {
1530 // Cannot be both expanded and collapsed -- this happens in ARIA expanded
1531 // combobox because of limitation of ARIAMap.
1532 // XXX: Perhaps we will be able to make this less hacky if we support
1533 // extended states in ARIAMap, e.g. derive COLLAPSED from
1534 // EXPANDABLE && !EXPANDED.
1535 state &= ~states::COLLAPSED;
1538 if (!(state & states::UNAVAILABLE)) {
1539 state |= states::ENABLED | states::SENSITIVE;
1541 // If the object is a current item of container widget then mark it as
1542 // ACTIVE. This allows screen reader virtual buffer modes to know which
1543 // descendant is the current one that would get focus if the user navigates
1544 // to the container widget.
1545 LocalAccessible* widget = ContainerWidget();
1546 if (widget && widget->CurrentItem() == this) state |= states::ACTIVE;
1549 if ((state & states::COLLAPSED) || (state & states::EXPANDED)) {
1550 state |= states::EXPANDABLE;
1553 // For some reasons DOM node may have not a frame. We tract such accessibles
1554 // as invisible.
1555 nsIFrame* frame = GetFrame();
1556 if (!frame) return state;
1558 if (frame->StyleEffects()->mOpacity == 1.0f && !(state & states::INVISIBLE)) {
1559 state |= states::OPAQUE1;
1562 return state;
1565 void LocalAccessible::ApplyARIAState(uint64_t* aState) const {
1566 if (!mContent->IsElement()) return;
1568 dom::Element* element = mContent->AsElement();
1570 // Test for universal states first
1571 *aState |= aria::UniversalStatesFor(element);
1573 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1574 if (roleMapEntry) {
1575 // We only force the readonly bit off if we have a real mapping for the aria
1576 // role. This preserves the ability for screen readers to use readonly
1577 // (primarily on the document) as the hint for creating a virtual buffer.
1578 if (roleMapEntry->role != roles::NOTHING) *aState &= ~states::READONLY;
1580 if (mContent->HasID()) {
1581 // If has a role & ID and aria-activedescendant on the container, assume
1582 // focusable.
1583 const LocalAccessible* ancestor = this;
1584 while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
1585 dom::Element* el = ancestor->Elm();
1586 if (el &&
1587 el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
1588 *aState |= states::FOCUSABLE;
1589 break;
1595 if (*aState & states::FOCUSABLE) {
1596 // Propogate aria-disabled from ancestors down to any focusable descendant.
1597 const LocalAccessible* ancestor = this;
1598 while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
1599 dom::Element* el = ancestor->Elm();
1600 if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
1601 nsGkAtoms::_true, eCaseMatters)) {
1602 *aState |= states::UNAVAILABLE;
1603 break;
1606 } else {
1607 // Sometimes, we use aria-activedescendant targeting something which isn't
1608 // actually a descendant. This is technically a spec violation, but it's a
1609 // useful hack which makes certain things much easier. For example, we use
1610 // this for "fake focus" for multi select browser tabs and Quantumbar
1611 // autocomplete suggestions.
1612 // In these cases, the aria-activedescendant code above won't make the
1613 // active item focusable. It doesn't make sense for something to have
1614 // focus when it isn't focusable, so fix that here.
1615 if (FocusMgr()->IsActiveItem(this)) {
1616 *aState |= states::FOCUSABLE;
1620 // special case: A native button element whose role got transformed by ARIA to
1621 // a toggle button Also applies to togglable button menus, like in the Dev
1622 // Tools Web Console.
1623 if (IsButton() || IsMenuButton()) {
1624 aria::MapToState(aria::eARIAPressed, element, aState);
1627 if (!roleMapEntry) return;
1629 *aState |= roleMapEntry->state;
1631 if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
1632 aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
1633 aria::MapToState(roleMapEntry->attributeMap3, element, aState)) {
1634 aria::MapToState(roleMapEntry->attributeMap4, element, aState);
1637 // ARIA gridcell inherits readonly state from the grid until it's overridden.
1638 if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
1639 roleMapEntry->Is(nsGkAtoms::columnheader) ||
1640 roleMapEntry->Is(nsGkAtoms::rowheader)) &&
1641 !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) {
1642 const TableCellAccessible* cell = AsTableCell();
1643 if (cell) {
1644 TableAccessible* table = cell->Table();
1645 if (table) {
1646 LocalAccessible* grid = table->AsAccessible();
1647 uint64_t gridState = 0;
1648 grid->ApplyARIAState(&gridState);
1649 *aState |= gridState & states::READONLY;
1655 void LocalAccessible::Value(nsString& aValue) const {
1656 if (HasNumericValue()) {
1657 // aria-valuenow is a number, and aria-valuetext is the optional text
1658 // equivalent. For the string value, we will try the optional text
1659 // equivalent first.
1660 if (!mContent->IsElement()) {
1661 return;
1664 if (!mContent->AsElement()->GetAttr(kNameSpaceID_None,
1665 nsGkAtoms::aria_valuetext, aValue)) {
1666 if (!NativeHasNumericValue()) {
1667 double checkValue = CurValue();
1668 if (!IsNaN(checkValue)) {
1669 aValue.AppendFloat(checkValue);
1673 return;
1676 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1677 if (!roleMapEntry) {
1678 return;
1681 // Value of textbox is a textified subtree.
1682 if (roleMapEntry->Is(nsGkAtoms::textbox)) {
1683 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
1684 return;
1687 // Value of combobox is a text of current or selected item.
1688 if (roleMapEntry->Is(nsGkAtoms::combobox)) {
1689 LocalAccessible* option = CurrentItem();
1690 if (!option) {
1691 uint32_t childCount = ChildCount();
1692 for (uint32_t idx = 0; idx < childCount; idx++) {
1693 LocalAccessible* child = mChildren.ElementAt(idx);
1694 if (child->IsListControl()) {
1695 option = child->GetSelectedItem(0);
1696 break;
1701 if (option) nsTextEquivUtils::GetTextEquivFromSubtree(option, aValue);
1705 double LocalAccessible::MaxValue() const {
1706 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemax);
1707 return IsNaN(checkValue) && !NativeHasNumericValue() ? 100 : checkValue;
1710 double LocalAccessible::MinValue() const {
1711 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemin);
1712 return IsNaN(checkValue) && !NativeHasNumericValue() ? 0 : checkValue;
1715 double LocalAccessible::Step() const {
1716 return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
1719 double LocalAccessible::CurValue() const {
1720 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuenow);
1721 if (IsNaN(checkValue) && !NativeHasNumericValue()) {
1722 double minValue = MinValue();
1723 return minValue + ((MaxValue() - minValue) / 2);
1726 return checkValue;
1729 bool LocalAccessible::SetCurValue(double aValue) {
1730 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1731 if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) return false;
1733 const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
1734 if (State() & kValueCannotChange) return false;
1736 double checkValue = MinValue();
1737 if (!IsNaN(checkValue) && aValue < checkValue) return false;
1739 checkValue = MaxValue();
1740 if (!IsNaN(checkValue) && aValue > checkValue) return false;
1742 nsAutoString strValue;
1743 strValue.AppendFloat(aValue);
1745 if (!mContent->IsElement()) return true;
1747 return NS_SUCCEEDED(mContent->AsElement()->SetAttr(
1748 kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
1751 role LocalAccessible::ARIATransformRole(role aRole) const {
1752 // Beginning with ARIA 1.1, user agents are expected to use the native host
1753 // language role of the element when the region role is used without a name.
1754 // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-region
1756 // XXX: While the name computation algorithm can be non-trivial in the general
1757 // case, it should not be especially bad here: If the author hasn't used the
1758 // region role, this calculation won't occur. And the region role's name
1759 // calculation rule excludes name from content. That said, this use case is
1760 // another example of why we should consider caching the accessible name. See:
1761 // https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
1762 if (aRole == roles::REGION) {
1763 nsAutoString name;
1764 Name(name);
1765 return name.IsEmpty() ? NativeRole() : aRole;
1768 // XXX: these unfortunate exceptions don't fit into the ARIA table. This is
1769 // where the accessible role depends on both the role and ARIA state.
1770 if (aRole == roles::PUSHBUTTON) {
1771 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) {
1772 // For simplicity, any existing pressed attribute except "" or "undefined"
1773 // indicates a toggle.
1774 return roles::TOGGLE_BUTTON;
1777 if (mContent->IsElement() &&
1778 mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1779 nsGkAtoms::aria_haspopup,
1780 nsGkAtoms::_true, eCaseMatters)) {
1781 // For button with aria-haspopup="true".
1782 return roles::BUTTONMENU;
1785 } else if (aRole == roles::LISTBOX) {
1786 // A listbox inside of a combobox needs a special role because of ATK
1787 // mapping to menu.
1788 if (mParent && mParent->IsCombobox()) {
1789 return roles::COMBOBOX_LIST;
1790 } else {
1791 // Listbox is owned by a combobox
1792 Relation rel = RelationByType(RelationType::NODE_CHILD_OF);
1793 LocalAccessible* targetAcc = nullptr;
1794 while ((targetAcc = rel.Next())) {
1795 if (targetAcc->IsCombobox()) return roles::COMBOBOX_LIST;
1799 } else if (aRole == roles::OPTION) {
1800 if (mParent && mParent->Role() == roles::COMBOBOX_LIST) {
1801 return roles::COMBOBOX_OPTION;
1804 } else if (aRole == roles::MENUITEM) {
1805 // Menuitem has a submenu.
1806 if (mContent->IsElement() &&
1807 mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1808 nsGkAtoms::aria_haspopup,
1809 nsGkAtoms::_true, eCaseMatters)) {
1810 return roles::PARENT_MENUITEM;
1813 } else if (aRole == roles::CELL) {
1814 // A cell inside an ancestor table element that has a grid role needs a
1815 // gridcell role
1816 // (https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings).
1817 const TableCellAccessible* cell = AsTableCell();
1818 if (cell) {
1819 TableAccessible* table = cell->Table();
1820 if (table && table->AsAccessible()->IsARIARole(nsGkAtoms::grid)) {
1821 return roles::GRID_CELL;
1826 return aRole;
1829 nsAtom* LocalAccessible::LandmarkRole() const {
1830 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1831 return roleMapEntry && roleMapEntry->IsOfType(eLandmark)
1832 ? roleMapEntry->roleAtom
1833 : nullptr;
1836 role LocalAccessible::NativeRole() const { return roles::NOTHING; }
1838 uint8_t LocalAccessible::ActionCount() const {
1839 return GetActionRule() == eNoAction ? 0 : 1;
1842 void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
1843 aName.Truncate();
1845 if (aIndex != 0) return;
1847 uint32_t actionRule = GetActionRule();
1849 switch (actionRule) {
1850 case eActivateAction:
1851 aName.AssignLiteral("activate");
1852 return;
1854 case eClickAction:
1855 aName.AssignLiteral("click");
1856 return;
1858 case ePressAction:
1859 aName.AssignLiteral("press");
1860 return;
1862 case eCheckUncheckAction: {
1863 uint64_t state = State();
1864 if (state & states::CHECKED) {
1865 aName.AssignLiteral("uncheck");
1866 } else if (state & states::MIXED) {
1867 aName.AssignLiteral("cycle");
1868 } else {
1869 aName.AssignLiteral("check");
1871 return;
1874 case eJumpAction:
1875 aName.AssignLiteral("jump");
1876 return;
1878 case eOpenCloseAction:
1879 if (State() & states::COLLAPSED) {
1880 aName.AssignLiteral("open");
1881 } else {
1882 aName.AssignLiteral("close");
1884 return;
1886 case eSelectAction:
1887 aName.AssignLiteral("select");
1888 return;
1890 case eSwitchAction:
1891 aName.AssignLiteral("switch");
1892 return;
1894 case eSortAction:
1895 aName.AssignLiteral("sort");
1896 return;
1898 case eExpandAction:
1899 if (State() & states::COLLAPSED) {
1900 aName.AssignLiteral("expand");
1901 } else {
1902 aName.AssignLiteral("collapse");
1904 return;
1908 bool LocalAccessible::DoAction(uint8_t aIndex) const {
1909 if (aIndex != 0) return false;
1911 if (GetActionRule() != eNoAction) {
1912 DoCommand();
1913 return true;
1916 return false;
1919 nsIContent* LocalAccessible::GetAtomicRegion() const {
1920 nsIContent* loopContent = mContent;
1921 nsAutoString atomic;
1922 while (loopContent &&
1923 (!loopContent->IsElement() ||
1924 !loopContent->AsElement()->GetAttr(kNameSpaceID_None,
1925 nsGkAtoms::aria_atomic, atomic))) {
1926 loopContent = loopContent->GetParent();
1929 return atomic.EqualsLiteral("true") ? loopContent : nullptr;
1932 Relation LocalAccessible::RelationByType(RelationType aType) const {
1933 if (!HasOwnContent()) return Relation();
1935 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1937 // Relationships are defined on the same content node that the role would be
1938 // defined on.
1939 switch (aType) {
1940 case RelationType::LABELLED_BY: {
1941 Relation rel(
1942 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_labelledby));
1943 if (mContent->IsHTMLElement()) {
1944 rel.AppendIter(new HTMLLabelIterator(Document(), this));
1946 rel.AppendIter(new XULLabelIterator(Document(), mContent));
1948 return rel;
1951 case RelationType::LABEL_FOR: {
1952 Relation rel(new RelatedAccIterator(Document(), mContent,
1953 nsGkAtoms::aria_labelledby));
1954 if (mContent->IsXULElement(nsGkAtoms::label)) {
1955 rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
1958 return rel;
1961 case RelationType::DESCRIBED_BY: {
1962 Relation rel(
1963 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_describedby));
1964 if (mContent->IsXULElement()) {
1965 rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
1968 return rel;
1971 case RelationType::DESCRIPTION_FOR: {
1972 Relation rel(new RelatedAccIterator(Document(), mContent,
1973 nsGkAtoms::aria_describedby));
1975 // This affectively adds an optional control attribute to xul:description,
1976 // which only affects accessibility, by allowing the description to be
1977 // tied to a control.
1978 if (mContent->IsXULElement(nsGkAtoms::description)) {
1979 rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
1982 return rel;
1985 case RelationType::NODE_CHILD_OF: {
1986 Relation rel;
1987 // This is an ARIA tree or treegrid that doesn't use owns, so we need to
1988 // get the parent the hard way.
1989 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
1990 roleMapEntry->role == roles::LISTITEM ||
1991 roleMapEntry->role == roles::ROW)) {
1992 rel.AppendTarget(GetGroupInfo()->ConceptualParent());
1995 // If this is an OOP iframe document, we can't support NODE_CHILD_OF
1996 // here, since the iframe resides in a different process. This is fine
1997 // because the client will then request the parent instead, which will be
1998 // correctly handled by platform code.
1999 if (XRE_IsContentProcess() && IsRoot()) {
2000 dom::Document* doc =
2001 const_cast<LocalAccessible*>(this)->AsDoc()->DocumentNode();
2002 dom::BrowsingContext* bc = doc->GetBrowsingContext();
2003 MOZ_ASSERT(bc);
2004 if (!bc->Top()->IsInProcess()) {
2005 return rel;
2009 // If accessible is in its own Window, or is the root of a document,
2010 // then we should provide NODE_CHILD_OF relation so that MSAA clients
2011 // can easily get to true parent instead of getting to oleacc's
2012 // ROLE_WINDOW accessible which will prevent us from going up further
2013 // (because it is system generated and has no idea about the hierarchy
2014 // above it).
2015 nsIFrame* frame = GetFrame();
2016 if (frame) {
2017 nsView* view = frame->GetView();
2018 if (view) {
2019 nsIScrollableFrame* scrollFrame = do_QueryFrame(frame);
2020 if (scrollFrame || view->GetWidget() || !frame->GetParent()) {
2021 rel.AppendTarget(LocalParent());
2026 return rel;
2029 case RelationType::NODE_PARENT_OF: {
2030 // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
2031 // also can be organized by groups.
2032 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2033 roleMapEntry->role == roles::LISTITEM ||
2034 roleMapEntry->role == roles::ROW ||
2035 roleMapEntry->role == roles::OUTLINE ||
2036 roleMapEntry->role == roles::LIST ||
2037 roleMapEntry->role == roles::TREE_TABLE)) {
2038 return Relation(new ItemIterator(this));
2041 return Relation();
2044 case RelationType::CONTROLLED_BY:
2045 return Relation(new RelatedAccIterator(Document(), mContent,
2046 nsGkAtoms::aria_controls));
2048 case RelationType::CONTROLLER_FOR: {
2049 Relation rel(
2050 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_controls));
2051 rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
2052 return rel;
2055 case RelationType::FLOWS_TO:
2056 return Relation(
2057 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_flowto));
2059 case RelationType::FLOWS_FROM:
2060 return Relation(
2061 new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_flowto));
2063 case RelationType::MEMBER_OF: {
2064 if (Role() == roles::RADIOBUTTON) {
2065 /* If we see a radio button role here, we're dealing with an aria
2066 * radio button (because input=radio buttons are
2067 * HTMLRadioButtonAccessibles) */
2068 Relation rel = Relation();
2069 LocalAccessible* currParent = LocalParent();
2070 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
2071 currParent = currParent->LocalParent();
2074 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
2075 /* If we found a radiogroup parent, search for all
2076 * roles::RADIOBUTTON children and add them to our relation.
2077 * This search will include the radio button this method
2078 * was called from, which is expected. */
2079 Pivot p = Pivot(currParent);
2080 PivotRoleRule rule(roles::RADIOBUTTON);
2081 Accessible* match = p.Next(currParent, rule);
2082 while (match) {
2083 MOZ_ASSERT(match->IsLocal(),
2084 "We shouldn't find any remote accs while building our "
2085 "relation!");
2086 rel.AppendTarget(match->AsLocal());
2087 match = p.Next(match, rule);
2091 /* By webkit's standard, aria radio buttons do not get grouped
2092 * if they lack a group parent, so we return an empty
2093 * relation here if the above check fails. */
2095 return rel;
2098 return Relation(mDoc, GetAtomicRegion());
2101 case RelationType::LINKS_TO: {
2102 Relation rel = Relation();
2103 if (Role() == roles::LINK) {
2104 dom::HTMLAnchorElement* anchor =
2105 dom::HTMLAnchorElement::FromNode(mContent);
2106 if (!anchor) {
2107 return rel;
2109 // If this node is an anchor element, query its hash to find the
2110 // target.
2111 nsAutoString hash;
2112 anchor->GetHash(hash);
2113 if (hash.IsEmpty()) {
2114 return rel;
2117 // GetHash returns an ID or name with a leading '#', trim it so we can
2118 // search the doc by ID or name alone.
2119 hash.Trim("#");
2120 if (dom::Element* elm = mContent->OwnerDoc()->GetElementById(hash)) {
2121 rel.AppendTarget(mDoc->GetAccessibleOrContainer(elm));
2122 } else if (nsCOMPtr<nsINodeList> list =
2123 mContent->OwnerDoc()->GetElementsByName(hash)) {
2124 // Loop through the named nodes looking for the first anchor
2125 uint32_t length = list->Length();
2126 for (uint32_t i = 0; i < length; i++) {
2127 nsIContent* node = list->Item(i);
2128 if (node->IsHTMLElement(nsGkAtoms::a)) {
2129 rel.AppendTarget(mDoc->GetAccessibleOrContainer(node));
2130 break;
2136 return rel;
2139 case RelationType::SUBWINDOW_OF:
2140 case RelationType::EMBEDS:
2141 case RelationType::EMBEDDED_BY:
2142 case RelationType::POPUP_FOR:
2143 case RelationType::PARENT_WINDOW_OF:
2144 return Relation();
2146 case RelationType::DEFAULT_BUTTON: {
2147 if (mContent->IsHTMLElement()) {
2148 // HTML form controls implements nsIFormControl interface.
2149 nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent));
2150 if (control) {
2151 if (dom::HTMLFormElement* form = control->GetForm()) {
2152 return Relation(mDoc, form->GetDefaultSubmitElement());
2155 } else {
2156 // In XUL, use first <button default="true" .../> in the document
2157 dom::Document* doc = mContent->OwnerDoc();
2158 nsIContent* buttonEl = nullptr;
2159 if (doc->AllowXULXBL()) {
2160 nsCOMPtr<nsIHTMLCollection> possibleDefaultButtons =
2161 doc->GetElementsByAttribute(u"default"_ns, u"true"_ns);
2162 if (possibleDefaultButtons) {
2163 uint32_t length = possibleDefaultButtons->Length();
2164 // Check for button in list of default="true" elements
2165 for (uint32_t count = 0; count < length && !buttonEl; count++) {
2166 nsIContent* item = possibleDefaultButtons->Item(count);
2167 RefPtr<nsIDOMXULButtonElement> button =
2168 item->IsElement() ? item->AsElement()->AsXULButton()
2169 : nullptr;
2170 if (button) {
2171 buttonEl = item;
2175 return Relation(mDoc, buttonEl);
2178 return Relation();
2181 case RelationType::CONTAINING_DOCUMENT:
2182 return Relation(mDoc);
2184 case RelationType::CONTAINING_TAB_PANE: {
2185 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2186 if (docShell) {
2187 // Walk up the parent chain without crossing the boundary at which item
2188 // types change, preventing us from walking up out of tab content.
2189 nsCOMPtr<nsIDocShellTreeItem> root;
2190 docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
2191 if (root) {
2192 // If the item type is typeContent, we assume we are in browser tab
2193 // content. Note, this includes content such as about:addons,
2194 // for consistency.
2195 if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
2196 return Relation(nsAccUtils::GetDocAccessibleFor(root));
2200 return Relation();
2203 case RelationType::CONTAINING_APPLICATION:
2204 return Relation(ApplicationAcc());
2206 case RelationType::DETAILS:
2207 return Relation(
2208 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_details));
2210 case RelationType::DETAILS_FOR:
2211 return Relation(
2212 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
2214 case RelationType::ERRORMSG:
2215 return Relation(
2216 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2218 case RelationType::ERRORMSG_FOR:
2219 return Relation(
2220 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2222 default:
2223 return Relation();
2227 void LocalAccessible::GetNativeInterface(void** aNativeAccessible) {}
2229 void LocalAccessible::DoCommand(nsIContent* aContent,
2230 uint32_t aActionIndex) const {
2231 class Runnable final : public mozilla::Runnable {
2232 public:
2233 Runnable(const LocalAccessible* aAcc, nsIContent* aContent, uint32_t aIdx)
2234 : mozilla::Runnable("Runnable"),
2235 mAcc(aAcc),
2236 mContent(aContent),
2237 mIdx(aIdx) {}
2239 // XXX Cannot mark as MOZ_CAN_RUN_SCRIPT because the base class change
2240 // requires too big changes across a lot of modules.
2241 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
2242 if (mAcc) {
2243 MOZ_KnownLive(mAcc)->DispatchClickEvent(MOZ_KnownLive(mContent), mIdx);
2245 return NS_OK;
2248 void Revoke() {
2249 mAcc = nullptr;
2250 mContent = nullptr;
2253 private:
2254 RefPtr<const LocalAccessible> mAcc;
2255 nsCOMPtr<nsIContent> mContent;
2256 uint32_t mIdx;
2259 nsIContent* content = aContent ? aContent : mContent.get();
2260 nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex);
2261 NS_DispatchToMainThread(runnable);
2264 void LocalAccessible::DispatchClickEvent(nsIContent* aContent,
2265 uint32_t aActionIndex) const {
2266 if (IsDefunct()) return;
2268 RefPtr<PresShell> presShell = mDoc->PresShellPtr();
2270 // Scroll into view.
2271 presShell->ScrollContentIntoView(aContent, ScrollAxis(), ScrollAxis(),
2272 ScrollFlags::ScrollOverflowHidden);
2274 AutoWeakFrame frame = aContent->GetPrimaryFrame();
2275 if (!frame) return;
2277 // Compute x and y coordinates.
2278 nsPoint point;
2279 nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
2280 if (!widget) return;
2282 nsSize size = frame->GetSize();
2284 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2285 int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
2286 int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
2288 // Simulate a touch interaction by dispatching touch events with mouse events.
2289 nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, aContent, frame, presShell,
2290 widget);
2291 nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, aContent, frame, presShell,
2292 widget);
2293 nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, aContent, frame, presShell,
2294 widget);
2295 nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, aContent, frame, presShell,
2296 widget);
2299 void LocalAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX,
2300 int32_t aY) {
2301 nsIFrame* frame = GetFrame();
2302 if (!frame) return;
2304 nsIntPoint coords =
2305 nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
2307 nsIFrame* parentFrame = frame;
2308 while ((parentFrame = parentFrame->GetParent())) {
2309 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2313 void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
2314 uint32_t aLength) {
2315 // Return text representation of non-text accessible within hypertext
2316 // accessible. Text accessible overrides this method to return enclosed text.
2317 if (aStartOffset != 0 || aLength == 0) return;
2319 nsIFrame* frame = GetFrame();
2320 if (!frame) {
2321 if (nsCoreUtils::IsDisplayContents(mContent)) {
2322 aText += kEmbeddedObjectChar;
2324 return;
2327 MOZ_ASSERT(mParent,
2328 "Called on accessible unbound from tree. Result can be wrong.");
2330 if (frame->IsBrFrame()) {
2331 aText += kForcedNewLineChar;
2332 } else if (mParent && nsAccUtils::MustPrune(mParent)) {
2333 // Expose the embedded object accessible as imaginary embedded object
2334 // character if its parent hypertext accessible doesn't expose children to
2335 // AT.
2336 aText += kImaginaryEmbeddedObjectChar;
2337 } else {
2338 aText += kEmbeddedObjectChar;
2342 void LocalAccessible::Shutdown() {
2343 // Mark the accessible as defunct, invalidate the child count and pointers to
2344 // other accessibles, also make sure none of its children point to this
2345 // parent
2346 mStateFlags |= eIsDefunct;
2348 int32_t childCount = mChildren.Length();
2349 for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
2350 mChildren.ElementAt(childIdx)->UnbindFromParent();
2352 mChildren.Clear();
2354 mEmbeddedObjCollector = nullptr;
2356 if (mParent) mParent->RemoveChild(this);
2358 mContent = nullptr;
2359 mDoc = nullptr;
2360 if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) {
2361 SelectionMgr()->ResetCaretOffset();
2365 // LocalAccessible protected
2366 void LocalAccessible::ARIAName(nsString& aName) const {
2367 // aria-labelledby now takes precedence over aria-label
2368 nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs(
2369 this, nsGkAtoms::aria_labelledby, aName);
2370 if (NS_SUCCEEDED(rv)) {
2371 aName.CompressWhitespace();
2374 if (aName.IsEmpty() && mContent->IsElement() &&
2375 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_label,
2376 aName)) {
2377 aName.CompressWhitespace();
2381 // LocalAccessible protected
2382 void LocalAccessible::ARIADescription(nsString& aDescription) const {
2383 // aria-describedby takes precedence over aria-description
2384 nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs(
2385 this, nsGkAtoms::aria_describedby, aDescription);
2386 if (NS_SUCCEEDED(rv)) {
2387 aDescription.CompressWhitespace();
2390 if (aDescription.IsEmpty() && mContent->IsElement() &&
2391 mContent->AsElement()->GetAttr(
2392 kNameSpaceID_None, nsGkAtoms::aria_description, aDescription)) {
2393 aDescription.CompressWhitespace();
2397 // LocalAccessible protected
2398 ENameValueFlag LocalAccessible::NativeName(nsString& aName) const {
2399 if (mContent->IsHTMLElement()) {
2400 LocalAccessible* label = nullptr;
2401 HTMLLabelIterator iter(Document(), this);
2402 while ((label = iter.Next())) {
2403 nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(),
2404 &aName);
2405 aName.CompressWhitespace();
2408 if (!aName.IsEmpty()) return eNameOK;
2410 NameFromAssociatedXULLabel(mDoc, mContent, aName);
2411 if (!aName.IsEmpty()) {
2412 return eNameOK;
2415 nsTextEquivUtils::GetNameFromSubtree(this, aName);
2416 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2419 if (mContent->IsXULElement()) {
2420 XULElmName(mDoc, mContent, aName);
2421 if (!aName.IsEmpty()) return eNameOK;
2423 nsTextEquivUtils::GetNameFromSubtree(this, aName);
2424 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2427 if (mContent->IsSVGElement()) {
2428 // If user agents need to choose among multiple 'desc' or 'title'
2429 // elements for processing, the user agent shall choose the first one.
2430 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2431 childElm = childElm->GetNextSibling()) {
2432 if (childElm->IsSVGElement(nsGkAtoms::title)) {
2433 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
2434 return eNameOK;
2439 return eNameOK;
2442 // LocalAccessible protected
2443 void LocalAccessible::NativeDescription(nsString& aDescription) const {
2444 bool isXUL = mContent->IsXULElement();
2445 if (isXUL) {
2446 // Try XUL <description control="[id]">description text</description>
2447 XULDescriptionIterator iter(Document(), mContent);
2448 LocalAccessible* descr = nullptr;
2449 while ((descr = iter.Next())) {
2450 nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
2451 &aDescription);
2456 // LocalAccessible protected
2457 void LocalAccessible::BindToParent(LocalAccessible* aParent,
2458 uint32_t aIndexInParent) {
2459 MOZ_ASSERT(aParent, "This method isn't used to set null parent");
2460 MOZ_ASSERT(!mParent, "The child was expected to be moved");
2462 #ifdef A11Y_LOG
2463 if (mParent) {
2464 logging::TreeInfo("BindToParent: stealing accessible", 0, "old parent",
2465 mParent, "new parent", aParent, "child", this, nullptr);
2467 #endif
2469 mParent = aParent;
2470 mIndexInParent = aIndexInParent;
2472 if (mParent->HasNameDependent() || mParent->IsXULListItem() ||
2473 RelationByType(RelationType::LABEL_FOR).Next() ||
2474 nsTextEquivUtils::HasNameRule(mParent, eNameFromSubtreeRule)) {
2475 mContextFlags |= eHasNameDependent;
2476 } else {
2477 mContextFlags &= ~eHasNameDependent;
2479 if (mParent->HasDescriptionDependent() ||
2480 RelationByType(RelationType::DESCRIPTION_FOR).Next()) {
2481 mContextFlags |= eHasDescriptionDependent;
2482 } else {
2483 mContextFlags &= ~eHasDescriptionDependent;
2486 // Add name/description dependent flags for dependent content once
2487 // a name/description provider is added to doc.
2488 Relation rel = RelationByType(RelationType::LABELLED_BY);
2489 LocalAccessible* relTarget = nullptr;
2490 while ((relTarget = rel.Next())) {
2491 if (!relTarget->HasNameDependent()) {
2492 relTarget->ModifySubtreeContextFlags(eHasNameDependent, true);
2496 rel = RelationByType(RelationType::DESCRIBED_BY);
2497 while ((relTarget = rel.Next())) {
2498 if (!relTarget->HasDescriptionDependent()) {
2499 relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
2503 mContextFlags |=
2504 static_cast<uint32_t>((mParent->IsAlert() || mParent->IsInsideAlert())) &
2505 eInsideAlert;
2507 // if a new column header is being added, invalidate the table's header cache.
2508 TableCellAccessible* cell = AsTableCell();
2509 if (cell && Role() == roles::COLUMNHEADER) {
2510 TableAccessible* table = cell->Table();
2511 if (table) {
2512 table->GetHeaderCache().Clear();
2517 // LocalAccessible protected
2518 void LocalAccessible::UnbindFromParent() {
2519 mParent = nullptr;
2520 mIndexInParent = -1;
2521 mIndexOfEmbeddedChild = -1;
2522 if (IsProxy()) MOZ_CRASH("this should never be called on proxy wrappers");
2524 delete mBits.groupInfo;
2525 mBits.groupInfo = nullptr;
2526 mContextFlags &= ~eHasNameDependent & ~eInsideAlert;
2529 ////////////////////////////////////////////////////////////////////////////////
2530 // LocalAccessible public methods
2532 RootAccessible* LocalAccessible::RootAccessible() const {
2533 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2534 NS_ASSERTION(docShell, "No docshell for mContent");
2535 if (!docShell) {
2536 return nullptr;
2539 nsCOMPtr<nsIDocShellTreeItem> root;
2540 docShell->GetInProcessRootTreeItem(getter_AddRefs(root));
2541 NS_ASSERTION(root, "No root content tree item");
2542 if (!root) {
2543 return nullptr;
2546 DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
2547 return docAcc ? docAcc->AsRoot() : nullptr;
2550 nsIFrame* LocalAccessible::GetFrame() const {
2551 return mContent ? mContent->GetPrimaryFrame() : nullptr;
2554 nsINode* LocalAccessible::GetNode() const { return mContent; }
2556 dom::Element* LocalAccessible::Elm() const {
2557 return dom::Element::FromNodeOrNull(mContent);
2560 void LocalAccessible::Language(nsAString& aLanguage) {
2561 aLanguage.Truncate();
2563 if (!mDoc) return;
2565 nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
2566 if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
2567 mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
2568 aLanguage);
2572 bool LocalAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) {
2573 if (!aChild) return false;
2575 if (aIndex == mChildren.Length()) {
2576 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2577 // pretended earlier.
2578 mChildren.AppendElement(aChild);
2579 } else {
2580 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2581 // pretended earlier.
2582 mChildren.InsertElementAt(aIndex, aChild);
2584 MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change");
2586 for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
2587 mChildren[idx]->mIndexInParent = idx;
2591 if (aChild->IsText()) {
2592 mStateFlags |= eHasTextKids;
2595 aChild->BindToParent(this, aIndex);
2596 return true;
2599 bool LocalAccessible::RemoveChild(LocalAccessible* aChild) {
2600 MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
2601 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent");
2602 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent");
2603 MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,
2604 "Unbound child was given");
2605 MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() ||
2606 aChild->IsDoc() || IsApplication(),
2607 "Illicit children change");
2609 int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
2610 if (mChildren.SafeElementAt(index) != aChild) {
2611 MOZ_ASSERT_UNREACHABLE("A wrong child index");
2612 index = mChildren.IndexOf(aChild);
2613 if (index == -1) {
2614 MOZ_ASSERT_UNREACHABLE("No child was found");
2615 return false;
2619 aChild->UnbindFromParent();
2620 mChildren.RemoveElementAt(index);
2622 for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
2623 mChildren[idx]->mIndexInParent = idx;
2626 return true;
2629 void LocalAccessible::RelocateChild(uint32_t aNewIndex,
2630 LocalAccessible* aChild) {
2631 MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
2632 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this,
2633 "A child from different subtree was given");
2634 MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,
2635 "Unbound child was given");
2636 MOZ_DIAGNOSTIC_ASSERT(
2637 aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild,
2638 "Wrong index in parent");
2639 MOZ_DIAGNOSTIC_ASSERT(
2640 static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,
2641 "No move, same index");
2642 MOZ_DIAGNOSTIC_ASSERT(aNewIndex <= mChildren.Length(),
2643 "Wrong new index was given");
2645 RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false);
2646 if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
2647 aChild->SetHideEventTarget(true);
2650 mEmbeddedObjCollector = nullptr;
2651 mChildren.RemoveElementAt(aChild->mIndexInParent);
2653 uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
2655 // If the child is moved after its current position.
2656 if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
2657 startIdx = aChild->mIndexInParent;
2658 if (aNewIndex == mChildren.Length() + 1) {
2659 // The child is moved to the end.
2660 mChildren.AppendElement(aChild);
2661 endIdx = mChildren.Length() - 1;
2662 } else {
2663 mChildren.InsertElementAt(aNewIndex - 1, aChild);
2664 endIdx = aNewIndex;
2666 } else {
2667 // The child is moved prior its current position.
2668 mChildren.InsertElementAt(aNewIndex, aChild);
2671 for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
2672 mChildren[idx]->mIndexInParent = idx;
2673 mChildren[idx]->mIndexOfEmbeddedChild = -1;
2676 for (uint32_t idx = 0; idx < mChildren.Length(); idx++) {
2677 mChildren[idx]->mStateFlags |= eGroupInfoDirty;
2680 RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild);
2681 DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
2682 MOZ_ASSERT(added);
2683 aChild->SetShowEventTarget(true);
2686 LocalAccessible* LocalAccessible::LocalChildAt(uint32_t aIndex) const {
2687 LocalAccessible* child = mChildren.SafeElementAt(aIndex, nullptr);
2688 if (!child) return nullptr;
2690 #ifdef DEBUG
2691 LocalAccessible* realParent = child->mParent;
2692 NS_ASSERTION(!realParent || realParent == this,
2693 "Two accessibles have the same first child accessible!");
2694 #endif
2696 return child;
2699 uint32_t LocalAccessible::ChildCount() const { return mChildren.Length(); }
2701 int32_t LocalAccessible::IndexInParent() const { return mIndexInParent; }
2703 uint32_t LocalAccessible::EmbeddedChildCount() {
2704 if (mStateFlags & eHasTextKids) {
2705 if (!mEmbeddedObjCollector) {
2706 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2708 return mEmbeddedObjCollector->Count();
2711 return ChildCount();
2714 LocalAccessible* LocalAccessible::EmbeddedChildAt(uint32_t aIndex) {
2715 if (mStateFlags & eHasTextKids) {
2716 if (!mEmbeddedObjCollector) {
2717 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2719 return mEmbeddedObjCollector.get()
2720 ? mEmbeddedObjCollector->GetAccessibleAt(aIndex)
2721 : nullptr;
2724 return LocalChildAt(aIndex);
2727 int32_t LocalAccessible::GetIndexOfEmbeddedChild(LocalAccessible* aChild) {
2728 if (mStateFlags & eHasTextKids) {
2729 if (!mEmbeddedObjCollector) {
2730 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2732 return mEmbeddedObjCollector.get()
2733 ? mEmbeddedObjCollector->GetIndexAt(aChild)
2734 : -1;
2737 return GetIndexOf(aChild);
2740 ////////////////////////////////////////////////////////////////////////////////
2741 // HyperLinkAccessible methods
2743 bool LocalAccessible::IsLink() const {
2744 // Every embedded accessible within hypertext accessible implements
2745 // hyperlink interface.
2746 return mParent && mParent->IsHyperText() && !IsText();
2749 uint32_t LocalAccessible::StartOffset() {
2750 MOZ_ASSERT(IsLink(), "StartOffset is called not on hyper link!");
2752 HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
2753 return hyperText ? hyperText->GetChildOffset(this) : 0;
2756 uint32_t LocalAccessible::EndOffset() {
2757 MOZ_ASSERT(IsLink(), "EndOffset is called on not hyper link!");
2759 HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
2760 return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0;
2763 uint32_t LocalAccessible::AnchorCount() {
2764 MOZ_ASSERT(IsLink(), "AnchorCount is called on not hyper link!");
2765 return 1;
2768 LocalAccessible* LocalAccessible::AnchorAt(uint32_t aAnchorIndex) {
2769 MOZ_ASSERT(IsLink(), "GetAnchor is called on not hyper link!");
2770 return aAnchorIndex == 0 ? this : nullptr;
2773 already_AddRefed<nsIURI> LocalAccessible::AnchorURIAt(
2774 uint32_t aAnchorIndex) const {
2775 MOZ_ASSERT(IsLink(), "AnchorURIAt is called on not hyper link!");
2776 return nullptr;
2779 void LocalAccessible::ToTextPoint(HyperTextAccessible** aContainer,
2780 int32_t* aOffset, bool aIsBefore) const {
2781 if (IsHyperText()) {
2782 *aContainer = const_cast<LocalAccessible*>(this)->AsHyperText();
2783 *aOffset = aIsBefore ? 0 : (*aContainer)->CharacterCount();
2784 return;
2787 const LocalAccessible* child = nullptr;
2788 const LocalAccessible* parent = this;
2789 do {
2790 child = parent;
2791 parent = parent->LocalParent();
2792 } while (parent && !parent->IsHyperText());
2794 if (parent) {
2795 *aContainer = const_cast<LocalAccessible*>(parent)->AsHyperText();
2796 *aOffset = (*aContainer)
2797 ->GetChildOffset(child->IndexInParent() +
2798 static_cast<int32_t>(!aIsBefore));
2802 ////////////////////////////////////////////////////////////////////////////////
2803 // SelectAccessible
2805 void LocalAccessible::SelectedItems(nsTArray<LocalAccessible*>* aItems) {
2806 AccIterator iter(this, filters::GetSelected);
2807 LocalAccessible* selected = nullptr;
2808 while ((selected = iter.Next())) aItems->AppendElement(selected);
2811 uint32_t LocalAccessible::SelectedItemCount() {
2812 uint32_t count = 0;
2813 AccIterator iter(this, filters::GetSelected);
2814 LocalAccessible* selected = nullptr;
2815 while ((selected = iter.Next())) ++count;
2817 return count;
2820 LocalAccessible* LocalAccessible::GetSelectedItem(uint32_t aIndex) {
2821 AccIterator iter(this, filters::GetSelected);
2822 LocalAccessible* selected = nullptr;
2824 uint32_t index = 0;
2825 while ((selected = iter.Next()) && index < aIndex) index++;
2827 return selected;
2830 bool LocalAccessible::IsItemSelected(uint32_t aIndex) {
2831 uint32_t index = 0;
2832 AccIterator iter(this, filters::GetSelectable);
2833 LocalAccessible* selected = nullptr;
2834 while ((selected = iter.Next()) && index < aIndex) index++;
2836 return selected && selected->State() & states::SELECTED;
2839 bool LocalAccessible::AddItemToSelection(uint32_t aIndex) {
2840 uint32_t index = 0;
2841 AccIterator iter(this, filters::GetSelectable);
2842 LocalAccessible* selected = nullptr;
2843 while ((selected = iter.Next()) && index < aIndex) index++;
2845 if (selected) selected->SetSelected(true);
2847 return static_cast<bool>(selected);
2850 bool LocalAccessible::RemoveItemFromSelection(uint32_t aIndex) {
2851 uint32_t index = 0;
2852 AccIterator iter(this, filters::GetSelectable);
2853 LocalAccessible* selected = nullptr;
2854 while ((selected = iter.Next()) && index < aIndex) index++;
2856 if (selected) selected->SetSelected(false);
2858 return static_cast<bool>(selected);
2861 bool LocalAccessible::SelectAll() {
2862 bool success = false;
2863 LocalAccessible* selectable = nullptr;
2865 AccIterator iter(this, filters::GetSelectable);
2866 while ((selectable = iter.Next())) {
2867 success = true;
2868 selectable->SetSelected(true);
2870 return success;
2873 bool LocalAccessible::UnselectAll() {
2874 bool success = false;
2875 LocalAccessible* selected = nullptr;
2877 AccIterator iter(this, filters::GetSelected);
2878 while ((selected = iter.Next())) {
2879 success = true;
2880 selected->SetSelected(false);
2882 return success;
2885 ////////////////////////////////////////////////////////////////////////////////
2886 // Widgets
2888 bool LocalAccessible::IsWidget() const { return false; }
2890 bool LocalAccessible::IsActiveWidget() const {
2891 if (FocusMgr()->HasDOMFocus(mContent)) return true;
2893 // If text entry of combobox widget has a focus then the combobox widget is
2894 // active.
2895 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
2896 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
2897 uint32_t childCount = ChildCount();
2898 for (uint32_t idx = 0; idx < childCount; idx++) {
2899 LocalAccessible* child = mChildren.ElementAt(idx);
2900 if (child->Role() == roles::ENTRY) {
2901 return FocusMgr()->HasDOMFocus(child->GetContent());
2906 return false;
2909 bool LocalAccessible::AreItemsOperable() const {
2910 return HasOwnContent() && mContent->IsElement() &&
2911 mContent->AsElement()->HasAttr(kNameSpaceID_None,
2912 nsGkAtoms::aria_activedescendant);
2915 LocalAccessible* LocalAccessible::CurrentItem() const {
2916 // Check for aria-activedescendant, which changes which element has focus.
2917 // For activedescendant, the ARIA spec does not require that the user agent
2918 // checks whether pointed node is actually a DOM descendant of the element
2919 // with the aria-activedescendant attribute.
2920 nsAutoString id;
2921 if (HasOwnContent() && mContent->IsElement() &&
2922 mContent->AsElement()->GetAttr(kNameSpaceID_None,
2923 nsGkAtoms::aria_activedescendant, id)) {
2924 dom::Element* activeDescendantElm = IDRefsIterator::GetElem(mContent, id);
2925 if (activeDescendantElm) {
2926 if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) {
2927 // Don't want a cyclical descendant relationship. That would be bad.
2928 return nullptr;
2931 DocAccessible* document = Document();
2932 if (document) return document->GetAccessible(activeDescendantElm);
2935 return nullptr;
2938 void LocalAccessible::SetCurrentItem(const LocalAccessible* aItem) {
2939 nsAtom* id = aItem->GetContent()->GetID();
2940 if (id) {
2941 nsAutoString idStr;
2942 id->ToString(idStr);
2943 mContent->AsElement()->SetAttr(
2944 kNameSpaceID_None, nsGkAtoms::aria_activedescendant, idStr, true);
2948 LocalAccessible* LocalAccessible::ContainerWidget() const {
2949 if (HasARIARole() && mContent->HasID()) {
2950 for (LocalAccessible* parent = LocalParent(); parent;
2951 parent = parent->LocalParent()) {
2952 nsIContent* parentContent = parent->GetContent();
2953 if (parentContent && parentContent->IsElement() &&
2954 parentContent->AsElement()->HasAttr(
2955 kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
2956 return parent;
2959 // Don't cross DOM document boundaries.
2960 if (parent->IsDoc()) break;
2963 return nullptr;
2966 void LocalAccessible::Announce(const nsAString& aAnnouncement,
2967 uint16_t aPriority) {
2968 RefPtr<AccAnnouncementEvent> event =
2969 new AccAnnouncementEvent(this, aAnnouncement, aPriority);
2970 nsEventShell::FireEvent(event);
2973 ////////////////////////////////////////////////////////////////////////////////
2974 // LocalAccessible protected methods
2976 void LocalAccessible::LastRelease() {
2977 // First cleanup if needed...
2978 if (mDoc) {
2979 Shutdown();
2980 NS_ASSERTION(!mDoc,
2981 "A Shutdown() impl forgot to call its parent's Shutdown?");
2983 // ... then die.
2984 delete this;
2987 LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset,
2988 nsresult* aError) const {
2989 if (!mParent || mIndexInParent == -1) {
2990 if (aError) *aError = NS_ERROR_UNEXPECTED;
2992 return nullptr;
2995 if (aError &&
2996 mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
2997 *aError = NS_OK; // fail peacefully
2998 return nullptr;
3001 LocalAccessible* child = mParent->LocalChildAt(mIndexInParent + aOffset);
3002 if (aError && !child) *aError = NS_ERROR_UNEXPECTED;
3004 return child;
3007 void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags,
3008 bool aAdd) {
3009 Pivot pivot(this);
3010 LocalAccInSameDocRule rule;
3011 for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) {
3012 MOZ_ASSERT(anchor->IsLocal());
3013 LocalAccessible* acc = anchor->AsLocal();
3014 if (aAdd) {
3015 acc->mContextFlags |= aContextFlags;
3016 } else {
3017 acc->mContextFlags &= ~aContextFlags;
3022 double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const {
3023 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3024 if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) {
3025 return UnspecifiedNaN<double>();
3028 nsAutoString attrValue;
3029 if (!mContent->IsElement() ||
3030 !mContent->AsElement()->GetAttr(kNameSpaceID_None, aAttr, attrValue)) {
3031 return UnspecifiedNaN<double>();
3034 nsresult error = NS_OK;
3035 double value = attrValue.ToDouble(&error);
3036 return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
3039 uint32_t LocalAccessible::GetActionRule() const {
3040 if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) {
3041 return eNoAction;
3044 // Return "click" action on elements that have an attached popup menu.
3045 if (mContent->IsXULElement()) {
3046 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) {
3047 return eClickAction;
3051 // Has registered 'click' event handler.
3052 bool isOnclick = nsCoreUtils::HasClickListener(mContent);
3054 if (isOnclick) return eClickAction;
3056 // Get an action based on ARIA role.
3057 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3058 if (roleMapEntry && roleMapEntry->actionRule != eNoAction) {
3059 return roleMapEntry->actionRule;
3062 // Get an action based on ARIA attribute.
3063 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) {
3064 return eExpandAction;
3067 return eNoAction;
3070 AccGroupInfo* LocalAccessible::GetGroupInfo() const {
3071 if (IsProxy()) MOZ_CRASH("This should never be called on proxy wrappers");
3073 if (mBits.groupInfo) {
3074 if (HasDirtyGroupInfo()) {
3075 mBits.groupInfo->Update();
3076 mStateFlags &= ~eGroupInfoDirty;
3079 return mBits.groupInfo;
3082 mBits.groupInfo = AccGroupInfo::CreateGroupInfo(this);
3083 mStateFlags &= ~eGroupInfoDirty;
3084 return mBits.groupInfo;
3087 void LocalAccessible::SendCache(uint64_t aCacheDomain,
3088 CacheUpdateType aUpdateType) {
3089 if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
3090 return;
3093 if (!IPCAccessibilityActive() || !Document()) {
3094 return;
3097 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
3098 if (!ipcDoc) {
3099 // This means DocAccessible::DoInitialUpdate hasn't been called yet, which
3100 // means the a11y tree hasn't been built yet. Therefore, this should only
3101 // be possible if this is a DocAccessible.
3102 MOZ_ASSERT(IsDoc(), "Called on a non-DocAccessible but IPCDoc is null");
3103 return;
3106 RefPtr<AccAttributes> fields =
3107 BundleFieldsForCache(aCacheDomain, aUpdateType);
3108 nsTArray<CacheData> data;
3109 data.AppendElement(
3110 CacheData(IsDoc() ? 0 : reinterpret_cast<uint64_t>(UniqueID()), fields));
3111 ipcDoc->SendCache(aUpdateType, data, true);
3114 already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
3115 uint64_t aCacheDomain, CacheUpdateType aUpdateType) {
3116 RefPtr<AccAttributes> fields = new AccAttributes();
3118 // Caching name for text leaf Accessibles is redundant, since their name is
3119 // always their text. Text gets handled below.
3120 if (aCacheDomain & CacheDomain::NameAndDescription && !IsText()) {
3121 nsString name;
3122 int32_t nameFlag = Name(name);
3123 if (nameFlag != eNameOK) {
3124 fields->SetAttribute(nsGkAtoms::explicit_name, nameFlag);
3125 } else if (aUpdateType == CacheUpdateType::Update) {
3126 fields->SetAttribute(nsGkAtoms::explicit_name, DeleteEntry());
3129 if (!name.IsEmpty()) {
3130 fields->SetAttribute(nsGkAtoms::name, std::move(name));
3131 } else if (aUpdateType == CacheUpdateType::Update) {
3132 fields->SetAttribute(nsGkAtoms::name, DeleteEntry());
3135 nsString description;
3136 Description(description);
3137 if (!description.IsEmpty()) {
3138 fields->SetAttribute(nsGkAtoms::description, std::move(description));
3139 } else if (aUpdateType == CacheUpdateType::Update) {
3140 fields->SetAttribute(nsGkAtoms::description, DeleteEntry());
3144 if ((aCacheDomain & CacheDomain::Value) && HasNumericValue()) {
3145 fields->SetAttribute(nsGkAtoms::value, CurValue());
3146 fields->SetAttribute(nsGkAtoms::max, MaxValue());
3147 fields->SetAttribute(nsGkAtoms::min, MinValue());
3148 fields->SetAttribute(nsGkAtoms::step, Step());
3151 if (aCacheDomain & CacheDomain::Bounds) {
3152 nsRect newBoundsRect = ParentRelativeBounds();
3154 if (mBounds.isNothing() || !newBoundsRect.IsEqualEdges(mBounds.value())) {
3155 mBounds = Some(newBoundsRect);
3157 nsTArray<int32_t> boundsArray(4);
3159 boundsArray.AppendElement(newBoundsRect.x);
3160 boundsArray.AppendElement(newBoundsRect.y);
3161 boundsArray.AppendElement(newBoundsRect.width);
3162 boundsArray.AppendElement(newBoundsRect.height);
3164 fields->SetAttribute(nsGkAtoms::relativeBounds, std::move(boundsArray));
3168 // We only cache text on leaf Accessibles.
3169 if ((aCacheDomain & CacheDomain::Text) && !HasChildren()) {
3170 // Only text Accessibles can have actual text.
3171 if (IsText()) {
3172 nsString text;
3173 AppendTextTo(text);
3174 fields->SetAttribute(nsGkAtoms::text, std::move(text));
3176 // We cache line start offsets for both text and non-text leaf Accessibles
3177 // because non-text leaf Accessibles can still start a line.
3178 nsTArray<int32_t> lineStarts;
3179 for (TextLeafPoint lineStart =
3180 TextLeafPoint(this, 0).FindNextLineStartSameLocalAcc(
3181 /* aIncludeOrigin */ true);
3182 lineStart;
3183 lineStart = lineStart.FindNextLineStartSameLocalAcc(false)) {
3184 lineStarts.AppendElement(lineStart.mOffset);
3186 if (!lineStarts.IsEmpty()) {
3187 fields->SetAttribute(nsGkAtoms::line, std::move(lineStarts));
3191 if (aCacheDomain & CacheDomain::DOMNodeID) {
3192 MOZ_ASSERT(mContent);
3193 nsAtom* id = mContent->GetID();
3194 if (id) {
3195 fields->SetAttribute(nsGkAtoms::id, id);
3196 } else if (aUpdateType == CacheUpdateType::Update) {
3197 fields->SetAttribute(nsGkAtoms::id, DeleteEntry());
3201 return fields.forget();
3204 void LocalAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet,
3205 int32_t* aSetSize) {
3206 AccGroupInfo* groupInfo = GetGroupInfo();
3207 if (groupInfo) {
3208 *aPosInSet = groupInfo->PosInSet();
3209 *aSetSize = groupInfo->SetSize();
3213 int32_t LocalAccessible::GetLevelInternal() {
3214 int32_t level = nsAccUtils::GetDefaultLevel(this);
3216 if (!IsBoundToParent()) return level;
3218 roles::Role role = Role();
3219 if (role == roles::OUTLINEITEM) {
3220 // Always expose 'level' attribute for 'outlineitem' accessible. The number
3221 // of nested 'grouping' accessibles containing 'outlineitem' accessible is
3222 // its level.
3223 level = 1;
3225 LocalAccessible* parent = this;
3226 while ((parent = parent->LocalParent())) {
3227 roles::Role parentRole = parent->Role();
3229 if (parentRole == roles::OUTLINE) break;
3230 if (parentRole == roles::GROUPING) ++level;
3233 } else if (role == roles::LISTITEM) {
3234 // Expose 'level' attribute on nested lists. We support two hierarchies:
3235 // a) list -> listitem -> list -> listitem (nested list is a last child
3236 // of listitem of the parent list);
3237 // b) list -> listitem -> group -> listitem (nested listitems are contained
3238 // by group that is a last child of the parent listitem).
3240 // Calculate 'level' attribute based on number of parent listitems.
3241 level = 0;
3242 LocalAccessible* parent = this;
3243 while ((parent = parent->LocalParent())) {
3244 roles::Role parentRole = parent->Role();
3246 if (parentRole == roles::LISTITEM) {
3247 ++level;
3248 } else if (parentRole != roles::LIST && parentRole != roles::GROUPING) {
3249 break;
3253 if (level == 0) {
3254 // If this listitem is on top of nested lists then expose 'level'
3255 // attribute.
3256 parent = LocalParent();
3257 uint32_t siblingCount = parent->ChildCount();
3258 for (uint32_t siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) {
3259 LocalAccessible* sibling = parent->LocalChildAt(siblingIdx);
3261 LocalAccessible* siblingChild = sibling->LocalLastChild();
3262 if (siblingChild) {
3263 roles::Role lastChildRole = siblingChild->Role();
3264 if (lastChildRole == roles::LIST ||
3265 lastChildRole == roles::GROUPING) {
3266 return 1;
3270 } else {
3271 ++level; // level is 1-index based
3273 } else if (role == roles::COMMENT) {
3274 // For comments, count the ancestor elements with the same role to get the
3275 // level.
3276 level = 1;
3278 LocalAccessible* parent = this;
3279 while ((parent = parent->LocalParent())) {
3280 roles::Role parentRole = parent->Role();
3281 if (parentRole == roles::COMMENT) {
3282 ++level;
3287 return level;
3290 void LocalAccessible::StaticAsserts() const {
3291 static_assert(
3292 eLastStateFlag <= (1 << kStateFlagsBits) - 1,
3293 "LocalAccessible::mStateFlags was oversized by eLastStateFlag!");
3294 static_assert(
3295 eLastContextFlag <= (1 << kContextFlagsBits) - 1,
3296 "LocalAccessible::mContextFlags was oversized by eLastContextFlag!");
3299 ////////////////////////////////////////////////////////////////////////////////
3300 // KeyBinding class
3302 // static
3303 uint32_t KeyBinding::AccelModifier() {
3304 switch (WidgetInputEvent::AccelModifier()) {
3305 case MODIFIER_ALT:
3306 return kAlt;
3307 case MODIFIER_CONTROL:
3308 return kControl;
3309 case MODIFIER_META:
3310 return kMeta;
3311 case MODIFIER_OS:
3312 return kOS;
3313 default:
3314 MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
3315 return 0;
3319 void KeyBinding::ToPlatformFormat(nsAString& aValue) const {
3320 nsCOMPtr<nsIStringBundle> keyStringBundle;
3321 nsCOMPtr<nsIStringBundleService> stringBundleService =
3322 mozilla::components::StringBundle::Service();
3323 if (stringBundleService) {
3324 stringBundleService->CreateBundle(
3325 "chrome://global-platform/locale/platformKeys.properties",
3326 getter_AddRefs(keyStringBundle));
3329 if (!keyStringBundle) return;
3331 nsAutoString separator;
3332 keyStringBundle->GetStringFromName("MODIFIER_SEPARATOR", separator);
3334 nsAutoString modifierName;
3335 if (mModifierMask & kControl) {
3336 keyStringBundle->GetStringFromName("VK_CONTROL", modifierName);
3338 aValue.Append(modifierName);
3339 aValue.Append(separator);
3342 if (mModifierMask & kAlt) {
3343 keyStringBundle->GetStringFromName("VK_ALT", modifierName);
3345 aValue.Append(modifierName);
3346 aValue.Append(separator);
3349 if (mModifierMask & kShift) {
3350 keyStringBundle->GetStringFromName("VK_SHIFT", modifierName);
3352 aValue.Append(modifierName);
3353 aValue.Append(separator);
3356 if (mModifierMask & kMeta) {
3357 keyStringBundle->GetStringFromName("VK_META", modifierName);
3359 aValue.Append(modifierName);
3360 aValue.Append(separator);
3363 aValue.Append(mKey);
3366 void KeyBinding::ToAtkFormat(nsAString& aValue) const {
3367 nsAutoString modifierName;
3368 if (mModifierMask & kControl) aValue.AppendLiteral("<Control>");
3370 if (mModifierMask & kAlt) aValue.AppendLiteral("<Alt>");
3372 if (mModifierMask & kShift) aValue.AppendLiteral("<Shift>");
3374 if (mModifierMask & kMeta) aValue.AppendLiteral("<Meta>");
3376 aValue.Append(mKey);