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