/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** * Accessibility service force enable/disable preference. * Supported values: * Accessibility is force enabled (accessibility should always be enabled): -1 * Accessibility is enabled (will be started upon a request, default value): 0 * Accessibility is force disabled (never enable accessibility): 1
*/ #define PREF_ACCESSIBILITY_FORCE_DISABLED "accessibility.force_disabled"
/** * If the element has an ARIA attribute that requires a specific Accessible * class, create and return it. Otherwise, return null.
*/ static LocalAccessible* MaybeCreateSpecificARIAAccessible( const nsRoleMapEntry* aRoleMapEntry, const LocalAccessible* aContext,
nsIContent* aContent, DocAccessible* aDocument) { if (aRoleMapEntry && aRoleMapEntry->accTypes & eTableCell) { if (aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th) &&
aContext->IsHTMLTableRow()) { // Don't use ARIAGridCellAccessible for a valid td/th because // HTMLTableCellAccessible can provide additional info; e.g. row/col span // from the layout engine. return nullptr;
} // A cell must be in a row. const Accessible* parent = aContext; if (parent->IsGeneric()) {
parent = parent->GetNonGenericParent();
} if (!parent || parent->Role() != roles::ROW) { return nullptr;
} // That row must be in a table, though there may be an intervening rowgroup.
parent = parent->GetNonGenericParent(); if (!parent) { return nullptr;
} if (!parent->IsTable() && parent->Role() == roles::ROWGROUP) {
parent = parent->GetNonGenericParent(); if (!parent) { return nullptr;
}
} if (parent->IsTable()) { returnnew ARIAGridCellAccessible(aContent, aDocument);
}
} return nullptr;
}
// Send a request to all content processes that they build and send back // information about the given cache domains. staticbool SendCacheDomainRequestToAllContentProcesses(
uint64_t aCacheDomains) { if (!XRE_IsParentProcess()) { returnfalse;
} bool sentAll = true;
nsTArray<ContentParent*> contentParents;
ContentParent::GetAll(contentParents); for (auto* parent : contentParents) {
sentAll = sentAll && parent->SendSetCacheDomains(aCacheDomains);
} return sentAll;
}
/** * Return true if the element must be a generic Accessible, even if it has been * marked presentational with role="presentation", etc. MustBeAccessible causes * an Accessible to be created as if it weren't marked presentational at all; * e.g. <table role="presentation" tabindex="0"> will expose roles::TABLE and * support TableAccessible. In contrast, this function causes a generic * Accessible to be created; e.g. <table role="presentation" style="position: * fixed;"> will expose roles::TEXT_CONTAINER and will not support * TableAccessible. This is necessary in certain cases for the * RemoteAccessible cache.
*/ staticbool MustBeGenericAccessible(nsIContent* aContent,
DocAccessible* aDocument) { if (aContent->IsInNativeAnonymousSubtree() || aContent->IsSVGElement()) { // We should not force create accs for anonymous content. // This is an issue for inputs, which have an intermediate // container with relevant overflow styling between the input // and its internal input content. // We should also avoid this for SVG elements (ie. `<foreignobject>`s // which have default overflow:hidden styling). returnfalse;
}
nsIFrame* frame = aContent->GetPrimaryFrame();
MOZ_ASSERT(frame);
nsAutoCString overflow;
frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow); // If the frame has been transformed, and the content has any children, we // should create an Accessible so that we can account for the transform when // calculating the Accessible's bounds using the parent process cache. // Ditto for content which is position: fixed or sticky or has overflow // styling (auto, scroll, hidden). // However, don't do this for XUL widgets, as this breaks XUL a11y code // expectations in some cases. XUL widgets are only used in the parent // process and can't be cached anyway. return !aContent->IsXULElement() &&
((aContent->HasChildren() && frame->IsTransformed()) ||
frame->IsStickyPositioned() ||
(frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(frame)) ||
overflow.Equals("auto"_ns) || overflow.Equals("scroll"_ns) ||
overflow.Equals("hidden"_ns));
}
/** * Return true if the element must be accessible.
*/ staticbool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) { if (nsIFrame* frame = aContent->GetPrimaryFrame()) { // This document might be invisible when it first loads. Therefore, we must // check focusability irrespective of visibility here. Otherwise, we might // not create Accessibles for some focusable elements; e.g. a span with only // a tabindex. Elements that are invisible within this document are excluded // earlier in CreateAccessible. if (frame->IsFocusable(IsFocusableFlags::IgnoreVisibility)) { returntrue;
}
}
// Return true if the element has an attribute (ARIA, title, or relation) that // requires the creation of an Accessible for the element. if (aContent->IsElement()) {
uint32_t attrCount = aContent->AsElement()->GetAttrCount(); for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) { const nsAttrName* attr = aContent->AsElement()->GetAttrNameAt(attrIdx); if (attr->NamespaceEquals(kNameSpaceID_None)) {
nsAtom* attrAtom = attr->Atom(); if (attrAtom == nsGkAtoms::title && aContent->IsHTMLElement()) { // If the author provided a title on an element that would not // be accessible normally, assume an intent and make it accessible. returntrue;
}
nsDependentAtomString attrStr(attrAtom); if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // not ARIA
// A global state or a property and in case of token defined.
uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom); if ((attrFlags & ATTR_GLOBAL) &&
(!(attrFlags & ATTR_VALTOKEN) ||
nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) { returntrue;
}
}
}
// If the given ID is referred by relation attribute then create an // Accessible for it.
nsAutoString id; if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) { return aDocument->IsDependentID(aContent->AsElement(), id);
}
}
returnfalse;
}
bool nsAccessibilityService::ShouldCreateImgAccessible(
mozilla::dom::Element* aElement, DocAccessible* aDocument) { // The element must have a layout frame for us to proceed. If there is no // frame, the image is likely hidden.
nsIFrame* frame = aElement->GetPrimaryFrame(); if (!frame) { returnfalse;
}
// If the element is not an img, not an embedded image via embed or object, // and not a pseudo-element with CSS content alt text, then we should not // create an accessible. if (!aElement->IsHTMLElement(nsGkAtoms::img) &&
((!aElement->IsHTMLElement(nsGkAtoms::embed) &&
!aElement->IsHTMLElement(nsGkAtoms::object)) ||
frame->AccessibleType() != AccType::eImageType) &&
!CssAltContent(aElement)) { returnfalse;
}
nsAutoString newAltText; constbool hasAlt = aElement->GetAttr(nsGkAtoms::alt, newAltText); if (!hasAlt || !newAltText.IsEmpty()) { // If there is no alt attribute, we should create an accessible. The // author may have missed the attribute, and the AT may want to provide a // name. If there is alt text, we should create an accessible. returntrue;
}
if (newAltText.IsEmpty() && (nsCoreUtils::HasClickListener(aElement) ||
MustBeAccessible(aElement, aDocument))) { // If there is empty alt text, but there is a click listener for this img, // or if it otherwise must be an accessible (e.g., if it has an aria-label // attribute), we should create an accessible. returntrue;
}
// Otherwise, no alt text means we should not create an accessible. returnfalse;
}
/** * Return true if the SVG element should be accessible
*/ staticbool MustSVGElementBeAccessible(nsIContent* aContent,
DocAccessible* aDocument) { // https://w3c.github.io/svg-aam/#include_elements for (nsIContent* childElm = aContent->GetFirstChild(); childElm;
childElm = childElm->GetNextSibling()) { if (childElm->IsAnyOfSVGElements(nsGkAtoms::title, nsGkAtoms::desc)) { returntrue;
}
} return MustBeAccessible(aContent, aDocument);
}
/** * Return an accessible for the content if the SVG element requires the creation * of an Accessible.
*/ static RefPtr<LocalAccessible> MaybeCreateSVGAccessible(
nsIContent* aContent, DocAccessible* aDocument) { if (aContent->IsSVGGeometryElement() ||
aContent->IsSVGElement(nsGkAtoms::image)) { // Shape elements: rect, circle, ellipse, line, path, polygon, and polyline. // 'use' and 'text' graphic elements require special support. if (MustSVGElementBeAccessible(aContent, aDocument)) { returnnew EnumRoleAccessible<roles::GRAPHIC>(aContent, aDocument);
}
} elseif (aContent->IsSVGElement(nsGkAtoms::text)) { returnnew HyperTextAccessible(aContent->AsElement(), aDocument);
} elseif (aContent->IsSVGElement(nsGkAtoms::svg)) { // An <svg> element could contain <foreignObject>, which contains HTML but // does not normally create its own Accessible. This means that the <svg> // Accessible could have TextLeafAccessible children, so it must be a // HyperTextAccessible. returnnew EnumRoleHyperTextAccessible<roles::DIAGRAM>(aContent, aDocument);
} elseif (aContent->IsSVGElement(nsGkAtoms::g) &&
MustSVGElementBeAccessible(aContent, aDocument)) { // <g> can also contain <foreignObject>. returnnew EnumRoleHyperTextAccessible<roles::GROUPING>(aContent,
aDocument);
} elseif (aContent->IsSVGElement(nsGkAtoms::a)) { returnnew HTMLLinkAccessible(aContent, aDocument);
} return nullptr;
}
/** * Used by XULMap.h to map both menupopup and popup elements
*/
LocalAccessible* CreateMenupopupAccessible(Element* aElement,
LocalAccessible* aContext) { #ifdef MOZ_ACCESSIBILITY_ATK // ATK considers this node to be redundant when within menubars, and it makes // menu navigation with assistive technologies more difficult // XXX In the future we will should this for consistency across the // nsIAccessible implementations on each platform for a consistent scripting // environment, but then strip out redundant accessibles in the AccessibleWrap // class for each platform.
nsIContent* parent = aElement->GetParent(); if (parent && parent->IsXULElement(nsGkAtoms::menu)) return nullptr; #endif
static uint64_t GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { // Only check clients in the parent process. if (!XRE_IsParentProcess()) { return aCacheDomains;
}
template <typename AccClass> static LocalAccessible* New_HTMLDtOrDd(Element* aElement,
LocalAccessible* aContext) {
nsIContent* parent = aContext->GetContent(); if (parent->IsHTMLElement(nsGkAtoms::div)) { // It is conforming in HTML to use a div to group dt/dd elements.
parent = parent->GetParent();
}
if (parent && parent->IsHTMLElement(nsGkAtoms::dl)) { returnnew AccClass(aElement, aContext->Document());
}
return nullptr;
}
/** * Cached value of the PREF_ACCESSIBILITY_FORCE_DISABLED preference.
*/ static int32_t sPlatformDisabledState = 0;
if (document) {
LocalAccessible* acc = document->GetAccessible(content); if (!acc && (content == document->GetContent() ||
content == document->DocumentNode()->GetRootElement())) {
acc = document;
} if (!acc && content->IsElement() &&
content->AsElement()->IsHTMLElement(nsGkAtoms::area)) { // For area accessibles, we have to recreate the entire image map, // since the image map accessible manages the tree itself. The click // listener change may require us to update the role for the // accessible associated with the area element.
LocalAccessible* areaAcc =
document->GetAccessibleEvenIfNotInMap(content); if (areaAcc && areaAcc->LocalParent()) {
document->RecreateAccessible(areaAcc->LocalParent()->GetContent());
}
} if (!acc && nsCoreUtils::HasClickListener(content)) { // Create an accessible for a inaccessible element having click event // handler.
document->ContentInserted(content, content->GetNextSibling());
} elseif (acc) { if ((acc->IsHTMLLink() && !acc->AsHTMLLink()->IsLinked()) ||
(content->IsElement() &&
content->AsElement()->IsHTMLElement(nsGkAtoms::a) &&
!acc->IsHTMLLink())) { // An HTML link without an href attribute should have a generic // role, unless it has a click listener. Since we might have gained // or lost a click listener here, recreate the accessible so that we // can create the correct type of accessible. If it was a link, it // may no longer be one. If it wasn't, it may become one.
document->RecreateAccessible(content);
}
// A click listener change might mean losing or gaining an action.
document->QueueCacheUpdate(acc, CacheDomain::Actions);
}
}
}
} return NS_OK;
}
void nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent* aTargetNode) {
Document* documentNode = aTargetNode->GetUncomposedDoc(); if (!documentNode) { return;
}
DocAccessible* document = GetDocAccessible(documentNode); if (!document) { return;
} // If the document has focus when we get this notification, ensure that // we fire a start scrolling event. const Accessible* focusedAcc = FocusedAccessible(); if (focusedAcc &&
(focusedAcc == document || focusedAcc->IsNonInteractive())) {
LocalAccessible* targetAcc =
document->GetAccessibleOrContainer(aTargetNode); // If targetAcc is the document, this isn't useful. It's possible we just // haven't built the initial tree yet. Regardless, we don't want to fire an // event for the document here. if (targetAcc && !targetAcc->IsDoc()) {
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
targetAcc);
document->SetAnchorJump(nullptr);
} else { // We can't find the target accessible in the document yet. Set the // anchor jump so that we can fire the scrolling start event later.
document->SetAnchorJump(aTargetNode);
}
} else {
document->SetAnchorJump(aTargetNode);
}
}
void nsAccessibilityService::NotifyOfPossibleBoundsChange(
mozilla::PresShell* aPresShell, nsIContent* aContent) { if (IPCAccessibilityActive()) {
DocAccessible* document = aPresShell->GetDocAccessible(); if (document) {
LocalAccessible* accessible = document->GetAccessible(aContent); if (!accessible && aContent == document->GetContent()) { // DocAccessible::GetAccessible() won't return the document if a root // element like body is passed. In that case we need the doc accessible // itself.
accessible = document;
}
if (accessible) {
document->QueueCacheUpdate(accessible, CacheDomain::Bounds);
}
}
}
}
LocalAccessible* accessible = document->GetAccessible(aContent); if (!accessible && aContent == document->GetContent()) { // DocAccessible::GetAccessible() won't return the document if a root // element like body is passed. In that case we need the doc accessible // itself.
accessible = document;
}
if (!accessible && aContent && aContent->HasChildren() &&
!aContent->IsInNativeAnonymousSubtree()) { // If the content has children and its frame has a transform, create an // Accessible so that we can account for the transform when calculating // the Accessible's bounds using the parent process cache. Ditto for // position: fixed/sticky and content with overflow styling (hidden, auto, // scroll) if (const nsIFrame* frame = aContent->GetPrimaryFrame()) { constauto& disp = *frame->StyleDisplay(); if (disp.HasTransform(frame) ||
disp.mPosition == StylePositionProperty::Fixed ||
disp.mPosition == StylePositionProperty::Sticky ||
disp.IsScrollableOverflow()) {
document->ContentInserted(aContent, aContent->GetNextSibling());
}
}
} elseif (accessible && IPCAccessibilityActive()) {
accessible->MaybeQueueCacheUpdateForStyleChanges();
}
}
// If image map was initialized after we created an accessible (that'll // be an image accessible) then recreate it.
RecreateAccessible(presShell, aImageFrame->GetContent());
}
}
}
void nsAccessibilityService::UpdateLabelValue(PresShell* aPresShell,
nsIContent* aLabelElm, const nsString& aNewValue) {
DocAccessible* document = GetDocAccessible(aPresShell); if (document) {
LocalAccessible* accessible = document->GetAccessible(aLabelElm); if (accessible) {
XULLabelAccessible* xulLabel = accessible->AsXULLabel();
NS_ASSERTION(xulLabel, "UpdateLabelValue was called for wrong accessible!"); if (xulLabel) xulLabel->UpdateLabelValue(aNewValue);
}
}
}
void nsAccessibilityService::PresShellActivated(PresShell* aPresShell) {
DocAccessible* document = aPresShell->GetDocAccessible(); if (document) {
RootAccessible* rootDocument = document->RootAccessible();
NS_ASSERTION(rootDocument, "Entirely broken tree: no root document!"); if (rootDocument) rootDocument->DocumentActivated(document);
}
}
if (aStates & states::UNAVAILABLE) {
stringStates->Add(u"unavailable"_ns);
} if (aStates & states::SELECTED) {
stringStates->Add(u"selected"_ns);
} if (aStates & states::FOCUSED) {
stringStates->Add(u"focused"_ns);
} if (aStates & states::PRESSED) {
stringStates->Add(u"pressed"_ns);
} if (aStates & states::CHECKED) {
stringStates->Add(u"checked"_ns);
} if (aStates & states::MIXED) {
stringStates->Add(u"mixed"_ns);
} if (aStates & states::READONLY) {
stringStates->Add(u"readonly"_ns);
} if (aStates & states::HOTTRACKED) {
stringStates->Add(u"hottracked"_ns);
} if (aStates & states::DEFAULT) {
stringStates->Add(u"default"_ns);
} if (aStates & states::EXPANDED) {
stringStates->Add(u"expanded"_ns);
} if (aStates & states::COLLAPSED) {
stringStates->Add(u"collapsed"_ns);
} if (aStates & states::BUSY) {
stringStates->Add(u"busy"_ns);
} if (aStates & states::FLOATING) {
stringStates->Add(u"floating"_ns);
} if (aStates & states::ANIMATED) {
stringStates->Add(u"animated"_ns);
} if (aStates & states::INVISIBLE) {
stringStates->Add(u"invisible"_ns);
} if (aStates & states::OFFSCREEN) {
stringStates->Add(u"offscreen"_ns);
} if (aStates & states::SIZEABLE) {
stringStates->Add(u"sizeable"_ns);
} if (aStates & states::MOVEABLE) {
stringStates->Add(u"moveable"_ns);
} if (aStates & states::SELFVOICING) {
stringStates->Add(u"selfvoicing"_ns);
} if (aStates & states::FOCUSABLE) {
stringStates->Add(u"focusable"_ns);
} if (aStates & states::SELECTABLE) {
stringStates->Add(u"selectable"_ns);
} if (aStates & states::LINKED) {
stringStates->Add(u"linked"_ns);
} if (aStates & states::TRAVERSED) {
stringStates->Add(u"traversed"_ns);
} if (aStates & states::MULTISELECTABLE) {
stringStates->Add(u"multiselectable"_ns);
} if (aStates & states::EXTSELECTABLE) {
stringStates->Add(u"extselectable"_ns);
} if (aStates & states::PROTECTED) {
stringStates->Add(u"protected"_ns);
} if (aStates & states::HASPOPUP) {
stringStates->Add(u"haspopup"_ns);
} if (aStates & states::REQUIRED) {
stringStates->Add(u"required"_ns);
} if (aStates & states::ALERT) {
stringStates->Add(u"alert"_ns);
} if (aStates & states::INVALID) {
stringStates->Add(u"invalid"_ns);
} if (aStates & states::CHECKABLE) {
stringStates->Add(u"checkable"_ns);
} if (aStates & states::SUPPORTS_AUTOCOMPLETION) {
stringStates->Add(u"autocompletion"_ns);
} if (aStates & states::DEFUNCT) {
stringStates->Add(u"defunct"_ns);
} if (aStates & states::SELECTABLE_TEXT) {
stringStates->Add(u"selectable text"_ns);
} if (aStates & states::EDITABLE) {
stringStates->Add(u"editable"_ns);
} if (aStates & states::ACTIVE) {
stringStates->Add(u"active"_ns);
} if (aStates & states::MODAL) {
stringStates->Add(u"modal"_ns);
} if (aStates & states::MULTI_LINE) {
stringStates->Add(u"multi line"_ns);
} if (aStates & states::HORIZONTAL) {
stringStates->Add(u"horizontal"_ns);
} if (aStates & states::OPAQUE1) {
stringStates->Add(u"opaque"_ns);
} if (aStates & states::SINGLE_LINE) {
stringStates->Add(u"single line"_ns);
} if (aStates & states::TRANSIENT) {
stringStates->Add(u"transient"_ns);
} if (aStates & states::VERTICAL) {
stringStates->Add(u"vertical"_ns);
} if (aStates & states::STALE) {
stringStates->Add(u"stale"_ns);
} if (aStates & states::ENABLED) {
stringStates->Add(u"enabled"_ns);
} if (aStates & states::SENSITIVE) {
stringStates->Add(u"sensitive"_ns);
} if (aStates & states::EXPANDABLE) {
stringStates->Add(u"expandable"_ns);
} if (aStates & states::PINNED) {
stringStates->Add(u"pinned"_ns);
} if (aStates & states::CURRENT) {
stringStates->Add(u"current"_ns);
}
return stringStates.forget();
}
void nsAccessibilityService::GetStringEventType(uint32_t aEventType,
nsAString& aString) {
static_assert(
nsIAccessibleEvent::EVENT_LAST_ENTRY == std::size(kEventTypeNames), "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
if (aEventType >= std::size(kEventTypeNames)) {
aString.AssignLiteral("unknown"); return;
}
void nsAccessibilityService::GetStringEventType(uint32_t aEventType,
nsACString& aString) {
MOZ_ASSERT(nsIAccessibleEvent::EVENT_LAST_ENTRY == std::size(kEventTypeNames), "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
if (aEventType >= std::size(kEventTypeNames)) {
aString.AssignLiteral("unknown"); return;
}
//////////////////////////////////////////////////////////////////////////////// // nsAccessibilityService public
LocalAccessible* nsAccessibilityService::CreateAccessible(
nsINode* aNode, LocalAccessible* aContext, bool* aIsSubtreeHidden) {
MOZ_ASSERT(aContext, "No context provided");
MOZ_ASSERT(aNode, "No node to create an accessible for");
MOZ_ASSERT(gConsumers, "No creation after shutdown");
if (aIsSubtreeHidden) *aIsSubtreeHidden = false;
DocAccessible* document = aContext->Document();
MOZ_ASSERT(!document->GetAccessible(aNode), "We already have an accessible for this node.");
if (aNode->IsDocument()) { // If it's document node then ask accessible document loader for // document accessible, otherwise return null. return GetDocAccessible(aNode->AsDocument());
}
// We have a content node. if (!aNode->GetComposedDoc()) {
NS_WARNING("Creating accessible for node with no document"); return nullptr;
}
if (aNode->OwnerDoc() != document->DocumentNode()) {
NS_ERROR("Creating accessible for wrong document"); return nullptr;
}
if (!aNode->IsContent()) return nullptr;
nsIContent* content = aNode->AsContent(); if (aria::HasDefinedARIAHidden(content)) { if (aIsSubtreeHidden) {
*aIsSubtreeHidden = true;
} return nullptr;
}
// Check frame and its visibility.
nsIFrame* frame = content->GetPrimaryFrame(); if (frame) { // If invisible or inert, we don't create an accessible, but we don't mark // it with aIsSubtreeHidden = true, since visibility: hidden frame allows // visible elements in subtree, and inert elements allow non-inert // elements. if (!frame->StyleVisibility()->IsVisible() || frame->StyleUI()->IsInert()) { return nullptr;
}
} elseif (nsCoreUtils::CanCreateAccessibleWithoutFrame(content)) { // display:contents element doesn't have a frame, but retains the // semantics. All its children are unaffected. const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement());
RefPtr<LocalAccessible> newAcc = MaybeCreateSpecificARIAAccessible(
roleMapEntry, aContext, content, document); const MarkupMapInfo* markupMap = nullptr; if (!newAcc) {
markupMap = GetMarkupMapInfoFor(content); if (markupMap && markupMap->new_func) {
newAcc = markupMap->new_func(content->AsElement(), aContext);
}
}
// SVG elements are not in a markup map, but we may still need to create an // accessible for one, even in the case of display:contents. if (!newAcc && content->IsSVGElement()) {
newAcc = MaybeCreateSVGAccessible(content, document);
}
// Check whether this element has an ARIA role or attribute that requires // us to create an Accessible. constbool hasNonPresentationalARIARole =
roleMapEntry && !roleMapEntry->Is(nsGkAtoms::presentation) &&
!roleMapEntry->Is(nsGkAtoms::none); if (!newAcc &&
(hasNonPresentationalARIARole || MustBeAccessible(content, document))) {
newAcc = new HyperTextAccessible(content, document);
}
// If there's still no Accessible but we do have an entry in the markup // map for this non-presentational element, create a generic // HyperTextAccessible. if (!newAcc && markupMap &&
(!roleMapEntry || hasNonPresentationalARIARole)) {
newAcc = new HyperTextAccessible(content, document);
}
if (frame->IsHiddenByContentVisibilityOnAnyAncestor(
nsIFrame::IncludeContentVisibility::Hidden)) { if (aIsSubtreeHidden) {
*aIsSubtreeHidden = true;
} return nullptr;
}
if (nsMenuPopupFrame* popupFrame = do_QueryFrame(frame)) { // Hidden tooltips and panels don't create accessibles in the whole subtree. // Showing them gets handled by RootAccessible::ProcessDOMEvent. if (content->IsAnyOfXULElements(nsGkAtoms::tooltip, nsGkAtoms::panel)) {
nsPopupState popupState = popupFrame->PopupState(); if (popupState == ePopupHiding || popupState == ePopupInvisible ||
popupState == ePopupClosed) { if (aIsSubtreeHidden) {
*aIsSubtreeHidden = true;
} return nullptr;
}
}
}
if (frame->GetContent() != content) { // Not the main content for this frame. This happens because <area> // elements return the image frame as their primary frame. The main content // for the image frame is the image content. If the frame is not an image // frame or the node is not an area element then null is returned. // This setup will change when bug 135040 is fixed. Make sure we don't // create area accessible here. Hopefully assertion below will handle that.
#ifdef DEBUG
nsImageFrame* imageFrame = do_QueryFrame(frame);
NS_ASSERTION(imageFrame && content->IsHTMLElement(nsGkAtoms::area), "Unknown case of not main content for the frame!"); #endif return nullptr;
}
#ifdef DEBUG
nsImageFrame* imageFrame = do_QueryFrame(frame);
NS_ASSERTION(!imageFrame || !content->IsHTMLElement(nsGkAtoms::area), "Image map manages the area accessible creation!"); #endif
// Attempt to create an accessible based on what we know.
RefPtr<LocalAccessible> newAcc;
// Create accessible for visible text frames. if (content->IsText()) {
nsIFrame::RenderedText text = frame->GetRenderedText(
0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
nsIFrame::TrailingWhitespace::DontTrim); // Ignore not rendered text nodes and whitespace text nodes between table // cells. if (text.mString.IsEmpty() ||
(aContext->IsTableRow() &&
nsCoreUtils::IsWhitespaceString(text.mString))) { if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
return nullptr;
}
newAcc = CreateAccessibleByFrameType(frame, content, aContext);
MOZ_ASSERT(newAcc, "Accessible not created for text node!");
document->BindToDocument(newAcc, nullptr); if (auto cssAlt = CssAltContent(content)) {
nsAutoString text;
cssAlt.AppendToString(text);
newAcc->AsTextLeaf()->SetText(text);
} else {
newAcc->AsTextLeaf()->SetText(text.mString);
} return newAcc;
}
if (content->IsHTMLElement(nsGkAtoms::map)) { // Create hyper text accessible for HTML map if it is used to group links // (see http://www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass). If the HTML // map rect is empty then it is used for links grouping. Otherwise it should // be used in conjunction with HTML image element and in this case we don't // create any accessible for it and don't walk into it. The accessibles for // HTML area (HTMLAreaAccessible) the map contains are attached as // children of the appropriate accessible for HTML image // (ImageAccessible). if (nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame->GetParent())
.IsEmpty()) { if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
return nullptr;
}
newAcc = new HyperTextAccessible(content, document);
document->BindToDocument(newAcc, aria::GetRoleMap(content->AsElement())); return newAcc;
}
if (roleMapEntry && (roleMapEntry->Is(nsGkAtoms::presentation) ||
roleMapEntry->Is(nsGkAtoms::none))) { if (MustBeAccessible(content, document)) { // If the element is focusable, a global ARIA attribute is applied to it // or it is referenced by an ARIA relationship, then treat // role="presentation" on the element as if the role is not there.
roleMapEntry = nullptr;
} elseif (MustBeGenericAccessible(content, document)) { // Clear roleMapEntry so that we use the generic role specified below. // Otherwise, we'd expose roles::NOTHING as specified for presentation in // ARIAMap.
roleMapEntry = nullptr;
newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content,
document);
} else { return nullptr;
}
}
// We should always use OuterDocAccessible for OuterDocs, even if there's a // specific ARIA class we would otherwise use. if (!newAcc && frame->AccessibleType() != eOuterDocType) {
newAcc = MaybeCreateSpecificARIAAccessible(roleMapEntry, aContext, content,
document);
}
if (!newAcc && content->IsHTMLElement()) { // HTML accessibles // Prefer to use markup to decide if and what kind of accessible to // create, const MarkupMapInfo* markupMap =
mHTMLMarkupMap.Get(content->NodeInfo()->NameAtom()); if (markupMap && markupMap->new_func) {
newAcc = markupMap->new_func(content->AsElement(), aContext);
}
if (!newAcc) { // try by frame accessible type.
newAcc = CreateAccessibleByFrameType(frame, content, aContext);
}
// If table has strong ARIA role then all table descendants shouldn't // expose their native roles. if (!roleMapEntry && newAcc && aContext->HasStrongARIARole()) { if (frame->AccessibleType() == eHTMLTableRowType) { const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap(); if (!contextRoleMap->IsOfType(eTable)) {
roleMapEntry = &aria::gEmptyRoleMap;
}
// XUL accessibles. if (!newAcc && content->IsXULElement()) { if (content->IsXULElement(nsGkAtoms::panel)) { // We filter here instead of in the XUL map because // if we filter there and return null, we still end up // creating a generic accessible at the end of this function. // Doing the filtering here ensures we never create accessibles // for panels whose popups aren't visible.
nsMenuPopupFrame* popupFrame = do_QueryFrame(frame); if (!popupFrame) { return nullptr;
}
// Prefer to use XUL to decide if and what kind of accessible to create. const XULMarkupMapInfo* xulMap =
mXULMarkupMap.Get(content->NodeInfo()->NameAtom()); if (xulMap && xulMap->new_func) {
newAcc = xulMap->new_func(content->AsElement(), aContext);
}
// Any XUL/flex box can be used as tabpanel, make sure we create a proper // accessible for it. if (!newAcc && aContext->IsXULTabpanels() &&
content->GetParent() == aContext->GetContent()) {
LayoutFrameType frameType = frame->Type(); // FIXME(emilio): Why only these frame types? if (frameType == LayoutFrameType::FlexContainer ||
frameType == LayoutFrameType::ScrollContainer) {
newAcc = new XULTabpanelAccessible(content, document);
}
}
}
if (!newAcc) { if (content->IsSVGElement()) {
newAcc = MaybeCreateSVGAccessible(content, document);
} elseif (content->IsMathMLElement()) { const MarkupMapInfo* markupMap =
mMathMLMarkupMap.Get(content->NodeInfo()->NameAtom()); if (markupMap && markupMap->new_func) {
newAcc = markupMap->new_func(content->AsElement(), aContext);
}
// Fall back to text when encountering Content MathML. if (!newAcc && !content->IsAnyOfMathMLElements(
nsGkAtoms::annotation_, nsGkAtoms::annotation_xml_,
nsGkAtoms::mpadded_, nsGkAtoms::mphantom_,
nsGkAtoms::maligngroup_, nsGkAtoms::malignmark_,
nsGkAtoms::mspace_, nsGkAtoms::semantics_)) {
newAcc = new HyperTextAccessible(content, document);
}
} elseif (content->IsGeneratedContentContainerForMarker()) { if (aContext->IsHTMLListItem()) {
newAcc = new HTMLListBulletAccessible(content, document);
} if (aIsSubtreeHidden) {
*aIsSubtreeHidden = true;
}
}
}
// If no accessible, see if we need to create a generic accessible because // of some property that makes this object interesting // We don't do this for <body>, <html>, <window>, <dialog> etc. which // correspond to the doc accessible and will be created in any case if (!newAcc && !content->IsHTMLElement(nsGkAtoms::body) &&
content->GetParent() &&
(roleMapEntry || MustBeAccessible(content, document) ||
(content->IsHTMLElement() && nsCoreUtils::HasClickListener(content)))) { // This content is focusable or has an interesting dynamic content // accessibility property. If it's interesting we need it in the // accessibility hierarchy so that events or other accessibles can point to // it, or so that it can hold a state, etc. if (content->IsHTMLElement() || content->IsMathMLElement() ||
content->IsSVGElement(nsGkAtoms::foreignObject)) { // Interesting container which may have selectable text and/or embedded // objects.
newAcc = new HyperTextAccessible(content, document);
} else { // XUL, other SVG, etc. // Interesting generic non-HTML container
newAcc = new AccessibleWrap(content, document);
}
} elseif (!newAcc && MustBeGenericAccessible(content, document)) {
newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content,
document);
}
if (newAcc) {
document->BindToDocument(newAcc, roleMapEntry);
} return newAcc;
}
#ifdefined(ANDROID) # include "mozilla/Monitor.h" # include "mozilla/Maybe.h"
bool nsAccessibilityService::Init(uint64_t aCacheDomains) {
AUTO_PROFILER_MARKER_TEXT("nsAccessibilityService::Init", A11Y, {}, ""_ns); // DO NOT ADD CODE ABOVE HERE: THIS CODE IS MEASURING TIMINGS.
// Initialize accessible document manager. if (!DocManager::Init()) returnfalse;
// Add observers.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService(); if (!observerService) returnfalse;
#ifdefined(XP_WIN) // This information needs to be initialized before the observer fires. if (XRE_IsParentProcess()) {
Compatibility::Init();
} #endif// defined(XP_WIN)
// Subscribe to EventListenerService.
nsCOMPtr<nsIEventListenerService> eventListenerService =
do_GetService("@mozilla.org/eventlistenerservice;1"); if (!eventListenerService) returnfalse;
for (uint32_t i = 0; i < std::size(sHTMLMarkupMapList); i++) {
mHTMLMarkupMap.InsertOrUpdate(sHTMLMarkupMapList[i].tag,
&sHTMLMarkupMapList[i]);
} for (constauto& info : sMathMLMarkupMapList) {
mMathMLMarkupMap.InsertOrUpdate(info.tag, &info);
}
for (uint32_t i = 0; i < std::size(sXULMarkupMapList); i++) {
mXULMarkupMap.InsertOrUpdate(sXULMarkupMapList[i].tag,
&sXULMarkupMapList[i]);
}
#ifdef A11Y_LOG
logging::CheckEnv(); #endif
gAccessibilityService = this;
NS_ADDREF(gAccessibilityService); // will release in Shutdown()
if (XRE_IsParentProcess()) {
gApplicationAccessible = new ApplicationAccessibleWrap();
} else {
gApplicationAccessible = new ApplicationAccessible();
}
NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
gApplicationAccessible->Init();
// Now its safe to start platform accessibility. if (XRE_IsParentProcess()) PlatformInit();
// Check the startup cache domain pref. We might be in a test environment // where we need to have all cache domains enabled (e.g., fuzzing). if (XRE_IsParentProcess() &&
StaticPrefs::accessibility_enable_all_cache_domains_AtStartup()) {
gCacheDomains = CacheDomain::All;
}
// Set the active accessibility cache domains. We might want to modify the // domains that we activate based on information about the instantiator.
gCacheDomains = ::GetCacheDomainsForKnownClients(aCacheDomains);
void nsAccessibilityService::Shutdown() { // Application is going to be closed, shutdown accessibility and mark // accessibility service as shutdown to prevent calls of its methods. // Don't null accessibility service static member at this point to be safe // if someone will try to operate with it.
MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI);
#ifdefined(ANDROID) // Don't allow the service to shut down while an a11y request is being handled // in the UI thread, as the request may depend on state from the service.
MonitorAutoLock mal(GetAndroidMonitor()); #endif
NS_RELEASE(gAccessibilityService);
gAccessibilityService = nullptr;
RefPtr<LocalAccessible> newAcc; switch (aFrame->AccessibleType()) { case eNoType: return nullptr; case eHTMLBRType:
newAcc = new HTMLBRAccessible(aContent, document); break; case eHTMLButtonType:
newAcc = new HTMLButtonAccessible(aContent, document); break; case eHTMLCanvasType:
newAcc = new HTMLCanvasAccessible(aContent, document); break; case eHTMLCaptionType: if (aContext->IsTable() &&
aContext->GetContent() == aContent->GetParent()) {
newAcc = new HTMLCaptionAccessible(aContent, document);
} break; case eHTMLCheckboxType:
newAcc = new CheckboxAccessible(aContent, document); break; case eHTMLComboboxType:
newAcc = new HTMLComboboxAccessible(aContent, document); break; case eHTMLFileInputType:
newAcc = new HTMLFileInputAccessible(aContent, document); break; case eHTMLGroupboxType:
newAcc = new HTMLGroupboxAccessible(aContent, document); break; case eHTMLHRType:
newAcc = new HTMLHRAccessible(aContent, document); break; case eHTMLImageMapType:
newAcc = new HTMLImageMapAccessible(aContent, document); break; case eHTMLLiType: if (aContext->IsList() &&
aContext->GetContent() == aContent->GetParent()) {
newAcc = new HTMLLIAccessible(aContent, document);
} else { // Otherwise create a generic text accessible to avoid text jamming.
newAcc = new HyperTextAccessible(aContent, document);
} break; case eHTMLSelectListType:
newAcc = new HTMLSelectListAccessible(aContent, document); break; case eHTMLMediaType:
newAcc = new EnumRoleAccessible<roles::GROUPING>(aContent, document); break; case eHTMLRadioButtonType:
newAcc = new HTMLRadioButtonAccessible(aContent, document); break; case eHTMLRangeType:
newAcc = new HTMLRangeAccessible(aContent, document); break; case eHTMLSpinnerType:
newAcc = new HTMLSpinnerAccessible(aContent, document); break; case eHTMLTableType: case eHTMLTableCellType: // We handle markup and ARIA tables elsewhere. If we reach here, this is // a CSS table part. Just create a generic text container.
newAcc = new HyperTextAccessible(aContent, document); break; case eHTMLTableRowType: // This is a CSS table row. Don't expose it at all. break; case eHTMLTextFieldType:
newAcc = new HTMLTextFieldAccessible(aContent, document); break; case eHyperTextType: { if (aContext->IsTable() || aContext->IsTableRow()) { // This is some generic hyperText, for example a block frame element // inserted between a table and table row. Treat it as presentational. return nullptr;
}
if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd,
nsGkAtoms::div, nsGkAtoms::thead,
nsGkAtoms::tfoot, nsGkAtoms::tbody)) {
newAcc = new HyperTextAccessible(aContent, document);
} break;
} case eImageType: if (aContent->IsElement() &&
ShouldCreateImgAccessible(aContent->AsElement(), document)) {
newAcc = new ImageAccessible(aContent, document);
} break; case eOuterDocType:
newAcc = new OuterDocAccessible(aContent, document); break; case eTextLeafType:
newAcc = new TextLeafAccessible(aContent, document); break; default:
MOZ_ASSERT(false); break;
}
dom::Element* el = aAcc->IsLocal() ? aAcc->AsLocal()->Elm() : nullptr; for (uint32_t i = 0; i < std::size(markupMap->attrs); i++) { const MarkupAttrInfo* info = markupMap->attrs + i; if (!info->name) break;
if (info->DOMAttrName) { if (!el) { // XXX Expose DOM attributes for cached RemoteAccessibles. continue;
} if (info->DOMAttrValue) { if (el->AttrValueIs(kNameSpaceID_None, info->DOMAttrName,
info->DOMAttrValue, eCaseMatters)) {
aAttributes->SetAttribute(info->name, info->DOMAttrValue);
} continue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.