tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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