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