Bug 1874684 - Part 28: Return DateDuration from DifferenceISODateTime. r=mgaudet
[gecko.git] / accessible / base / nsAccessibilityService.cpp
blob615af596a7b0fcc6626e0e8ee8c27898037dfad6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsAccessibilityService.h"
8 // NOTE: alphabetically ordered
9 #include "ApplicationAccessibleWrap.h"
10 #include "ARIAGridAccessible.h"
11 #include "ARIAMap.h"
12 #include "DocAccessible-inl.h"
13 #include "DocAccessibleChild.h"
14 #include "FocusManager.h"
15 #include "HTMLCanvasAccessible.h"
16 #include "HTMLElementAccessibles.h"
17 #include "HTMLImageMapAccessible.h"
18 #include "HTMLLinkAccessible.h"
19 #include "HTMLListAccessible.h"
20 #include "HTMLSelectAccessible.h"
21 #include "HTMLTableAccessible.h"
22 #include "HyperTextAccessible.h"
23 #include "RootAccessible.h"
24 #include "nsAccUtils.h"
25 #include "nsArrayUtils.h"
26 #include "nsAttrName.h"
27 #include "nsDOMTokenList.h"
28 #include "nsCRT.h"
29 #include "nsEventShell.h"
30 #include "nsGkAtoms.h"
31 #include "nsIFrameInlines.h"
32 #include "nsServiceManagerUtils.h"
33 #include "nsTextFormatter.h"
34 #include "OuterDocAccessible.h"
35 #include "mozilla/a11y/Role.h"
36 #ifdef MOZ_ACCESSIBILITY_ATK
37 # include "RootAccessibleWrap.h"
38 #endif
39 #include "States.h"
40 #include "Statistics.h"
41 #include "TextLeafAccessible.h"
42 #include "xpcAccessibleApplication.h"
44 #ifdef XP_WIN
45 # include "mozilla/a11y/Compatibility.h"
46 # include "mozilla/StaticPtr.h"
47 #endif
49 #ifdef A11Y_LOG
50 # include "Logging.h"
51 #endif
53 #include "nsExceptionHandler.h"
54 #include "nsImageFrame.h"
55 #include "nsIObserverService.h"
56 #include "nsMenuPopupFrame.h"
57 #include "nsLayoutUtils.h"
58 #include "nsTreeBodyFrame.h"
59 #include "nsTreeUtils.h"
60 #include "mozilla/a11y/AccTypes.h"
61 #include "mozilla/ArrayUtils.h"
62 #include "mozilla/dom/DOMStringList.h"
63 #include "mozilla/dom/EventTarget.h"
64 #include "mozilla/dom/HTMLTableElement.h"
65 #include "mozilla/Preferences.h"
66 #include "mozilla/PresShell.h"
67 #include "mozilla/ProfilerMarkers.h"
68 #include "mozilla/RefPtr.h"
69 #include "mozilla/Services.h"
71 #include "XULAlertAccessible.h"
72 #include "XULComboboxAccessible.h"
73 #include "XULElementAccessibles.h"
74 #include "XULFormControlAccessible.h"
75 #include "XULListboxAccessible.h"
76 #include "XULMenuAccessible.h"
77 #include "XULTabAccessible.h"
78 #include "XULTreeGridAccessible.h"
80 using namespace mozilla;
81 using namespace mozilla::a11y;
82 using namespace mozilla::dom;
84 /**
85 * Accessibility service force enable/disable preference.
86 * Supported values:
87 * Accessibility is force enabled (accessibility should always be enabled): -1
88 * Accessibility is enabled (will be started upon a request, default value): 0
89 * Accessibility is force disabled (never enable accessibility): 1
91 #define PREF_ACCESSIBILITY_FORCE_DISABLED "accessibility.force_disabled"
93 ////////////////////////////////////////////////////////////////////////////////
94 // Statics
95 ////////////////////////////////////////////////////////////////////////////////
97 /**
98 * If the element has an ARIA attribute that requires a specific Accessible
99 * class, create and return it. Otherwise, return null.
101 static LocalAccessible* MaybeCreateSpecificARIAAccessible(
102 const nsRoleMapEntry* aRoleMapEntry, const LocalAccessible* aContext,
103 nsIContent* aContent, DocAccessible* aDocument) {
104 if (aRoleMapEntry && aRoleMapEntry->accTypes & eTableCell) {
105 if (aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th) &&
106 aContext->IsHTMLTableRow()) {
107 // Don't use ARIAGridCellAccessible for a valid td/th because
108 // HTMLTableCellAccessible can provide additional info; e.g. row/col span
109 // from the layout engine.
110 return nullptr;
112 // A cell must be in a row.
113 const Accessible* parent = aContext;
114 if (parent->IsGeneric()) {
115 parent = parent->GetNonGenericParent();
117 if (!parent || parent->Role() != roles::ROW) {
118 return nullptr;
120 // That row must be in a table, though there may be an intervening rowgroup.
121 parent = parent->GetNonGenericParent();
122 if (!parent) {
123 return nullptr;
125 if (!parent->IsTable() && parent->Role() == roles::ROWGROUP) {
126 parent = parent->GetNonGenericParent();
127 if (!parent) {
128 return nullptr;
131 if (parent->IsTable()) {
132 return new ARIAGridCellAccessible(aContent, aDocument);
135 return nullptr;
139 * Return true if the element has an attribute (ARIA, title, or relation) that
140 * requires the creation of an Accessible for the element.
142 static bool AttributesMustBeAccessible(nsIContent* aContent,
143 DocAccessible* aDocument) {
144 if (aContent->IsElement()) {
145 uint32_t attrCount = aContent->AsElement()->GetAttrCount();
146 for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
147 const nsAttrName* attr = aContent->AsElement()->GetAttrNameAt(attrIdx);
148 if (attr->NamespaceEquals(kNameSpaceID_None)) {
149 nsAtom* attrAtom = attr->Atom();
150 if (attrAtom == nsGkAtoms::title && aContent->IsHTMLElement()) {
151 // If the author provided a title on an element that would not
152 // be accessible normally, assume an intent and make it accessible.
153 return true;
156 nsDependentAtomString attrStr(attrAtom);
157 if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // not ARIA
159 // A global state or a property and in case of token defined.
160 uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
161 if ((attrFlags & ATTR_GLOBAL) &&
162 (!(attrFlags & ATTR_VALTOKEN) ||
163 nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) {
164 return true;
169 // If the given ID is referred by relation attribute then create an
170 // Accessible for it.
171 nsAutoString id;
172 if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) {
173 return aDocument->IsDependentID(aContent->AsElement(), id);
177 return false;
181 * Return true if the element must be a generic Accessible, even if it has been
182 * marked presentational with role="presentation", etc. MustBeAccessible causes
183 * an Accessible to be created as if it weren't marked presentational at all;
184 * e.g. <table role="presentation" tabindex="0"> will expose roles::TABLE and
185 * support TableAccessible. In contrast, this function causes a generic
186 * Accessible to be created; e.g. <table role="presentation" style="position:
187 * fixed;"> will expose roles::TEXT_CONTAINER and will not support
188 * TableAccessible. This is necessary in certain cases for the
189 * RemoteAccessible cache.
191 static bool MustBeGenericAccessible(nsIContent* aContent,
192 DocAccessible* aDocument) {
193 if (aContent->IsInNativeAnonymousSubtree() || aContent->IsSVGElement()) {
194 // We should not force create accs for anonymous content.
195 // This is an issue for inputs, which have an intermediate
196 // container with relevant overflow styling between the input
197 // and its internal input content.
198 // We should also avoid this for SVG elements (ie. `<foreignobject>`s
199 // which have default overflow:hidden styling).
200 return false;
202 nsIFrame* frame = aContent->GetPrimaryFrame();
203 MOZ_ASSERT(frame);
204 nsAutoCString overflow;
205 frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow);
206 // If the frame has been transformed, and the content has any children, we
207 // should create an Accessible so that we can account for the transform when
208 // calculating the Accessible's bounds using the parent process cache.
209 // Ditto for content which is position: fixed or sticky or has overflow
210 // styling (auto, scroll, hidden).
211 // However, don't do this for XUL widgets, as this breaks XUL a11y code
212 // expectations in some cases. XUL widgets are only used in the parent
213 // process and can't be cached anyway.
214 return !aContent->IsXULElement() &&
215 ((aContent->HasChildren() && frame->IsTransformed()) ||
216 frame->IsStickyPositioned() ||
217 (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
218 nsLayoutUtils::IsReallyFixedPos(frame)) ||
219 overflow.Equals("auto"_ns) || overflow.Equals("scroll"_ns) ||
220 overflow.Equals("hidden"_ns));
224 * Return true if the element must be accessible.
226 static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
227 nsIFrame* frame = aContent->GetPrimaryFrame();
228 MOZ_ASSERT(frame);
229 // This document might be invisible when it first loads. Therefore, we must
230 // check focusability irrespective of visibility here. Otherwise, we might not
231 // create Accessibles for some focusable elements; e.g. a span with only a
232 // tabindex. Elements that are invisible within this document are excluded
233 // earlier in CreateAccessible.
234 if (frame->IsFocusable(/* aWithMouse */ false,
235 /* aCheckVisibility */ false)) {
236 return true;
239 return AttributesMustBeAccessible(aContent, aDocument);
242 bool nsAccessibilityService::ShouldCreateImgAccessible(
243 mozilla::dom::Element* aElement, DocAccessible* aDocument) {
244 // The element must have a layout frame for us to proceed. If there is no
245 // frame, the image is likely hidden.
246 nsIFrame* frame = aElement->GetPrimaryFrame();
247 if (!frame) {
248 return false;
251 // If the element is not an img, and also not an embedded image via embed or
252 // object, then we should not create an accessible.
253 if (!aElement->IsHTMLElement(nsGkAtoms::img) &&
254 ((!aElement->IsHTMLElement(nsGkAtoms::embed) &&
255 !aElement->IsHTMLElement(nsGkAtoms::object)) ||
256 frame->AccessibleType() != AccType::eImageType)) {
257 return false;
260 nsAutoString newAltText;
261 const bool hasAlt = aElement->GetAttr(nsGkAtoms::alt, newAltText);
262 if (!hasAlt || !newAltText.IsEmpty()) {
263 // If there is no alt attribute, we should create an accessible. The
264 // author may have missed the attribute, and the AT may want to provide a
265 // name. If there is alt text, we should create an accessible.
266 return true;
269 if (newAltText.IsEmpty() && (nsCoreUtils::HasClickListener(aElement) ||
270 MustBeAccessible(aElement, aDocument))) {
271 // If there is empty alt text, but there is a click listener for this img,
272 // or if it otherwise must be an accessible (e.g., if it has an aria-label
273 // attribute), we should create an accessible.
274 return true;
277 // Otherwise, no alt text means we should not create an accessible.
278 return false;
282 * Return true if the SVG element should be accessible
284 static bool MustSVGElementBeAccessible(nsIContent* aContent,
285 DocAccessible* aDocument) {
286 // https://w3c.github.io/svg-aam/#include_elements
287 for (nsIContent* childElm = aContent->GetFirstChild(); childElm;
288 childElm = childElm->GetNextSibling()) {
289 if (childElm->IsAnyOfSVGElements(nsGkAtoms::title, nsGkAtoms::desc)) {
290 return true;
293 return MustBeAccessible(aContent, aDocument);
297 * Used by XULMap.h to map both menupopup and popup elements
299 LocalAccessible* CreateMenupopupAccessible(Element* aElement,
300 LocalAccessible* aContext) {
301 #ifdef MOZ_ACCESSIBILITY_ATK
302 // ATK considers this node to be redundant when within menubars, and it makes
303 // menu navigation with assistive technologies more difficult
304 // XXX In the future we will should this for consistency across the
305 // nsIAccessible implementations on each platform for a consistent scripting
306 // environment, but then strip out redundant accessibles in the AccessibleWrap
307 // class for each platform.
308 nsIContent* parent = aElement->GetParent();
309 if (parent && parent->IsXULElement(nsGkAtoms::menu)) return nullptr;
310 #endif
312 return new XULMenupopupAccessible(aElement, aContext->Document());
315 ////////////////////////////////////////////////////////////////////////////////
316 // LocalAccessible constructors
318 static LocalAccessible* New_HyperText(Element* aElement,
319 LocalAccessible* aContext) {
320 return new HyperTextAccessible(aElement, aContext->Document());
323 template <typename AccClass>
324 static LocalAccessible* New_HTMLDtOrDd(Element* aElement,
325 LocalAccessible* aContext) {
326 nsIContent* parent = aContext->GetContent();
327 if (parent->IsHTMLElement(nsGkAtoms::div)) {
328 // It is conforming in HTML to use a div to group dt/dd elements.
329 parent = parent->GetParent();
332 if (parent && parent->IsHTMLElement(nsGkAtoms::dl)) {
333 return new AccClass(aElement, aContext->Document());
336 return nullptr;
340 * Cached value of the PREF_ACCESSIBILITY_FORCE_DISABLED preference.
342 static int32_t sPlatformDisabledState = 0;
344 ////////////////////////////////////////////////////////////////////////////////
345 // Markup maps array.
347 #define Attr(name, value) \
348 { nsGkAtoms::name, nsGkAtoms::value }
350 #define AttrFromDOM(name, DOMAttrName) \
351 { nsGkAtoms::name, nullptr, nsGkAtoms::DOMAttrName }
353 #define AttrFromDOMIf(name, DOMAttrName, DOMAttrValue) \
354 { nsGkAtoms::name, nullptr, nsGkAtoms::DOMAttrName, nsGkAtoms::DOMAttrValue }
356 #define MARKUPMAP(atom, new_func, r, ...) \
357 {nsGkAtoms::atom, new_func, static_cast<a11y::role>(r), {__VA_ARGS__}},
359 static const MarkupMapInfo sHTMLMarkupMapList[] = {
360 #include "HTMLMarkupMap.h"
363 static const MarkupMapInfo sMathMLMarkupMapList[] = {
364 #include "MathMLMarkupMap.h"
367 #undef MARKUPMAP
369 #define XULMAP(atom, ...) {nsGkAtoms::atom, __VA_ARGS__},
371 #define XULMAP_TYPE(atom, new_type) \
372 XULMAP( \
373 atom, \
374 [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { \
375 return new new_type(aElement, aContext->Document()); \
378 static const XULMarkupMapInfo sXULMarkupMapList[] = {
379 #include "XULMap.h"
382 #undef XULMAP_TYPE
383 #undef XULMAP
385 #undef Attr
386 #undef AttrFromDOM
387 #undef AttrFromDOMIf
389 ////////////////////////////////////////////////////////////////////////////////
390 // nsAccessibilityService
391 ////////////////////////////////////////////////////////////////////////////////
393 nsAccessibilityService* nsAccessibilityService::gAccessibilityService = nullptr;
394 ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr;
395 xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible =
396 nullptr;
397 uint32_t nsAccessibilityService::gConsumers = 0;
399 nsAccessibilityService::nsAccessibilityService()
400 : mHTMLMarkupMap(ArrayLength(sHTMLMarkupMapList)),
401 mMathMLMarkupMap(ArrayLength(sMathMLMarkupMapList)),
402 mXULMarkupMap(ArrayLength(sXULMarkupMapList)) {}
404 nsAccessibilityService::~nsAccessibilityService() {
405 NS_ASSERTION(IsShutdown(), "Accessibility wasn't shutdown!");
406 gAccessibilityService = nullptr;
409 ////////////////////////////////////////////////////////////////////////////////
410 // nsIListenerChangeListener
412 NS_IMETHODIMP
413 nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges) {
414 uint32_t targetCount;
415 nsresult rv = aEventChanges->GetLength(&targetCount);
416 NS_ENSURE_SUCCESS(rv, rv);
418 for (uint32_t i = 0; i < targetCount; i++) {
419 nsCOMPtr<nsIEventListenerChange> change =
420 do_QueryElementAt(aEventChanges, i);
422 RefPtr<EventTarget> target;
423 change->GetTarget(getter_AddRefs(target));
424 nsIContent* content(nsIContent::FromEventTargetOrNull(target));
425 if (!content || !content->IsHTMLElement()) {
426 continue;
429 uint32_t changeCount;
430 change->GetCountOfEventListenerChangesAffectingAccessibility(&changeCount);
431 NS_ENSURE_SUCCESS(rv, rv);
433 if (changeCount) {
434 Document* ownerDoc = content->OwnerDoc();
435 DocAccessible* document = GetExistingDocAccessible(ownerDoc);
437 if (document) {
438 LocalAccessible* acc = document->GetAccessible(content);
439 if (!acc && (content == document->GetContent() ||
440 content == document->DocumentNode()->GetRootElement())) {
441 acc = document;
443 if (!acc && content->IsElement() &&
444 content->AsElement()->IsHTMLElement(nsGkAtoms::area)) {
445 // For area accessibles, we have to recreate the entire image map,
446 // since the image map accessible manages the tree itself. The click
447 // listener change may require us to update the role for the
448 // accessible associated with the area element.
449 LocalAccessible* areaAcc =
450 document->GetAccessibleEvenIfNotInMap(content);
451 if (areaAcc && areaAcc->LocalParent()) {
452 document->RecreateAccessible(areaAcc->LocalParent()->GetContent());
455 if (!acc && nsCoreUtils::HasClickListener(content)) {
456 // Create an accessible for a inaccessible element having click event
457 // handler.
458 document->ContentInserted(content, content->GetNextSibling());
459 } else if (acc) {
460 if ((acc->IsHTMLLink() && !acc->AsHTMLLink()->IsLinked()) ||
461 (content->IsElement() &&
462 content->AsElement()->IsHTMLElement(nsGkAtoms::a) &&
463 !acc->IsHTMLLink())) {
464 // An HTML link without an href attribute should have a generic
465 // role, unless it has a click listener. Since we might have gained
466 // or lost a click listener here, recreate the accessible so that we
467 // can create the correct type of accessible. If it was a link, it
468 // may no longer be one. If it wasn't, it may become one.
469 document->RecreateAccessible(content);
472 // A click listener change might mean losing or gaining an action.
473 document->QueueCacheUpdate(acc, CacheDomain::Actions);
478 return NS_OK;
481 ////////////////////////////////////////////////////////////////////////////////
482 // nsISupports
484 NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService, DocManager, nsIObserver,
485 nsIListenerChangeListener,
486 nsISelectionListener) // from SelectionManager
488 ////////////////////////////////////////////////////////////////////////////////
489 // nsIObserver
491 NS_IMETHODIMP
492 nsAccessibilityService::Observe(nsISupports* aSubject, const char* aTopic,
493 const char16_t* aData) {
494 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
495 Shutdown();
498 return NS_OK;
501 void nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent* aTargetNode) {
502 Document* documentNode = aTargetNode->GetUncomposedDoc();
503 if (!documentNode) {
504 return;
506 DocAccessible* document = GetDocAccessible(documentNode);
507 if (!document) {
508 return;
510 // If the document has focus when we get this notification, ensure that
511 // we fire a start scrolling event.
512 const Accessible* focusedAcc = FocusedAccessible();
513 if (focusedAcc &&
514 (focusedAcc == document || focusedAcc->IsNonInteractive())) {
515 LocalAccessible* targetAcc = document->GetAccessible(aTargetNode);
516 if (targetAcc) {
517 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
518 targetAcc);
519 document->SetAnchorJump(nullptr);
520 } else {
521 // We can't find the target accessible in the document yet. Set the
522 // anchor jump so that we can fire the scrolling start event later.
523 document->SetAnchorJump(aTargetNode);
525 } else {
526 document->SetAnchorJump(aTargetNode);
530 void nsAccessibilityService::FireAccessibleEvent(uint32_t aEvent,
531 LocalAccessible* aTarget) {
532 nsEventShell::FireEvent(aEvent, aTarget);
535 void nsAccessibilityService::NotifyOfPossibleBoundsChange(
536 mozilla::PresShell* aPresShell, nsIContent* aContent) {
537 if (IPCAccessibilityActive()) {
538 DocAccessible* document = aPresShell->GetDocAccessible();
539 if (document) {
540 // DocAccessible::GetAccessible() won't return the document if a root
541 // element like body is passed.
542 LocalAccessible* accessible = aContent == document->GetContent()
543 ? document
544 : document->GetAccessible(aContent);
545 if (accessible) {
546 document->QueueCacheUpdate(accessible, CacheDomain::Bounds);
552 void nsAccessibilityService::NotifyOfComputedStyleChange(
553 mozilla::PresShell* aPresShell, nsIContent* aContent) {
554 DocAccessible* document = aPresShell->GetDocAccessible();
555 if (!document) {
556 return;
559 // DocAccessible::GetAccessible() won't return the document if a root
560 // element like body is passed.
561 LocalAccessible* accessible = aContent == document->GetContent()
562 ? document
563 : document->GetAccessible(aContent);
564 if (!accessible && aContent && aContent->HasChildren() &&
565 !aContent->IsInNativeAnonymousSubtree()) {
566 // If the content has children and its frame has a transform, create an
567 // Accessible so that we can account for the transform when calculating
568 // the Accessible's bounds using the parent process cache. Ditto for
569 // position: fixed/sticky and content with overflow styling (hidden, auto,
570 // scroll)
571 if (const nsIFrame* frame = aContent->GetPrimaryFrame()) {
572 const auto& disp = *frame->StyleDisplay();
573 if (disp.HasTransform(frame) ||
574 disp.mPosition == StylePositionProperty::Fixed ||
575 disp.mPosition == StylePositionProperty::Sticky ||
576 disp.IsScrollableOverflow()) {
577 document->ContentInserted(aContent, aContent->GetNextSibling());
580 } else if (accessible && IPCAccessibilityActive()) {
581 accessible->MaybeQueueCacheUpdateForStyleChanges();
585 void nsAccessibilityService::NotifyOfResolutionChange(
586 mozilla::PresShell* aPresShell, float aResolution) {
587 DocAccessible* document = aPresShell->GetDocAccessible();
588 if (document && document->IPCDoc()) {
589 AutoTArray<mozilla::a11y::CacheData, 1> data;
590 RefPtr<AccAttributes> fields = new AccAttributes();
591 fields->SetAttribute(CacheKey::Resolution, aResolution);
592 data.AppendElement(mozilla::a11y::CacheData(0, fields));
593 document->IPCDoc()->SendCache(CacheUpdateType::Update, data);
597 void nsAccessibilityService::NotifyOfDevPixelRatioChange(
598 mozilla::PresShell* aPresShell, int32_t aAppUnitsPerDevPixel) {
599 DocAccessible* document = aPresShell->GetDocAccessible();
600 if (document && document->IPCDoc()) {
601 AutoTArray<mozilla::a11y::CacheData, 1> data;
602 RefPtr<AccAttributes> fields = new AccAttributes();
603 fields->SetAttribute(CacheKey::AppUnitsPerDevPixel, aAppUnitsPerDevPixel);
604 data.AppendElement(mozilla::a11y::CacheData(0, fields));
605 document->IPCDoc()->SendCache(CacheUpdateType::Update, data);
609 void nsAccessibilityService::NotifyAttrElementWillChange(
610 mozilla::dom::Element* aElement, nsAtom* aAttr) {
611 mozilla::dom::Document* doc = aElement->OwnerDoc();
612 MOZ_ASSERT(doc);
613 if (DocAccessible* docAcc = GetDocAccessible(doc)) {
614 docAcc->AttrElementWillChange(aElement, aAttr);
618 void nsAccessibilityService::NotifyAttrElementChanged(
619 mozilla::dom::Element* aElement, nsAtom* aAttr) {
620 mozilla::dom::Document* doc = aElement->OwnerDoc();
621 MOZ_ASSERT(doc);
622 if (DocAccessible* docAcc = GetDocAccessible(doc)) {
623 docAcc->AttrElementChanged(aElement, aAttr);
627 LocalAccessible* nsAccessibilityService::GetRootDocumentAccessible(
628 PresShell* aPresShell, bool aCanCreate) {
629 PresShell* presShell = aPresShell;
630 Document* documentNode = aPresShell->GetDocument();
631 if (documentNode) {
632 nsCOMPtr<nsIDocShellTreeItem> treeItem(documentNode->GetDocShell());
633 if (treeItem) {
634 nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
635 treeItem->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem));
636 if (treeItem != rootTreeItem) {
637 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootTreeItem));
638 presShell = docShell->GetPresShell();
641 return aCanCreate ? GetDocAccessible(presShell)
642 : presShell->GetDocAccessible();
645 return nullptr;
648 void nsAccessibilityService::NotifyOfTabPanelVisibilityChange(
649 PresShell* aPresShell, Element* aPanel, bool aNowVisible) {
650 MOZ_ASSERT(aPanel->GetParent()->IsXULElement(nsGkAtoms::tabpanels));
652 DocAccessible* document = GetDocAccessible(aPresShell);
653 if (!document) {
654 return;
657 if (LocalAccessible* acc = document->GetAccessible(aPanel)) {
658 RefPtr<AccEvent> event =
659 new AccStateChangeEvent(acc, states::OFFSCREEN, aNowVisible);
660 document->FireDelayedEvent(event);
664 void nsAccessibilityService::ContentRangeInserted(PresShell* aPresShell,
665 nsIContent* aStartChild,
666 nsIContent* aEndChild) {
667 DocAccessible* document = GetDocAccessible(aPresShell);
668 #ifdef A11Y_LOG
669 if (logging::IsEnabled(logging::eTree)) {
670 logging::MsgBegin("TREE", "content inserted; doc: %p", document);
671 logging::Node("container", aStartChild->GetParentNode());
672 for (nsIContent* child = aStartChild; child != aEndChild;
673 child = child->GetNextSibling()) {
674 logging::Node("content", child);
676 logging::MsgEnd();
677 logging::Stack();
679 #endif
681 if (document) {
682 document->ContentInserted(aStartChild, aEndChild);
686 void nsAccessibilityService::ScheduleAccessibilitySubtreeUpdate(
687 PresShell* aPresShell, nsIContent* aContent) {
688 DocAccessible* document = GetDocAccessible(aPresShell);
689 #ifdef A11Y_LOG
690 if (logging::IsEnabled(logging::eTree)) {
691 logging::MsgBegin("TREE", "schedule update; doc: %p", document);
692 logging::Node("content node", aContent);
693 logging::MsgEnd();
695 #endif
697 if (document) {
698 document->ScheduleTreeUpdate(aContent);
702 void nsAccessibilityService::ContentRemoved(PresShell* aPresShell,
703 nsIContent* aChildNode) {
704 DocAccessible* document = GetDocAccessible(aPresShell);
705 #ifdef A11Y_LOG
706 if (logging::IsEnabled(logging::eTree)) {
707 logging::MsgBegin("TREE", "content removed; doc: %p", document);
708 logging::Node("container node", aChildNode->GetFlattenedTreeParent());
709 logging::Node("content node", aChildNode);
710 logging::MsgEnd();
712 #endif
714 if (document) {
715 document->ContentRemoved(aChildNode);
718 #ifdef A11Y_LOG
719 if (logging::IsEnabled(logging::eTree)) {
720 logging::MsgEnd();
721 logging::Stack();
723 #endif
726 void nsAccessibilityService::TableLayoutGuessMaybeChanged(
727 PresShell* aPresShell, nsIContent* aContent) {
728 if (DocAccessible* document = GetDocAccessible(aPresShell)) {
729 if (LocalAccessible* acc = document->GetAccessible(aContent)) {
730 if (LocalAccessible* table = nsAccUtils::TableFor(acc)) {
731 document->QueueCacheUpdate(table, CacheDomain::Table);
737 void nsAccessibilityService::ComboboxOptionMaybeChanged(
738 PresShell* aPresShell, nsIContent* aMutatingNode) {
739 DocAccessible* document = GetDocAccessible(aPresShell);
740 if (!document) {
741 return;
744 for (nsIContent* cur = aMutatingNode; cur; cur = cur->GetParent()) {
745 if (cur->IsHTMLElement(nsGkAtoms::option)) {
746 if (LocalAccessible* accessible = document->GetAccessible(cur)) {
747 document->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
748 accessible);
749 break;
751 if (cur->IsHTMLElement(nsGkAtoms::select)) {
752 break;
758 void nsAccessibilityService::UpdateText(PresShell* aPresShell,
759 nsIContent* aContent) {
760 DocAccessible* document = GetDocAccessible(aPresShell);
761 if (document) document->UpdateText(aContent);
764 void nsAccessibilityService::TreeViewChanged(PresShell* aPresShell,
765 nsIContent* aContent,
766 nsITreeView* aView) {
767 DocAccessible* document = GetDocAccessible(aPresShell);
768 if (document) {
769 LocalAccessible* accessible = document->GetAccessible(aContent);
770 if (accessible) {
771 XULTreeAccessible* treeAcc = accessible->AsXULTree();
772 if (treeAcc) treeAcc->TreeViewChanged(aView);
777 void nsAccessibilityService::RangeValueChanged(PresShell* aPresShell,
778 nsIContent* aContent) {
779 DocAccessible* document = GetDocAccessible(aPresShell);
780 if (document) {
781 LocalAccessible* accessible = document->GetAccessible(aContent);
782 if (accessible) {
783 document->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
784 accessible);
789 void nsAccessibilityService::UpdateImageMap(nsImageFrame* aImageFrame) {
790 PresShell* presShell = aImageFrame->PresShell();
791 DocAccessible* document = GetDocAccessible(presShell);
792 if (document) {
793 LocalAccessible* accessible =
794 document->GetAccessible(aImageFrame->GetContent());
795 if (accessible) {
796 HTMLImageMapAccessible* imageMap = accessible->AsImageMap();
797 if (imageMap) {
798 imageMap->UpdateChildAreas();
799 return;
802 // If image map was initialized after we created an accessible (that'll
803 // be an image accessible) then recreate it.
804 RecreateAccessible(presShell, aImageFrame->GetContent());
809 void nsAccessibilityService::UpdateLabelValue(PresShell* aPresShell,
810 nsIContent* aLabelElm,
811 const nsString& aNewValue) {
812 DocAccessible* document = GetDocAccessible(aPresShell);
813 if (document) {
814 LocalAccessible* accessible = document->GetAccessible(aLabelElm);
815 if (accessible) {
816 XULLabelAccessible* xulLabel = accessible->AsXULLabel();
817 NS_ASSERTION(xulLabel,
818 "UpdateLabelValue was called for wrong accessible!");
819 if (xulLabel) xulLabel->UpdateLabelValue(aNewValue);
824 void nsAccessibilityService::PresShellActivated(PresShell* aPresShell) {
825 DocAccessible* document = aPresShell->GetDocAccessible();
826 if (document) {
827 RootAccessible* rootDocument = document->RootAccessible();
828 NS_ASSERTION(rootDocument, "Entirely broken tree: no root document!");
829 if (rootDocument) rootDocument->DocumentActivated(document);
833 void nsAccessibilityService::RecreateAccessible(PresShell* aPresShell,
834 nsIContent* aContent) {
835 DocAccessible* document = GetDocAccessible(aPresShell);
836 if (document) document->RecreateAccessible(aContent);
839 void nsAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString) {
840 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
841 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
842 nameRule) \
843 case roles::geckoRole: \
844 aString.AssignLiteral(stringRole); \
845 return;
847 switch (aRole) {
848 #include "RoleMap.h"
849 default:
850 aString.AssignLiteral("unknown");
851 return;
854 #undef ROLE
857 void nsAccessibilityService::GetStringStates(uint32_t aState,
858 uint32_t aExtraState,
859 nsISupports** aStringStates) {
860 RefPtr<DOMStringList> stringStates =
861 GetStringStates(nsAccUtils::To64State(aState, aExtraState));
863 // unknown state
864 if (!stringStates->Length()) {
865 stringStates->Add(u"unknown"_ns);
868 stringStates.forget(aStringStates);
871 already_AddRefed<DOMStringList> nsAccessibilityService::GetStringStates(
872 uint64_t aStates) const {
873 RefPtr<DOMStringList> stringStates = new DOMStringList();
875 if (aStates & states::UNAVAILABLE) {
876 stringStates->Add(u"unavailable"_ns);
878 if (aStates & states::SELECTED) {
879 stringStates->Add(u"selected"_ns);
881 if (aStates & states::FOCUSED) {
882 stringStates->Add(u"focused"_ns);
884 if (aStates & states::PRESSED) {
885 stringStates->Add(u"pressed"_ns);
887 if (aStates & states::CHECKED) {
888 stringStates->Add(u"checked"_ns);
890 if (aStates & states::MIXED) {
891 stringStates->Add(u"mixed"_ns);
893 if (aStates & states::READONLY) {
894 stringStates->Add(u"readonly"_ns);
896 if (aStates & states::HOTTRACKED) {
897 stringStates->Add(u"hottracked"_ns);
899 if (aStates & states::DEFAULT) {
900 stringStates->Add(u"default"_ns);
902 if (aStates & states::EXPANDED) {
903 stringStates->Add(u"expanded"_ns);
905 if (aStates & states::COLLAPSED) {
906 stringStates->Add(u"collapsed"_ns);
908 if (aStates & states::BUSY) {
909 stringStates->Add(u"busy"_ns);
911 if (aStates & states::FLOATING) {
912 stringStates->Add(u"floating"_ns);
914 if (aStates & states::ANIMATED) {
915 stringStates->Add(u"animated"_ns);
917 if (aStates & states::INVISIBLE) {
918 stringStates->Add(u"invisible"_ns);
920 if (aStates & states::OFFSCREEN) {
921 stringStates->Add(u"offscreen"_ns);
923 if (aStates & states::SIZEABLE) {
924 stringStates->Add(u"sizeable"_ns);
926 if (aStates & states::MOVEABLE) {
927 stringStates->Add(u"moveable"_ns);
929 if (aStates & states::SELFVOICING) {
930 stringStates->Add(u"selfvoicing"_ns);
932 if (aStates & states::FOCUSABLE) {
933 stringStates->Add(u"focusable"_ns);
935 if (aStates & states::SELECTABLE) {
936 stringStates->Add(u"selectable"_ns);
938 if (aStates & states::LINKED) {
939 stringStates->Add(u"linked"_ns);
941 if (aStates & states::TRAVERSED) {
942 stringStates->Add(u"traversed"_ns);
944 if (aStates & states::MULTISELECTABLE) {
945 stringStates->Add(u"multiselectable"_ns);
947 if (aStates & states::EXTSELECTABLE) {
948 stringStates->Add(u"extselectable"_ns);
950 if (aStates & states::PROTECTED) {
951 stringStates->Add(u"protected"_ns);
953 if (aStates & states::HASPOPUP) {
954 stringStates->Add(u"haspopup"_ns);
956 if (aStates & states::REQUIRED) {
957 stringStates->Add(u"required"_ns);
959 if (aStates & states::ALERT) {
960 stringStates->Add(u"alert"_ns);
962 if (aStates & states::INVALID) {
963 stringStates->Add(u"invalid"_ns);
965 if (aStates & states::CHECKABLE) {
966 stringStates->Add(u"checkable"_ns);
968 if (aStates & states::SUPPORTS_AUTOCOMPLETION) {
969 stringStates->Add(u"autocompletion"_ns);
971 if (aStates & states::DEFUNCT) {
972 stringStates->Add(u"defunct"_ns);
974 if (aStates & states::SELECTABLE_TEXT) {
975 stringStates->Add(u"selectable text"_ns);
977 if (aStates & states::EDITABLE) {
978 stringStates->Add(u"editable"_ns);
980 if (aStates & states::ACTIVE) {
981 stringStates->Add(u"active"_ns);
983 if (aStates & states::MODAL) {
984 stringStates->Add(u"modal"_ns);
986 if (aStates & states::MULTI_LINE) {
987 stringStates->Add(u"multi line"_ns);
989 if (aStates & states::HORIZONTAL) {
990 stringStates->Add(u"horizontal"_ns);
992 if (aStates & states::OPAQUE1) {
993 stringStates->Add(u"opaque"_ns);
995 if (aStates & states::SINGLE_LINE) {
996 stringStates->Add(u"single line"_ns);
998 if (aStates & states::TRANSIENT) {
999 stringStates->Add(u"transient"_ns);
1001 if (aStates & states::VERTICAL) {
1002 stringStates->Add(u"vertical"_ns);
1004 if (aStates & states::STALE) {
1005 stringStates->Add(u"stale"_ns);
1007 if (aStates & states::ENABLED) {
1008 stringStates->Add(u"enabled"_ns);
1010 if (aStates & states::SENSITIVE) {
1011 stringStates->Add(u"sensitive"_ns);
1013 if (aStates & states::EXPANDABLE) {
1014 stringStates->Add(u"expandable"_ns);
1016 if (aStates & states::PINNED) {
1017 stringStates->Add(u"pinned"_ns);
1019 if (aStates & states::CURRENT) {
1020 stringStates->Add(u"current"_ns);
1023 return stringStates.forget();
1026 void nsAccessibilityService::GetStringEventType(uint32_t aEventType,
1027 nsAString& aString) {
1028 static_assert(
1029 nsIAccessibleEvent::EVENT_LAST_ENTRY == ArrayLength(kEventTypeNames),
1030 "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
1032 if (aEventType >= ArrayLength(kEventTypeNames)) {
1033 aString.AssignLiteral("unknown");
1034 return;
1037 aString.AssignASCII(kEventTypeNames[aEventType]);
1040 void nsAccessibilityService::GetStringEventType(uint32_t aEventType,
1041 nsACString& aString) {
1042 MOZ_ASSERT(
1043 nsIAccessibleEvent::EVENT_LAST_ENTRY == ArrayLength(kEventTypeNames),
1044 "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
1046 if (aEventType >= ArrayLength(kEventTypeNames)) {
1047 aString.AssignLiteral("unknown");
1048 return;
1051 aString = nsDependentCString(kEventTypeNames[aEventType]);
1054 void nsAccessibilityService::GetStringRelationType(uint32_t aRelationType,
1055 nsAString& aString) {
1056 NS_ENSURE_TRUE_VOID(aRelationType <=
1057 static_cast<uint32_t>(RelationType::LAST));
1059 #define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
1060 case RelationType::geckoType: \
1061 aString.AssignLiteral(geckoTypeName); \
1062 return;
1064 RelationType relationType = static_cast<RelationType>(aRelationType);
1065 switch (relationType) {
1066 #include "RelationTypeMap.h"
1067 default:
1068 aString.AssignLiteral("unknown");
1069 return;
1072 #undef RELATIONTYPE
1075 ////////////////////////////////////////////////////////////////////////////////
1076 // nsAccessibilityService public
1078 LocalAccessible* nsAccessibilityService::CreateAccessible(
1079 nsINode* aNode, LocalAccessible* aContext, bool* aIsSubtreeHidden) {
1080 MOZ_ASSERT(aContext, "No context provided");
1081 MOZ_ASSERT(aNode, "No node to create an accessible for");
1082 MOZ_ASSERT(gConsumers, "No creation after shutdown");
1084 if (aIsSubtreeHidden) *aIsSubtreeHidden = false;
1086 DocAccessible* document = aContext->Document();
1087 MOZ_ASSERT(!document->GetAccessible(aNode),
1088 "We already have an accessible for this node.");
1090 if (aNode->IsDocument()) {
1091 // If it's document node then ask accessible document loader for
1092 // document accessible, otherwise return null.
1093 return GetDocAccessible(aNode->AsDocument());
1096 // We have a content node.
1097 if (!aNode->GetComposedDoc()) {
1098 NS_WARNING("Creating accessible for node with no document");
1099 return nullptr;
1102 if (aNode->OwnerDoc() != document->DocumentNode()) {
1103 NS_ERROR("Creating accessible for wrong document");
1104 return nullptr;
1107 if (!aNode->IsContent()) return nullptr;
1109 nsIContent* content = aNode->AsContent();
1110 if (aria::HasDefinedARIAHidden(content)) {
1111 if (aIsSubtreeHidden) {
1112 *aIsSubtreeHidden = true;
1114 return nullptr;
1117 // Check frame and its visibility.
1118 nsIFrame* frame = content->GetPrimaryFrame();
1119 if (frame) {
1120 // If invisible or inert, we don't create an accessible, but we don't mark
1121 // it with aIsSubtreeHidden = true, since visibility: hidden frame allows
1122 // visible elements in subtree, and inert elements allow non-inert
1123 // elements.
1124 if (!frame->StyleVisibility()->IsVisible() || frame->StyleUI()->IsInert()) {
1125 return nullptr;
1127 } else if (nsCoreUtils::CanCreateAccessibleWithoutFrame(content)) {
1128 // display:contents element doesn't have a frame, but retains the
1129 // semantics. All its children are unaffected.
1130 const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement());
1131 RefPtr<LocalAccessible> newAcc = MaybeCreateSpecificARIAAccessible(
1132 roleMapEntry, aContext, content, document);
1133 const MarkupMapInfo* markupMap = nullptr;
1134 if (!newAcc) {
1135 markupMap = GetMarkupMapInfoFor(content);
1136 if (markupMap && markupMap->new_func) {
1137 newAcc = markupMap->new_func(content->AsElement(), aContext);
1141 // Check whether this element has an ARIA role or attribute that requires
1142 // us to create an Accessible.
1143 const bool hasNonPresentationalARIARole =
1144 roleMapEntry && !roleMapEntry->Is(nsGkAtoms::presentation) &&
1145 !roleMapEntry->Is(nsGkAtoms::none);
1146 if (!newAcc && (hasNonPresentationalARIARole ||
1147 AttributesMustBeAccessible(content, document))) {
1148 newAcc = new HyperTextAccessible(content, document);
1151 // If there's still no Accessible but we do have an entry in the markup
1152 // map for this non-presentational element, create a generic
1153 // HyperTextAccessible.
1154 if (!newAcc && markupMap &&
1155 (!roleMapEntry || hasNonPresentationalARIARole)) {
1156 newAcc = new HyperTextAccessible(content, document);
1159 if (newAcc) {
1160 document->BindToDocument(newAcc, roleMapEntry);
1162 return newAcc;
1163 } else {
1164 if (aIsSubtreeHidden) {
1165 *aIsSubtreeHidden = true;
1167 return nullptr;
1170 if (frame->IsHiddenByContentVisibilityOnAnyAncestor(
1171 nsIFrame::IncludeContentVisibility::Hidden)) {
1172 if (aIsSubtreeHidden) {
1173 *aIsSubtreeHidden = true;
1175 return nullptr;
1178 if (nsMenuPopupFrame* popupFrame = do_QueryFrame(frame)) {
1179 // Hidden tooltips and panels don't create accessibles in the whole subtree.
1180 // Showing them gets handled by RootAccessible::ProcessDOMEvent.
1181 if (content->IsAnyOfXULElements(nsGkAtoms::tooltip, nsGkAtoms::panel)) {
1182 nsPopupState popupState = popupFrame->PopupState();
1183 if (popupState == ePopupHiding || popupState == ePopupInvisible ||
1184 popupState == ePopupClosed) {
1185 if (aIsSubtreeHidden) {
1186 *aIsSubtreeHidden = true;
1188 return nullptr;
1193 if (frame->GetContent() != content) {
1194 // Not the main content for this frame. This happens because <area>
1195 // elements return the image frame as their primary frame. The main content
1196 // for the image frame is the image content. If the frame is not an image
1197 // frame or the node is not an area element then null is returned.
1198 // This setup will change when bug 135040 is fixed. Make sure we don't
1199 // create area accessible here. Hopefully assertion below will handle that.
1201 #ifdef DEBUG
1202 nsImageFrame* imageFrame = do_QueryFrame(frame);
1203 NS_ASSERTION(imageFrame && content->IsHTMLElement(nsGkAtoms::area),
1204 "Unknown case of not main content for the frame!");
1205 #endif
1206 return nullptr;
1209 #ifdef DEBUG
1210 nsImageFrame* imageFrame = do_QueryFrame(frame);
1211 NS_ASSERTION(!imageFrame || !content->IsHTMLElement(nsGkAtoms::area),
1212 "Image map manages the area accessible creation!");
1213 #endif
1215 // Attempt to create an accessible based on what we know.
1216 RefPtr<LocalAccessible> newAcc;
1218 // Create accessible for visible text frames.
1219 if (content->IsText()) {
1220 nsIFrame::RenderedText text = frame->GetRenderedText(
1221 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
1222 nsIFrame::TrailingWhitespace::DontTrim);
1223 // Ignore not rendered text nodes and whitespace text nodes between table
1224 // cells.
1225 if (text.mString.IsEmpty() ||
1226 (aContext->IsTableRow() &&
1227 nsCoreUtils::IsWhitespaceString(text.mString))) {
1228 if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
1230 return nullptr;
1233 newAcc = CreateAccessibleByFrameType(frame, content, aContext);
1234 MOZ_ASSERT(newAcc, "Accessible not created for text node!");
1235 document->BindToDocument(newAcc, nullptr);
1236 newAcc->AsTextLeaf()->SetText(text.mString);
1237 return newAcc;
1240 if (content->IsHTMLElement(nsGkAtoms::map)) {
1241 // Create hyper text accessible for HTML map if it is used to group links
1242 // (see http://www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass). If the HTML
1243 // map rect is empty then it is used for links grouping. Otherwise it should
1244 // be used in conjunction with HTML image element and in this case we don't
1245 // create any accessible for it and don't walk into it. The accessibles for
1246 // HTML area (HTMLAreaAccessible) the map contains are attached as
1247 // children of the appropriate accessible for HTML image
1248 // (ImageAccessible).
1249 if (nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame->GetParent())
1250 .IsEmpty()) {
1251 if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
1253 return nullptr;
1256 newAcc = new HyperTextAccessible(content, document);
1257 document->BindToDocument(newAcc, aria::GetRoleMap(content->AsElement()));
1258 return newAcc;
1261 const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement());
1263 if (roleMapEntry && (roleMapEntry->Is(nsGkAtoms::presentation) ||
1264 roleMapEntry->Is(nsGkAtoms::none))) {
1265 if (MustBeAccessible(content, document)) {
1266 // If the element is focusable, a global ARIA attribute is applied to it
1267 // or it is referenced by an ARIA relationship, then treat
1268 // role="presentation" on the element as if the role is not there.
1269 roleMapEntry = nullptr;
1270 } else if (MustBeGenericAccessible(content, document)) {
1271 // Clear roleMapEntry so that we use the generic role specified below.
1272 // Otherwise, we'd expose roles::NOTHING as specified for presentation in
1273 // ARIAMap.
1274 roleMapEntry = nullptr;
1275 newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content,
1276 document);
1277 } else {
1278 return nullptr;
1282 // We should always use OuterDocAccessible for OuterDocs, even if there's a
1283 // specific ARIA class we would otherwise use.
1284 if (!newAcc && frame->AccessibleType() != eOuterDocType) {
1285 newAcc = MaybeCreateSpecificARIAAccessible(roleMapEntry, aContext, content,
1286 document);
1289 if (!newAcc && content->IsHTMLElement()) { // HTML accessibles
1290 // Prefer to use markup to decide if and what kind of accessible to
1291 // create,
1292 const MarkupMapInfo* markupMap =
1293 mHTMLMarkupMap.Get(content->NodeInfo()->NameAtom());
1294 if (markupMap && markupMap->new_func) {
1295 newAcc = markupMap->new_func(content->AsElement(), aContext);
1298 if (!newAcc) { // try by frame accessible type.
1299 newAcc = CreateAccessibleByFrameType(frame, content, aContext);
1302 // If table has strong ARIA role then all table descendants shouldn't
1303 // expose their native roles.
1304 if (!roleMapEntry && newAcc && aContext->HasStrongARIARole()) {
1305 if (frame->AccessibleType() == eHTMLTableRowType) {
1306 const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap();
1307 if (!contextRoleMap->IsOfType(eTable)) {
1308 roleMapEntry = &aria::gEmptyRoleMap;
1311 } else if (frame->AccessibleType() == eHTMLTableCellType &&
1312 aContext->ARIARoleMap() == &aria::gEmptyRoleMap) {
1313 roleMapEntry = &aria::gEmptyRoleMap;
1315 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::li,
1316 nsGkAtoms::dd) ||
1317 frame->AccessibleType() == eHTMLLiType) {
1318 const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap();
1319 if (!contextRoleMap->IsOfType(eList)) {
1320 roleMapEntry = &aria::gEmptyRoleMap;
1326 // XUL accessibles.
1327 if (!newAcc && content->IsXULElement()) {
1328 if (content->IsXULElement(nsGkAtoms::panel)) {
1329 // We filter here instead of in the XUL map because
1330 // if we filter there and return null, we still end up
1331 // creating a generic accessible at the end of this function.
1332 // Doing the filtering here ensures we never create accessibles
1333 // for panels whose popups aren't visible.
1334 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
1335 if (!popupFrame) {
1336 return nullptr;
1339 nsPopupState popupState = popupFrame->PopupState();
1340 if (popupState == ePopupHiding || popupState == ePopupInvisible ||
1341 popupState == ePopupClosed) {
1342 return nullptr;
1346 // Prefer to use XUL to decide if and what kind of accessible to create.
1347 const XULMarkupMapInfo* xulMap =
1348 mXULMarkupMap.Get(content->NodeInfo()->NameAtom());
1349 if (xulMap && xulMap->new_func) {
1350 newAcc = xulMap->new_func(content->AsElement(), aContext);
1353 // Any XUL/flex box can be used as tabpanel, make sure we create a proper
1354 // accessible for it.
1355 if (!newAcc && aContext->IsXULTabpanels() &&
1356 content->GetParent() == aContext->GetContent()) {
1357 LayoutFrameType frameType = frame->Type();
1358 // FIXME(emilio): Why only these frame types?
1359 if (frameType == LayoutFrameType::FlexContainer ||
1360 frameType == LayoutFrameType::Scroll) {
1361 newAcc = new XULTabpanelAccessible(content, document);
1366 if (!newAcc) {
1367 if (content->IsSVGElement()) {
1368 if (content->IsSVGGeometryElement() ||
1369 content->IsSVGElement(nsGkAtoms::image)) {
1370 // Shape elements: rect, circle, ellipse, line, path, polygon,
1371 // and polyline. 'use' and 'text' graphic elements require
1372 // special support.
1373 if (MustSVGElementBeAccessible(content, document)) {
1374 newAcc = new EnumRoleAccessible<roles::GRAPHIC>(content, document);
1376 } else if (content->IsSVGElement(nsGkAtoms::text)) {
1377 newAcc = new HyperTextAccessible(content->AsElement(), document);
1378 } else if (content->IsSVGElement(nsGkAtoms::svg)) {
1379 // An <svg> element could contain <foreignObject>, which contains HTML
1380 // but does not normally create its own Accessible. This means that the
1381 // <svg> Accessible could have TextLeafAccessible children, so it must
1382 // be a HyperTextAccessible.
1383 newAcc =
1384 new EnumRoleHyperTextAccessible<roles::DIAGRAM>(content, document);
1385 } else if (content->IsSVGElement(nsGkAtoms::g) &&
1386 MustSVGElementBeAccessible(content, document)) {
1387 // <g> can also contain <foreignObject>.
1388 newAcc =
1389 new EnumRoleHyperTextAccessible<roles::GROUPING>(content, document);
1390 } else if (content->IsSVGElement(nsGkAtoms::a)) {
1391 newAcc = new HTMLLinkAccessible(content, document);
1394 } else if (content->IsMathMLElement()) {
1395 const MarkupMapInfo* markupMap =
1396 mMathMLMarkupMap.Get(content->NodeInfo()->NameAtom());
1397 if (markupMap && markupMap->new_func) {
1398 newAcc = markupMap->new_func(content->AsElement(), aContext);
1401 // Fall back to text when encountering Content MathML.
1402 if (!newAcc && !content->IsAnyOfMathMLElements(
1403 nsGkAtoms::annotation_, nsGkAtoms::annotation_xml_,
1404 nsGkAtoms::mpadded_, nsGkAtoms::mphantom_,
1405 nsGkAtoms::maligngroup_, nsGkAtoms::malignmark_,
1406 nsGkAtoms::mspace_, nsGkAtoms::semantics_)) {
1407 newAcc = new HyperTextAccessible(content, document);
1409 } else if (content->IsGeneratedContentContainerForMarker()) {
1410 if (aContext->IsHTMLListItem()) {
1411 newAcc = new HTMLListBulletAccessible(content, document);
1413 if (aIsSubtreeHidden) {
1414 *aIsSubtreeHidden = true;
1419 // If no accessible, see if we need to create a generic accessible because
1420 // of some property that makes this object interesting
1421 // We don't do this for <body>, <html>, <window>, <dialog> etc. which
1422 // correspond to the doc accessible and will be created in any case
1423 if (!newAcc && !content->IsHTMLElement(nsGkAtoms::body) &&
1424 content->GetParent() &&
1425 (roleMapEntry || MustBeAccessible(content, document) ||
1426 (content->IsHTMLElement() && nsCoreUtils::HasClickListener(content)))) {
1427 // This content is focusable or has an interesting dynamic content
1428 // accessibility property. If it's interesting we need it in the
1429 // accessibility hierarchy so that events or other accessibles can point to
1430 // it, or so that it can hold a state, etc.
1431 if (content->IsHTMLElement() || content->IsMathMLElement() ||
1432 content->IsSVGElement(nsGkAtoms::foreignObject)) {
1433 // Interesting container which may have selectable text and/or embedded
1434 // objects.
1435 newAcc = new HyperTextAccessible(content, document);
1436 } else { // XUL, other SVG, etc.
1437 // Interesting generic non-HTML container
1438 newAcc = new AccessibleWrap(content, document);
1440 } else if (!newAcc && MustBeGenericAccessible(content, document)) {
1441 newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content,
1442 document);
1445 if (newAcc) {
1446 document->BindToDocument(newAcc, roleMapEntry);
1448 return newAcc;
1451 #if defined(ANDROID)
1452 # include "mozilla/Monitor.h"
1453 # include "mozilla/Maybe.h"
1455 static Maybe<Monitor> sAndroidMonitor;
1457 mozilla::Monitor& nsAccessibilityService::GetAndroidMonitor() {
1458 if (!sAndroidMonitor.isSome()) {
1459 sAndroidMonitor.emplace("nsAccessibility::sAndroidMonitor");
1462 return *sAndroidMonitor;
1464 #endif
1466 ////////////////////////////////////////////////////////////////////////////////
1467 // nsAccessibilityService private
1469 bool nsAccessibilityService::Init() {
1470 AUTO_PROFILER_MARKER_TEXT("nsAccessibilityService::Init", A11Y, {}, ""_ns);
1471 // DO NOT ADD CODE ABOVE HERE: THIS CODE IS MEASURING TIMINGS.
1473 // Initialize accessible document manager.
1474 if (!DocManager::Init()) return false;
1476 // Add observers.
1477 nsCOMPtr<nsIObserverService> observerService =
1478 mozilla::services::GetObserverService();
1479 if (!observerService) return false;
1481 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
1483 #if defined(XP_WIN)
1484 // This information needs to be initialized before the observer fires.
1485 if (XRE_IsParentProcess()) {
1486 Compatibility::Init();
1488 #endif // defined(XP_WIN)
1490 // Subscribe to EventListenerService.
1491 nsCOMPtr<nsIEventListenerService> eventListenerService =
1492 do_GetService("@mozilla.org/eventlistenerservice;1");
1493 if (!eventListenerService) return false;
1495 eventListenerService->AddListenerChangeListener(this);
1497 for (uint32_t i = 0; i < ArrayLength(sHTMLMarkupMapList); i++) {
1498 mHTMLMarkupMap.InsertOrUpdate(sHTMLMarkupMapList[i].tag,
1499 &sHTMLMarkupMapList[i]);
1501 for (const auto& info : sMathMLMarkupMapList) {
1502 mMathMLMarkupMap.InsertOrUpdate(info.tag, &info);
1505 for (uint32_t i = 0; i < ArrayLength(sXULMarkupMapList); i++) {
1506 mXULMarkupMap.InsertOrUpdate(sXULMarkupMapList[i].tag,
1507 &sXULMarkupMapList[i]);
1510 #ifdef A11Y_LOG
1511 logging::CheckEnv();
1512 #endif
1514 gAccessibilityService = this;
1515 NS_ADDREF(gAccessibilityService); // will release in Shutdown()
1517 if (XRE_IsParentProcess()) {
1518 gApplicationAccessible = new ApplicationAccessibleWrap();
1519 } else {
1520 gApplicationAccessible = new ApplicationAccessible();
1523 NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
1524 gApplicationAccessible->Init();
1526 CrashReporter::RecordAnnotationCString(
1527 CrashReporter::Annotation::Accessibility, "Active");
1529 // Now its safe to start platform accessibility.
1530 if (XRE_IsParentProcess()) PlatformInit();
1532 statistics::A11yInitialized();
1534 static const char16_t kInitIndicator[] = {'1', 0};
1535 observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown",
1536 kInitIndicator);
1538 return true;
1541 void nsAccessibilityService::Shutdown() {
1542 // Application is going to be closed, shutdown accessibility and mark
1543 // accessibility service as shutdown to prevent calls of its methods.
1544 // Don't null accessibility service static member at this point to be safe
1545 // if someone will try to operate with it.
1547 MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
1548 UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI);
1550 // Remove observers.
1551 nsCOMPtr<nsIObserverService> observerService =
1552 mozilla::services::GetObserverService();
1553 if (observerService) {
1554 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1557 // Stop accessible document loader.
1558 DocManager::Shutdown();
1560 SelectionManager::Shutdown();
1562 if (XRE_IsParentProcess()) PlatformShutdown();
1564 gApplicationAccessible->Shutdown();
1565 NS_RELEASE(gApplicationAccessible);
1566 gApplicationAccessible = nullptr;
1568 NS_IF_RELEASE(gXPCApplicationAccessible);
1569 gXPCApplicationAccessible = nullptr;
1571 #if defined(ANDROID)
1572 // Don't allow the service to shut down while an a11y request is being handled
1573 // in the UI thread, as the request may depend on state from the service.
1574 MonitorAutoLock mal(GetAndroidMonitor());
1575 #endif
1576 NS_RELEASE(gAccessibilityService);
1577 gAccessibilityService = nullptr;
1579 if (observerService) {
1580 static const char16_t kShutdownIndicator[] = {'0', 0};
1581 observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown",
1582 kShutdownIndicator);
1586 already_AddRefed<LocalAccessible>
1587 nsAccessibilityService::CreateAccessibleByFrameType(nsIFrame* aFrame,
1588 nsIContent* aContent,
1589 LocalAccessible* aContext) {
1590 DocAccessible* document = aContext->Document();
1592 RefPtr<LocalAccessible> newAcc;
1593 switch (aFrame->AccessibleType()) {
1594 case eNoType:
1595 return nullptr;
1596 case eHTMLBRType:
1597 newAcc = new HTMLBRAccessible(aContent, document);
1598 break;
1599 case eHTMLButtonType:
1600 newAcc = new HTMLButtonAccessible(aContent, document);
1601 break;
1602 case eHTMLCanvasType:
1603 newAcc = new HTMLCanvasAccessible(aContent, document);
1604 break;
1605 case eHTMLCaptionType:
1606 if (aContext->IsTable() &&
1607 aContext->GetContent() == aContent->GetParent()) {
1608 newAcc = new HTMLCaptionAccessible(aContent, document);
1610 break;
1611 case eHTMLCheckboxType:
1612 newAcc = new CheckboxAccessible(aContent, document);
1613 break;
1614 case eHTMLComboboxType:
1615 newAcc = new HTMLComboboxAccessible(aContent, document);
1616 break;
1617 case eHTMLFileInputType:
1618 newAcc = new HTMLFileInputAccessible(aContent, document);
1619 break;
1620 case eHTMLGroupboxType:
1621 newAcc = new HTMLGroupboxAccessible(aContent, document);
1622 break;
1623 case eHTMLHRType:
1624 newAcc = new HTMLHRAccessible(aContent, document);
1625 break;
1626 case eHTMLImageMapType:
1627 newAcc = new HTMLImageMapAccessible(aContent, document);
1628 break;
1629 case eHTMLLiType:
1630 if (aContext->IsList() &&
1631 aContext->GetContent() == aContent->GetParent()) {
1632 newAcc = new HTMLLIAccessible(aContent, document);
1633 } else {
1634 // Otherwise create a generic text accessible to avoid text jamming.
1635 newAcc = new HyperTextAccessible(aContent, document);
1637 break;
1638 case eHTMLSelectListType:
1639 newAcc = new HTMLSelectListAccessible(aContent, document);
1640 break;
1641 case eHTMLMediaType:
1642 newAcc = new EnumRoleAccessible<roles::GROUPING>(aContent, document);
1643 break;
1644 case eHTMLRadioButtonType:
1645 newAcc = new HTMLRadioButtonAccessible(aContent, document);
1646 break;
1647 case eHTMLRangeType:
1648 newAcc = new HTMLRangeAccessible(aContent, document);
1649 break;
1650 case eHTMLSpinnerType:
1651 newAcc = new HTMLSpinnerAccessible(aContent, document);
1652 break;
1653 case eHTMLTableType:
1654 case eHTMLTableCellType:
1655 // We handle markup and ARIA tables elsewhere. If we reach here, this is
1656 // a CSS table part. Just create a generic text container.
1657 newAcc = new HyperTextAccessible(aContent, document);
1658 break;
1659 case eHTMLTableRowType:
1660 // This is a CSS table row. Don't expose it at all.
1661 break;
1662 case eHTMLTextFieldType:
1663 newAcc = new HTMLTextFieldAccessible(aContent, document);
1664 break;
1665 case eHyperTextType: {
1666 if (aContext->IsTable() || aContext->IsTableRow()) {
1667 // This is some generic hyperText, for example a block frame element
1668 // inserted between a table and table row. Treat it as presentational.
1669 return nullptr;
1672 if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd,
1673 nsGkAtoms::div, nsGkAtoms::thead,
1674 nsGkAtoms::tfoot, nsGkAtoms::tbody)) {
1675 newAcc = new HyperTextAccessible(aContent, document);
1677 break;
1679 case eImageType:
1680 if (aContent->IsElement() &&
1681 ShouldCreateImgAccessible(aContent->AsElement(), document)) {
1682 newAcc = new ImageAccessible(aContent, document);
1684 break;
1685 case eOuterDocType:
1686 newAcc = new OuterDocAccessible(aContent, document);
1687 break;
1688 case eTextLeafType:
1689 newAcc = new TextLeafAccessible(aContent, document);
1690 break;
1691 default:
1692 MOZ_ASSERT(false);
1693 break;
1696 return newAcc.forget();
1699 void nsAccessibilityService::MarkupAttributes(
1700 Accessible* aAcc, AccAttributes* aAttributes) const {
1701 const mozilla::a11y::MarkupMapInfo* markupMap = GetMarkupMapInfoFor(aAcc);
1702 if (!markupMap) return;
1704 dom::Element* el = aAcc->IsLocal() ? aAcc->AsLocal()->Elm() : nullptr;
1705 for (uint32_t i = 0; i < ArrayLength(markupMap->attrs); i++) {
1706 const MarkupAttrInfo* info = markupMap->attrs + i;
1707 if (!info->name) break;
1709 if (info->DOMAttrName) {
1710 if (!el) {
1711 // XXX Expose DOM attributes for cached RemoteAccessibles.
1712 continue;
1714 if (info->DOMAttrValue) {
1715 if (el->AttrValueIs(kNameSpaceID_None, info->DOMAttrName,
1716 info->DOMAttrValue, eCaseMatters)) {
1717 aAttributes->SetAttribute(info->name, info->DOMAttrValue);
1719 continue;
1722 nsString value;
1723 el->GetAttr(info->DOMAttrName, value);
1725 if (!value.IsEmpty()) {
1726 aAttributes->SetAttribute(info->name, std::move(value));
1729 continue;
1732 aAttributes->SetAttribute(info->name, info->value);
1736 LocalAccessible* nsAccessibilityService::AddNativeRootAccessible(
1737 void* aAtkAccessible) {
1738 #ifdef MOZ_ACCESSIBILITY_ATK
1739 ApplicationAccessible* applicationAcc = ApplicationAcc();
1740 if (!applicationAcc) return nullptr;
1742 GtkWindowAccessible* nativeWnd =
1743 new GtkWindowAccessible(static_cast<AtkObject*>(aAtkAccessible));
1745 if (applicationAcc->AppendChild(nativeWnd)) return nativeWnd;
1746 #endif
1748 return nullptr;
1751 void nsAccessibilityService::RemoveNativeRootAccessible(
1752 LocalAccessible* aAccessible) {
1753 #ifdef MOZ_ACCESSIBILITY_ATK
1754 ApplicationAccessible* applicationAcc = ApplicationAcc();
1756 if (applicationAcc) applicationAcc->RemoveChild(aAccessible);
1757 #endif
1760 bool nsAccessibilityService::HasAccessible(nsINode* aDOMNode) {
1761 if (!aDOMNode) return false;
1763 Document* document = aDOMNode->OwnerDoc();
1764 if (!document) return false;
1766 DocAccessible* docAcc = GetExistingDocAccessible(aDOMNode->OwnerDoc());
1767 if (!docAcc) return false;
1769 return docAcc->HasAccessible(aDOMNode);
1772 ////////////////////////////////////////////////////////////////////////////////
1773 // nsAccessibilityService private (DON'T put methods here)
1775 void nsAccessibilityService::SetConsumers(uint32_t aConsumers, bool aNotify) {
1776 if (gConsumers & aConsumers) {
1777 return;
1780 gConsumers |= aConsumers;
1781 if (aNotify) {
1782 NotifyOfConsumersChange();
1786 void nsAccessibilityService::UnsetConsumers(uint32_t aConsumers) {
1787 if (!(gConsumers & aConsumers)) {
1788 return;
1791 gConsumers &= ~aConsumers;
1792 NotifyOfConsumersChange();
1795 void nsAccessibilityService::GetConsumers(nsAString& aString) {
1796 const char16_t* kJSONFmt =
1797 u"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
1798 nsString json;
1799 nsTextFormatter::ssprintf(json, kJSONFmt,
1800 gConsumers & eXPCOM ? "true" : "false",
1801 gConsumers & eMainProcess ? "true" : "false",
1802 gConsumers & ePlatformAPI ? "true" : "false");
1803 aString.Assign(json);
1806 void nsAccessibilityService::NotifyOfConsumersChange() {
1807 nsCOMPtr<nsIObserverService> observerService =
1808 mozilla::services::GetObserverService();
1810 if (!observerService) {
1811 return;
1814 nsAutoString consumers;
1815 GetConsumers(consumers);
1816 observerService->NotifyObservers(nullptr, "a11y-consumers-changed",
1817 consumers.get());
1820 const mozilla::a11y::MarkupMapInfo* nsAccessibilityService::GetMarkupMapInfoFor(
1821 Accessible* aAcc) const {
1822 if (LocalAccessible* localAcc = aAcc->AsLocal()) {
1823 return localAcc->HasOwnContent()
1824 ? GetMarkupMapInfoFor(localAcc->GetContent())
1825 : nullptr;
1827 // XXX For now, we assume all RemoteAccessibles are HTML elements. This
1828 // isn't strictly correct, but as far as current callers are concerned,
1829 // this doesn't matter. If that changes in future, we could expose the
1830 // element type via AccGenericType.
1831 return mHTMLMarkupMap.Get(aAcc->TagName());
1834 nsAccessibilityService* GetOrCreateAccService(uint32_t aNewConsumer) {
1835 // Do not initialize accessibility if it is force disabled.
1836 if (PlatformDisabledState() == ePlatformIsDisabled) {
1837 return nullptr;
1840 if (!nsAccessibilityService::gAccessibilityService) {
1841 RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
1842 if (!service->Init()) {
1843 service->Shutdown();
1844 return nullptr;
1848 MOZ_ASSERT(nsAccessibilityService::gAccessibilityService,
1849 "LocalAccessible service is not initialized.");
1850 nsAccessibilityService::gAccessibilityService->SetConsumers(aNewConsumer);
1851 return nsAccessibilityService::gAccessibilityService;
1854 void MaybeShutdownAccService(uint32_t aFormerConsumer) {
1855 nsAccessibilityService* accService =
1856 nsAccessibilityService::gAccessibilityService;
1858 if (!accService || nsAccessibilityService::IsShutdown()) {
1859 return;
1862 // Still used by XPCOM
1863 if (nsCoreUtils::AccEventObserversExist() ||
1864 xpcAccessibilityService::IsInUse() || accService->HasXPCDocuments()) {
1865 // In case the XPCOM flag was unset (possibly because of the shutdown
1866 // timer in the xpcAccessibilityService) ensure it is still present. Note:
1867 // this should be fixed when all the consumer logic is taken out as a
1868 // separate class.
1869 accService->SetConsumers(nsAccessibilityService::eXPCOM, false);
1871 if (aFormerConsumer != nsAccessibilityService::eXPCOM) {
1872 // Only unset non-XPCOM consumers.
1873 accService->UnsetConsumers(aFormerConsumer);
1875 return;
1878 if (nsAccessibilityService::gConsumers & ~aFormerConsumer) {
1879 accService->UnsetConsumers(aFormerConsumer);
1880 } else {
1881 accService
1882 ->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
1886 ////////////////////////////////////////////////////////////////////////////////
1887 // Services
1888 ////////////////////////////////////////////////////////////////////////////////
1890 namespace mozilla {
1891 namespace a11y {
1893 FocusManager* FocusMgr() {
1894 return nsAccessibilityService::gAccessibilityService;
1897 SelectionManager* SelectionMgr() {
1898 return nsAccessibilityService::gAccessibilityService;
1901 ApplicationAccessible* ApplicationAcc() {
1902 return nsAccessibilityService::gApplicationAccessible;
1905 xpcAccessibleApplication* XPCApplicationAcc() {
1906 if (!nsAccessibilityService::gXPCApplicationAccessible &&
1907 nsAccessibilityService::gApplicationAccessible) {
1908 nsAccessibilityService::gXPCApplicationAccessible =
1909 new xpcAccessibleApplication(
1910 nsAccessibilityService::gApplicationAccessible);
1911 NS_ADDREF(nsAccessibilityService::gXPCApplicationAccessible);
1914 return nsAccessibilityService::gXPCApplicationAccessible;
1917 EPlatformDisabledState PlatformDisabledState() {
1918 static bool platformDisabledStateCached = false;
1919 if (platformDisabledStateCached) {
1920 return static_cast<EPlatformDisabledState>(sPlatformDisabledState);
1923 platformDisabledStateCached = true;
1924 Preferences::RegisterCallback(PrefChanged, PREF_ACCESSIBILITY_FORCE_DISABLED);
1925 return ReadPlatformDisabledState();
1928 EPlatformDisabledState ReadPlatformDisabledState() {
1929 sPlatformDisabledState =
1930 Preferences::GetInt(PREF_ACCESSIBILITY_FORCE_DISABLED, 0);
1931 if (sPlatformDisabledState < ePlatformIsForceEnabled) {
1932 sPlatformDisabledState = ePlatformIsForceEnabled;
1933 } else if (sPlatformDisabledState > ePlatformIsDisabled) {
1934 sPlatformDisabledState = ePlatformIsDisabled;
1937 return static_cast<EPlatformDisabledState>(sPlatformDisabledState);
1940 void PrefChanged(const char* aPref, void* aClosure) {
1941 if (ReadPlatformDisabledState() == ePlatformIsDisabled) {
1942 // Force shut down accessibility.
1943 nsAccessibilityService* accService =
1944 nsAccessibilityService::gAccessibilityService;
1945 if (accService && !nsAccessibilityService::IsShutdown()) {
1946 accService->Shutdown();
1951 } // namespace a11y
1952 } // namespace mozilla