nsAccessibilityService.cpp (78421B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsAccessibilityService.h" 7 8 // NOTE: alphabetically ordered 9 #include "ApplicationAccessibleWrap.h" 10 #include "ARIAGridAccessible.h" 11 #include "ARIAMap.h" 12 #include "CssAltContent.h" 13 #include "DocAccessible-inl.h" 14 #include "DocAccessibleChild.h" 15 #include "FocusManager.h" 16 #include "mozilla/FocusModel.h" 17 #include "HTMLCanvasAccessible.h" 18 #include "HTMLElementAccessibles.h" 19 #include "HTMLImageMapAccessible.h" 20 #include "HTMLLinkAccessible.h" 21 #include "HTMLListAccessible.h" 22 #include "HTMLSelectAccessible.h" 23 #include "HTMLTableAccessible.h" 24 #include "HyperTextAccessible.h" 25 #include "RootAccessible.h" 26 #include "nsAccUtils.h" 27 #include "nsArrayUtils.h" 28 #include "nsAttrName.h" 29 #include "nsDOMTokenList.h" 30 #include "nsCRT.h" 31 #include "nsEventShell.h" 32 #include "nsGkAtoms.h" 33 #include "nsIFrameInlines.h" 34 #include "nsServiceManagerUtils.h" 35 #include "nsTextFormatter.h" 36 #include "OuterDocAccessible.h" 37 #include "Pivot.h" 38 #include "mozilla/a11y/Role.h" 39 #ifdef MOZ_ACCESSIBILITY_ATK 40 # include "RootAccessibleWrap.h" 41 #endif 42 #include "States.h" 43 #include "TextLeafAccessible.h" 44 #include "xpcAccessibleApplication.h" 45 46 #ifdef XP_WIN 47 # include "mozilla/a11y/Compatibility.h" 48 # include "mozilla/StaticPtr.h" 49 #endif 50 51 #ifdef A11Y_LOG 52 # include "Logging.h" 53 #endif 54 55 #include "nsExceptionHandler.h" 56 #include "nsImageFrame.h" 57 #include "nsIObserverService.h" 58 #include "nsMenuPopupFrame.h" 59 #include "nsLayoutUtils.h" 60 #include "nsTreeBodyFrame.h" 61 #include "nsTreeUtils.h" 62 #include "mozilla/a11y/AccTypes.h" 63 #include "mozilla/dom/ContentParent.h" 64 #include "mozilla/dom/DOMStringList.h" 65 #include "mozilla/dom/EventTarget.h" 66 #include "mozilla/dom/HTMLTableElement.h" 67 #include "mozilla/Preferences.h" 68 #include "mozilla/PresShell.h" 69 #include "mozilla/ProfilerMarkers.h" 70 #include "mozilla/RefPtr.h" 71 #include "mozilla/Services.h" 72 73 #include "XULAlertAccessible.h" 74 #include "XULComboboxAccessible.h" 75 #include "XULElementAccessibles.h" 76 #include "XULFormControlAccessible.h" 77 #include "XULListboxAccessible.h" 78 #include "XULMenuAccessible.h" 79 #include "XULTabAccessible.h" 80 #include "XULTreeGridAccessible.h" 81 82 using namespace mozilla; 83 using namespace mozilla::a11y; 84 using namespace mozilla::dom; 85 86 /** 87 * Accessibility service force enable/disable preference. 88 * Supported values: 89 * Accessibility is force enabled (accessibility should always be enabled): -1 90 * Accessibility is enabled (will be started upon a request, default value): 0 91 * Accessibility is force disabled (never enable accessibility): 1 92 */ 93 #define PREF_ACCESSIBILITY_FORCE_DISABLED "accessibility.force_disabled" 94 95 //////////////////////////////////////////////////////////////////////////////// 96 // Statics 97 //////////////////////////////////////////////////////////////////////////////// 98 99 /** 100 * If the element has an ARIA attribute that requires a specific Accessible 101 * class, create and return it. Otherwise, return null. 102 */ 103 static LocalAccessible* MaybeCreateSpecificARIAAccessible( 104 const nsRoleMapEntry* aRoleMapEntry, const LocalAccessible* aContext, 105 nsIContent* aContent, DocAccessible* aDocument) { 106 if (aRoleMapEntry && aRoleMapEntry->accTypes & eTableCell) { 107 if (aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th) && 108 aContext->IsHTMLTableRow()) { 109 // Don't use ARIAGridCellAccessible for a valid td/th because 110 // HTMLTableCellAccessible can provide additional info; e.g. row/col span 111 // from the layout engine. 112 return nullptr; 113 } 114 // A cell must be in a row. 115 const Accessible* parent = aContext; 116 if (parent->IsGeneric()) { 117 parent = parent->GetNonGenericParent(); 118 } 119 if (!parent || parent->Role() != roles::ROW) { 120 return nullptr; 121 } 122 // That row must be in a table, though there may be an intervening rowgroup. 123 parent = parent->GetNonGenericParent(); 124 if (!parent) { 125 return nullptr; 126 } 127 if (!parent->IsTable() && parent->Role() == roles::ROWGROUP) { 128 parent = parent->GetNonGenericParent(); 129 if (!parent) { 130 return nullptr; 131 } 132 } 133 if (parent->IsTable()) { 134 return new ARIAGridCellAccessible(aContent, aDocument); 135 } 136 } 137 return nullptr; 138 } 139 140 // Send a request to all content processes that they build and send back 141 // information about the given cache domains. 142 static bool SendCacheDomainRequestToAllContentProcesses( 143 uint64_t aCacheDomains) { 144 if (!XRE_IsParentProcess()) { 145 return false; 146 } 147 bool sentAll = true; 148 nsTArray<ContentParent*> contentParents; 149 ContentParent::GetAll(contentParents); 150 for (auto* parent : contentParents) { 151 sentAll = sentAll && parent->SendSetCacheDomains(aCacheDomains); 152 } 153 return sentAll; 154 } 155 156 /** 157 * Return true if the element must be a generic Accessible, even if it has been 158 * marked presentational with role="presentation", etc. MustBeAccessible causes 159 * an Accessible to be created as if it weren't marked presentational at all; 160 * e.g. <table role="presentation" tabindex="0"> will expose roles::TABLE and 161 * support TableAccessible. In contrast, this function causes a generic 162 * Accessible to be created; e.g. <table role="presentation" style="position: 163 * fixed;"> will expose roles::TEXT_CONTAINER and will not support 164 * TableAccessible. This is necessary in certain cases for the 165 * RemoteAccessible cache. 166 */ 167 static bool MustBeGenericAccessible(nsIContent* aContent, 168 DocAccessible* aDocument) { 169 if (aContent->IsInNativeAnonymousSubtree() || aContent->IsSVGElement() || 170 aContent == aDocument->DocumentNode()->GetRootElement()) { 171 // We should not force create accs for anonymous content. 172 // This is an issue for inputs, which have an intermediate 173 // container with relevant overflow styling between the input 174 // and its internal input content. 175 // We should also avoid this for SVG elements (ie. `<foreignobject>`s 176 // which have default overflow:hidden styling). 177 // We should avoid this for the document root. 178 return false; 179 } 180 nsIFrame* frame = aContent->GetPrimaryFrame(); 181 MOZ_ASSERT(frame); 182 nsAutoCString overflow; 183 frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow); 184 // If the frame has been transformed, and the content has any children, we 185 // should create an Accessible so that we can account for the transform when 186 // calculating the Accessible's bounds using the parent process cache. 187 // Ditto for content which is position: fixed or sticky or has overflow 188 // styling (auto, scroll, hidden). 189 // However, don't do this for XUL widgets, as this breaks XUL a11y code 190 // expectations in some cases. XUL widgets are only used in the parent 191 // process and can't be cached anyway. 192 return !aContent->IsXULElement() && 193 ((aContent->HasChildren() && frame->IsTransformed()) || 194 frame->IsStickyPositioned() || 195 (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && 196 nsLayoutUtils::IsReallyFixedPos(frame)) || 197 overflow.Equals("auto"_ns) || overflow.Equals("scroll"_ns) || 198 overflow.Equals("hidden"_ns)); 199 } 200 201 /** 202 * Return true if the element must be accessible. 203 */ 204 static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) { 205 if (nsIFrame* frame = aContent->GetPrimaryFrame()) { 206 // This document might be invisible when it first loads. Therefore, we must 207 // check focusability irrespective of visibility here. Otherwise, we might 208 // not create Accessibles for some focusable elements; e.g. a span with only 209 // a tabindex. Elements that are invisible within this document are excluded 210 // earlier in CreateAccessible. 211 if (frame->IsFocusable(IsFocusableFlags::IgnoreVisibility)) { 212 return true; 213 } 214 } 215 216 // Return true if the element has an attribute (ARIA, title, or relation) that 217 // requires the creation of an Accessible for the element. 218 if (aContent->IsElement()) { 219 uint32_t attrCount = aContent->AsElement()->GetAttrCount(); 220 for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) { 221 const nsAttrName* attr = aContent->AsElement()->GetAttrNameAt(attrIdx); 222 if (attr->NamespaceEquals(kNameSpaceID_None)) { 223 nsAtom* attrAtom = attr->Atom(); 224 if (attrAtom == nsGkAtoms::title && aContent->IsHTMLElement()) { 225 // If the author provided a title on an element that would not 226 // be accessible normally, assume an intent and make it accessible. 227 return true; 228 } 229 230 nsDependentAtomString attrStr(attrAtom); 231 if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // not ARIA 232 233 // A global state or a property and in case of token defined. 234 uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom); 235 if ((attrFlags & ATTR_GLOBAL) && 236 (!(attrFlags & ATTR_VALTOKEN) || 237 nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) { 238 return true; 239 } 240 } 241 } 242 243 // If the given ID is referred by relation attribute then create an 244 // Accessible for it. 245 nsAutoString id; 246 if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) { 247 return aDocument->IsDependentID(aContent->AsElement(), id); 248 } 249 } 250 251 return false; 252 } 253 254 bool nsAccessibilityService::ShouldCreateImgAccessible( 255 mozilla::dom::Element* aElement, DocAccessible* aDocument) { 256 // The element must have a layout frame for us to proceed. If there is no 257 // frame, the image is likely hidden. 258 nsIFrame* frame = aElement->GetPrimaryFrame(); 259 if (!frame) { 260 return false; 261 } 262 263 // If the element is not an img, not an embedded image via embed or object, 264 // and not a pseudo-element with CSS content alt text, then we should not 265 // create an accessible. 266 if (!aElement->IsHTMLElement(nsGkAtoms::img) && 267 ((!aElement->IsHTMLElement(nsGkAtoms::embed) && 268 !aElement->IsHTMLElement(nsGkAtoms::object)) || 269 frame->AccessibleType() != AccType::eImageType) && 270 !CssAltContent(aElement)) { 271 return false; 272 } 273 274 nsAutoString newAltText; 275 const bool hasAlt = aElement->GetAttr(nsGkAtoms::alt, newAltText); 276 if (!hasAlt || !newAltText.IsEmpty()) { 277 // If there is no alt attribute, we should create an accessible. The 278 // author may have missed the attribute, and the AT may want to provide a 279 // name. If there is alt text, we should create an accessible. 280 return true; 281 } 282 283 if (newAltText.IsEmpty() && (nsCoreUtils::HasClickListener(aElement) || 284 MustBeAccessible(aElement, aDocument))) { 285 // If there is empty alt text, but there is a click listener for this img, 286 // or if it otherwise must be an accessible (e.g., if it has an aria-label 287 // attribute), we should create an accessible. 288 return true; 289 } 290 291 // Otherwise, no alt text means we should not create an accessible. 292 return false; 293 } 294 295 /** 296 * Return true if the SVG element should be accessible 297 */ 298 static bool MustSVGElementBeAccessible(nsIContent* aContent, 299 DocAccessible* aDocument) { 300 // https://w3c.github.io/svg-aam/#include_elements 301 for (nsIContent* childElm = aContent->GetFirstChild(); childElm; 302 childElm = childElm->GetNextSibling()) { 303 if (childElm->IsAnyOfSVGElements(nsGkAtoms::title, nsGkAtoms::desc)) { 304 return true; 305 } 306 } 307 return MustBeAccessible(aContent, aDocument); 308 } 309 310 /** 311 * Return an accessible for the content if the SVG element requires the creation 312 * of an Accessible. 313 */ 314 static RefPtr<LocalAccessible> MaybeCreateSVGAccessible( 315 nsIContent* aContent, DocAccessible* aDocument) { 316 if (aContent->IsSVGGeometryElement() || 317 aContent->IsSVGElement(nsGkAtoms::image)) { 318 // Shape elements: rect, circle, ellipse, line, path, polygon, and polyline. 319 // 'use' and 'text' graphic elements require special support. 320 if (MustSVGElementBeAccessible(aContent, aDocument)) { 321 return new EnumRoleAccessible<roles::GRAPHIC>(aContent, aDocument); 322 } 323 } else if (aContent->IsSVGElement(nsGkAtoms::text)) { 324 return new HyperTextAccessible(aContent->AsElement(), aDocument); 325 } else if (aContent->IsSVGElement(nsGkAtoms::svg)) { 326 // An <svg> element could contain <foreignObject>, which contains HTML but 327 // does not normally create its own Accessible. This means that the <svg> 328 // Accessible could have TextLeafAccessible children, so it must be a 329 // HyperTextAccessible. 330 return new EnumRoleHyperTextAccessible<roles::DIAGRAM>(aContent, aDocument); 331 } else if (aContent->IsSVGElement(nsGkAtoms::g) && 332 MustSVGElementBeAccessible(aContent, aDocument)) { 333 // <g> can also contain <foreignObject>. 334 return new EnumRoleHyperTextAccessible<roles::GROUPING>(aContent, 335 aDocument); 336 } else if (aContent->IsSVGElement(nsGkAtoms::a)) { 337 return new HTMLLinkAccessible(aContent, aDocument); 338 } 339 return nullptr; 340 } 341 342 /** 343 * Used by XULMap.h to map both menupopup and popup elements 344 */ 345 LocalAccessible* CreateMenupopupAccessible(Element* aElement, 346 LocalAccessible* aContext) { 347 #ifdef MOZ_ACCESSIBILITY_ATK 348 // ATK considers this node to be redundant when within menubars, and it makes 349 // menu navigation with assistive technologies more difficult 350 // XXX In the future we will should this for consistency across the 351 // nsIAccessible implementations on each platform for a consistent scripting 352 // environment, but then strip out redundant accessibles in the AccessibleWrap 353 // class for each platform. 354 nsIContent* parent = aElement->GetParent(); 355 if (parent && parent->IsXULElement(nsGkAtoms::menu)) return nullptr; 356 #endif 357 358 return new XULMenupopupAccessible(aElement, aContext->Document()); 359 } 360 361 static uint64_t GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { 362 // Only check clients in the parent process. 363 if (!XRE_IsParentProcess()) { 364 return aCacheDomains; 365 } 366 367 return a11y::GetCacheDomainsForKnownClients(aCacheDomains); 368 } 369 370 //////////////////////////////////////////////////////////////////////////////// 371 // LocalAccessible constructors 372 373 static LocalAccessible* New_HyperText(Element* aElement, 374 LocalAccessible* aContext) { 375 return new HyperTextAccessible(aElement, aContext->Document()); 376 } 377 378 template <typename AccClass> 379 static LocalAccessible* New_HTMLDtOrDd(Element* aElement, 380 LocalAccessible* aContext) { 381 nsIContent* parent = aContext->GetContent(); 382 if (parent->IsHTMLElement(nsGkAtoms::div)) { 383 // It is conforming in HTML to use a div to group dt/dd elements. 384 parent = parent->GetParent(); 385 } 386 387 if (parent && parent->IsHTMLElement(nsGkAtoms::dl)) { 388 return new AccClass(aElement, aContext->Document()); 389 } 390 391 return nullptr; 392 } 393 394 /** 395 * Cached value of the PREF_ACCESSIBILITY_FORCE_DISABLED preference. 396 */ 397 static int32_t sPlatformDisabledState = 0; 398 399 //////////////////////////////////////////////////////////////////////////////// 400 // Markup maps array. 401 402 #define Attr(name, value) {nsGkAtoms::name, nsGkAtoms::value} 403 404 #define AttrFromDOM(name, DOMAttrName) \ 405 {nsGkAtoms::name, nullptr, nsGkAtoms::DOMAttrName} 406 407 #define AttrFromDOMIf(name, DOMAttrName, DOMAttrValue) \ 408 {nsGkAtoms::name, nullptr, nsGkAtoms::DOMAttrName, nsGkAtoms::DOMAttrValue} 409 410 #define MARKUPMAP(atom, new_func, r, ...) \ 411 {nsGkAtoms::atom, new_func, static_cast<a11y::role>(r), {__VA_ARGS__}}, 412 413 static const MarkupMapInfo sHTMLMarkupMapList[] = { 414 #include "HTMLMarkupMap.h" 415 }; 416 417 static const MarkupMapInfo sMathMLMarkupMapList[] = { 418 #include "MathMLMarkupMap.h" 419 }; 420 421 #undef MARKUPMAP 422 423 #define XULMAP(atom, ...) {nsGkAtoms::atom, __VA_ARGS__}, 424 425 #define XULMAP_TYPE(atom, new_type) \ 426 XULMAP( \ 427 atom, \ 428 [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { \ 429 return new new_type(aElement, aContext->Document()); \ 430 }) 431 432 static const XULMarkupMapInfo sXULMarkupMapList[] = { 433 #include "XULMap.h" 434 }; 435 436 #undef XULMAP_TYPE 437 #undef XULMAP 438 439 #undef Attr 440 #undef AttrFromDOM 441 #undef AttrFromDOMIf 442 443 //////////////////////////////////////////////////////////////////////////////// 444 // nsAccessibilityService 445 //////////////////////////////////////////////////////////////////////////////// 446 447 nsAccessibilityService* nsAccessibilityService::gAccessibilityService = nullptr; 448 ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr; 449 xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible = 450 nullptr; 451 uint32_t nsAccessibilityService::gConsumers = 0; 452 uint64_t nsAccessibilityService::gCacheDomains = 453 nsAccessibilityService::kDefaultCacheDomains; 454 455 nsAccessibilityService::nsAccessibilityService() 456 : mHTMLMarkupMap(std::size(sHTMLMarkupMapList)), 457 mMathMLMarkupMap(std::size(sMathMLMarkupMapList)), 458 mXULMarkupMap(std::size(sXULMarkupMapList)) {} 459 460 nsAccessibilityService::~nsAccessibilityService() { 461 NS_ASSERTION(IsShutdown(), "Accessibility wasn't shutdown!"); 462 gAccessibilityService = nullptr; 463 } 464 465 //////////////////////////////////////////////////////////////////////////////// 466 // nsIListenerChangeListener 467 468 NS_IMETHODIMP 469 nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges) { 470 uint32_t targetCount; 471 nsresult rv = aEventChanges->GetLength(&targetCount); 472 NS_ENSURE_SUCCESS(rv, rv); 473 474 for (uint32_t i = 0; i < targetCount; i++) { 475 nsCOMPtr<nsIEventListenerChange> change = 476 do_QueryElementAt(aEventChanges, i); 477 478 RefPtr<EventTarget> target; 479 change->GetTarget(getter_AddRefs(target)); 480 nsIContent* content(nsIContent::FromEventTargetOrNull(target)); 481 if (!content || !content->IsHTMLElement()) { 482 continue; 483 } 484 485 uint32_t changeCount; 486 change->GetCountOfEventListenerChangesAffectingAccessibility(&changeCount); 487 NS_ENSURE_SUCCESS(rv, rv); 488 489 if (changeCount) { 490 Document* ownerDoc = content->OwnerDoc(); 491 DocAccessible* document = GetExistingDocAccessible(ownerDoc); 492 493 if (document) { 494 LocalAccessible* acc = document->GetAccessible(content); 495 if (!acc && (content == document->GetContent() || 496 content == document->DocumentNode()->GetRootElement())) { 497 acc = document; 498 } 499 if (!acc && content->IsElement() && 500 content->AsElement()->IsHTMLElement(nsGkAtoms::area)) { 501 // For area accessibles, we have to recreate the entire image map, 502 // since the image map accessible manages the tree itself. The click 503 // listener change may require us to update the role for the 504 // accessible associated with the area element. 505 LocalAccessible* areaAcc = 506 document->GetAccessibleEvenIfNotInMap(content); 507 if (areaAcc && areaAcc->LocalParent()) { 508 document->RecreateAccessible(areaAcc->LocalParent()->GetContent()); 509 } 510 } 511 if (!acc && nsCoreUtils::HasClickListener(content)) { 512 // Create an accessible for a inaccessible element having click event 513 // handler. 514 document->ContentInserted(content, content->GetNextSibling()); 515 } else if (acc) { 516 if ((acc->IsHTMLLink() && !acc->AsHTMLLink()->IsLinked()) || 517 (content->IsElement() && 518 content->AsElement()->IsHTMLElement(nsGkAtoms::a) && 519 !acc->IsHTMLLink())) { 520 // An HTML link without an href attribute should have a generic 521 // role, unless it has a click listener. Since we might have gained 522 // or lost a click listener here, recreate the accessible so that we 523 // can create the correct type of accessible. If it was a link, it 524 // may no longer be one. If it wasn't, it may become one. 525 document->RecreateAccessible(content); 526 } 527 528 // A click listener change might mean losing or gaining an action. 529 document->QueueCacheUpdate(acc, CacheDomain::Actions); 530 } 531 } 532 } 533 } 534 return NS_OK; 535 } 536 537 //////////////////////////////////////////////////////////////////////////////// 538 // nsISupports 539 540 NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService, DocManager, nsIObserver, 541 nsIListenerChangeListener, 542 nsISelectionListener) // from SelectionManager 543 544 //////////////////////////////////////////////////////////////////////////////// 545 // nsIObserver 546 547 NS_IMETHODIMP 548 nsAccessibilityService::Observe(nsISupports* aSubject, const char* aTopic, 549 const char16_t* aData) { 550 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 551 Shutdown(); 552 } 553 554 return NS_OK; 555 } 556 557 void nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent* aTargetNode) { 558 Document* documentNode = aTargetNode->GetUncomposedDoc(); 559 if (!documentNode) { 560 return; 561 } 562 DocAccessible* document = GetDocAccessible(documentNode); 563 if (!document) { 564 return; 565 } 566 document->SetAnchorJump(aTargetNode); 567 // If there is a pending update, the target node might not have been added to 568 // the accessibility tree yet, so do not process the anchor jump here. It will 569 // be processed in NotificationController::WillRefresh after the tree is up to 570 // date. On the other hand, if there is no pending update, process the anchor 571 // jump here because the tree is already up to date and there might not be an 572 // update in the near future. 573 if (!document->Controller()->IsUpdatePending()) { 574 document->ProcessAnchorJump(); 575 } 576 } 577 578 void nsAccessibilityService::FireAccessibleEvent(uint32_t aEvent, 579 LocalAccessible* aTarget) { 580 nsEventShell::FireEvent(aEvent, aTarget); 581 } 582 583 void nsAccessibilityService::NotifyOfPossibleBoundsChange( 584 mozilla::PresShell* aPresShell, nsIContent* aContent) { 585 if (!aContent || (!IPCAccessibilityActive() && !aContent->IsText())) { 586 return; 587 } 588 DocAccessible* document = aPresShell->GetDocAccessible(); 589 if (!document) { 590 return; 591 } 592 LocalAccessible* accessible = document->GetAccessible(aContent); 593 if (!accessible && aContent == document->GetContent()) { 594 // DocAccessible::GetAccessible() won't return the document if a root 595 // element like body is passed. In that case we need the doc accessible 596 // itself. 597 accessible = document; 598 } 599 if (!accessible) { 600 return; 601 } 602 if (IPCAccessibilityActive()) { 603 document->QueueCacheUpdate(accessible, CacheDomain::Bounds); 604 } 605 if (accessible->IsTextLeaf() && 606 accessible->AsTextLeaf()->Text().EqualsLiteral(" ")) { 607 // This space might be becoming invisible, even though it still has a frame. 608 // In this case, the frame will have 0 width. Unfortunately, we can't check 609 // the frame width here because layout isn't ready yet, so we need to defer 610 // this until the refresh driver tick. 611 MOZ_ASSERT(aContent->IsText()); 612 document->UpdateText(aContent); 613 } 614 } 615 616 void nsAccessibilityService::NotifyOfComputedStyleChange( 617 mozilla::PresShell* aPresShell, nsIContent* aContent) { 618 DocAccessible* document = aPresShell->GetDocAccessible(); 619 if (!document) { 620 return; 621 } 622 623 LocalAccessible* accessible = document->GetAccessible(aContent); 624 if (!accessible && aContent == document->GetContent()) { 625 // DocAccessible::GetAccessible() won't return the document if a root 626 // element like body is passed. In that case we need the doc accessible 627 // itself. 628 accessible = document; 629 } 630 631 if (!accessible && aContent && aContent->HasChildren() && 632 !aContent->IsInNativeAnonymousSubtree()) { 633 // If the content has children and its frame has a transform, create an 634 // Accessible so that we can account for the transform when calculating 635 // the Accessible's bounds using the parent process cache. Ditto for 636 // position: fixed/sticky and content with overflow styling (hidden, auto, 637 // scroll) 638 if (const nsIFrame* frame = aContent->GetPrimaryFrame()) { 639 const auto& disp = *frame->StyleDisplay(); 640 if (disp.HasTransform(frame) || 641 disp.mPosition == StylePositionProperty::Fixed || 642 disp.mPosition == StylePositionProperty::Sticky || 643 disp.IsScrollableOverflow()) { 644 document->ContentInserted(aContent, aContent->GetNextSibling()); 645 } 646 } 647 } else if (accessible && IPCAccessibilityActive()) { 648 accessible->MaybeQueueCacheUpdateForStyleChanges(); 649 } 650 } 651 652 void nsAccessibilityService::NotifyOfResolutionChange( 653 mozilla::PresShell* aPresShell, float aResolution) { 654 DocAccessible* document = aPresShell->GetDocAccessible(); 655 if (document && document->IPCDoc()) { 656 AutoTArray<mozilla::a11y::CacheData, 1> data; 657 RefPtr<AccAttributes> fields = new AccAttributes(); 658 fields->SetAttribute(CacheKey::Resolution, aResolution); 659 data.AppendElement(mozilla::a11y::CacheData(0, fields)); 660 document->IPCDoc()->SendCache(CacheUpdateType::Update, data); 661 } 662 } 663 664 void nsAccessibilityService::NotifyOfDevPixelRatioChange( 665 mozilla::PresShell* aPresShell, int32_t aAppUnitsPerDevPixel) { 666 DocAccessible* document = aPresShell->GetDocAccessible(); 667 if (document && document->IPCDoc()) { 668 AutoTArray<mozilla::a11y::CacheData, 1> data; 669 RefPtr<AccAttributes> fields = new AccAttributes(); 670 fields->SetAttribute(CacheKey::AppUnitsPerDevPixel, aAppUnitsPerDevPixel); 671 data.AppendElement(mozilla::a11y::CacheData(0, fields)); 672 document->IPCDoc()->SendCache(CacheUpdateType::Update, data); 673 } 674 } 675 676 void nsAccessibilityService::NotifyAnchorPositionedRemoved( 677 mozilla::PresShell* aPresShell, nsIFrame* aFrame) { 678 DocAccessible* document = aPresShell->GetDocAccessible(); 679 if (!document) { 680 return; 681 } 682 683 const nsIFrame* anchorFrame = 684 nsCoreUtils::GetAnchorForPositionedFrame(aPresShell, aFrame); 685 if (!anchorFrame) { 686 return; 687 } 688 689 if (LocalAccessible* anchorAcc = 690 document->GetAccessible(anchorFrame->GetContent())) { 691 document->QueueCacheUpdate(anchorAcc, CacheDomain::Relations); 692 } 693 } 694 695 void nsAccessibilityService::NotifyAnchorRemoved(mozilla::PresShell* aPresShell, 696 nsIFrame* aFrame) { 697 DocAccessible* document = aPresShell->GetDocAccessible(); 698 if (!document) { 699 return; 700 } 701 702 nsIFrame* positionedFrame = 703 nsCoreUtils::GetPositionedFrameForAnchor(aPresShell, aFrame); 704 if (!positionedFrame) { 705 return; 706 } 707 708 if (LocalAccessible* positionedAcc = 709 document->GetAccessible(positionedFrame->GetContent())) { 710 // If the anchor was removed, its positioned element may now have a 1:1 711 // relation with another anchor, and they would get a description a11y 712 // relation. So we need to go one level deeper here and refresh the cache of 713 // any potential anchors that remain on the positioned element. 714 document->RefreshAnchorRelationCacheForTarget(positionedAcc); 715 } 716 } 717 718 void nsAccessibilityService::NotifyAnchorPositionedScrollUpdate( 719 mozilla::PresShell* aPresShell, nsIFrame* aFrame) { 720 DocAccessible* document = aPresShell->GetDocAccessible(); 721 if (!document) { 722 return; 723 } 724 725 if (LocalAccessible* positionedAcc = 726 document->GetAccessible(aFrame->GetContent())) { 727 // Refresh relations before reflow to notify current anchor. 728 document->RefreshAnchorRelationCacheForTarget(positionedAcc); 729 730 // Refresh relations after next tick when reflow updated to the 731 // new anchor state. 732 document->Controller()->ScheduleNotification<DocAccessible>( 733 document, &DocAccessible::RefreshAnchorRelationCacheForTarget, 734 positionedAcc); 735 } 736 } 737 738 void nsAccessibilityService::NotifyAttrElementWillChange( 739 mozilla::dom::Element* aElement, nsAtom* aAttr) { 740 mozilla::dom::Document* doc = aElement->OwnerDoc(); 741 MOZ_ASSERT(doc); 742 if (DocAccessible* docAcc = GetDocAccessible(doc)) { 743 docAcc->AttrElementWillChange(aElement, aAttr); 744 } 745 } 746 747 void nsAccessibilityService::NotifyAttrElementChanged( 748 mozilla::dom::Element* aElement, nsAtom* aAttr) { 749 mozilla::dom::Document* doc = aElement->OwnerDoc(); 750 MOZ_ASSERT(doc); 751 if (DocAccessible* docAcc = GetDocAccessible(doc)) { 752 docAcc->AttrElementChanged(aElement, aAttr); 753 } 754 } 755 756 LocalAccessible* nsAccessibilityService::GetRootDocumentAccessible( 757 PresShell* aPresShell, bool aCanCreate) { 758 PresShell* presShell = aPresShell; 759 Document* documentNode = aPresShell->GetDocument(); 760 if (documentNode) { 761 nsCOMPtr<nsIDocShellTreeItem> treeItem(documentNode->GetDocShell()); 762 if (treeItem) { 763 nsCOMPtr<nsIDocShellTreeItem> rootTreeItem; 764 treeItem->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem)); 765 if (treeItem != rootTreeItem) { 766 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootTreeItem)); 767 presShell = docShell->GetPresShell(); 768 } 769 770 return aCanCreate ? GetDocAccessible(presShell) 771 : presShell->GetDocAccessible(); 772 } 773 } 774 return nullptr; 775 } 776 777 void nsAccessibilityService::NotifyOfTabPanelVisibilityChange( 778 PresShell* aPresShell, Element* aPanel, bool aNowVisible) { 779 MOZ_ASSERT(aPanel->GetParent()->IsXULElement(nsGkAtoms::tabpanels)); 780 781 DocAccessible* document = GetDocAccessible(aPresShell); 782 if (!document) { 783 return; 784 } 785 786 if (LocalAccessible* acc = document->GetAccessible(aPanel)) { 787 RefPtr<AccEvent> event = 788 new AccStateChangeEvent(acc, states::OFFSCREEN, aNowVisible); 789 document->FireDelayedEvent(event); 790 } 791 } 792 793 void nsAccessibilityService::ContentRangeInserted(PresShell* aPresShell, 794 nsIContent* aStartChild, 795 nsIContent* aEndChild) { 796 DocAccessible* document = GetDocAccessible(aPresShell); 797 #ifdef A11Y_LOG 798 if (logging::IsEnabled(logging::eTree)) { 799 logging::MsgBegin("TREE", "content inserted; doc: %p", document); 800 logging::Node("container", aStartChild->GetParentNode()); 801 for (nsIContent* child = aStartChild; child != aEndChild; 802 child = child->GetNextSibling()) { 803 logging::Node("content", child); 804 } 805 logging::MsgEnd(); 806 logging::Stack(); 807 } 808 #endif 809 810 if (document) { 811 document->ContentInserted(aStartChild, aEndChild); 812 } 813 } 814 815 void nsAccessibilityService::ScheduleAccessibilitySubtreeUpdate( 816 PresShell* aPresShell, nsIContent* aContent) { 817 DocAccessible* document = GetDocAccessible(aPresShell); 818 #ifdef A11Y_LOG 819 if (logging::IsEnabled(logging::eTree)) { 820 logging::MsgBegin("TREE", "schedule update; doc: %p", document); 821 logging::Node("content node", aContent); 822 logging::MsgEnd(); 823 } 824 #endif 825 826 if (document) { 827 document->ScheduleTreeUpdate(aContent); 828 } 829 } 830 831 void nsAccessibilityService::ContentRemoved(PresShell* aPresShell, 832 nsIContent* aChildNode) { 833 DocAccessible* document = GetDocAccessible(aPresShell); 834 #ifdef A11Y_LOG 835 if (logging::IsEnabled(logging::eTree)) { 836 logging::MsgBegin("TREE", "content removed; doc: %p", document); 837 logging::Node("container node", aChildNode->GetFlattenedTreeParent()); 838 logging::Node("content node", aChildNode); 839 logging::MsgEnd(); 840 } 841 #endif 842 843 if (document) { 844 document->ContentRemoved(aChildNode); 845 } 846 847 #ifdef A11Y_LOG 848 if (logging::IsEnabled(logging::eTree)) { 849 logging::MsgEnd(); 850 logging::Stack(); 851 } 852 #endif 853 } 854 855 void nsAccessibilityService::TableLayoutGuessMaybeChanged( 856 PresShell* aPresShell, nsIContent* aContent) { 857 if (DocAccessible* document = GetDocAccessible(aPresShell)) { 858 if (LocalAccessible* acc = document->GetAccessible(aContent)) { 859 if (LocalAccessible* table = nsAccUtils::TableFor(acc)) { 860 document->QueueCacheUpdate(table, CacheDomain::Table); 861 } 862 } 863 } 864 } 865 866 void nsAccessibilityService::ComboboxOptionMaybeChanged( 867 PresShell* aPresShell, nsIContent* aMutatingNode) { 868 DocAccessible* document = GetDocAccessible(aPresShell); 869 if (!document) { 870 return; 871 } 872 873 for (nsIContent* cur = aMutatingNode; cur; cur = cur->GetParent()) { 874 if (cur->IsHTMLElement(nsGkAtoms::option)) { 875 if (LocalAccessible* accessible = document->GetAccessible(cur)) { 876 document->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, 877 accessible); 878 break; 879 } 880 if (cur->IsHTMLElement(nsGkAtoms::select)) { 881 break; 882 } 883 } 884 } 885 } 886 887 void nsAccessibilityService::UpdateText(PresShell* aPresShell, 888 nsIContent* aContent) { 889 DocAccessible* document = GetDocAccessible(aPresShell); 890 if (document) document->UpdateText(aContent); 891 } 892 893 void nsAccessibilityService::TreeViewChanged(PresShell* aPresShell, 894 nsIContent* aContent, 895 nsITreeView* aView) { 896 DocAccessible* document = GetDocAccessible(aPresShell); 897 if (document) { 898 LocalAccessible* accessible = document->GetAccessible(aContent); 899 if (accessible) { 900 XULTreeAccessible* treeAcc = accessible->AsXULTree(); 901 if (treeAcc) treeAcc->TreeViewChanged(aView); 902 } 903 } 904 } 905 906 void nsAccessibilityService::RangeValueChanged(PresShell* aPresShell, 907 nsIContent* aContent) { 908 DocAccessible* document = GetDocAccessible(aPresShell); 909 if (document) { 910 LocalAccessible* accessible = document->GetAccessible(aContent); 911 if (accessible) { 912 document->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, 913 accessible); 914 } 915 } 916 } 917 918 void nsAccessibilityService::UpdateImageMap(nsImageFrame* aImageFrame) { 919 PresShell* presShell = aImageFrame->PresShell(); 920 DocAccessible* document = GetDocAccessible(presShell); 921 if (document) { 922 LocalAccessible* accessible = 923 document->GetAccessible(aImageFrame->GetContent()); 924 if (accessible) { 925 HTMLImageMapAccessible* imageMap = accessible->AsImageMap(); 926 if (imageMap) { 927 imageMap->UpdateChildAreas(); 928 return; 929 } 930 931 // If image map was initialized after we created an accessible (that'll 932 // be an image accessible) then recreate it. 933 RecreateAccessible(presShell, aImageFrame->GetContent()); 934 } 935 } 936 } 937 938 void nsAccessibilityService::UpdateLabelValue(PresShell* aPresShell, 939 nsIContent* aLabelElm, 940 const nsString& aNewValue) { 941 DocAccessible* document = GetDocAccessible(aPresShell); 942 if (document) { 943 LocalAccessible* accessible = document->GetAccessible(aLabelElm); 944 if (accessible) { 945 XULLabelAccessible* xulLabel = accessible->AsXULLabel(); 946 NS_ASSERTION(xulLabel, 947 "UpdateLabelValue was called for wrong accessible!"); 948 if (xulLabel) xulLabel->UpdateLabelValue(aNewValue); 949 } 950 } 951 } 952 953 void nsAccessibilityService::PresShellActivated(PresShell* aPresShell) { 954 DocAccessible* document = aPresShell->GetDocAccessible(); 955 if (document) { 956 RootAccessible* rootDocument = document->RootAccessible(); 957 NS_ASSERTION(rootDocument, "Entirely broken tree: no root document!"); 958 if (rootDocument) rootDocument->DocumentActivated(document); 959 } 960 } 961 962 void nsAccessibilityService::RecreateAccessible(PresShell* aPresShell, 963 nsIContent* aContent) { 964 DocAccessible* document = GetDocAccessible(aPresShell); 965 if (document) document->RecreateAccessible(aContent); 966 } 967 968 void nsAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString) { 969 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \ 970 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \ 971 nameRule) \ 972 case roles::geckoRole: \ 973 aString.AssignLiteral(stringRole); \ 974 return; 975 976 switch (aRole) { 977 #include "RoleMap.h" 978 default: 979 aString.AssignLiteral("unknown"); 980 return; 981 } 982 983 #undef ROLE 984 } 985 986 void nsAccessibilityService::GetStringStates(uint32_t aState, 987 uint32_t aExtraState, 988 nsISupports** aStringStates) { 989 RefPtr<DOMStringList> stringStates = 990 GetStringStates(nsAccUtils::To64State(aState, aExtraState)); 991 992 // unknown state 993 if (!stringStates->Length()) { 994 stringStates->Add(u"unknown"_ns); 995 } 996 997 stringStates.forget(aStringStates); 998 } 999 1000 already_AddRefed<DOMStringList> nsAccessibilityService::GetStringStates( 1001 uint64_t aStates) const { 1002 RefPtr<DOMStringList> stringStates = new DOMStringList(); 1003 1004 if (aStates & states::UNAVAILABLE) { 1005 stringStates->Add(u"unavailable"_ns); 1006 } 1007 if (aStates & states::SELECTED) { 1008 stringStates->Add(u"selected"_ns); 1009 } 1010 if (aStates & states::FOCUSED) { 1011 stringStates->Add(u"focused"_ns); 1012 } 1013 if (aStates & states::PRESSED) { 1014 stringStates->Add(u"pressed"_ns); 1015 } 1016 if (aStates & states::CHECKED) { 1017 stringStates->Add(u"checked"_ns); 1018 } 1019 if (aStates & states::MIXED) { 1020 stringStates->Add(u"mixed"_ns); 1021 } 1022 if (aStates & states::READONLY) { 1023 stringStates->Add(u"readonly"_ns); 1024 } 1025 if (aStates & states::HOTTRACKED) { 1026 stringStates->Add(u"hottracked"_ns); 1027 } 1028 if (aStates & states::DEFAULT) { 1029 stringStates->Add(u"default"_ns); 1030 } 1031 if (aStates & states::EXPANDED) { 1032 stringStates->Add(u"expanded"_ns); 1033 } 1034 if (aStates & states::COLLAPSED) { 1035 stringStates->Add(u"collapsed"_ns); 1036 } 1037 if (aStates & states::BUSY) { 1038 stringStates->Add(u"busy"_ns); 1039 } 1040 if (aStates & states::FLOATING) { 1041 stringStates->Add(u"floating"_ns); 1042 } 1043 if (aStates & states::ANIMATED) { 1044 stringStates->Add(u"animated"_ns); 1045 } 1046 if (aStates & states::INVISIBLE) { 1047 stringStates->Add(u"invisible"_ns); 1048 } 1049 if (aStates & states::OFFSCREEN) { 1050 stringStates->Add(u"offscreen"_ns); 1051 } 1052 if (aStates & states::SIZEABLE) { 1053 stringStates->Add(u"sizeable"_ns); 1054 } 1055 if (aStates & states::MOVEABLE) { 1056 stringStates->Add(u"moveable"_ns); 1057 } 1058 if (aStates & states::SELFVOICING) { 1059 stringStates->Add(u"selfvoicing"_ns); 1060 } 1061 if (aStates & states::FOCUSABLE) { 1062 stringStates->Add(u"focusable"_ns); 1063 } 1064 if (aStates & states::SELECTABLE) { 1065 stringStates->Add(u"selectable"_ns); 1066 } 1067 if (aStates & states::LINKED) { 1068 stringStates->Add(u"linked"_ns); 1069 } 1070 if (aStates & states::TRAVERSED) { 1071 stringStates->Add(u"traversed"_ns); 1072 } 1073 if (aStates & states::MULTISELECTABLE) { 1074 stringStates->Add(u"multiselectable"_ns); 1075 } 1076 if (aStates & states::EXTSELECTABLE) { 1077 stringStates->Add(u"extselectable"_ns); 1078 } 1079 if (aStates & states::PROTECTED) { 1080 stringStates->Add(u"protected"_ns); 1081 } 1082 if (aStates & states::HASPOPUP) { 1083 stringStates->Add(u"haspopup"_ns); 1084 } 1085 if (aStates & states::REQUIRED) { 1086 stringStates->Add(u"required"_ns); 1087 } 1088 if (aStates & states::ALERT) { 1089 stringStates->Add(u"alert"_ns); 1090 } 1091 if (aStates & states::INVALID) { 1092 stringStates->Add(u"invalid"_ns); 1093 } 1094 if (aStates & states::CHECKABLE) { 1095 stringStates->Add(u"checkable"_ns); 1096 } 1097 if (aStates & states::SUPPORTS_AUTOCOMPLETION) { 1098 stringStates->Add(u"autocompletion"_ns); 1099 } 1100 if (aStates & states::DEFUNCT) { 1101 stringStates->Add(u"defunct"_ns); 1102 } 1103 if (aStates & states::SELECTABLE_TEXT) { 1104 stringStates->Add(u"selectable text"_ns); 1105 } 1106 if (aStates & states::EDITABLE) { 1107 stringStates->Add(u"editable"_ns); 1108 } 1109 if (aStates & states::ACTIVE) { 1110 stringStates->Add(u"active"_ns); 1111 } 1112 if (aStates & states::MODAL) { 1113 stringStates->Add(u"modal"_ns); 1114 } 1115 if (aStates & states::MULTI_LINE) { 1116 stringStates->Add(u"multi line"_ns); 1117 } 1118 if (aStates & states::HORIZONTAL) { 1119 stringStates->Add(u"horizontal"_ns); 1120 } 1121 if (aStates & states::OPAQUE1) { 1122 stringStates->Add(u"opaque"_ns); 1123 } 1124 if (aStates & states::SINGLE_LINE) { 1125 stringStates->Add(u"single line"_ns); 1126 } 1127 if (aStates & states::TRANSIENT) { 1128 stringStates->Add(u"transient"_ns); 1129 } 1130 if (aStates & states::VERTICAL) { 1131 stringStates->Add(u"vertical"_ns); 1132 } 1133 if (aStates & states::STALE) { 1134 stringStates->Add(u"stale"_ns); 1135 } 1136 if (aStates & states::ENABLED) { 1137 stringStates->Add(u"enabled"_ns); 1138 } 1139 if (aStates & states::SENSITIVE) { 1140 stringStates->Add(u"sensitive"_ns); 1141 } 1142 if (aStates & states::EXPANDABLE) { 1143 stringStates->Add(u"expandable"_ns); 1144 } 1145 if (aStates & states::PINNED) { 1146 stringStates->Add(u"pinned"_ns); 1147 } 1148 if (aStates & states::CURRENT) { 1149 stringStates->Add(u"current"_ns); 1150 } 1151 1152 return stringStates.forget(); 1153 } 1154 1155 void nsAccessibilityService::GetStringEventType(uint32_t aEventType, 1156 nsAString& aString) { 1157 static_assert( 1158 nsIAccessibleEvent::EVENT_LAST_ENTRY == std::size(kEventTypeNames), 1159 "nsIAccessibleEvent constants are out of sync to kEventTypeNames"); 1160 1161 if (aEventType >= std::size(kEventTypeNames)) { 1162 aString.AssignLiteral("unknown"); 1163 return; 1164 } 1165 1166 aString.AssignASCII(kEventTypeNames[aEventType]); 1167 } 1168 1169 void nsAccessibilityService::GetStringEventType(uint32_t aEventType, 1170 nsACString& aString) { 1171 MOZ_ASSERT(nsIAccessibleEvent::EVENT_LAST_ENTRY == std::size(kEventTypeNames), 1172 "nsIAccessibleEvent constants are out of sync to kEventTypeNames"); 1173 1174 if (aEventType >= std::size(kEventTypeNames)) { 1175 aString.AssignLiteral("unknown"); 1176 return; 1177 } 1178 1179 aString = nsDependentCString(kEventTypeNames[aEventType]); 1180 } 1181 1182 void nsAccessibilityService::GetStringRelationType(uint32_t aRelationType, 1183 nsAString& aString) { 1184 NS_ENSURE_TRUE_VOID(aRelationType <= 1185 static_cast<uint32_t>(RelationType::LAST)); 1186 1187 #define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \ 1188 case RelationType::geckoType: \ 1189 aString.AssignLiteral(geckoTypeName); \ 1190 return; 1191 1192 RelationType relationType = static_cast<RelationType>(aRelationType); 1193 switch (relationType) { 1194 #include "RelationTypeMap.h" 1195 default: 1196 aString.AssignLiteral("unknown"); 1197 return; 1198 } 1199 1200 #undef RELATIONTYPE 1201 } 1202 1203 //////////////////////////////////////////////////////////////////////////////// 1204 // nsAccessibilityService public 1205 1206 LocalAccessible* nsAccessibilityService::CreateAccessible( 1207 nsINode* aNode, LocalAccessible* aContext, bool* aIsSubtreeHidden) { 1208 MOZ_ASSERT(aContext, "No context provided"); 1209 MOZ_ASSERT(aNode, "No node to create an accessible for"); 1210 MOZ_ASSERT(gConsumers, "No creation after shutdown"); 1211 1212 if (aIsSubtreeHidden) *aIsSubtreeHidden = false; 1213 1214 DocAccessible* document = aContext->Document(); 1215 MOZ_ASSERT(!document->GetAccessible(aNode), 1216 "We already have an accessible for this node."); 1217 1218 if (aNode->IsDocument()) { 1219 // If it's document node then ask accessible document loader for 1220 // document accessible, otherwise return null. 1221 return GetDocAccessible(aNode->AsDocument()); 1222 } 1223 1224 // We have a content node. 1225 if (!aNode->GetComposedDoc()) { 1226 NS_WARNING("Creating accessible for node with no document"); 1227 return nullptr; 1228 } 1229 1230 if (aNode->OwnerDoc() != document->DocumentNode()) { 1231 NS_ERROR("Creating accessible for wrong document"); 1232 return nullptr; 1233 } 1234 1235 if (!aNode->IsContent()) return nullptr; 1236 1237 nsIContent* content = aNode->AsContent(); 1238 if (aria::IsValidARIAHidden(content)) { 1239 if (aIsSubtreeHidden) { 1240 *aIsSubtreeHidden = true; 1241 } 1242 return nullptr; 1243 } 1244 1245 // Check frame and its visibility. 1246 nsIFrame* frame = content->GetPrimaryFrame(); 1247 if (frame) { 1248 // If invisible or inert, we don't create an accessible, but we don't mark 1249 // it with aIsSubtreeHidden = true, since visibility: hidden frame allows 1250 // visible elements in subtree, and inert elements allow non-inert 1251 // elements. 1252 if (!frame->StyleVisibility()->IsVisible() || frame->StyleUI()->IsInert()) { 1253 return nullptr; 1254 } 1255 } else if (nsCoreUtils::CanCreateAccessibleWithoutFrame(content)) { 1256 // display:contents element doesn't have a frame, but retains the 1257 // semantics. All its children are unaffected. 1258 const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement()); 1259 RefPtr<LocalAccessible> newAcc = MaybeCreateSpecificARIAAccessible( 1260 roleMapEntry, aContext, content, document); 1261 const MarkupMapInfo* markupMap = nullptr; 1262 if (!newAcc) { 1263 markupMap = GetMarkupMapInfoFor(content); 1264 if (markupMap && markupMap->new_func) { 1265 newAcc = markupMap->new_func(content->AsElement(), aContext); 1266 } 1267 } 1268 1269 // SVG elements are not in a markup map, but we may still need to create an 1270 // accessible for one, even in the case of display:contents. 1271 if (!newAcc && content->IsSVGElement()) { 1272 newAcc = MaybeCreateSVGAccessible(content, document); 1273 } 1274 1275 // Check whether this element has an ARIA role or attribute that requires 1276 // us to create an Accessible. 1277 const bool hasNonPresentationalARIARole = 1278 roleMapEntry && !roleMapEntry->Is(nsGkAtoms::presentation) && 1279 !roleMapEntry->Is(nsGkAtoms::none); 1280 if (!newAcc && 1281 (hasNonPresentationalARIARole || MustBeAccessible(content, document))) { 1282 newAcc = new HyperTextAccessible(content, document); 1283 } 1284 1285 // If there's still no Accessible but we do have an entry in the markup 1286 // map for this non-presentational element, create a generic 1287 // HyperTextAccessible. 1288 if (!newAcc && markupMap && 1289 (!roleMapEntry || hasNonPresentationalARIARole)) { 1290 newAcc = new HyperTextAccessible(content, document); 1291 } 1292 1293 if (newAcc) { 1294 document->BindToDocument(newAcc, roleMapEntry); 1295 } 1296 return newAcc; 1297 } else { 1298 if (aIsSubtreeHidden) { 1299 *aIsSubtreeHidden = true; 1300 } 1301 return nullptr; 1302 } 1303 1304 if (frame->IsHiddenByContentVisibilityOnAnyAncestor( 1305 nsIFrame::IncludeContentVisibility::Hidden)) { 1306 if (aIsSubtreeHidden) { 1307 *aIsSubtreeHidden = true; 1308 } 1309 return nullptr; 1310 } 1311 1312 if (nsMenuPopupFrame* popupFrame = do_QueryFrame(frame)) { 1313 // Hidden tooltips and panels don't create accessibles in the whole subtree. 1314 // Showing them gets handled by RootAccessible::ProcessDOMEvent. 1315 if (content->IsAnyOfXULElements(nsGkAtoms::tooltip, nsGkAtoms::panel)) { 1316 nsPopupState popupState = popupFrame->PopupState(); 1317 if (popupState == ePopupHiding || popupState == ePopupInvisible || 1318 popupState == ePopupClosed) { 1319 if (aIsSubtreeHidden) { 1320 *aIsSubtreeHidden = true; 1321 } 1322 return nullptr; 1323 } 1324 } 1325 } 1326 1327 if (frame->GetContent() != content) { 1328 // Not the main content for this frame. This happens because <area> 1329 // elements return the image frame as their primary frame. The main content 1330 // for the image frame is the image content. If the frame is not an image 1331 // frame or the node is not an area element then null is returned. 1332 // This setup will change when bug 135040 is fixed. Make sure we don't 1333 // create area accessible here. Hopefully assertion below will handle that. 1334 1335 #ifdef DEBUG 1336 nsImageFrame* imageFrame = do_QueryFrame(frame); 1337 NS_ASSERTION(imageFrame && content->IsHTMLElement(nsGkAtoms::area), 1338 "Unknown case of not main content for the frame!"); 1339 #endif 1340 return nullptr; 1341 } 1342 1343 #ifdef DEBUG 1344 nsImageFrame* imageFrame = do_QueryFrame(frame); 1345 NS_ASSERTION(!imageFrame || !content->IsHTMLElement(nsGkAtoms::area), 1346 "Image map manages the area accessible creation!"); 1347 #endif 1348 1349 // Attempt to create an accessible based on what we know. 1350 RefPtr<LocalAccessible> newAcc; 1351 1352 // Create accessible for visible text frames. 1353 if (content->IsText()) { 1354 nsIFrame::RenderedText text = frame->GetRenderedText( 1355 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText, 1356 nsIFrame::TrailingWhitespace::DontTrim); 1357 auto cssAlt = CssAltContent(content); 1358 // Ignore not rendered text nodes and whitespace text nodes between table 1359 // cells. 1360 if (text.mString.IsEmpty() || 1361 (nsCoreUtils::IsTrimmedWhitespaceBeforeHardLineBreak(frame) && 1362 // If there is CSS alt text, it's okay if the text itself is just 1363 // whitespace; e.g. content: " " / "alt" 1364 !cssAlt) || 1365 (aContext->IsTableRow() && 1366 nsCoreUtils::IsWhitespaceString(text.mString))) { 1367 if (aIsSubtreeHidden) *aIsSubtreeHidden = true; 1368 1369 return nullptr; 1370 } 1371 1372 newAcc = CreateAccessibleByFrameType(frame, content, aContext); 1373 MOZ_ASSERT(newAcc, "Accessible not created for text node!"); 1374 document->BindToDocument(newAcc, nullptr); 1375 if (cssAlt) { 1376 nsAutoString text; 1377 cssAlt.AppendToString(text); 1378 newAcc->AsTextLeaf()->SetText(text); 1379 } else { 1380 newAcc->AsTextLeaf()->SetText(text.mString); 1381 } 1382 return newAcc; 1383 } 1384 1385 if (content->IsHTMLElement(nsGkAtoms::map)) { 1386 // Create hyper text accessible for HTML map if it is used to group links 1387 // (see http://www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass). If the HTML 1388 // map rect is empty then it is used for links grouping. Otherwise it should 1389 // be used in conjunction with HTML image element and in this case we don't 1390 // create any accessible for it and don't walk into it. The accessibles for 1391 // HTML area (HTMLAreaAccessible) the map contains are attached as 1392 // children of the appropriate accessible for HTML image 1393 // (ImageAccessible). 1394 if (nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame->GetParent()) 1395 .IsEmpty()) { 1396 if (aIsSubtreeHidden) *aIsSubtreeHidden = true; 1397 1398 return nullptr; 1399 } 1400 1401 newAcc = new HyperTextAccessible(content, document); 1402 document->BindToDocument(newAcc, aria::GetRoleMap(content->AsElement())); 1403 return newAcc; 1404 } 1405 1406 const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement()); 1407 1408 if (roleMapEntry && (roleMapEntry->Is(nsGkAtoms::presentation) || 1409 roleMapEntry->Is(nsGkAtoms::none))) { 1410 if (MustBeAccessible(content, document)) { 1411 // If the element is focusable, a global ARIA attribute is applied to it 1412 // or it is referenced by an ARIA relationship, then treat 1413 // role="presentation" on the element as if the role is not there. 1414 roleMapEntry = nullptr; 1415 } else if (MustBeGenericAccessible(content, document)) { 1416 // Clear roleMapEntry so that we use the generic role specified below. 1417 // Otherwise, we'd expose roles::NOTHING as specified for presentation in 1418 // ARIAMap. 1419 roleMapEntry = nullptr; 1420 newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content, 1421 document); 1422 } else { 1423 return nullptr; 1424 } 1425 } 1426 1427 // We should always use OuterDocAccessible for OuterDocs, even if there's a 1428 // specific ARIA class we would otherwise use. 1429 if (!newAcc && frame->AccessibleType() != eOuterDocType) { 1430 newAcc = MaybeCreateSpecificARIAAccessible(roleMapEntry, aContext, content, 1431 document); 1432 } 1433 1434 if (!newAcc && content->IsHTMLElement()) { // HTML accessibles 1435 // Prefer to use markup to decide if and what kind of accessible to 1436 // create, 1437 const MarkupMapInfo* markupMap = 1438 mHTMLMarkupMap.Get(content->NodeInfo()->NameAtom()); 1439 if (markupMap && markupMap->new_func) { 1440 newAcc = markupMap->new_func(content->AsElement(), aContext); 1441 } 1442 1443 if (!newAcc) { // try by frame accessible type. 1444 newAcc = CreateAccessibleByFrameType(frame, content, aContext); 1445 } 1446 1447 // If table has strong ARIA role then all table descendants shouldn't 1448 // expose their native roles. 1449 if (!roleMapEntry && newAcc && aContext->HasStrongARIARole()) { 1450 if (frame->AccessibleType() == eHTMLTableRowType) { 1451 const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap(); 1452 if (!contextRoleMap->IsOfType(eTable)) { 1453 roleMapEntry = &aria::gEmptyRoleMap; 1454 } 1455 1456 } else if (frame->AccessibleType() == eHTMLTableCellType && 1457 aContext->ARIARoleMap() == &aria::gEmptyRoleMap) { 1458 roleMapEntry = &aria::gEmptyRoleMap; 1459 1460 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::li, 1461 nsGkAtoms::dd) || 1462 frame->AccessibleType() == eHTMLLiType) { 1463 const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap(); 1464 if (!contextRoleMap->IsOfType(eList)) { 1465 roleMapEntry = &aria::gEmptyRoleMap; 1466 } 1467 } 1468 } 1469 } 1470 1471 // XUL accessibles. 1472 if (!newAcc && content->IsXULElement()) { 1473 if (content->IsXULElement(nsGkAtoms::panel)) { 1474 // We filter here instead of in the XUL map because 1475 // if we filter there and return null, we still end up 1476 // creating a generic accessible at the end of this function. 1477 // Doing the filtering here ensures we never create accessibles 1478 // for panels whose popups aren't visible. 1479 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame); 1480 if (!popupFrame) { 1481 return nullptr; 1482 } 1483 1484 nsPopupState popupState = popupFrame->PopupState(); 1485 if (popupState == ePopupHiding || popupState == ePopupInvisible || 1486 popupState == ePopupClosed) { 1487 return nullptr; 1488 } 1489 } 1490 1491 // Prefer to use XUL to decide if and what kind of accessible to create. 1492 const XULMarkupMapInfo* xulMap = 1493 mXULMarkupMap.Get(content->NodeInfo()->NameAtom()); 1494 if (xulMap && xulMap->new_func) { 1495 newAcc = xulMap->new_func(content->AsElement(), aContext); 1496 } 1497 1498 // Any XUL/flex box can be used as tabpanel, make sure we create a proper 1499 // accessible for it. 1500 if (!newAcc && aContext->IsXULTabpanels() && 1501 content->GetParent() == aContext->GetContent()) { 1502 LayoutFrameType frameType = frame->Type(); 1503 // FIXME(emilio): Why only these frame types? 1504 if (frameType == LayoutFrameType::FlexContainer || 1505 frameType == LayoutFrameType::ScrollContainer) { 1506 newAcc = new XULTabpanelAccessible(content, document); 1507 } 1508 } 1509 } 1510 1511 if (!newAcc) { 1512 if (content->IsSVGElement()) { 1513 newAcc = MaybeCreateSVGAccessible(content, document); 1514 } else if (content->IsMathMLElement()) { 1515 const MarkupMapInfo* markupMap = 1516 mMathMLMarkupMap.Get(content->NodeInfo()->NameAtom()); 1517 if (markupMap && markupMap->new_func) { 1518 newAcc = markupMap->new_func(content->AsElement(), aContext); 1519 } 1520 1521 // Fall back to text when encountering Content MathML. 1522 if (!newAcc && 1523 !content->IsAnyOfMathMLElements( 1524 nsGkAtoms::annotation, nsGkAtoms::annotation_xml, 1525 nsGkAtoms::mpadded, nsGkAtoms::mphantom, nsGkAtoms::maligngroup, 1526 nsGkAtoms::malignmark, nsGkAtoms::mspace, nsGkAtoms::semantics)) { 1527 newAcc = new HyperTextAccessible(content, document); 1528 } 1529 } else if (content->IsGeneratedContentContainerForMarker()) { 1530 if (aContext->IsHTMLListItem()) { 1531 newAcc = new HTMLListBulletAccessible(content, document); 1532 } 1533 if (aIsSubtreeHidden) { 1534 *aIsSubtreeHidden = true; 1535 } 1536 } else if (auto cssAlt = CssAltContent(content)) { 1537 // This is a pseudo-element without children that has CSS alt text. This 1538 // only happens when there is alt text with an empty content string; e.g. 1539 // content: "" / "alt" 1540 // In this case, we need to expose the alt text on the pseudo-element 1541 // itself, since we don't have a child to use. We create a 1542 // TextLeafAccessible with the pseudo-element as the backing DOM node. 1543 newAcc = new TextLeafAccessible(content, document); 1544 nsAutoString text; 1545 cssAlt.AppendToString(text); 1546 newAcc->AsTextLeaf()->SetText(text); 1547 } 1548 } 1549 1550 // If no accessible, see if we need to create a generic accessible because 1551 // of some property that makes this object interesting 1552 // We don't do this for <body>, <html>, <window>, <dialog> etc. which 1553 // correspond to the doc accessible and will be created in any case 1554 if (!newAcc && !content->IsHTMLElement(nsGkAtoms::body) && 1555 content->GetParent() && 1556 (roleMapEntry || MustBeAccessible(content, document) || 1557 (content->IsHTMLElement() && nsCoreUtils::HasClickListener(content)))) { 1558 // This content is focusable or has an interesting dynamic content 1559 // accessibility property. If it's interesting we need it in the 1560 // accessibility hierarchy so that events or other accessibles can point to 1561 // it, or so that it can hold a state, etc. 1562 if (content->IsHTMLElement() || content->IsMathMLElement() || 1563 content->IsSVGElement(nsGkAtoms::foreignObject)) { 1564 // Interesting container which may have selectable text and/or embedded 1565 // objects. 1566 newAcc = new HyperTextAccessible(content, document); 1567 } else { // XUL, other SVG, etc. 1568 // Interesting generic non-HTML container 1569 newAcc = new AccessibleWrap(content, document); 1570 } 1571 } else if (!newAcc && MustBeGenericAccessible(content, document)) { 1572 newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content, 1573 document); 1574 } 1575 1576 if (newAcc) { 1577 document->BindToDocument(newAcc, roleMapEntry); 1578 } 1579 return newAcc; 1580 } 1581 1582 #if defined(ANDROID) 1583 # include "mozilla/Monitor.h" 1584 # include "mozilla/Maybe.h" 1585 1586 MOZ_RUNINIT static Maybe<Monitor> sAndroidMonitor; 1587 1588 mozilla::Monitor& nsAccessibilityService::GetAndroidMonitor() { 1589 if (!sAndroidMonitor.isSome()) { 1590 sAndroidMonitor.emplace("nsAccessibility::sAndroidMonitor"); 1591 } 1592 1593 return *sAndroidMonitor; 1594 } 1595 #endif 1596 1597 //////////////////////////////////////////////////////////////////////////////// 1598 // nsAccessibilityService private 1599 1600 bool nsAccessibilityService::Init(uint64_t aCacheDomains) { 1601 AUTO_PROFILER_MARKER_UNTYPED("nsAccessibilityService::Init", A11Y, {}); 1602 // DO NOT ADD CODE ABOVE HERE: THIS CODE IS MEASURING TIMINGS. 1603 PerfStats::AutoMetricRecording< 1604 PerfStats::Metric::A11Y_AccessibilityServiceInit> 1605 autoRecording; 1606 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS. 1607 1608 // Initialize accessible document manager. 1609 if (!DocManager::Init()) return false; 1610 1611 // Add observers. 1612 nsCOMPtr<nsIObserverService> observerService = 1613 mozilla::services::GetObserverService(); 1614 if (!observerService) return false; 1615 1616 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 1617 1618 #if defined(XP_WIN) 1619 // This information needs to be initialized before the observer fires. 1620 if (XRE_IsParentProcess()) { 1621 Compatibility::Init(); 1622 } 1623 #endif // defined(XP_WIN) 1624 1625 // Subscribe to EventListenerService. 1626 nsCOMPtr<nsIEventListenerService> eventListenerService = 1627 do_GetService("@mozilla.org/eventlistenerservice;1"); 1628 if (!eventListenerService) return false; 1629 1630 eventListenerService->AddListenerChangeListener(this); 1631 1632 for (uint32_t i = 0; i < std::size(sHTMLMarkupMapList); i++) { 1633 mHTMLMarkupMap.InsertOrUpdate(sHTMLMarkupMapList[i].tag, 1634 &sHTMLMarkupMapList[i]); 1635 } 1636 for (const auto& info : sMathMLMarkupMapList) { 1637 mMathMLMarkupMap.InsertOrUpdate(info.tag, &info); 1638 } 1639 1640 for (uint32_t i = 0; i < std::size(sXULMarkupMapList); i++) { 1641 mXULMarkupMap.InsertOrUpdate(sXULMarkupMapList[i].tag, 1642 &sXULMarkupMapList[i]); 1643 } 1644 1645 #ifdef A11Y_LOG 1646 logging::CheckEnv(); 1647 #endif 1648 1649 gAccessibilityService = this; 1650 NS_ADDREF(gAccessibilityService); // will release in Shutdown() 1651 1652 if (XRE_IsParentProcess()) { 1653 gApplicationAccessible = new ApplicationAccessibleWrap(); 1654 } else { 1655 gApplicationAccessible = new ApplicationAccessible(); 1656 } 1657 1658 NS_ADDREF(gApplicationAccessible); // will release in Shutdown() 1659 gApplicationAccessible->Init(); 1660 1661 CrashReporter::RecordAnnotationCString( 1662 CrashReporter::Annotation::Accessibility, "Active"); 1663 1664 // Now its safe to start platform accessibility. 1665 if (XRE_IsParentProcess()) PlatformInit(); 1666 1667 // Check the startup cache domain pref. We might be in a test environment 1668 // where we need to have all cache domains enabled (e.g., fuzzing). 1669 if (XRE_IsParentProcess() && 1670 StaticPrefs::accessibility_enable_all_cache_domains_AtStartup()) { 1671 gCacheDomains = CacheDomain::All; 1672 } 1673 1674 // Set the active accessibility cache domains. We might want to modify the 1675 // domains that we activate based on information about the instantiator. 1676 gCacheDomains = ::GetCacheDomainsForKnownClients(aCacheDomains); 1677 1678 static const char16_t kInitIndicator[] = {'1', 0}; 1679 observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", 1680 kInitIndicator); 1681 1682 return true; 1683 } 1684 1685 void nsAccessibilityService::Shutdown() { 1686 // Application is going to be closed, shutdown accessibility and mark 1687 // accessibility service as shutdown to prevent calls of its methods. 1688 // Don't null accessibility service static member at this point to be safe 1689 // if someone will try to operate with it. 1690 1691 MOZ_ASSERT(gConsumers, "Accessibility was shutdown already"); 1692 UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI); 1693 1694 // Remove observers. 1695 nsCOMPtr<nsIObserverService> observerService = 1696 mozilla::services::GetObserverService(); 1697 if (observerService) { 1698 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 1699 } 1700 1701 // Stop accessible document loader. 1702 DocManager::Shutdown(); 1703 1704 SelectionManager::Shutdown(); 1705 1706 if (XRE_IsParentProcess()) PlatformShutdown(); 1707 1708 gApplicationAccessible->Shutdown(); 1709 NS_RELEASE(gApplicationAccessible); 1710 gApplicationAccessible = nullptr; 1711 1712 NS_IF_RELEASE(gXPCApplicationAccessible); 1713 gXPCApplicationAccessible = nullptr; 1714 1715 #if defined(ANDROID) 1716 // Don't allow the service to shut down while an a11y request is being handled 1717 // in the UI thread, as the request may depend on state from the service. 1718 MonitorAutoLock mal(GetAndroidMonitor()); 1719 #endif 1720 NS_RELEASE(gAccessibilityService); 1721 gAccessibilityService = nullptr; 1722 1723 if (observerService) { 1724 static const char16_t kShutdownIndicator[] = {'0', 0}; 1725 observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", 1726 kShutdownIndicator); 1727 } 1728 } 1729 1730 already_AddRefed<LocalAccessible> 1731 nsAccessibilityService::CreateAccessibleByFrameType(nsIFrame* aFrame, 1732 nsIContent* aContent, 1733 LocalAccessible* aContext) { 1734 DocAccessible* document = aContext->Document(); 1735 1736 RefPtr<LocalAccessible> newAcc; 1737 switch (aFrame->AccessibleType()) { 1738 case eNoType: 1739 return nullptr; 1740 case eHTMLBRType: 1741 newAcc = new HTMLBRAccessible(aContent, document); 1742 break; 1743 case eHTMLButtonType: 1744 newAcc = new HTMLButtonAccessible(aContent, document); 1745 break; 1746 case eHTMLCanvasType: 1747 newAcc = new HTMLCanvasAccessible(aContent, document); 1748 break; 1749 case eHTMLCaptionType: 1750 if (aContext->IsTable() && 1751 aContext->GetContent() == aContent->GetParent()) { 1752 newAcc = new HTMLCaptionAccessible(aContent, document); 1753 } 1754 break; 1755 case eHTMLCheckboxType: 1756 newAcc = new CheckboxAccessible(aContent, document); 1757 break; 1758 case eHTMLComboboxType: 1759 newAcc = new HTMLComboboxAccessible(aContent, document); 1760 break; 1761 case eHTMLFileInputType: 1762 newAcc = new HTMLFileInputAccessible(aContent, document); 1763 break; 1764 case eHTMLGroupboxType: 1765 newAcc = new HTMLGroupboxAccessible(aContent, document); 1766 break; 1767 case eHTMLHRType: 1768 newAcc = new HTMLHRAccessible(aContent, document); 1769 break; 1770 case eHTMLImageMapType: 1771 newAcc = new HTMLImageMapAccessible(aContent, document); 1772 break; 1773 case eHTMLLiType: 1774 if (aContext->IsList() && 1775 aContext->GetContent() == aContent->GetParent()) { 1776 newAcc = new HTMLLIAccessible(aContent, document); 1777 } else { 1778 // Otherwise create a generic text accessible to avoid text jamming. 1779 newAcc = new HyperTextAccessible(aContent, document); 1780 } 1781 break; 1782 case eHTMLSelectListType: 1783 newAcc = new HTMLSelectListAccessible(aContent, document); 1784 break; 1785 case eHTMLMediaType: 1786 // The video Accessible can have TextLeafAccessibles as direct children; 1787 // e.g. if there are captions. Therefore, it must be a 1788 // HyperTextAccessible. 1789 newAcc = 1790 new EnumRoleHyperTextAccessible<roles::GROUPING>(aContent, document); 1791 break; 1792 case eHTMLRadioButtonType: 1793 newAcc = new HTMLRadioButtonAccessible(aContent, document); 1794 break; 1795 case eHTMLRangeType: 1796 newAcc = new HTMLRangeAccessible(aContent, document); 1797 break; 1798 case eHTMLSpinnerType: 1799 newAcc = new HTMLSpinnerAccessible(aContent, document); 1800 break; 1801 case eHTMLTableType: 1802 case eHTMLTableCellType: 1803 // We handle markup and ARIA tables elsewhere. If we reach here, this is 1804 // a CSS table part. Just create a generic text container. 1805 newAcc = new HyperTextAccessible(aContent, document); 1806 break; 1807 case eHTMLTableRowType: 1808 // This is a CSS table row. Don't expose it at all. 1809 break; 1810 case eHTMLTextFieldType: 1811 newAcc = new HTMLTextFieldAccessible(aContent, document); 1812 break; 1813 case eHyperTextType: { 1814 if (aContext->IsTable() || aContext->IsTableRow()) { 1815 // This is some generic hyperText, for example a block frame element 1816 // inserted between a table and table row. Treat it as presentational. 1817 return nullptr; 1818 } 1819 1820 if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd, 1821 nsGkAtoms::div, nsGkAtoms::thead, 1822 nsGkAtoms::tfoot, nsGkAtoms::tbody)) { 1823 newAcc = new HyperTextAccessible(aContent, document); 1824 } 1825 break; 1826 } 1827 case eImageType: 1828 if (aContent->IsElement() && 1829 ShouldCreateImgAccessible(aContent->AsElement(), document)) { 1830 newAcc = new ImageAccessible(aContent, document); 1831 } 1832 break; 1833 case eOuterDocType: 1834 newAcc = new OuterDocAccessible(aContent, document); 1835 break; 1836 case eTextLeafType: 1837 newAcc = new TextLeafAccessible(aContent, document); 1838 break; 1839 default: 1840 MOZ_ASSERT(false); 1841 break; 1842 } 1843 1844 return newAcc.forget(); 1845 } 1846 1847 void nsAccessibilityService::MarkupAttributes( 1848 Accessible* aAcc, AccAttributes* aAttributes) const { 1849 const mozilla::a11y::MarkupMapInfo* markupMap = GetMarkupMapInfoFor(aAcc); 1850 if (!markupMap) return; 1851 1852 dom::Element* el = aAcc->IsLocal() ? aAcc->AsLocal()->Elm() : nullptr; 1853 for (uint32_t i = 0; i < std::size(markupMap->attrs); i++) { 1854 const MarkupAttrInfo* info = markupMap->attrs + i; 1855 if (!info->name) break; 1856 1857 if (info->DOMAttrName) { 1858 if (!el) { 1859 // XXX Expose DOM attributes for cached RemoteAccessibles. 1860 continue; 1861 } 1862 if (info->DOMAttrValue) { 1863 if (el->AttrValueIs(kNameSpaceID_None, info->DOMAttrName, 1864 info->DOMAttrValue, eCaseMatters)) { 1865 aAttributes->SetAttribute(info->name, info->DOMAttrValue); 1866 } 1867 continue; 1868 } 1869 1870 nsString value; 1871 el->GetAttr(info->DOMAttrName, value); 1872 1873 if (!value.IsEmpty()) { 1874 aAttributes->SetAttribute(info->name, std::move(value)); 1875 } 1876 1877 continue; 1878 } 1879 1880 aAttributes->SetAttribute(info->name, info->value); 1881 } 1882 } 1883 1884 LocalAccessible* nsAccessibilityService::AddNativeRootAccessible( 1885 void* aAtkAccessible) { 1886 #ifdef MOZ_ACCESSIBILITY_ATK 1887 ApplicationAccessible* applicationAcc = ApplicationAcc(); 1888 if (!applicationAcc) return nullptr; 1889 1890 GtkWindowAccessible* nativeWnd = 1891 new GtkWindowAccessible(static_cast<AtkObject*>(aAtkAccessible)); 1892 1893 if (applicationAcc->AppendChild(nativeWnd)) return nativeWnd; 1894 #endif 1895 1896 return nullptr; 1897 } 1898 1899 void nsAccessibilityService::RemoveNativeRootAccessible( 1900 LocalAccessible* aAccessible) { 1901 #ifdef MOZ_ACCESSIBILITY_ATK 1902 ApplicationAccessible* applicationAcc = ApplicationAcc(); 1903 1904 if (applicationAcc) applicationAcc->RemoveChild(aAccessible); 1905 #endif 1906 } 1907 1908 bool nsAccessibilityService::HasAccessible(nsINode* aDOMNode) { 1909 if (!aDOMNode) return false; 1910 1911 Document* document = aDOMNode->OwnerDoc(); 1912 if (!document) return false; 1913 1914 DocAccessible* docAcc = GetExistingDocAccessible(aDOMNode->OwnerDoc()); 1915 if (!docAcc) return false; 1916 1917 return docAcc->HasAccessible(aDOMNode); 1918 } 1919 1920 //////////////////////////////////////////////////////////////////////////////// 1921 // nsAccessibilityService private (DON'T put methods here) 1922 1923 void nsAccessibilityService::SetConsumers(uint32_t aConsumers, bool aNotify) { 1924 if (gConsumers & aConsumers) { 1925 return; 1926 } 1927 1928 gConsumers |= aConsumers; 1929 if (aNotify) { 1930 NotifyOfConsumersChange(); 1931 } 1932 } 1933 1934 void nsAccessibilityService::UnsetConsumers(uint32_t aConsumers) { 1935 if (!(gConsumers & aConsumers)) { 1936 return; 1937 } 1938 1939 gConsumers &= ~aConsumers; 1940 NotifyOfConsumersChange(); 1941 } 1942 1943 void nsAccessibilityService::GetConsumers(nsAString& aString) { 1944 const char16_t* kJSONFmt = 1945 u"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }"; 1946 nsString json; 1947 nsTextFormatter::ssprintf(json, kJSONFmt, 1948 gConsumers & eXPCOM ? "true" : "false", 1949 gConsumers & eMainProcess ? "true" : "false", 1950 gConsumers & ePlatformAPI ? "true" : "false"); 1951 aString.Assign(json); 1952 } 1953 1954 void nsAccessibilityService::SetCacheDomains(uint64_t aCacheDomains) { 1955 if (XRE_IsParentProcess()) { 1956 const DebugOnly<bool> requestSent = 1957 SendCacheDomainRequestToAllContentProcesses(aCacheDomains); 1958 MOZ_ASSERT(requestSent, 1959 "Could not send cache domain request to content processes."); 1960 gCacheDomains = aCacheDomains; 1961 return; 1962 } 1963 1964 // Bail out if we're not a content process. 1965 if (!XRE_IsContentProcess()) { 1966 return; 1967 } 1968 1969 // Anything not enabled already but enabled now is a newly-enabled domain. 1970 const uint64_t newDomains = ~gCacheDomains & aCacheDomains; 1971 1972 // Queue cache updates on all accessibles in all documents within this 1973 // process. 1974 if (newDomains != CacheDomain::None) { 1975 for (const RefPtr<DocAccessible>& doc : mDocAccessibleCache.Values()) { 1976 MOZ_ASSERT(doc, "DocAccessible in cache is null!"); 1977 doc->QueueCacheUpdate(doc.get(), newDomains, true); 1978 Pivot pivot(doc.get()); 1979 LocalAccInSameDocRule rule; 1980 for (Accessible* anchor = doc.get(); anchor; 1981 anchor = pivot.Next(anchor, rule)) { 1982 LocalAccessible* acc = anchor->AsLocal(); 1983 1984 // Note: Queueing changes for domains that aren't yet active. The 1985 // domains will become active at the end of the function. 1986 doc->QueueCacheUpdate(acc, newDomains, true); 1987 } 1988 // Process queued cache updates immediately. 1989 doc->ProcessQueuedCacheUpdates(newDomains); 1990 } 1991 } 1992 1993 gCacheDomains = aCacheDomains; 1994 } 1995 1996 void nsAccessibilityService::NotifyOfConsumersChange() { 1997 nsCOMPtr<nsIObserverService> observerService = 1998 mozilla::services::GetObserverService(); 1999 2000 if (!observerService) { 2001 return; 2002 } 2003 2004 nsAutoString consumers; 2005 GetConsumers(consumers); 2006 observerService->NotifyObservers(nullptr, "a11y-consumers-changed", 2007 consumers.get()); 2008 } 2009 2010 const mozilla::a11y::MarkupMapInfo* nsAccessibilityService::GetMarkupMapInfoFor( 2011 Accessible* aAcc) const { 2012 if (LocalAccessible* localAcc = aAcc->AsLocal()) { 2013 return localAcc->HasOwnContent() 2014 ? GetMarkupMapInfoFor(localAcc->GetContent()) 2015 : nullptr; 2016 } 2017 // XXX For now, we assume all RemoteAccessibles are HTML elements. This 2018 // isn't strictly correct, but as far as current callers are concerned, 2019 // this doesn't matter. If that changes in future, we could expose the 2020 // element type via AccGenericType. 2021 return mHTMLMarkupMap.Get(aAcc->TagName()); 2022 } 2023 2024 nsAccessibilityService* GetOrCreateAccService(uint32_t aNewConsumer, 2025 uint64_t aCacheDomains) { 2026 // Do not initialize accessibility if it is force disabled. 2027 if (PlatformDisabledState() == ePlatformIsDisabled) { 2028 return nullptr; 2029 } 2030 2031 if (!nsAccessibilityService::gAccessibilityService) { 2032 uint64_t cacheDomains = aCacheDomains; 2033 if (aNewConsumer == nsAccessibilityService::eXPCOM) { 2034 // When instantiated via XPCOM, cache all accessibility information. 2035 cacheDomains = CacheDomain::All; 2036 } 2037 2038 RefPtr<nsAccessibilityService> service = new nsAccessibilityService(); 2039 if (!service->Init(cacheDomains)) { 2040 service->Shutdown(); 2041 return nullptr; 2042 } 2043 } 2044 2045 MOZ_ASSERT(nsAccessibilityService::gAccessibilityService, 2046 "LocalAccessible service is not initialized."); 2047 nsAccessibilityService::gAccessibilityService->SetConsumers(aNewConsumer); 2048 return nsAccessibilityService::gAccessibilityService; 2049 } 2050 2051 void MaybeShutdownAccService(uint32_t aFormerConsumer, bool aAsync) { 2052 nsAccessibilityService* accService = 2053 nsAccessibilityService::gAccessibilityService; 2054 2055 if (!accService || nsAccessibilityService::IsShutdown()) { 2056 return; 2057 } 2058 2059 // Still used by XPCOM 2060 if (nsCoreUtils::AccEventObserversExist() || 2061 xpcAccessibilityService::IsInUse() || accService->HasXPCDocuments()) { 2062 // In case the XPCOM flag was unset (possibly because of the shutdown 2063 // timer in the xpcAccessibilityService) ensure it is still present. Note: 2064 // this should be fixed when all the consumer logic is taken out as a 2065 // separate class. 2066 accService->SetConsumers(nsAccessibilityService::eXPCOM, false); 2067 2068 if (aFormerConsumer != nsAccessibilityService::eXPCOM) { 2069 // Only unset non-XPCOM consumers. 2070 accService->UnsetConsumers(aFormerConsumer); 2071 } 2072 return; 2073 } 2074 2075 if (nsAccessibilityService::gConsumers & ~aFormerConsumer) { 2076 // There are still other consumers of the accessibility service, so we 2077 // can't shut down. 2078 accService->UnsetConsumers(aFormerConsumer); 2079 return; 2080 } 2081 2082 if (!aAsync) { 2083 accService 2084 ->Shutdown(); // Will unset all nsAccessibilityService::gConsumers 2085 return; 2086 } 2087 2088 static bool sIsPending = false; 2089 if (sIsPending) { 2090 // An async shutdown runnable is pending. Don't dispatch another. 2091 return; 2092 } 2093 NS_DispatchToMainThread(NS_NewRunnableFunction( 2094 "a11y::MaybeShutdownAccService", [aFormerConsumer]() { 2095 // It's possible (albeit very unlikely) that another accessibility 2096 // service consumer arrived since this runnable was dispatched. Use 2097 // MaybeShutdownAccService to be safe. 2098 MaybeShutdownAccService(aFormerConsumer, false); 2099 sIsPending = false; 2100 })); 2101 sIsPending = true; 2102 } 2103 2104 //////////////////////////////////////////////////////////////////////////////// 2105 // Services 2106 //////////////////////////////////////////////////////////////////////////////// 2107 2108 namespace mozilla { 2109 namespace a11y { 2110 2111 FocusManager* FocusMgr() { 2112 return nsAccessibilityService::gAccessibilityService; 2113 } 2114 2115 SelectionManager* SelectionMgr() { 2116 return nsAccessibilityService::gAccessibilityService; 2117 } 2118 2119 ApplicationAccessible* ApplicationAcc() { 2120 return nsAccessibilityService::gApplicationAccessible; 2121 } 2122 2123 xpcAccessibleApplication* XPCApplicationAcc() { 2124 if (!nsAccessibilityService::gXPCApplicationAccessible && 2125 nsAccessibilityService::gApplicationAccessible) { 2126 nsAccessibilityService::gXPCApplicationAccessible = 2127 new xpcAccessibleApplication( 2128 nsAccessibilityService::gApplicationAccessible); 2129 NS_ADDREF(nsAccessibilityService::gXPCApplicationAccessible); 2130 } 2131 2132 return nsAccessibilityService::gXPCApplicationAccessible; 2133 } 2134 2135 EPlatformDisabledState PlatformDisabledState() { 2136 static bool platformDisabledStateCached = false; 2137 if (platformDisabledStateCached) { 2138 return static_cast<EPlatformDisabledState>(sPlatformDisabledState); 2139 } 2140 2141 platformDisabledStateCached = true; 2142 Preferences::RegisterCallback(PrefChanged, PREF_ACCESSIBILITY_FORCE_DISABLED); 2143 return ReadPlatformDisabledState(); 2144 } 2145 2146 EPlatformDisabledState ReadPlatformDisabledState() { 2147 sPlatformDisabledState = 2148 Preferences::GetInt(PREF_ACCESSIBILITY_FORCE_DISABLED, 0); 2149 if (sPlatformDisabledState < ePlatformIsForceEnabled) { 2150 sPlatformDisabledState = ePlatformIsForceEnabled; 2151 } else if (sPlatformDisabledState > ePlatformIsDisabled) { 2152 sPlatformDisabledState = ePlatformIsDisabled; 2153 } 2154 2155 return static_cast<EPlatformDisabledState>(sPlatformDisabledState); 2156 } 2157 2158 void PrefChanged(const char* aPref, void* aClosure) { 2159 if (ReadPlatformDisabledState() == ePlatformIsDisabled) { 2160 // Force shut down accessibility. 2161 nsAccessibilityService* accService = 2162 nsAccessibilityService::gAccessibilityService; 2163 if (accService && !nsAccessibilityService::IsShutdown()) { 2164 accService->Shutdown(); 2165 } 2166 } 2167 } 2168 2169 uint32_t CacheDomainActivationBlocker::sEntryCount = 0; 2170 2171 CacheDomainActivationBlocker::CacheDomainActivationBlocker() { 2172 AssertIsOnMainThread(); 2173 if (sEntryCount++ != 0) { 2174 // We're re-entering. This can happen if an earlier event (even in a 2175 // different document) ends up calling an XUL method, since that can run 2176 // script which can cause other events to fire. Only the outermost usage 2177 // should change the flag. 2178 return; 2179 } 2180 if (nsAccessibilityService* service = GetAccService()) { 2181 MOZ_ASSERT(service->mShouldAllowNewCacheDomains); 2182 service->mShouldAllowNewCacheDomains = false; 2183 } 2184 } 2185 2186 CacheDomainActivationBlocker::~CacheDomainActivationBlocker() { 2187 AssertIsOnMainThread(); 2188 if (--sEntryCount != 0) { 2189 // Only the outermost usage should change the flag. 2190 return; 2191 } 2192 if (nsAccessibilityService* service = GetAccService()) { 2193 MOZ_ASSERT(!service->mShouldAllowNewCacheDomains); 2194 service->mShouldAllowNewCacheDomains = true; 2195 } 2196 } 2197 2198 } // namespace a11y 2199 } // namespace mozilla