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