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::PopovertargetMaybeChanged(PresShell
* aPresShell
,
720 nsIContent
* aContent
) {
721 DocAccessible
* document
= GetDocAccessible(aPresShell
);
725 if (LocalAccessible
* acc
= document
->GetAccessible(aContent
)) {
726 RefPtr
<AccEvent
> expandedChangeEvent
=
727 new AccStateChangeEvent(acc
, states::EXPANDED
);
728 document
->FireDelayedEvent(expandedChangeEvent
);
732 void nsAccessibilityService::ComboboxOptionMaybeChanged(
733 PresShell
* aPresShell
, nsIContent
* aMutatingNode
) {
734 DocAccessible
* document
= GetDocAccessible(aPresShell
);
739 for (nsIContent
* cur
= aMutatingNode
; cur
; cur
= cur
->GetParent()) {
740 if (cur
->IsHTMLElement(nsGkAtoms::option
)) {
741 if (LocalAccessible
* accessible
= document
->GetAccessible(cur
)) {
742 document
->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
,
746 if (cur
->IsHTMLElement(nsGkAtoms::select
)) {
753 void nsAccessibilityService::UpdateText(PresShell
* aPresShell
,
754 nsIContent
* aContent
) {
755 DocAccessible
* document
= GetDocAccessible(aPresShell
);
756 if (document
) document
->UpdateText(aContent
);
759 void nsAccessibilityService::TreeViewChanged(PresShell
* aPresShell
,
760 nsIContent
* aContent
,
761 nsITreeView
* aView
) {
762 DocAccessible
* document
= GetDocAccessible(aPresShell
);
764 LocalAccessible
* accessible
= document
->GetAccessible(aContent
);
766 XULTreeAccessible
* treeAcc
= accessible
->AsXULTree();
767 if (treeAcc
) treeAcc
->TreeViewChanged(aView
);
772 void nsAccessibilityService::RangeValueChanged(PresShell
* aPresShell
,
773 nsIContent
* aContent
) {
774 DocAccessible
* document
= GetDocAccessible(aPresShell
);
776 LocalAccessible
* accessible
= document
->GetAccessible(aContent
);
778 document
->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE
,
784 void nsAccessibilityService::UpdateImageMap(nsImageFrame
* aImageFrame
) {
785 PresShell
* presShell
= aImageFrame
->PresShell();
786 DocAccessible
* document
= GetDocAccessible(presShell
);
788 LocalAccessible
* accessible
=
789 document
->GetAccessible(aImageFrame
->GetContent());
791 HTMLImageMapAccessible
* imageMap
= accessible
->AsImageMap();
793 imageMap
->UpdateChildAreas();
797 // If image map was initialized after we created an accessible (that'll
798 // be an image accessible) then recreate it.
799 RecreateAccessible(presShell
, aImageFrame
->GetContent());
804 void nsAccessibilityService::UpdateLabelValue(PresShell
* aPresShell
,
805 nsIContent
* aLabelElm
,
806 const nsString
& aNewValue
) {
807 DocAccessible
* document
= GetDocAccessible(aPresShell
);
809 LocalAccessible
* accessible
= document
->GetAccessible(aLabelElm
);
811 XULLabelAccessible
* xulLabel
= accessible
->AsXULLabel();
812 NS_ASSERTION(xulLabel
,
813 "UpdateLabelValue was called for wrong accessible!");
814 if (xulLabel
) xulLabel
->UpdateLabelValue(aNewValue
);
819 void nsAccessibilityService::PresShellActivated(PresShell
* aPresShell
) {
820 DocAccessible
* document
= aPresShell
->GetDocAccessible();
822 RootAccessible
* rootDocument
= document
->RootAccessible();
823 NS_ASSERTION(rootDocument
, "Entirely broken tree: no root document!");
824 if (rootDocument
) rootDocument
->DocumentActivated(document
);
828 void nsAccessibilityService::RecreateAccessible(PresShell
* aPresShell
,
829 nsIContent
* aContent
) {
830 DocAccessible
* document
= GetDocAccessible(aPresShell
);
831 if (document
) document
->RecreateAccessible(aContent
);
834 void nsAccessibilityService::GetStringRole(uint32_t aRole
, nsAString
& aString
) {
835 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
836 msaaRole, ia2Role, androidClass, nameRule) \
837 case roles::geckoRole: \
838 aString.AssignLiteral(stringRole); \
844 aString
.AssignLiteral("unknown");
851 void nsAccessibilityService::GetStringStates(uint32_t aState
,
852 uint32_t aExtraState
,
853 nsISupports
** aStringStates
) {
854 RefPtr
<DOMStringList
> stringStates
=
855 GetStringStates(nsAccUtils::To64State(aState
, aExtraState
));
858 if (!stringStates
->Length()) {
859 stringStates
->Add(u
"unknown"_ns
);
862 stringStates
.forget(aStringStates
);
865 already_AddRefed
<DOMStringList
> nsAccessibilityService::GetStringStates(
866 uint64_t aStates
) const {
867 RefPtr
<DOMStringList
> stringStates
= new DOMStringList();
869 if (aStates
& states::UNAVAILABLE
) {
870 stringStates
->Add(u
"unavailable"_ns
);
872 if (aStates
& states::SELECTED
) {
873 stringStates
->Add(u
"selected"_ns
);
875 if (aStates
& states::FOCUSED
) {
876 stringStates
->Add(u
"focused"_ns
);
878 if (aStates
& states::PRESSED
) {
879 stringStates
->Add(u
"pressed"_ns
);
881 if (aStates
& states::CHECKED
) {
882 stringStates
->Add(u
"checked"_ns
);
884 if (aStates
& states::MIXED
) {
885 stringStates
->Add(u
"mixed"_ns
);
887 if (aStates
& states::READONLY
) {
888 stringStates
->Add(u
"readonly"_ns
);
890 if (aStates
& states::HOTTRACKED
) {
891 stringStates
->Add(u
"hottracked"_ns
);
893 if (aStates
& states::DEFAULT
) {
894 stringStates
->Add(u
"default"_ns
);
896 if (aStates
& states::EXPANDED
) {
897 stringStates
->Add(u
"expanded"_ns
);
899 if (aStates
& states::COLLAPSED
) {
900 stringStates
->Add(u
"collapsed"_ns
);
902 if (aStates
& states::BUSY
) {
903 stringStates
->Add(u
"busy"_ns
);
905 if (aStates
& states::FLOATING
) {
906 stringStates
->Add(u
"floating"_ns
);
908 if (aStates
& states::ANIMATED
) {
909 stringStates
->Add(u
"animated"_ns
);
911 if (aStates
& states::INVISIBLE
) {
912 stringStates
->Add(u
"invisible"_ns
);
914 if (aStates
& states::OFFSCREEN
) {
915 stringStates
->Add(u
"offscreen"_ns
);
917 if (aStates
& states::SIZEABLE
) {
918 stringStates
->Add(u
"sizeable"_ns
);
920 if (aStates
& states::MOVEABLE
) {
921 stringStates
->Add(u
"moveable"_ns
);
923 if (aStates
& states::SELFVOICING
) {
924 stringStates
->Add(u
"selfvoicing"_ns
);
926 if (aStates
& states::FOCUSABLE
) {
927 stringStates
->Add(u
"focusable"_ns
);
929 if (aStates
& states::SELECTABLE
) {
930 stringStates
->Add(u
"selectable"_ns
);
932 if (aStates
& states::LINKED
) {
933 stringStates
->Add(u
"linked"_ns
);
935 if (aStates
& states::TRAVERSED
) {
936 stringStates
->Add(u
"traversed"_ns
);
938 if (aStates
& states::MULTISELECTABLE
) {
939 stringStates
->Add(u
"multiselectable"_ns
);
941 if (aStates
& states::EXTSELECTABLE
) {
942 stringStates
->Add(u
"extselectable"_ns
);
944 if (aStates
& states::PROTECTED
) {
945 stringStates
->Add(u
"protected"_ns
);
947 if (aStates
& states::HASPOPUP
) {
948 stringStates
->Add(u
"haspopup"_ns
);
950 if (aStates
& states::REQUIRED
) {
951 stringStates
->Add(u
"required"_ns
);
953 if (aStates
& states::ALERT
) {
954 stringStates
->Add(u
"alert"_ns
);
956 if (aStates
& states::INVALID
) {
957 stringStates
->Add(u
"invalid"_ns
);
959 if (aStates
& states::CHECKABLE
) {
960 stringStates
->Add(u
"checkable"_ns
);
962 if (aStates
& states::SUPPORTS_AUTOCOMPLETION
) {
963 stringStates
->Add(u
"autocompletion"_ns
);
965 if (aStates
& states::DEFUNCT
) {
966 stringStates
->Add(u
"defunct"_ns
);
968 if (aStates
& states::SELECTABLE_TEXT
) {
969 stringStates
->Add(u
"selectable text"_ns
);
971 if (aStates
& states::EDITABLE
) {
972 stringStates
->Add(u
"editable"_ns
);
974 if (aStates
& states::ACTIVE
) {
975 stringStates
->Add(u
"active"_ns
);
977 if (aStates
& states::MODAL
) {
978 stringStates
->Add(u
"modal"_ns
);
980 if (aStates
& states::MULTI_LINE
) {
981 stringStates
->Add(u
"multi line"_ns
);
983 if (aStates
& states::HORIZONTAL
) {
984 stringStates
->Add(u
"horizontal"_ns
);
986 if (aStates
& states::OPAQUE1
) {
987 stringStates
->Add(u
"opaque"_ns
);
989 if (aStates
& states::SINGLE_LINE
) {
990 stringStates
->Add(u
"single line"_ns
);
992 if (aStates
& states::TRANSIENT
) {
993 stringStates
->Add(u
"transient"_ns
);
995 if (aStates
& states::VERTICAL
) {
996 stringStates
->Add(u
"vertical"_ns
);
998 if (aStates
& states::STALE
) {
999 stringStates
->Add(u
"stale"_ns
);
1001 if (aStates
& states::ENABLED
) {
1002 stringStates
->Add(u
"enabled"_ns
);
1004 if (aStates
& states::SENSITIVE
) {
1005 stringStates
->Add(u
"sensitive"_ns
);
1007 if (aStates
& states::EXPANDABLE
) {
1008 stringStates
->Add(u
"expandable"_ns
);
1010 if (aStates
& states::PINNED
) {
1011 stringStates
->Add(u
"pinned"_ns
);
1013 if (aStates
& states::CURRENT
) {
1014 stringStates
->Add(u
"current"_ns
);
1017 return stringStates
.forget();
1020 void nsAccessibilityService::GetStringEventType(uint32_t aEventType
,
1021 nsAString
& aString
) {
1023 nsIAccessibleEvent::EVENT_LAST_ENTRY
== ArrayLength(kEventTypeNames
),
1024 "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
1026 if (aEventType
>= ArrayLength(kEventTypeNames
)) {
1027 aString
.AssignLiteral("unknown");
1031 aString
.AssignASCII(kEventTypeNames
[aEventType
]);
1034 void nsAccessibilityService::GetStringEventType(uint32_t aEventType
,
1035 nsACString
& aString
) {
1037 nsIAccessibleEvent::EVENT_LAST_ENTRY
== ArrayLength(kEventTypeNames
),
1038 "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
1040 if (aEventType
>= ArrayLength(kEventTypeNames
)) {
1041 aString
.AssignLiteral("unknown");
1045 aString
= nsDependentCString(kEventTypeNames
[aEventType
]);
1048 void nsAccessibilityService::GetStringRelationType(uint32_t aRelationType
,
1049 nsAString
& aString
) {
1050 NS_ENSURE_TRUE_VOID(aRelationType
<=
1051 static_cast<uint32_t>(RelationType::LAST
));
1053 #define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
1054 case RelationType::geckoType: \
1055 aString.AssignLiteral(geckoTypeName); \
1058 RelationType relationType
= static_cast<RelationType
>(aRelationType
);
1059 switch (relationType
) {
1060 #include "RelationTypeMap.h"
1062 aString
.AssignLiteral("unknown");
1069 ////////////////////////////////////////////////////////////////////////////////
1070 // nsAccessibilityService public
1072 LocalAccessible
* nsAccessibilityService::CreateAccessible(
1073 nsINode
* aNode
, LocalAccessible
* aContext
, bool* aIsSubtreeHidden
) {
1074 MOZ_ASSERT(aContext
, "No context provided");
1075 MOZ_ASSERT(aNode
, "No node to create an accessible for");
1076 MOZ_ASSERT(gConsumers
, "No creation after shutdown");
1078 if (aIsSubtreeHidden
) *aIsSubtreeHidden
= false;
1080 DocAccessible
* document
= aContext
->Document();
1081 MOZ_ASSERT(!document
->GetAccessible(aNode
),
1082 "We already have an accessible for this node.");
1084 if (aNode
->IsDocument()) {
1085 // If it's document node then ask accessible document loader for
1086 // document accessible, otherwise return null.
1087 return GetDocAccessible(aNode
->AsDocument());
1090 // We have a content node.
1091 if (!aNode
->GetComposedDoc()) {
1092 NS_WARNING("Creating accessible for node with no document");
1096 if (aNode
->OwnerDoc() != document
->DocumentNode()) {
1097 NS_ERROR("Creating accessible for wrong document");
1101 if (!aNode
->IsContent()) return nullptr;
1103 nsIContent
* content
= aNode
->AsContent();
1104 if (aria::HasDefinedARIAHidden(content
)) {
1105 if (aIsSubtreeHidden
) {
1106 *aIsSubtreeHidden
= true;
1111 // Check frame and its visibility.
1112 nsIFrame
* frame
= content
->GetPrimaryFrame();
1114 // If invisible or inert, we don't create an accessible, but we don't mark
1115 // it with aIsSubtreeHidden = true, since visibility: hidden frame allows
1116 // visible elements in subtree, and inert elements allow non-inert
1118 if (!frame
->StyleVisibility()->IsVisible() || frame
->StyleUI()->IsInert()) {
1121 } else if (nsCoreUtils::CanCreateAccessibleWithoutFrame(content
)) {
1122 // display:contents element doesn't have a frame, but retains the
1123 // semantics. All its children are unaffected.
1124 const nsRoleMapEntry
* roleMapEntry
= aria::GetRoleMap(content
->AsElement());
1125 RefPtr
<LocalAccessible
> newAcc
= MaybeCreateSpecificARIAAccessible(
1126 roleMapEntry
, aContext
, content
, document
);
1127 const MarkupMapInfo
* markupMap
= nullptr;
1129 markupMap
= GetMarkupMapInfoFor(content
);
1130 if (markupMap
&& markupMap
->new_func
) {
1131 newAcc
= markupMap
->new_func(content
->AsElement(), aContext
);
1135 // Check whether this element has an ARIA role or attribute that requires
1136 // us to create an Accessible.
1137 const bool hasNonPresentationalARIARole
=
1138 roleMapEntry
&& !roleMapEntry
->Is(nsGkAtoms::presentation
) &&
1139 !roleMapEntry
->Is(nsGkAtoms::none
);
1140 if (!newAcc
&& (hasNonPresentationalARIARole
||
1141 AttributesMustBeAccessible(content
, document
))) {
1142 newAcc
= new HyperTextAccessible(content
, document
);
1145 // If there's still no Accessible but we do have an entry in the markup
1146 // map for this non-presentational element, create a generic
1147 // HyperTextAccessible.
1148 if (!newAcc
&& markupMap
&&
1149 (!roleMapEntry
|| hasNonPresentationalARIARole
)) {
1150 newAcc
= new HyperTextAccessible(content
, document
);
1154 document
->BindToDocument(newAcc
, roleMapEntry
);
1158 if (aIsSubtreeHidden
) {
1159 *aIsSubtreeHidden
= true;
1164 if (frame
->IsHiddenByContentVisibilityOnAnyAncestor(
1165 nsIFrame::IncludeContentVisibility::Hidden
)) {
1166 if (aIsSubtreeHidden
) {
1167 *aIsSubtreeHidden
= true;
1172 if (nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
)) {
1173 // Hidden tooltips and panels don't create accessibles in the whole subtree.
1174 // Showing them gets handled by RootAccessible::ProcessDOMEvent.
1175 if (content
->IsAnyOfXULElements(nsGkAtoms::tooltip
, nsGkAtoms::panel
)) {
1176 nsPopupState popupState
= popupFrame
->PopupState();
1177 if (popupState
== ePopupHiding
|| popupState
== ePopupInvisible
||
1178 popupState
== ePopupClosed
) {
1179 if (aIsSubtreeHidden
) {
1180 *aIsSubtreeHidden
= true;
1187 if (frame
->GetContent() != content
) {
1188 // Not the main content for this frame. This happens because <area>
1189 // elements return the image frame as their primary frame. The main content
1190 // for the image frame is the image content. If the frame is not an image
1191 // frame or the node is not an area element then null is returned.
1192 // This setup will change when bug 135040 is fixed. Make sure we don't
1193 // create area accessible here. Hopefully assertion below will handle that.
1196 nsImageFrame
* imageFrame
= do_QueryFrame(frame
);
1197 NS_ASSERTION(imageFrame
&& content
->IsHTMLElement(nsGkAtoms::area
),
1198 "Unknown case of not main content for the frame!");
1204 nsImageFrame
* imageFrame
= do_QueryFrame(frame
);
1205 NS_ASSERTION(!imageFrame
|| !content
->IsHTMLElement(nsGkAtoms::area
),
1206 "Image map manages the area accessible creation!");
1209 // Attempt to create an accessible based on what we know.
1210 RefPtr
<LocalAccessible
> newAcc
;
1212 // Create accessible for visible text frames.
1213 if (content
->IsText()) {
1214 nsIFrame::RenderedText text
= frame
->GetRenderedText(
1215 0, UINT32_MAX
, nsIFrame::TextOffsetType::OffsetsInContentText
,
1216 nsIFrame::TrailingWhitespace::DontTrim
);
1217 // Ignore not rendered text nodes and whitespace text nodes between table
1219 if (text
.mString
.IsEmpty() ||
1220 (aContext
->IsTableRow() &&
1221 nsCoreUtils::IsWhitespaceString(text
.mString
))) {
1222 if (aIsSubtreeHidden
) *aIsSubtreeHidden
= true;
1227 newAcc
= CreateAccessibleByFrameType(frame
, content
, aContext
);
1228 MOZ_ASSERT(newAcc
, "Accessible not created for text node!");
1229 document
->BindToDocument(newAcc
, nullptr);
1230 newAcc
->AsTextLeaf()->SetText(text
.mString
);
1234 if (content
->IsHTMLElement(nsGkAtoms::map
)) {
1235 // Create hyper text accessible for HTML map if it is used to group links
1236 // (see http://www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass). If the HTML
1237 // map rect is empty then it is used for links grouping. Otherwise it should
1238 // be used in conjunction with HTML image element and in this case we don't
1239 // create any accessible for it and don't walk into it. The accessibles for
1240 // HTML area (HTMLAreaAccessible) the map contains are attached as
1241 // children of the appropriate accessible for HTML image
1242 // (ImageAccessible).
1243 if (nsLayoutUtils::GetAllInFlowRectsUnion(frame
, frame
->GetParent())
1245 if (aIsSubtreeHidden
) *aIsSubtreeHidden
= true;
1250 newAcc
= new HyperTextAccessible(content
, document
);
1251 document
->BindToDocument(newAcc
, aria::GetRoleMap(content
->AsElement()));
1255 const nsRoleMapEntry
* roleMapEntry
= aria::GetRoleMap(content
->AsElement());
1257 if (roleMapEntry
&& (roleMapEntry
->Is(nsGkAtoms::presentation
) ||
1258 roleMapEntry
->Is(nsGkAtoms::none
))) {
1259 if (MustBeAccessible(content
, document
)) {
1260 // If the element is focusable, a global ARIA attribute is applied to it
1261 // or it is referenced by an ARIA relationship, then treat
1262 // role="presentation" on the element as if the role is not there.
1263 roleMapEntry
= nullptr;
1264 } else if (MustBeGenericAccessible(content
, document
)) {
1265 // Clear roleMapEntry so that we use the generic role specified below.
1266 // Otherwise, we'd expose roles::NOTHING as specified for presentation in
1268 roleMapEntry
= nullptr;
1269 newAcc
= new EnumRoleHyperTextAccessible
<roles::TEXT_CONTAINER
>(content
,
1276 // We should always use OuterDocAccessible for OuterDocs, even if there's a
1277 // specific ARIA class we would otherwise use.
1278 if (!newAcc
&& frame
->AccessibleType() != eOuterDocType
) {
1279 newAcc
= MaybeCreateSpecificARIAAccessible(roleMapEntry
, aContext
, content
,
1283 if (!newAcc
&& content
->IsHTMLElement()) { // HTML accessibles
1284 // Prefer to use markup to decide if and what kind of accessible to
1286 const MarkupMapInfo
* markupMap
=
1287 mHTMLMarkupMap
.Get(content
->NodeInfo()->NameAtom());
1288 if (markupMap
&& markupMap
->new_func
) {
1289 newAcc
= markupMap
->new_func(content
->AsElement(), aContext
);
1292 if (!newAcc
) { // try by frame accessible type.
1293 newAcc
= CreateAccessibleByFrameType(frame
, content
, aContext
);
1296 // If table has strong ARIA role then all table descendants shouldn't
1297 // expose their native roles.
1298 if (!roleMapEntry
&& newAcc
&& aContext
->HasStrongARIARole()) {
1299 if (frame
->AccessibleType() == eHTMLTableRowType
) {
1300 const nsRoleMapEntry
* contextRoleMap
= aContext
->ARIARoleMap();
1301 if (!contextRoleMap
->IsOfType(eTable
)) {
1302 roleMapEntry
= &aria::gEmptyRoleMap
;
1305 } else if (frame
->AccessibleType() == eHTMLTableCellType
&&
1306 aContext
->ARIARoleMap() == &aria::gEmptyRoleMap
) {
1307 roleMapEntry
= &aria::gEmptyRoleMap
;
1309 } else if (content
->IsAnyOfHTMLElements(nsGkAtoms::dt
, nsGkAtoms::li
,
1311 frame
->AccessibleType() == eHTMLLiType
) {
1312 const nsRoleMapEntry
* contextRoleMap
= aContext
->ARIARoleMap();
1313 if (!contextRoleMap
->IsOfType(eList
)) {
1314 roleMapEntry
= &aria::gEmptyRoleMap
;
1321 if (!newAcc
&& content
->IsXULElement()) {
1322 if (content
->IsXULElement(nsGkAtoms::panel
)) {
1323 // We filter here instead of in the XUL map because
1324 // if we filter there and return null, we still end up
1325 // creating a generic accessible at the end of this function.
1326 // Doing the filtering here ensures we never create accessibles
1327 // for panels whose popups aren't visible.
1328 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
);
1333 nsPopupState popupState
= popupFrame
->PopupState();
1334 if (popupState
== ePopupHiding
|| popupState
== ePopupInvisible
||
1335 popupState
== ePopupClosed
) {
1340 // Prefer to use XUL to decide if and what kind of accessible to create.
1341 const XULMarkupMapInfo
* xulMap
=
1342 mXULMarkupMap
.Get(content
->NodeInfo()->NameAtom());
1343 if (xulMap
&& xulMap
->new_func
) {
1344 newAcc
= xulMap
->new_func(content
->AsElement(), aContext
);
1347 // Any XUL/flex box can be used as tabpanel, make sure we create a proper
1348 // accessible for it.
1349 if (!newAcc
&& aContext
->IsXULTabpanels() &&
1350 content
->GetParent() == aContext
->GetContent()) {
1351 LayoutFrameType frameType
= frame
->Type();
1352 // FIXME(emilio): Why only these frame types?
1353 if (frameType
== LayoutFrameType::FlexContainer
||
1354 frameType
== LayoutFrameType::Scroll
) {
1355 newAcc
= new XULTabpanelAccessible(content
, document
);
1361 if (content
->IsSVGElement()) {
1362 if (content
->IsSVGGeometryElement() ||
1363 content
->IsSVGElement(nsGkAtoms::image
)) {
1364 // Shape elements: rect, circle, ellipse, line, path, polygon,
1365 // and polyline. 'use' and 'text' graphic elements require
1367 if (MustSVGElementBeAccessible(content
, document
)) {
1368 newAcc
= new EnumRoleAccessible
<roles::GRAPHIC
>(content
, document
);
1370 } else if (content
->IsSVGElement(nsGkAtoms::text
)) {
1371 newAcc
= new HyperTextAccessible(content
->AsElement(), document
);
1372 } else if (content
->IsSVGElement(nsGkAtoms::svg
)) {
1373 // An <svg> element could contain <foreignObject>, which contains HTML
1374 // but does not normally create its own Accessible. This means that the
1375 // <svg> Accessible could have TextLeafAccessible children, so it must
1376 // be a HyperTextAccessible.
1378 new EnumRoleHyperTextAccessible
<roles::DIAGRAM
>(content
, document
);
1379 } else if (content
->IsSVGElement(nsGkAtoms::g
) &&
1380 MustSVGElementBeAccessible(content
, document
)) {
1381 // <g> can also contain <foreignObject>.
1383 new EnumRoleHyperTextAccessible
<roles::GROUPING
>(content
, document
);
1384 } else if (content
->IsSVGElement(nsGkAtoms::a
)) {
1385 newAcc
= new HTMLLinkAccessible(content
, document
);
1388 } else if (content
->IsMathMLElement()) {
1389 const MarkupMapInfo
* markupMap
=
1390 mMathMLMarkupMap
.Get(content
->NodeInfo()->NameAtom());
1391 if (markupMap
&& markupMap
->new_func
) {
1392 newAcc
= markupMap
->new_func(content
->AsElement(), aContext
);
1395 // Fall back to text when encountering Content MathML.
1396 if (!newAcc
&& !content
->IsAnyOfMathMLElements(
1397 nsGkAtoms::annotation_
, nsGkAtoms::annotation_xml_
,
1398 nsGkAtoms::mpadded_
, nsGkAtoms::mphantom_
,
1399 nsGkAtoms::maligngroup_
, nsGkAtoms::malignmark_
,
1400 nsGkAtoms::mspace_
, nsGkAtoms::semantics_
)) {
1401 newAcc
= new HyperTextAccessible(content
, document
);
1403 } else if (content
->IsGeneratedContentContainerForMarker()) {
1404 if (aContext
->IsHTMLListItem()) {
1405 newAcc
= new HTMLListBulletAccessible(content
, document
);
1407 if (aIsSubtreeHidden
) {
1408 *aIsSubtreeHidden
= true;
1413 // If no accessible, see if we need to create a generic accessible because
1414 // of some property that makes this object interesting
1415 // We don't do this for <body>, <html>, <window>, <dialog> etc. which
1416 // correspond to the doc accessible and will be created in any case
1417 if (!newAcc
&& !content
->IsHTMLElement(nsGkAtoms::body
) &&
1418 content
->GetParent() &&
1419 (roleMapEntry
|| MustBeAccessible(content
, document
) ||
1420 (content
->IsHTMLElement() && nsCoreUtils::HasClickListener(content
)))) {
1421 // This content is focusable or has an interesting dynamic content
1422 // accessibility property. If it's interesting we need it in the
1423 // accessibility hierarchy so that events or other accessibles can point to
1424 // it, or so that it can hold a state, etc.
1425 if (content
->IsHTMLElement() || content
->IsMathMLElement() ||
1426 content
->IsSVGElement(nsGkAtoms::foreignObject
)) {
1427 // Interesting container which may have selectable text and/or embedded
1429 newAcc
= new HyperTextAccessible(content
, document
);
1430 } else { // XUL, other SVG, etc.
1431 // Interesting generic non-HTML container
1432 newAcc
= new AccessibleWrap(content
, document
);
1434 } else if (!newAcc
&& MustBeGenericAccessible(content
, document
)) {
1435 newAcc
= new EnumRoleHyperTextAccessible
<roles::TEXT_CONTAINER
>(content
,
1440 document
->BindToDocument(newAcc
, roleMapEntry
);
1445 #if defined(ANDROID)
1446 # include "mozilla/Monitor.h"
1447 # include "mozilla/Maybe.h"
1449 static Maybe
<Monitor
> sAndroidMonitor
;
1451 mozilla::Monitor
& nsAccessibilityService::GetAndroidMonitor() {
1452 if (!sAndroidMonitor
.isSome()) {
1453 sAndroidMonitor
.emplace("nsAccessibility::sAndroidMonitor");
1456 return *sAndroidMonitor
;
1460 ////////////////////////////////////////////////////////////////////////////////
1461 // nsAccessibilityService private
1463 bool nsAccessibilityService::Init() {
1464 AUTO_PROFILER_MARKER_TEXT("nsAccessibilityService::Init", A11Y
, {}, ""_ns
);
1465 // DO NOT ADD CODE ABOVE HERE: THIS CODE IS MEASURING TIMINGS.
1467 // Initialize accessible document manager.
1468 if (!DocManager::Init()) return false;
1471 nsCOMPtr
<nsIObserverService
> observerService
=
1472 mozilla::services::GetObserverService();
1473 if (!observerService
) return false;
1475 observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
1478 // This information needs to be initialized before the observer fires.
1479 if (XRE_IsParentProcess()) {
1480 Compatibility::Init();
1482 #endif // defined(XP_WIN)
1484 // Subscribe to EventListenerService.
1485 nsCOMPtr
<nsIEventListenerService
> eventListenerService
=
1486 do_GetService("@mozilla.org/eventlistenerservice;1");
1487 if (!eventListenerService
) return false;
1489 eventListenerService
->AddListenerChangeListener(this);
1491 for (uint32_t i
= 0; i
< ArrayLength(sHTMLMarkupMapList
); i
++) {
1492 mHTMLMarkupMap
.InsertOrUpdate(sHTMLMarkupMapList
[i
].tag
,
1493 &sHTMLMarkupMapList
[i
]);
1495 for (const auto& info
: sMathMLMarkupMapList
) {
1496 mMathMLMarkupMap
.InsertOrUpdate(info
.tag
, &info
);
1499 for (uint32_t i
= 0; i
< ArrayLength(sXULMarkupMapList
); i
++) {
1500 mXULMarkupMap
.InsertOrUpdate(sXULMarkupMapList
[i
].tag
,
1501 &sXULMarkupMapList
[i
]);
1505 logging::CheckEnv();
1508 gAccessibilityService
= this;
1509 NS_ADDREF(gAccessibilityService
); // will release in Shutdown()
1511 if (XRE_IsParentProcess()) {
1512 gApplicationAccessible
= new ApplicationAccessibleWrap();
1514 gApplicationAccessible
= new ApplicationAccessible();
1517 NS_ADDREF(gApplicationAccessible
); // will release in Shutdown()
1518 gApplicationAccessible
->Init();
1520 CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Accessibility
,
1523 // Now its safe to start platform accessibility.
1524 if (XRE_IsParentProcess()) PlatformInit();
1526 statistics::A11yInitialized();
1528 static const char16_t kInitIndicator
[] = {'1', 0};
1529 observerService
->NotifyObservers(nullptr, "a11y-init-or-shutdown",
1535 void nsAccessibilityService::Shutdown() {
1536 // Application is going to be closed, shutdown accessibility and mark
1537 // accessibility service as shutdown to prevent calls of its methods.
1538 // Don't null accessibility service static member at this point to be safe
1539 // if someone will try to operate with it.
1541 MOZ_ASSERT(gConsumers
, "Accessibility was shutdown already");
1542 UnsetConsumers(eXPCOM
| eMainProcess
| ePlatformAPI
);
1544 // Remove observers.
1545 nsCOMPtr
<nsIObserverService
> observerService
=
1546 mozilla::services::GetObserverService();
1547 if (observerService
) {
1548 observerService
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1551 // Stop accessible document loader.
1552 DocManager::Shutdown();
1554 SelectionManager::Shutdown();
1556 if (XRE_IsParentProcess()) PlatformShutdown();
1558 gApplicationAccessible
->Shutdown();
1559 NS_RELEASE(gApplicationAccessible
);
1560 gApplicationAccessible
= nullptr;
1562 NS_IF_RELEASE(gXPCApplicationAccessible
);
1563 gXPCApplicationAccessible
= nullptr;
1565 #if defined(ANDROID)
1566 // Don't allow the service to shut down while an a11y request is being handled
1567 // in the UI thread, as the request may depend on state from the service.
1568 MonitorAutoLock
mal(GetAndroidMonitor());
1570 NS_RELEASE(gAccessibilityService
);
1571 gAccessibilityService
= nullptr;
1573 if (observerService
) {
1574 static const char16_t kShutdownIndicator
[] = {'0', 0};
1575 observerService
->NotifyObservers(nullptr, "a11y-init-or-shutdown",
1576 kShutdownIndicator
);
1580 already_AddRefed
<LocalAccessible
>
1581 nsAccessibilityService::CreateAccessibleByFrameType(nsIFrame
* aFrame
,
1582 nsIContent
* aContent
,
1583 LocalAccessible
* aContext
) {
1584 DocAccessible
* document
= aContext
->Document();
1586 RefPtr
<LocalAccessible
> newAcc
;
1587 switch (aFrame
->AccessibleType()) {
1591 newAcc
= new HTMLBRAccessible(aContent
, document
);
1593 case eHTMLButtonType
:
1594 newAcc
= new HTMLButtonAccessible(aContent
, document
);
1596 case eHTMLCanvasType
:
1597 newAcc
= new HTMLCanvasAccessible(aContent
, document
);
1599 case eHTMLCaptionType
:
1600 if (aContext
->IsTable() &&
1601 aContext
->GetContent() == aContent
->GetParent()) {
1602 newAcc
= new HTMLCaptionAccessible(aContent
, document
);
1605 case eHTMLCheckboxType
:
1606 newAcc
= new CheckboxAccessible(aContent
, document
);
1608 case eHTMLComboboxType
:
1609 newAcc
= new HTMLComboboxAccessible(aContent
, document
);
1611 case eHTMLFileInputType
:
1612 newAcc
= new HTMLFileInputAccessible(aContent
, document
);
1614 case eHTMLGroupboxType
:
1615 newAcc
= new HTMLGroupboxAccessible(aContent
, document
);
1618 newAcc
= new HTMLHRAccessible(aContent
, document
);
1620 case eHTMLImageMapType
:
1621 newAcc
= new HTMLImageMapAccessible(aContent
, document
);
1624 if (aContext
->IsList() &&
1625 aContext
->GetContent() == aContent
->GetParent()) {
1626 newAcc
= new HTMLLIAccessible(aContent
, document
);
1628 // Otherwise create a generic text accessible to avoid text jamming.
1629 newAcc
= new HyperTextAccessible(aContent
, document
);
1632 case eHTMLSelectListType
:
1633 newAcc
= new HTMLSelectListAccessible(aContent
, document
);
1635 case eHTMLMediaType
:
1636 newAcc
= new EnumRoleAccessible
<roles::GROUPING
>(aContent
, document
);
1638 case eHTMLRadioButtonType
:
1639 newAcc
= new HTMLRadioButtonAccessible(aContent
, document
);
1641 case eHTMLRangeType
:
1642 newAcc
= new HTMLRangeAccessible(aContent
, document
);
1644 case eHTMLSpinnerType
:
1645 newAcc
= new HTMLSpinnerAccessible(aContent
, document
);
1647 case eHTMLTableType
:
1648 case eHTMLTableCellType
:
1649 // We handle markup and ARIA tables elsewhere. If we reach here, this is
1650 // a CSS table part. Just create a generic text container.
1651 newAcc
= new HyperTextAccessible(aContent
, document
);
1653 case eHTMLTableRowType
:
1654 // This is a CSS table row. Don't expose it at all.
1656 case eHTMLTextFieldType
:
1657 newAcc
= new HTMLTextFieldAccessible(aContent
, document
);
1659 case eHyperTextType
: {
1660 if (aContext
->IsTable() || aContext
->IsTableRow()) {
1661 // This is some generic hyperText, for example a block frame element
1662 // inserted between a table and table row. Treat it as presentational.
1666 if (!aContent
->IsAnyOfHTMLElements(nsGkAtoms::dt
, nsGkAtoms::dd
,
1667 nsGkAtoms::div
, nsGkAtoms::thead
,
1668 nsGkAtoms::tfoot
, nsGkAtoms::tbody
)) {
1669 newAcc
= new HyperTextAccessible(aContent
, document
);
1674 if (aContent
->IsElement() &&
1675 ShouldCreateImgAccessible(aContent
->AsElement(), document
)) {
1676 newAcc
= new ImageAccessible(aContent
, document
);
1680 newAcc
= new OuterDocAccessible(aContent
, document
);
1683 newAcc
= new TextLeafAccessible(aContent
, document
);
1690 return newAcc
.forget();
1693 void nsAccessibilityService::MarkupAttributes(
1694 Accessible
* aAcc
, AccAttributes
* aAttributes
) const {
1695 const mozilla::a11y::MarkupMapInfo
* markupMap
= GetMarkupMapInfoFor(aAcc
);
1696 if (!markupMap
) return;
1698 dom::Element
* el
= aAcc
->IsLocal() ? aAcc
->AsLocal()->Elm() : nullptr;
1699 for (uint32_t i
= 0; i
< ArrayLength(markupMap
->attrs
); i
++) {
1700 const MarkupAttrInfo
* info
= markupMap
->attrs
+ i
;
1701 if (!info
->name
) break;
1703 if (info
->DOMAttrName
) {
1705 // XXX Expose DOM attributes for cached RemoteAccessibles.
1708 if (info
->DOMAttrValue
) {
1709 if (el
->AttrValueIs(kNameSpaceID_None
, info
->DOMAttrName
,
1710 info
->DOMAttrValue
, eCaseMatters
)) {
1711 aAttributes
->SetAttribute(info
->name
, info
->DOMAttrValue
);
1717 el
->GetAttr(info
->DOMAttrName
, value
);
1719 if (!value
.IsEmpty()) {
1720 aAttributes
->SetAttribute(info
->name
, std::move(value
));
1726 aAttributes
->SetAttribute(info
->name
, info
->value
);
1730 LocalAccessible
* nsAccessibilityService::AddNativeRootAccessible(
1731 void* aAtkAccessible
) {
1732 #ifdef MOZ_ACCESSIBILITY_ATK
1733 ApplicationAccessible
* applicationAcc
= ApplicationAcc();
1734 if (!applicationAcc
) return nullptr;
1736 GtkWindowAccessible
* nativeWnd
=
1737 new GtkWindowAccessible(static_cast<AtkObject
*>(aAtkAccessible
));
1739 if (applicationAcc
->AppendChild(nativeWnd
)) return nativeWnd
;
1745 void nsAccessibilityService::RemoveNativeRootAccessible(
1746 LocalAccessible
* aAccessible
) {
1747 #ifdef MOZ_ACCESSIBILITY_ATK
1748 ApplicationAccessible
* applicationAcc
= ApplicationAcc();
1750 if (applicationAcc
) applicationAcc
->RemoveChild(aAccessible
);
1754 bool nsAccessibilityService::HasAccessible(nsINode
* aDOMNode
) {
1755 if (!aDOMNode
) return false;
1757 Document
* document
= aDOMNode
->OwnerDoc();
1758 if (!document
) return false;
1760 DocAccessible
* docAcc
= GetExistingDocAccessible(aDOMNode
->OwnerDoc());
1761 if (!docAcc
) return false;
1763 return docAcc
->HasAccessible(aDOMNode
);
1766 ////////////////////////////////////////////////////////////////////////////////
1767 // nsAccessibilityService private (DON'T put methods here)
1769 void nsAccessibilityService::SetConsumers(uint32_t aConsumers
, bool aNotify
) {
1770 if (gConsumers
& aConsumers
) {
1774 gConsumers
|= aConsumers
;
1776 NotifyOfConsumersChange();
1780 void nsAccessibilityService::UnsetConsumers(uint32_t aConsumers
) {
1781 if (!(gConsumers
& aConsumers
)) {
1785 gConsumers
&= ~aConsumers
;
1786 NotifyOfConsumersChange();
1789 void nsAccessibilityService::GetConsumers(nsAString
& aString
) {
1790 const char16_t
* kJSONFmt
=
1791 u
"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
1793 nsTextFormatter::ssprintf(json
, kJSONFmt
,
1794 gConsumers
& eXPCOM
? "true" : "false",
1795 gConsumers
& eMainProcess
? "true" : "false",
1796 gConsumers
& ePlatformAPI
? "true" : "false");
1797 aString
.Assign(json
);
1800 void nsAccessibilityService::NotifyOfConsumersChange() {
1801 nsCOMPtr
<nsIObserverService
> observerService
=
1802 mozilla::services::GetObserverService();
1804 if (!observerService
) {
1808 nsAutoString consumers
;
1809 GetConsumers(consumers
);
1810 observerService
->NotifyObservers(nullptr, "a11y-consumers-changed",
1814 const mozilla::a11y::MarkupMapInfo
* nsAccessibilityService::GetMarkupMapInfoFor(
1815 Accessible
* aAcc
) const {
1816 if (LocalAccessible
* localAcc
= aAcc
->AsLocal()) {
1817 return localAcc
->HasOwnContent()
1818 ? GetMarkupMapInfoFor(localAcc
->GetContent())
1821 // XXX For now, we assume all RemoteAccessibles are HTML elements. This
1822 // isn't strictly correct, but as far as current callers are concerned,
1823 // this doesn't matter. If that changes in future, we could expose the
1824 // element type via AccGenericType.
1825 return mHTMLMarkupMap
.Get(aAcc
->TagName());
1828 nsAccessibilityService
* GetOrCreateAccService(uint32_t aNewConsumer
) {
1829 // Do not initialize accessibility if it is force disabled.
1830 if (PlatformDisabledState() == ePlatformIsDisabled
) {
1834 if (!nsAccessibilityService::gAccessibilityService
) {
1835 RefPtr
<nsAccessibilityService
> service
= new nsAccessibilityService();
1836 if (!service
->Init()) {
1837 service
->Shutdown();
1842 MOZ_ASSERT(nsAccessibilityService::gAccessibilityService
,
1843 "LocalAccessible service is not initialized.");
1844 nsAccessibilityService::gAccessibilityService
->SetConsumers(aNewConsumer
);
1845 return nsAccessibilityService::gAccessibilityService
;
1848 void MaybeShutdownAccService(uint32_t aFormerConsumer
) {
1849 nsAccessibilityService
* accService
=
1850 nsAccessibilityService::gAccessibilityService
;
1852 if (!accService
|| nsAccessibilityService::IsShutdown()) {
1856 // Still used by XPCOM
1857 if (nsCoreUtils::AccEventObserversExist() ||
1858 xpcAccessibilityService::IsInUse() || accService
->HasXPCDocuments()) {
1859 // In case the XPCOM flag was unset (possibly because of the shutdown
1860 // timer in the xpcAccessibilityService) ensure it is still present. Note:
1861 // this should be fixed when all the consumer logic is taken out as a
1863 accService
->SetConsumers(nsAccessibilityService::eXPCOM
, false);
1865 if (aFormerConsumer
!= nsAccessibilityService::eXPCOM
) {
1866 // Only unset non-XPCOM consumers.
1867 accService
->UnsetConsumers(aFormerConsumer
);
1872 if (nsAccessibilityService::gConsumers
& ~aFormerConsumer
) {
1873 accService
->UnsetConsumers(aFormerConsumer
);
1876 ->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
1880 ////////////////////////////////////////////////////////////////////////////////
1882 ////////////////////////////////////////////////////////////////////////////////
1887 FocusManager
* FocusMgr() {
1888 return nsAccessibilityService::gAccessibilityService
;
1891 SelectionManager
* SelectionMgr() {
1892 return nsAccessibilityService::gAccessibilityService
;
1895 ApplicationAccessible
* ApplicationAcc() {
1896 return nsAccessibilityService::gApplicationAccessible
;
1899 xpcAccessibleApplication
* XPCApplicationAcc() {
1900 if (!nsAccessibilityService::gXPCApplicationAccessible
&&
1901 nsAccessibilityService::gApplicationAccessible
) {
1902 nsAccessibilityService::gXPCApplicationAccessible
=
1903 new xpcAccessibleApplication(
1904 nsAccessibilityService::gApplicationAccessible
);
1905 NS_ADDREF(nsAccessibilityService::gXPCApplicationAccessible
);
1908 return nsAccessibilityService::gXPCApplicationAccessible
;
1911 EPlatformDisabledState
PlatformDisabledState() {
1912 static bool platformDisabledStateCached
= false;
1913 if (platformDisabledStateCached
) {
1914 return static_cast<EPlatformDisabledState
>(sPlatformDisabledState
);
1917 platformDisabledStateCached
= true;
1918 Preferences::RegisterCallback(PrefChanged
, PREF_ACCESSIBILITY_FORCE_DISABLED
);
1919 return ReadPlatformDisabledState();
1922 EPlatformDisabledState
ReadPlatformDisabledState() {
1923 sPlatformDisabledState
=
1924 Preferences::GetInt(PREF_ACCESSIBILITY_FORCE_DISABLED
, 0);
1925 if (sPlatformDisabledState
< ePlatformIsForceEnabled
) {
1926 sPlatformDisabledState
= ePlatformIsForceEnabled
;
1927 } else if (sPlatformDisabledState
> ePlatformIsDisabled
) {
1928 sPlatformDisabledState
= ePlatformIsDisabled
;
1931 return static_cast<EPlatformDisabledState
>(sPlatformDisabledState
);
1934 void PrefChanged(const char* aPref
, void* aClosure
) {
1935 if (ReadPlatformDisabledState() == ePlatformIsDisabled
) {
1936 // Force shut down accessibility.
1937 nsAccessibilityService
* accService
=
1938 nsAccessibilityService::gAccessibilityService
;
1939 if (accService
&& !nsAccessibilityService::IsShutdown()) {
1940 accService
->Shutdown();
1946 } // namespace mozilla