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"
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"
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"
40 #include "Statistics.h"
41 #include "TextLeafAccessible.h"
42 #include "xpcAccessibleApplication.h"
45 # include "mozilla/a11y/Compatibility.h"
46 # include "mozilla/StaticPtr.h"
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
;
85 * Accessibility service force enable/disable preference.
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 ////////////////////////////////////////////////////////////////////////////////
95 ////////////////////////////////////////////////////////////////////////////////
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.
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
) {
120 // That row must be in a table, though there may be an intervening rowgroup.
121 parent
= parent
->GetNonGenericParent();
125 if (!parent
->IsTable() && parent
->Role() == roles::GROUPING
) {
126 parent
= parent
->GetNonGenericParent();
131 if (parent
->IsTable()) {
132 return new ARIAGridCellAccessible(aContent
, aDocument
);
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.
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
))) {
169 // If the given ID is referred by relation attribute then create an
170 // Accessible for it.
172 if (nsCoreUtils::GetID(aContent
, id
) && !id
.IsEmpty()) {
173 return aDocument
->IsDependentID(aContent
->AsElement(), id
);
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).
202 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
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();
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)) {
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();
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
)) {
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.
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.
277 // Otherwise, no alt text means we should not create an accessible.
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
)) {
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;
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());
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"
369 #define XULMAP(atom, ...) {nsGkAtoms::atom, __VA_ARGS__},
371 #define XULMAP_TYPE(atom, new_type) \
374 [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { \
375 return new new_type(aElement, aContext->Document()); \
378 static const XULMarkupMapInfo sXULMarkupMapList
[] = {
389 ////////////////////////////////////////////////////////////////////////////////
390 // nsAccessibilityService
391 ////////////////////////////////////////////////////////////////////////////////
393 nsAccessibilityService
* nsAccessibilityService::gAccessibilityService
= nullptr;
394 ApplicationAccessible
* nsAccessibilityService::gApplicationAccessible
= nullptr;
395 xpcAccessibleApplication
* nsAccessibilityService::gXPCApplicationAccessible
=
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
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()) {
429 uint32_t changeCount
;
430 change
->GetCountOfEventListenerChangesAffectingAccessibility(&changeCount
);
431 NS_ENSURE_SUCCESS(rv
, rv
);
434 Document
* ownerDoc
= content
->OwnerDoc();
435 DocAccessible
* document
= GetExistingDocAccessible(ownerDoc
);
438 LocalAccessible
* acc
= document
->GetAccessible(content
);
439 if (!acc
&& (content
== document
->GetContent() ||
440 content
== document
->DocumentNode()->GetRootElement())) {
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
458 document
->ContentInserted(content
, content
->GetNextSibling());
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
);
481 ////////////////////////////////////////////////////////////////////////////////
484 NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService
, DocManager
, nsIObserver
,
485 nsIListenerChangeListener
,
486 nsISelectionListener
) // from SelectionManager
488 ////////////////////////////////////////////////////////////////////////////////
492 nsAccessibilityService::Observe(nsISupports
* aSubject
, const char* aTopic
,
493 const char16_t
* aData
) {
494 if (!nsCRT::strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
501 void nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent
* aTargetNode
) {
502 Document
* documentNode
= aTargetNode
->GetUncomposedDoc();
506 DocAccessible
* document
= GetDocAccessible(documentNode
);
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();
514 (focusedAcc
== document
|| focusedAcc
->IsNonInteractive())) {
515 LocalAccessible
* targetAcc
= document
->GetAccessible(aTargetNode
);
517 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START
,
519 document
->SetAnchorJump(nullptr);
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
);
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();
540 // DocAccessible::GetAccessible() won't return the document if a root
541 // element like body is passed.
542 LocalAccessible
* accessible
= aContent
== document
->GetContent()
544 : document
->GetAccessible(aContent
);
546 document
->QueueCacheUpdate(accessible
, CacheDomain::Bounds
);
552 void nsAccessibilityService::NotifyOfComputedStyleChange(
553 mozilla::PresShell
* aPresShell
, nsIContent
* aContent
) {
554 DocAccessible
* document
= aPresShell
->GetDocAccessible();
559 // DocAccessible::GetAccessible() won't return the document if a root
560 // element like body is passed.
561 LocalAccessible
* accessible
= aContent
== document
->GetContent()
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,
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 LocalAccessible
* nsAccessibilityService::GetRootDocumentAccessible(
610 PresShell
* aPresShell
, bool aCanCreate
) {
611 PresShell
* presShell
= aPresShell
;
612 Document
* documentNode
= aPresShell
->GetDocument();
614 nsCOMPtr
<nsIDocShellTreeItem
> treeItem(documentNode
->GetDocShell());
616 nsCOMPtr
<nsIDocShellTreeItem
> rootTreeItem
;
617 treeItem
->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem
));
618 if (treeItem
!= rootTreeItem
) {
619 nsCOMPtr
<nsIDocShell
> docShell(do_QueryInterface(rootTreeItem
));
620 presShell
= docShell
->GetPresShell();
623 return aCanCreate
? GetDocAccessible(presShell
)
624 : presShell
->GetDocAccessible();
630 void nsAccessibilityService::NotifyOfTabPanelVisibilityChange(
631 PresShell
* aPresShell
, Element
* aPanel
, bool aNowVisible
) {
632 MOZ_ASSERT(aPanel
->GetParent()->IsXULElement(nsGkAtoms::tabpanels
));
634 DocAccessible
* document
= GetDocAccessible(aPresShell
);
639 if (LocalAccessible
* acc
= document
->GetAccessible(aPanel
)) {
640 RefPtr
<AccEvent
> event
=
641 new AccStateChangeEvent(acc
, states::OFFSCREEN
, aNowVisible
);
642 document
->FireDelayedEvent(event
);
646 void nsAccessibilityService::ContentRangeInserted(PresShell
* aPresShell
,
647 nsIContent
* aStartChild
,
648 nsIContent
* aEndChild
) {
649 DocAccessible
* document
= GetDocAccessible(aPresShell
);
651 if (logging::IsEnabled(logging::eTree
)) {
652 logging::MsgBegin("TREE", "content inserted; doc: %p", document
);
653 logging::Node("container", aStartChild
->GetParentNode());
654 for (nsIContent
* child
= aStartChild
; child
!= aEndChild
;
655 child
= child
->GetNextSibling()) {
656 logging::Node("content", child
);
664 document
->ContentInserted(aStartChild
, aEndChild
);
668 void nsAccessibilityService::ScheduleAccessibilitySubtreeUpdate(
669 PresShell
* aPresShell
, nsIContent
* aContent
) {
670 DocAccessible
* document
= GetDocAccessible(aPresShell
);
672 if (logging::IsEnabled(logging::eTree
)) {
673 logging::MsgBegin("TREE", "schedule update; doc: %p", document
);
674 logging::Node("content node", aContent
);
680 document
->ScheduleTreeUpdate(aContent
);
684 void nsAccessibilityService::ContentRemoved(PresShell
* aPresShell
,
685 nsIContent
* aChildNode
) {
686 DocAccessible
* document
= GetDocAccessible(aPresShell
);
688 if (logging::IsEnabled(logging::eTree
)) {
689 logging::MsgBegin("TREE", "content removed; doc: %p", document
);
690 logging::Node("container node", aChildNode
->GetFlattenedTreeParent());
691 logging::Node("content node", aChildNode
);
697 document
->ContentRemoved(aChildNode
);
701 if (logging::IsEnabled(logging::eTree
)) {
708 void nsAccessibilityService::TableLayoutGuessMaybeChanged(
709 PresShell
* aPresShell
, nsIContent
* aContent
) {
710 if (DocAccessible
* document
= GetDocAccessible(aPresShell
)) {
711 if (LocalAccessible
* acc
= document
->GetAccessible(aContent
)) {
712 if (LocalAccessible
* table
= nsAccUtils::TableFor(acc
)) {
713 document
->QueueCacheUpdate(table
, CacheDomain::Table
);
719 void nsAccessibilityService::ComboboxOptionMaybeChanged(
720 PresShell
* aPresShell
, nsIContent
* aMutatingNode
) {
721 DocAccessible
* document
= GetDocAccessible(aPresShell
);
726 for (nsIContent
* cur
= aMutatingNode
; cur
; cur
= cur
->GetParent()) {
727 if (cur
->IsHTMLElement(nsGkAtoms::option
)) {
728 if (LocalAccessible
* accessible
= document
->GetAccessible(cur
)) {
729 document
->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
,
733 if (cur
->IsHTMLElement(nsGkAtoms::select
)) {
740 void nsAccessibilityService::UpdateText(PresShell
* aPresShell
,
741 nsIContent
* aContent
) {
742 DocAccessible
* document
= GetDocAccessible(aPresShell
);
743 if (document
) document
->UpdateText(aContent
);
746 void nsAccessibilityService::TreeViewChanged(PresShell
* aPresShell
,
747 nsIContent
* aContent
,
748 nsITreeView
* aView
) {
749 DocAccessible
* document
= GetDocAccessible(aPresShell
);
751 LocalAccessible
* accessible
= document
->GetAccessible(aContent
);
753 XULTreeAccessible
* treeAcc
= accessible
->AsXULTree();
754 if (treeAcc
) treeAcc
->TreeViewChanged(aView
);
759 void nsAccessibilityService::RangeValueChanged(PresShell
* aPresShell
,
760 nsIContent
* aContent
) {
761 DocAccessible
* document
= GetDocAccessible(aPresShell
);
763 LocalAccessible
* accessible
= document
->GetAccessible(aContent
);
765 document
->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE
,
771 void nsAccessibilityService::UpdateImageMap(nsImageFrame
* aImageFrame
) {
772 PresShell
* presShell
= aImageFrame
->PresShell();
773 DocAccessible
* document
= GetDocAccessible(presShell
);
775 LocalAccessible
* accessible
=
776 document
->GetAccessible(aImageFrame
->GetContent());
778 HTMLImageMapAccessible
* imageMap
= accessible
->AsImageMap();
780 imageMap
->UpdateChildAreas();
784 // If image map was initialized after we created an accessible (that'll
785 // be an image accessible) then recreate it.
786 RecreateAccessible(presShell
, aImageFrame
->GetContent());
791 void nsAccessibilityService::UpdateLabelValue(PresShell
* aPresShell
,
792 nsIContent
* aLabelElm
,
793 const nsString
& aNewValue
) {
794 DocAccessible
* document
= GetDocAccessible(aPresShell
);
796 LocalAccessible
* accessible
= document
->GetAccessible(aLabelElm
);
798 XULLabelAccessible
* xulLabel
= accessible
->AsXULLabel();
799 NS_ASSERTION(xulLabel
,
800 "UpdateLabelValue was called for wrong accessible!");
801 if (xulLabel
) xulLabel
->UpdateLabelValue(aNewValue
);
806 void nsAccessibilityService::PresShellActivated(PresShell
* aPresShell
) {
807 DocAccessible
* document
= aPresShell
->GetDocAccessible();
809 RootAccessible
* rootDocument
= document
->RootAccessible();
810 NS_ASSERTION(rootDocument
, "Entirely broken tree: no root document!");
811 if (rootDocument
) rootDocument
->DocumentActivated(document
);
815 void nsAccessibilityService::RecreateAccessible(PresShell
* aPresShell
,
816 nsIContent
* aContent
) {
817 DocAccessible
* document
= GetDocAccessible(aPresShell
);
818 if (document
) document
->RecreateAccessible(aContent
);
821 void nsAccessibilityService::GetStringRole(uint32_t aRole
, nsAString
& aString
) {
822 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
823 msaaRole, ia2Role, androidClass, nameRule) \
824 case roles::geckoRole: \
825 aString.AssignLiteral(stringRole); \
831 aString
.AssignLiteral("unknown");
838 void nsAccessibilityService::GetStringStates(uint32_t aState
,
839 uint32_t aExtraState
,
840 nsISupports
** aStringStates
) {
841 RefPtr
<DOMStringList
> stringStates
=
842 GetStringStates(nsAccUtils::To64State(aState
, aExtraState
));
845 if (!stringStates
->Length()) {
846 stringStates
->Add(u
"unknown"_ns
);
849 stringStates
.forget(aStringStates
);
852 already_AddRefed
<DOMStringList
> nsAccessibilityService::GetStringStates(
853 uint64_t aStates
) const {
854 RefPtr
<DOMStringList
> stringStates
= new DOMStringList();
856 if (aStates
& states::UNAVAILABLE
) {
857 stringStates
->Add(u
"unavailable"_ns
);
859 if (aStates
& states::SELECTED
) {
860 stringStates
->Add(u
"selected"_ns
);
862 if (aStates
& states::FOCUSED
) {
863 stringStates
->Add(u
"focused"_ns
);
865 if (aStates
& states::PRESSED
) {
866 stringStates
->Add(u
"pressed"_ns
);
868 if (aStates
& states::CHECKED
) {
869 stringStates
->Add(u
"checked"_ns
);
871 if (aStates
& states::MIXED
) {
872 stringStates
->Add(u
"mixed"_ns
);
874 if (aStates
& states::READONLY
) {
875 stringStates
->Add(u
"readonly"_ns
);
877 if (aStates
& states::HOTTRACKED
) {
878 stringStates
->Add(u
"hottracked"_ns
);
880 if (aStates
& states::DEFAULT
) {
881 stringStates
->Add(u
"default"_ns
);
883 if (aStates
& states::EXPANDED
) {
884 stringStates
->Add(u
"expanded"_ns
);
886 if (aStates
& states::COLLAPSED
) {
887 stringStates
->Add(u
"collapsed"_ns
);
889 if (aStates
& states::BUSY
) {
890 stringStates
->Add(u
"busy"_ns
);
892 if (aStates
& states::FLOATING
) {
893 stringStates
->Add(u
"floating"_ns
);
895 if (aStates
& states::ANIMATED
) {
896 stringStates
->Add(u
"animated"_ns
);
898 if (aStates
& states::INVISIBLE
) {
899 stringStates
->Add(u
"invisible"_ns
);
901 if (aStates
& states::OFFSCREEN
) {
902 stringStates
->Add(u
"offscreen"_ns
);
904 if (aStates
& states::SIZEABLE
) {
905 stringStates
->Add(u
"sizeable"_ns
);
907 if (aStates
& states::MOVEABLE
) {
908 stringStates
->Add(u
"moveable"_ns
);
910 if (aStates
& states::SELFVOICING
) {
911 stringStates
->Add(u
"selfvoicing"_ns
);
913 if (aStates
& states::FOCUSABLE
) {
914 stringStates
->Add(u
"focusable"_ns
);
916 if (aStates
& states::SELECTABLE
) {
917 stringStates
->Add(u
"selectable"_ns
);
919 if (aStates
& states::LINKED
) {
920 stringStates
->Add(u
"linked"_ns
);
922 if (aStates
& states::TRAVERSED
) {
923 stringStates
->Add(u
"traversed"_ns
);
925 if (aStates
& states::MULTISELECTABLE
) {
926 stringStates
->Add(u
"multiselectable"_ns
);
928 if (aStates
& states::EXTSELECTABLE
) {
929 stringStates
->Add(u
"extselectable"_ns
);
931 if (aStates
& states::PROTECTED
) {
932 stringStates
->Add(u
"protected"_ns
);
934 if (aStates
& states::HASPOPUP
) {
935 stringStates
->Add(u
"haspopup"_ns
);
937 if (aStates
& states::REQUIRED
) {
938 stringStates
->Add(u
"required"_ns
);
940 if (aStates
& states::ALERT
) {
941 stringStates
->Add(u
"alert"_ns
);
943 if (aStates
& states::INVALID
) {
944 stringStates
->Add(u
"invalid"_ns
);
946 if (aStates
& states::CHECKABLE
) {
947 stringStates
->Add(u
"checkable"_ns
);
949 if (aStates
& states::SUPPORTS_AUTOCOMPLETION
) {
950 stringStates
->Add(u
"autocompletion"_ns
);
952 if (aStates
& states::DEFUNCT
) {
953 stringStates
->Add(u
"defunct"_ns
);
955 if (aStates
& states::SELECTABLE_TEXT
) {
956 stringStates
->Add(u
"selectable text"_ns
);
958 if (aStates
& states::EDITABLE
) {
959 stringStates
->Add(u
"editable"_ns
);
961 if (aStates
& states::ACTIVE
) {
962 stringStates
->Add(u
"active"_ns
);
964 if (aStates
& states::MODAL
) {
965 stringStates
->Add(u
"modal"_ns
);
967 if (aStates
& states::MULTI_LINE
) {
968 stringStates
->Add(u
"multi line"_ns
);
970 if (aStates
& states::HORIZONTAL
) {
971 stringStates
->Add(u
"horizontal"_ns
);
973 if (aStates
& states::OPAQUE1
) {
974 stringStates
->Add(u
"opaque"_ns
);
976 if (aStates
& states::SINGLE_LINE
) {
977 stringStates
->Add(u
"single line"_ns
);
979 if (aStates
& states::TRANSIENT
) {
980 stringStates
->Add(u
"transient"_ns
);
982 if (aStates
& states::VERTICAL
) {
983 stringStates
->Add(u
"vertical"_ns
);
985 if (aStates
& states::STALE
) {
986 stringStates
->Add(u
"stale"_ns
);
988 if (aStates
& states::ENABLED
) {
989 stringStates
->Add(u
"enabled"_ns
);
991 if (aStates
& states::SENSITIVE
) {
992 stringStates
->Add(u
"sensitive"_ns
);
994 if (aStates
& states::EXPANDABLE
) {
995 stringStates
->Add(u
"expandable"_ns
);
997 if (aStates
& states::PINNED
) {
998 stringStates
->Add(u
"pinned"_ns
);
1000 if (aStates
& states::CURRENT
) {
1001 stringStates
->Add(u
"current"_ns
);
1004 return stringStates
.forget();
1007 void nsAccessibilityService::GetStringEventType(uint32_t aEventType
,
1008 nsAString
& aString
) {
1010 nsIAccessibleEvent::EVENT_LAST_ENTRY
== ArrayLength(kEventTypeNames
),
1011 "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
1013 if (aEventType
>= ArrayLength(kEventTypeNames
)) {
1014 aString
.AssignLiteral("unknown");
1018 aString
.AssignASCII(kEventTypeNames
[aEventType
]);
1021 void nsAccessibilityService::GetStringEventType(uint32_t aEventType
,
1022 nsACString
& aString
) {
1024 nsIAccessibleEvent::EVENT_LAST_ENTRY
== ArrayLength(kEventTypeNames
),
1025 "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
1027 if (aEventType
>= ArrayLength(kEventTypeNames
)) {
1028 aString
.AssignLiteral("unknown");
1032 aString
= nsDependentCString(kEventTypeNames
[aEventType
]);
1035 void nsAccessibilityService::GetStringRelationType(uint32_t aRelationType
,
1036 nsAString
& aString
) {
1037 NS_ENSURE_TRUE_VOID(aRelationType
<=
1038 static_cast<uint32_t>(RelationType::LAST
));
1040 #define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
1041 case RelationType::geckoType: \
1042 aString.AssignLiteral(geckoTypeName); \
1045 RelationType relationType
= static_cast<RelationType
>(aRelationType
);
1046 switch (relationType
) {
1047 #include "RelationTypeMap.h"
1049 aString
.AssignLiteral("unknown");
1056 ////////////////////////////////////////////////////////////////////////////////
1057 // nsAccessibilityService public
1059 LocalAccessible
* nsAccessibilityService::CreateAccessible(
1060 nsINode
* aNode
, LocalAccessible
* aContext
, bool* aIsSubtreeHidden
) {
1061 MOZ_ASSERT(aContext
, "No context provided");
1062 MOZ_ASSERT(aNode
, "No node to create an accessible for");
1063 MOZ_ASSERT(gConsumers
, "No creation after shutdown");
1065 if (aIsSubtreeHidden
) *aIsSubtreeHidden
= false;
1067 DocAccessible
* document
= aContext
->Document();
1068 MOZ_ASSERT(!document
->GetAccessible(aNode
),
1069 "We already have an accessible for this node.");
1071 if (aNode
->IsDocument()) {
1072 // If it's document node then ask accessible document loader for
1073 // document accessible, otherwise return null.
1074 return GetDocAccessible(aNode
->AsDocument());
1077 // We have a content node.
1078 if (!aNode
->GetComposedDoc()) {
1079 NS_WARNING("Creating accessible for node with no document");
1083 if (aNode
->OwnerDoc() != document
->DocumentNode()) {
1084 NS_ERROR("Creating accessible for wrong document");
1088 if (!aNode
->IsContent()) return nullptr;
1090 nsIContent
* content
= aNode
->AsContent();
1091 if (aria::HasDefinedARIAHidden(content
)) {
1092 if (aIsSubtreeHidden
) {
1093 *aIsSubtreeHidden
= true;
1098 // Check frame and its visibility.
1099 nsIFrame
* frame
= content
->GetPrimaryFrame();
1101 // If invisible or inert, we don't create an accessible, but we don't mark
1102 // it with aIsSubtreeHidden = true, since visibility: hidden frame allows
1103 // visible elements in subtree, and inert elements allow non-inert
1105 if (!frame
->StyleVisibility()->IsVisible() || frame
->StyleUI()->IsInert()) {
1108 } else if (nsCoreUtils::CanCreateAccessibleWithoutFrame(content
)) {
1109 // display:contents element doesn't have a frame, but retains the
1110 // semantics. All its children are unaffected.
1111 const nsRoleMapEntry
* roleMapEntry
= aria::GetRoleMap(content
->AsElement());
1112 RefPtr
<LocalAccessible
> newAcc
= MaybeCreateSpecificARIAAccessible(
1113 roleMapEntry
, aContext
, content
, document
);
1114 const MarkupMapInfo
* markupMap
= nullptr;
1116 markupMap
= GetMarkupMapInfoFor(content
);
1117 if (markupMap
&& markupMap
->new_func
) {
1118 newAcc
= markupMap
->new_func(content
->AsElement(), aContext
);
1122 // Check whether this element has an ARIA role or attribute that requires
1123 // us to create an Accessible.
1124 const bool hasNonPresentationalARIARole
=
1125 roleMapEntry
&& !roleMapEntry
->Is(nsGkAtoms::presentation
) &&
1126 !roleMapEntry
->Is(nsGkAtoms::none
);
1127 if (!newAcc
&& (hasNonPresentationalARIARole
||
1128 AttributesMustBeAccessible(content
, document
))) {
1129 newAcc
= new HyperTextAccessible(content
, document
);
1132 // If there's still no Accessible but we do have an entry in the markup
1133 // map for this non-presentational element, create a generic
1134 // HyperTextAccessible.
1135 if (!newAcc
&& markupMap
&&
1136 (!roleMapEntry
|| hasNonPresentationalARIARole
)) {
1137 newAcc
= new HyperTextAccessible(content
, document
);
1141 document
->BindToDocument(newAcc
, roleMapEntry
);
1145 if (aIsSubtreeHidden
) {
1146 *aIsSubtreeHidden
= true;
1151 if (frame
->IsHiddenByContentVisibilityOnAnyAncestor(
1152 nsIFrame::IncludeContentVisibility::Hidden
)) {
1153 if (aIsSubtreeHidden
) {
1154 *aIsSubtreeHidden
= true;
1159 if (nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
)) {
1160 // Hidden tooltips and panels don't create accessibles in the whole subtree.
1161 // Showing them gets handled by RootAccessible::ProcessDOMEvent.
1162 if (content
->IsAnyOfXULElements(nsGkAtoms::tooltip
, nsGkAtoms::panel
)) {
1163 nsPopupState popupState
= popupFrame
->PopupState();
1164 if (popupState
== ePopupHiding
|| popupState
== ePopupInvisible
||
1165 popupState
== ePopupClosed
) {
1166 if (aIsSubtreeHidden
) {
1167 *aIsSubtreeHidden
= true;
1174 if (frame
->GetContent() != content
) {
1175 // Not the main content for this frame. This happens because <area>
1176 // elements return the image frame as their primary frame. The main content
1177 // for the image frame is the image content. If the frame is not an image
1178 // frame or the node is not an area element then null is returned.
1179 // This setup will change when bug 135040 is fixed. Make sure we don't
1180 // create area accessible here. Hopefully assertion below will handle that.
1183 nsImageFrame
* imageFrame
= do_QueryFrame(frame
);
1184 NS_ASSERTION(imageFrame
&& content
->IsHTMLElement(nsGkAtoms::area
),
1185 "Unknown case of not main content for the frame!");
1191 nsImageFrame
* imageFrame
= do_QueryFrame(frame
);
1192 NS_ASSERTION(!imageFrame
|| !content
->IsHTMLElement(nsGkAtoms::area
),
1193 "Image map manages the area accessible creation!");
1196 // Attempt to create an accessible based on what we know.
1197 RefPtr
<LocalAccessible
> newAcc
;
1199 // Create accessible for visible text frames.
1200 if (content
->IsText()) {
1201 nsIFrame::RenderedText text
= frame
->GetRenderedText(
1202 0, UINT32_MAX
, nsIFrame::TextOffsetType::OffsetsInContentText
,
1203 nsIFrame::TrailingWhitespace::DontTrim
);
1204 // Ignore not rendered text nodes and whitespace text nodes between table
1206 if (text
.mString
.IsEmpty() ||
1207 (aContext
->IsTableRow() &&
1208 nsCoreUtils::IsWhitespaceString(text
.mString
))) {
1209 if (aIsSubtreeHidden
) *aIsSubtreeHidden
= true;
1214 newAcc
= CreateAccessibleByFrameType(frame
, content
, aContext
);
1215 MOZ_ASSERT(newAcc
, "Accessible not created for text node!");
1216 document
->BindToDocument(newAcc
, nullptr);
1217 newAcc
->AsTextLeaf()->SetText(text
.mString
);
1221 if (content
->IsHTMLElement(nsGkAtoms::map
)) {
1222 // Create hyper text accessible for HTML map if it is used to group links
1223 // (see http://www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass). If the HTML
1224 // map rect is empty then it is used for links grouping. Otherwise it should
1225 // be used in conjunction with HTML image element and in this case we don't
1226 // create any accessible for it and don't walk into it. The accessibles for
1227 // HTML area (HTMLAreaAccessible) the map contains are attached as
1228 // children of the appropriate accessible for HTML image
1229 // (ImageAccessible).
1230 if (nsLayoutUtils::GetAllInFlowRectsUnion(frame
, frame
->GetParent())
1232 if (aIsSubtreeHidden
) *aIsSubtreeHidden
= true;
1237 newAcc
= new HyperTextAccessible(content
, document
);
1238 document
->BindToDocument(newAcc
, aria::GetRoleMap(content
->AsElement()));
1242 const nsRoleMapEntry
* roleMapEntry
= aria::GetRoleMap(content
->AsElement());
1244 if (roleMapEntry
&& (roleMapEntry
->Is(nsGkAtoms::presentation
) ||
1245 roleMapEntry
->Is(nsGkAtoms::none
))) {
1246 if (MustBeAccessible(content
, document
)) {
1247 // If the element is focusable, a global ARIA attribute is applied to it
1248 // or it is referenced by an ARIA relationship, then treat
1249 // role="presentation" on the element as if the role is not there.
1250 roleMapEntry
= nullptr;
1251 } else if (MustBeGenericAccessible(content
, document
)) {
1252 // Clear roleMapEntry so that we use the generic role specified below.
1253 // Otherwise, we'd expose roles::NOTHING as specified for presentation in
1255 roleMapEntry
= nullptr;
1256 newAcc
= new EnumRoleHyperTextAccessible
<roles::TEXT_CONTAINER
>(content
,
1263 // We should always use OuterDocAccessible for OuterDocs, even if there's a
1264 // specific ARIA class we would otherwise use.
1265 if (!newAcc
&& frame
->AccessibleType() != eOuterDocType
) {
1266 newAcc
= MaybeCreateSpecificARIAAccessible(roleMapEntry
, aContext
, content
,
1270 if (!newAcc
&& content
->IsHTMLElement()) { // HTML accessibles
1271 // Prefer to use markup to decide if and what kind of accessible to
1273 const MarkupMapInfo
* markupMap
=
1274 mHTMLMarkupMap
.Get(content
->NodeInfo()->NameAtom());
1275 if (markupMap
&& markupMap
->new_func
) {
1276 newAcc
= markupMap
->new_func(content
->AsElement(), aContext
);
1279 if (!newAcc
) { // try by frame accessible type.
1280 newAcc
= CreateAccessibleByFrameType(frame
, content
, aContext
);
1283 // If table has strong ARIA role then all table descendants shouldn't
1284 // expose their native roles.
1285 if (!roleMapEntry
&& newAcc
&& aContext
->HasStrongARIARole()) {
1286 if (frame
->AccessibleType() == eHTMLTableRowType
) {
1287 const nsRoleMapEntry
* contextRoleMap
= aContext
->ARIARoleMap();
1288 if (!contextRoleMap
->IsOfType(eTable
)) {
1289 roleMapEntry
= &aria::gEmptyRoleMap
;
1292 } else if (frame
->AccessibleType() == eHTMLTableCellType
&&
1293 aContext
->ARIARoleMap() == &aria::gEmptyRoleMap
) {
1294 roleMapEntry
= &aria::gEmptyRoleMap
;
1296 } else if (content
->IsAnyOfHTMLElements(nsGkAtoms::dt
, nsGkAtoms::li
,
1298 frame
->AccessibleType() == eHTMLLiType
) {
1299 const nsRoleMapEntry
* contextRoleMap
= aContext
->ARIARoleMap();
1300 if (!contextRoleMap
->IsOfType(eList
)) {
1301 roleMapEntry
= &aria::gEmptyRoleMap
;
1308 if (!newAcc
&& content
->IsXULElement()) {
1309 if (content
->IsXULElement(nsGkAtoms::panel
)) {
1310 // We filter here instead of in the XUL map because
1311 // if we filter there and return null, we still end up
1312 // creating a generic accessible at the end of this function.
1313 // Doing the filtering here ensures we never create accessibles
1314 // for panels whose popups aren't visible.
1315 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
);
1320 nsPopupState popupState
= popupFrame
->PopupState();
1321 if (popupState
== ePopupHiding
|| popupState
== ePopupInvisible
||
1322 popupState
== ePopupClosed
) {
1327 // Prefer to use XUL to decide if and what kind of accessible to create.
1328 const XULMarkupMapInfo
* xulMap
=
1329 mXULMarkupMap
.Get(content
->NodeInfo()->NameAtom());
1330 if (xulMap
&& xulMap
->new_func
) {
1331 newAcc
= xulMap
->new_func(content
->AsElement(), aContext
);
1334 // Any XUL/flex box can be used as tabpanel, make sure we create a proper
1335 // accessible for it.
1336 if (!newAcc
&& aContext
->IsXULTabpanels() &&
1337 content
->GetParent() == aContext
->GetContent()) {
1338 LayoutFrameType frameType
= frame
->Type();
1339 // FIXME(emilio): Why only these frame types?
1340 if (frameType
== LayoutFrameType::FlexContainer
||
1341 frameType
== LayoutFrameType::Scroll
) {
1342 newAcc
= new XULTabpanelAccessible(content
, document
);
1348 if (content
->IsSVGElement()) {
1349 if (content
->IsSVGGeometryElement() ||
1350 content
->IsSVGElement(nsGkAtoms::image
)) {
1351 // Shape elements: rect, circle, ellipse, line, path, polygon,
1352 // and polyline. 'use' and 'text' graphic elements require
1354 if (MustSVGElementBeAccessible(content
, document
)) {
1355 newAcc
= new EnumRoleAccessible
<roles::GRAPHIC
>(content
, document
);
1357 } else if (content
->IsSVGElement(nsGkAtoms::text
)) {
1358 newAcc
= new HyperTextAccessible(content
->AsElement(), document
);
1359 } else if (content
->IsSVGElement(nsGkAtoms::svg
)) {
1360 // An <svg> element could contain <foreignObject>, which contains HTML
1361 // but does not normally create its own Accessible. This means that the
1362 // <svg> Accessible could have TextLeafAccessible children, so it must
1363 // be a HyperTextAccessible.
1365 new EnumRoleHyperTextAccessible
<roles::DIAGRAM
>(content
, document
);
1366 } else if (content
->IsSVGElement(nsGkAtoms::g
) &&
1367 MustSVGElementBeAccessible(content
, document
)) {
1368 // <g> can also contain <foreignObject>.
1370 new EnumRoleHyperTextAccessible
<roles::GROUPING
>(content
, document
);
1371 } else if (content
->IsSVGElement(nsGkAtoms::a
)) {
1372 newAcc
= new HTMLLinkAccessible(content
, document
);
1375 } else if (content
->IsMathMLElement()) {
1376 const MarkupMapInfo
* markupMap
=
1377 mMathMLMarkupMap
.Get(content
->NodeInfo()->NameAtom());
1378 if (markupMap
&& markupMap
->new_func
) {
1379 newAcc
= markupMap
->new_func(content
->AsElement(), aContext
);
1382 // Fall back to text when encountering Content MathML.
1383 if (!newAcc
&& !content
->IsAnyOfMathMLElements(
1384 nsGkAtoms::annotation_
, nsGkAtoms::annotation_xml_
,
1385 nsGkAtoms::mpadded_
, nsGkAtoms::mphantom_
,
1386 nsGkAtoms::maligngroup_
, nsGkAtoms::malignmark_
,
1387 nsGkAtoms::mspace_
, nsGkAtoms::semantics_
)) {
1388 newAcc
= new HyperTextAccessible(content
, document
);
1390 } else if (content
->IsGeneratedContentContainerForMarker()) {
1391 if (aContext
->IsHTMLListItem()) {
1392 newAcc
= new HTMLListBulletAccessible(content
, document
);
1394 if (aIsSubtreeHidden
) {
1395 *aIsSubtreeHidden
= true;
1400 // If no accessible, see if we need to create a generic accessible because
1401 // of some property that makes this object interesting
1402 // We don't do this for <body>, <html>, <window>, <dialog> etc. which
1403 // correspond to the doc accessible and will be created in any case
1404 if (!newAcc
&& !content
->IsHTMLElement(nsGkAtoms::body
) &&
1405 content
->GetParent() &&
1406 (roleMapEntry
|| MustBeAccessible(content
, document
) ||
1407 (content
->IsHTMLElement() && nsCoreUtils::HasClickListener(content
)))) {
1408 // This content is focusable or has an interesting dynamic content
1409 // accessibility property. If it's interesting we need it in the
1410 // accessibility hierarchy so that events or other accessibles can point to
1411 // it, or so that it can hold a state, etc.
1412 if (content
->IsHTMLElement() || content
->IsMathMLElement() ||
1413 content
->IsSVGElement(nsGkAtoms::foreignObject
)) {
1414 // Interesting container which may have selectable text and/or embedded
1416 newAcc
= new HyperTextAccessible(content
, document
);
1417 } else { // XUL, other SVG, etc.
1418 // Interesting generic non-HTML container
1419 newAcc
= new AccessibleWrap(content
, document
);
1421 } else if (!newAcc
&& MustBeGenericAccessible(content
, document
)) {
1422 newAcc
= new EnumRoleHyperTextAccessible
<roles::TEXT_CONTAINER
>(content
,
1427 document
->BindToDocument(newAcc
, roleMapEntry
);
1432 #if defined(ANDROID)
1433 # include "mozilla/Monitor.h"
1434 # include "mozilla/Maybe.h"
1436 static Maybe
<Monitor
> sAndroidMonitor
;
1438 mozilla::Monitor
& nsAccessibilityService::GetAndroidMonitor() {
1439 if (!sAndroidMonitor
.isSome()) {
1440 sAndroidMonitor
.emplace("nsAccessibility::sAndroidMonitor");
1443 return *sAndroidMonitor
;
1447 ////////////////////////////////////////////////////////////////////////////////
1448 // nsAccessibilityService private
1450 bool nsAccessibilityService::Init() {
1451 AUTO_PROFILER_MARKER_TEXT("nsAccessibilityService::Init", A11Y
, {}, ""_ns
);
1452 // DO NOT ADD CODE ABOVE HERE: THIS CODE IS MEASURING TIMINGS.
1454 // Initialize accessible document manager.
1455 if (!DocManager::Init()) return false;
1458 nsCOMPtr
<nsIObserverService
> observerService
=
1459 mozilla::services::GetObserverService();
1460 if (!observerService
) return false;
1462 observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
1465 // This information needs to be initialized before the observer fires.
1466 if (XRE_IsParentProcess()) {
1467 Compatibility::Init();
1469 #endif // defined(XP_WIN)
1471 // Subscribe to EventListenerService.
1472 nsCOMPtr
<nsIEventListenerService
> eventListenerService
=
1473 do_GetService("@mozilla.org/eventlistenerservice;1");
1474 if (!eventListenerService
) return false;
1476 eventListenerService
->AddListenerChangeListener(this);
1478 for (uint32_t i
= 0; i
< ArrayLength(sHTMLMarkupMapList
); i
++) {
1479 mHTMLMarkupMap
.InsertOrUpdate(sHTMLMarkupMapList
[i
].tag
,
1480 &sHTMLMarkupMapList
[i
]);
1482 for (const auto& info
: sMathMLMarkupMapList
) {
1483 mMathMLMarkupMap
.InsertOrUpdate(info
.tag
, &info
);
1486 for (uint32_t i
= 0; i
< ArrayLength(sXULMarkupMapList
); i
++) {
1487 mXULMarkupMap
.InsertOrUpdate(sXULMarkupMapList
[i
].tag
,
1488 &sXULMarkupMapList
[i
]);
1492 logging::CheckEnv();
1495 gAccessibilityService
= this;
1496 NS_ADDREF(gAccessibilityService
); // will release in Shutdown()
1498 if (XRE_IsParentProcess()) {
1499 gApplicationAccessible
= new ApplicationAccessibleWrap();
1501 gApplicationAccessible
= new ApplicationAccessible();
1504 NS_ADDREF(gApplicationAccessible
); // will release in Shutdown()
1505 gApplicationAccessible
->Init();
1507 CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Accessibility
,
1510 // Now its safe to start platform accessibility.
1511 if (XRE_IsParentProcess()) PlatformInit();
1513 statistics::A11yInitialized();
1515 static const char16_t kInitIndicator
[] = {'1', 0};
1516 observerService
->NotifyObservers(nullptr, "a11y-init-or-shutdown",
1522 void nsAccessibilityService::Shutdown() {
1523 // Application is going to be closed, shutdown accessibility and mark
1524 // accessibility service as shutdown to prevent calls of its methods.
1525 // Don't null accessibility service static member at this point to be safe
1526 // if someone will try to operate with it.
1528 MOZ_ASSERT(gConsumers
, "Accessibility was shutdown already");
1529 UnsetConsumers(eXPCOM
| eMainProcess
| ePlatformAPI
);
1531 // Remove observers.
1532 nsCOMPtr
<nsIObserverService
> observerService
=
1533 mozilla::services::GetObserverService();
1534 if (observerService
) {
1535 observerService
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1538 // Stop accessible document loader.
1539 DocManager::Shutdown();
1541 SelectionManager::Shutdown();
1543 if (XRE_IsParentProcess()) PlatformShutdown();
1545 gApplicationAccessible
->Shutdown();
1546 NS_RELEASE(gApplicationAccessible
);
1547 gApplicationAccessible
= nullptr;
1549 NS_IF_RELEASE(gXPCApplicationAccessible
);
1550 gXPCApplicationAccessible
= nullptr;
1552 #if defined(ANDROID)
1553 // Don't allow the service to shut down while an a11y request is being handled
1554 // in the UI thread, as the request may depend on state from the service.
1555 MonitorAutoLock
mal(GetAndroidMonitor());
1557 NS_RELEASE(gAccessibilityService
);
1558 gAccessibilityService
= nullptr;
1560 if (observerService
) {
1561 static const char16_t kShutdownIndicator
[] = {'0', 0};
1562 observerService
->NotifyObservers(nullptr, "a11y-init-or-shutdown",
1563 kShutdownIndicator
);
1567 already_AddRefed
<LocalAccessible
>
1568 nsAccessibilityService::CreateAccessibleByFrameType(nsIFrame
* aFrame
,
1569 nsIContent
* aContent
,
1570 LocalAccessible
* aContext
) {
1571 DocAccessible
* document
= aContext
->Document();
1573 RefPtr
<LocalAccessible
> newAcc
;
1574 switch (aFrame
->AccessibleType()) {
1578 newAcc
= new HTMLBRAccessible(aContent
, document
);
1580 case eHTMLButtonType
:
1581 newAcc
= new HTMLButtonAccessible(aContent
, document
);
1583 case eHTMLCanvasType
:
1584 newAcc
= new HTMLCanvasAccessible(aContent
, document
);
1586 case eHTMLCaptionType
:
1587 if (aContext
->IsTable() &&
1588 aContext
->GetContent() == aContent
->GetParent()) {
1589 newAcc
= new HTMLCaptionAccessible(aContent
, document
);
1592 case eHTMLCheckboxType
:
1593 newAcc
= new CheckboxAccessible(aContent
, document
);
1595 case eHTMLComboboxType
:
1596 newAcc
= new HTMLComboboxAccessible(aContent
, document
);
1598 case eHTMLFileInputType
:
1599 newAcc
= new HTMLFileInputAccessible(aContent
, document
);
1601 case eHTMLGroupboxType
:
1602 newAcc
= new HTMLGroupboxAccessible(aContent
, document
);
1605 newAcc
= new HTMLHRAccessible(aContent
, document
);
1607 case eHTMLImageMapType
:
1608 newAcc
= new HTMLImageMapAccessible(aContent
, document
);
1611 if (aContext
->IsList() &&
1612 aContext
->GetContent() == aContent
->GetParent()) {
1613 newAcc
= new HTMLLIAccessible(aContent
, document
);
1615 // Otherwise create a generic text accessible to avoid text jamming.
1616 newAcc
= new HyperTextAccessible(aContent
, document
);
1619 case eHTMLSelectListType
:
1620 newAcc
= new HTMLSelectListAccessible(aContent
, document
);
1622 case eHTMLMediaType
:
1623 newAcc
= new EnumRoleAccessible
<roles::GROUPING
>(aContent
, document
);
1625 case eHTMLRadioButtonType
:
1626 newAcc
= new HTMLRadioButtonAccessible(aContent
, document
);
1628 case eHTMLRangeType
:
1629 newAcc
= new HTMLRangeAccessible(aContent
, document
);
1631 case eHTMLSpinnerType
:
1632 newAcc
= new HTMLSpinnerAccessible(aContent
, document
);
1634 case eHTMLTableType
:
1635 case eHTMLTableCellType
:
1636 // We handle markup and ARIA tables elsewhere. If we reach here, this is
1637 // a CSS table part. Just create a generic text container.
1638 newAcc
= new HyperTextAccessible(aContent
, document
);
1640 case eHTMLTableRowType
:
1641 // This is a CSS table row. Don't expose it at all.
1643 case eHTMLTextFieldType
:
1644 newAcc
= new HTMLTextFieldAccessible(aContent
, document
);
1646 case eHyperTextType
: {
1647 if (aContext
->IsTable() || aContext
->IsTableRow()) {
1648 // This is some generic hyperText, for example a block frame element
1649 // inserted between a table and table row. Treat it as presentational.
1653 if (!aContent
->IsAnyOfHTMLElements(nsGkAtoms::dt
, nsGkAtoms::dd
,
1654 nsGkAtoms::div
, nsGkAtoms::thead
,
1655 nsGkAtoms::tfoot
, nsGkAtoms::tbody
)) {
1656 newAcc
= new HyperTextAccessible(aContent
, document
);
1661 if (aContent
->IsElement() &&
1662 ShouldCreateImgAccessible(aContent
->AsElement(), document
)) {
1663 newAcc
= new ImageAccessible(aContent
, document
);
1667 newAcc
= new OuterDocAccessible(aContent
, document
);
1670 newAcc
= new TextLeafAccessible(aContent
, document
);
1677 return newAcc
.forget();
1680 void nsAccessibilityService::MarkupAttributes(
1681 Accessible
* aAcc
, AccAttributes
* aAttributes
) const {
1682 const mozilla::a11y::MarkupMapInfo
* markupMap
= GetMarkupMapInfoFor(aAcc
);
1683 if (!markupMap
) return;
1685 dom::Element
* el
= aAcc
->IsLocal() ? aAcc
->AsLocal()->Elm() : nullptr;
1686 for (uint32_t i
= 0; i
< ArrayLength(markupMap
->attrs
); i
++) {
1687 const MarkupAttrInfo
* info
= markupMap
->attrs
+ i
;
1688 if (!info
->name
) break;
1690 if (info
->DOMAttrName
) {
1692 // XXX Expose DOM attributes for cached RemoteAccessibles.
1695 if (info
->DOMAttrValue
) {
1696 if (el
->AttrValueIs(kNameSpaceID_None
, info
->DOMAttrName
,
1697 info
->DOMAttrValue
, eCaseMatters
)) {
1698 aAttributes
->SetAttribute(info
->name
, info
->DOMAttrValue
);
1704 el
->GetAttr(info
->DOMAttrName
, value
);
1706 if (!value
.IsEmpty()) {
1707 aAttributes
->SetAttribute(info
->name
, std::move(value
));
1713 aAttributes
->SetAttribute(info
->name
, info
->value
);
1717 LocalAccessible
* nsAccessibilityService::AddNativeRootAccessible(
1718 void* aAtkAccessible
) {
1719 #ifdef MOZ_ACCESSIBILITY_ATK
1720 ApplicationAccessible
* applicationAcc
= ApplicationAcc();
1721 if (!applicationAcc
) return nullptr;
1723 GtkWindowAccessible
* nativeWnd
=
1724 new GtkWindowAccessible(static_cast<AtkObject
*>(aAtkAccessible
));
1726 if (applicationAcc
->AppendChild(nativeWnd
)) return nativeWnd
;
1732 void nsAccessibilityService::RemoveNativeRootAccessible(
1733 LocalAccessible
* aAccessible
) {
1734 #ifdef MOZ_ACCESSIBILITY_ATK
1735 ApplicationAccessible
* applicationAcc
= ApplicationAcc();
1737 if (applicationAcc
) applicationAcc
->RemoveChild(aAccessible
);
1741 bool nsAccessibilityService::HasAccessible(nsINode
* aDOMNode
) {
1742 if (!aDOMNode
) return false;
1744 Document
* document
= aDOMNode
->OwnerDoc();
1745 if (!document
) return false;
1747 DocAccessible
* docAcc
= GetExistingDocAccessible(aDOMNode
->OwnerDoc());
1748 if (!docAcc
) return false;
1750 return docAcc
->HasAccessible(aDOMNode
);
1753 ////////////////////////////////////////////////////////////////////////////////
1754 // nsAccessibilityService private (DON'T put methods here)
1756 void nsAccessibilityService::SetConsumers(uint32_t aConsumers
, bool aNotify
) {
1757 if (gConsumers
& aConsumers
) {
1761 gConsumers
|= aConsumers
;
1763 NotifyOfConsumersChange();
1767 void nsAccessibilityService::UnsetConsumers(uint32_t aConsumers
) {
1768 if (!(gConsumers
& aConsumers
)) {
1772 gConsumers
&= ~aConsumers
;
1773 NotifyOfConsumersChange();
1776 void nsAccessibilityService::GetConsumers(nsAString
& aString
) {
1777 const char16_t
* kJSONFmt
=
1778 u
"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
1780 nsTextFormatter::ssprintf(json
, kJSONFmt
,
1781 gConsumers
& eXPCOM
? "true" : "false",
1782 gConsumers
& eMainProcess
? "true" : "false",
1783 gConsumers
& ePlatformAPI
? "true" : "false");
1784 aString
.Assign(json
);
1787 void nsAccessibilityService::NotifyOfConsumersChange() {
1788 nsCOMPtr
<nsIObserverService
> observerService
=
1789 mozilla::services::GetObserverService();
1791 if (!observerService
) {
1795 nsAutoString consumers
;
1796 GetConsumers(consumers
);
1797 observerService
->NotifyObservers(nullptr, "a11y-consumers-changed",
1801 const mozilla::a11y::MarkupMapInfo
* nsAccessibilityService::GetMarkupMapInfoFor(
1802 Accessible
* aAcc
) const {
1803 if (LocalAccessible
* localAcc
= aAcc
->AsLocal()) {
1804 return localAcc
->HasOwnContent()
1805 ? GetMarkupMapInfoFor(localAcc
->GetContent())
1808 // XXX For now, we assume all RemoteAccessibles are HTML elements. This
1809 // isn't strictly correct, but as far as current callers are concerned,
1810 // this doesn't matter. If that changes in future, we could expose the
1811 // element type via AccGenericType.
1812 return mHTMLMarkupMap
.Get(aAcc
->TagName());
1815 nsAccessibilityService
* GetOrCreateAccService(uint32_t aNewConsumer
) {
1816 // Do not initialize accessibility if it is force disabled.
1817 if (PlatformDisabledState() == ePlatformIsDisabled
) {
1821 if (!nsAccessibilityService::gAccessibilityService
) {
1822 RefPtr
<nsAccessibilityService
> service
= new nsAccessibilityService();
1823 if (!service
->Init()) {
1824 service
->Shutdown();
1829 MOZ_ASSERT(nsAccessibilityService::gAccessibilityService
,
1830 "LocalAccessible service is not initialized.");
1831 nsAccessibilityService::gAccessibilityService
->SetConsumers(aNewConsumer
);
1832 return nsAccessibilityService::gAccessibilityService
;
1835 void MaybeShutdownAccService(uint32_t aFormerConsumer
) {
1836 nsAccessibilityService
* accService
=
1837 nsAccessibilityService::gAccessibilityService
;
1839 if (!accService
|| nsAccessibilityService::IsShutdown()) {
1843 // Still used by XPCOM
1844 if (nsCoreUtils::AccEventObserversExist() ||
1845 xpcAccessibilityService::IsInUse() || accService
->HasXPCDocuments()) {
1846 // In case the XPCOM flag was unset (possibly because of the shutdown
1847 // timer in the xpcAccessibilityService) ensure it is still present. Note:
1848 // this should be fixed when all the consumer logic is taken out as a
1850 accService
->SetConsumers(nsAccessibilityService::eXPCOM
, false);
1852 if (aFormerConsumer
!= nsAccessibilityService::eXPCOM
) {
1853 // Only unset non-XPCOM consumers.
1854 accService
->UnsetConsumers(aFormerConsumer
);
1859 if (nsAccessibilityService::gConsumers
& ~aFormerConsumer
) {
1860 accService
->UnsetConsumers(aFormerConsumer
);
1863 ->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
1867 ////////////////////////////////////////////////////////////////////////////////
1869 ////////////////////////////////////////////////////////////////////////////////
1874 FocusManager
* FocusMgr() {
1875 return nsAccessibilityService::gAccessibilityService
;
1878 SelectionManager
* SelectionMgr() {
1879 return nsAccessibilityService::gAccessibilityService
;
1882 ApplicationAccessible
* ApplicationAcc() {
1883 return nsAccessibilityService::gApplicationAccessible
;
1886 xpcAccessibleApplication
* XPCApplicationAcc() {
1887 if (!nsAccessibilityService::gXPCApplicationAccessible
&&
1888 nsAccessibilityService::gApplicationAccessible
) {
1889 nsAccessibilityService::gXPCApplicationAccessible
=
1890 new xpcAccessibleApplication(
1891 nsAccessibilityService::gApplicationAccessible
);
1892 NS_ADDREF(nsAccessibilityService::gXPCApplicationAccessible
);
1895 return nsAccessibilityService::gXPCApplicationAccessible
;
1898 EPlatformDisabledState
PlatformDisabledState() {
1899 static bool platformDisabledStateCached
= false;
1900 if (platformDisabledStateCached
) {
1901 return static_cast<EPlatformDisabledState
>(sPlatformDisabledState
);
1904 platformDisabledStateCached
= true;
1905 Preferences::RegisterCallback(PrefChanged
, PREF_ACCESSIBILITY_FORCE_DISABLED
);
1906 return ReadPlatformDisabledState();
1909 EPlatformDisabledState
ReadPlatformDisabledState() {
1910 sPlatformDisabledState
=
1911 Preferences::GetInt(PREF_ACCESSIBILITY_FORCE_DISABLED
, 0);
1912 if (sPlatformDisabledState
< ePlatformIsForceEnabled
) {
1913 sPlatformDisabledState
= ePlatformIsForceEnabled
;
1914 } else if (sPlatformDisabledState
> ePlatformIsDisabled
) {
1915 sPlatformDisabledState
= ePlatformIsDisabled
;
1918 return static_cast<EPlatformDisabledState
>(sPlatformDisabledState
);
1921 void PrefChanged(const char* aPref
, void* aClosure
) {
1922 if (ReadPlatformDisabledState() == ePlatformIsDisabled
) {
1923 // Force shut down accessibility.
1924 nsAccessibilityService
* accService
=
1925 nsAccessibilityService::gAccessibilityService
;
1926 if (accService
&& !nsAccessibilityService::IsShutdown()) {
1927 accService
->Shutdown();
1933 } // namespace mozilla