tor-browser

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

nsAccUtils.cpp (22281B)


      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 "nsAccUtils.h"
      7 
      8 #include "AccAttributes.h"
      9 #include "ARIAMap.h"
     10 #include "nsCoreUtils.h"
     11 #include "nsGenericHTMLElement.h"
     12 #include "DocAccessible.h"
     13 #include "DocAccessibleParent.h"
     14 #include "HyperTextAccessible.h"
     15 #include "nsIAccessibleTypes.h"
     16 #include "mozilla/a11y/Role.h"
     17 #include "States.h"
     18 #include "TextLeafAccessible.h"
     19 
     20 #include "nsIBaseWindow.h"
     21 #include "nsIDocShellTreeOwner.h"
     22 #include "nsIDOMXULContainerElement.h"
     23 #include "mozilla/a11y/RemoteAccessible.h"
     24 #include "mozilla/dom/Document.h"
     25 #include "mozilla/dom/Element.h"
     26 #include "mozilla/dom/ElementInternals.h"
     27 #include "nsAccessibilityService.h"
     28 
     29 using namespace mozilla;
     30 using namespace mozilla::a11y;
     31 
     32 void nsAccUtils::SetAccGroupAttrs(AccAttributes* aAttributes, int32_t aLevel,
     33                                  int32_t aSetSize, int32_t aPosInSet) {
     34  nsAutoString value;
     35 
     36  if (aLevel) {
     37    aAttributes->SetAttribute(nsGkAtoms::level, aLevel);
     38  }
     39 
     40  if (aSetSize && aPosInSet) {
     41    aAttributes->SetAttribute(nsGkAtoms::posinset, aPosInSet);
     42    aAttributes->SetAttribute(nsGkAtoms::setsize, aSetSize);
     43  }
     44 }
     45 
     46 void nsAccUtils::SetAccGroupAttrs(AccAttributes* aAttributes,
     47                                  Accessible* aAcc) {
     48  GroupPos groupPos = aAcc->GroupPosition();
     49  nsAccUtils::SetAccGroupAttrs(aAttributes, groupPos.level, groupPos.setSize,
     50                               groupPos.posInSet);
     51 }
     52 
     53 int32_t nsAccUtils::GetLevelForXULContainerItem(nsIContent* aContent) {
     54  nsCOMPtr<nsIDOMXULContainerItemElement> item =
     55      aContent->AsElement()->AsXULContainerItem();
     56  if (!item) return 0;
     57 
     58  nsCOMPtr<dom::Element> containerElement;
     59  item->GetParentContainer(getter_AddRefs(containerElement));
     60  nsCOMPtr<nsIDOMXULContainerElement> container =
     61      containerElement ? containerElement->AsXULContainer() : nullptr;
     62  if (!container) return 0;
     63 
     64  // Get level of the item.
     65  int32_t level = -1;
     66  while (container) {
     67    level++;
     68 
     69    container->GetParentContainer(getter_AddRefs(containerElement));
     70    container = containerElement ? containerElement->AsXULContainer() : nullptr;
     71  }
     72 
     73  return level;
     74 }
     75 
     76 void nsAccUtils::SetLiveContainerAttributes(AccAttributes* aAttributes,
     77                                            Accessible* aStartAcc) {
     78  nsAutoString live, relevant, busy;
     79  nsStaticAtom* role = nullptr;
     80  Maybe<bool> atomic;
     81  for (Accessible* acc = aStartAcc; acc; acc = acc->Parent()) {
     82    // We only want the nearest value for each attribute. If we already got a
     83    // value, don't bother fetching it from further ancestors.
     84    const bool wasLiveEmpty = live.IsEmpty();
     85    acc->LiveRegionAttributes(wasLiveEmpty ? &live : nullptr,
     86                              relevant.IsEmpty() ? &relevant : nullptr,
     87                              atomic ? nullptr : &atomic,
     88                              busy.IsEmpty() ? &busy : nullptr);
     89    if (wasLiveEmpty) {
     90      const nsRoleMapEntry* roleMap = acc->ARIARoleMap();
     91      if (live.IsEmpty()) {
     92        // aria-live wasn't explicitly set. See if an aria-live value is implied
     93        // by an ARIA role or markup element.
     94        MOZ_ASSERT(GetAccService());
     95        if (roleMap) {
     96          GetLiveAttrValue(roleMap->liveAttRule, live);
     97        } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
     98                       acc, nsGkAtoms::aria_live)) {
     99          value->ToString(live);
    100        }
    101      }
    102      if (!live.IsEmpty() && roleMap &&
    103          roleMap->roleAtom != nsGkAtoms::_empty) {
    104        role = roleMap->roleAtom;
    105      }
    106    }
    107    if (acc->IsDoc()) {
    108      break;
    109    }
    110  }
    111  if (!live.IsEmpty()) {
    112    aAttributes->SetAttribute(nsGkAtoms::containerLive, std::move(live));
    113  }
    114  if (role) {
    115    aAttributes->SetAttribute(nsGkAtoms::containerLiveRole, std::move(role));
    116  }
    117  if (!relevant.IsEmpty()) {
    118    aAttributes->SetAttribute(nsGkAtoms::containerRelevant,
    119                              std::move(relevant));
    120  }
    121  if (atomic) {
    122    aAttributes->SetAttribute(nsGkAtoms::containerAtomic, *atomic);
    123  }
    124  if (!busy.IsEmpty()) {
    125    aAttributes->SetAttribute(nsGkAtoms::containerBusy, std::move(busy));
    126  }
    127 }
    128 
    129 bool nsAccUtils::HasDefinedARIAToken(nsIContent* aContent, nsAtom* aAtom) {
    130  NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!");
    131 
    132  if (!aContent->IsElement()) return false;
    133 
    134  dom::Element* element = aContent->AsElement();
    135  if (auto* htmlElement = nsGenericHTMLElement::FromNode(element);
    136      htmlElement && !element->HasAttr(aAtom)) {
    137    const auto* defaults = GetARIADefaults(htmlElement);
    138    if (!defaults) {
    139      return false;
    140    }
    141    return HasDefinedARIAToken(defaults, aAtom);
    142  }
    143  return HasDefinedARIAToken(&element->GetAttrs(), aAtom);
    144 }
    145 
    146 bool nsAccUtils::HasDefinedARIAToken(const AttrArray* aAttrs, nsAtom* aAtom) {
    147  return aAttrs->HasAttr(aAtom) &&
    148         !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_empty,
    149                              eCaseMatters) &&
    150         !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_undefined,
    151                              eCaseMatters);
    152 }
    153 
    154 nsStaticAtom* nsAccUtils::NormalizeARIAToken(const AttrArray* aAttrs,
    155                                             nsAtom* aAttr) {
    156  if (!HasDefinedARIAToken(aAttrs, aAttr)) {
    157    return nsGkAtoms::_empty;
    158  }
    159 
    160  if (aAttr == nsGkAtoms::aria_current) {
    161    static AttrArray::AttrValuesArray tokens[] = {
    162        nsGkAtoms::page, nsGkAtoms::step, nsGkAtoms::location,
    163        nsGkAtoms::date, nsGkAtoms::time, nsGkAtoms::_true,
    164        nullptr};
    165    int32_t idx =
    166        aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters);
    167    // If the token is present, return it, otherwise TRUE as per spec.
    168    return (idx >= 0) ? tokens[idx] : nsGkAtoms::_true;
    169  }
    170 
    171  static AttrArray::AttrValuesArray tokens[] = {
    172      nsGkAtoms::_false, nsGkAtoms::_true, nsGkAtoms::mixed, nullptr};
    173  int32_t idx =
    174      aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters);
    175  if (idx >= 0) {
    176    return tokens[idx];
    177  }
    178 
    179  return nullptr;
    180 }
    181 
    182 nsStaticAtom* nsAccUtils::NormalizeARIAToken(dom::Element* aElement,
    183                                             nsAtom* aAttr) {
    184  if (auto* htmlElement = nsGenericHTMLElement::FromNode(aElement);
    185      htmlElement && !aElement->HasAttr(aAttr)) {
    186    const auto* defaults = GetARIADefaults(htmlElement);
    187    if (!defaults) {
    188      return nsGkAtoms::_empty;
    189    }
    190    return NormalizeARIAToken(defaults, aAttr);
    191  }
    192  return NormalizeARIAToken(&aElement->GetAttrs(), aAttr);
    193 }
    194 
    195 Accessible* nsAccUtils::GetSelectableContainer(const Accessible* aAccessible,
    196                                               uint64_t aState) {
    197  if (!aAccessible) return nullptr;
    198 
    199  if (!(aState & states::SELECTABLE)) return nullptr;
    200  MOZ_ASSERT(!aAccessible->IsDoc());
    201 
    202  const Accessible* parent = aAccessible;
    203  while ((parent = parent->Parent()) && !parent->IsSelect()) {
    204    if (parent->IsDoc() || parent->Role() == roles::PANE) {
    205      return nullptr;
    206    }
    207  }
    208  return const_cast<Accessible*>(parent);
    209 }
    210 
    211 LocalAccessible* nsAccUtils::GetSelectableContainer(
    212    LocalAccessible* aAccessible, uint64_t aState) {
    213  Accessible* selectable =
    214      GetSelectableContainer(static_cast<Accessible*>(aAccessible), aState);
    215  return selectable ? selectable->AsLocal() : nullptr;
    216 }
    217 
    218 bool nsAccUtils::IsDOMAttrTrue(const LocalAccessible* aAccessible,
    219                               nsAtom* aAttr) {
    220  dom::Element* el = aAccessible->Elm();
    221  return el && ARIAAttrValueIs(el, aAttr, nsGkAtoms::_true, eCaseMatters);
    222 }
    223 
    224 Accessible* nsAccUtils::TableFor(const Accessible* aAcc) {
    225  if (!aAcc ||
    226      (!aAcc->IsTable() && !aAcc->IsTableRow() && !aAcc->IsTableCell())) {
    227    return nullptr;
    228  }
    229  Accessible* table = const_cast<Accessible*>(aAcc);
    230  for (; table && !table->IsTable(); table = table->Parent()) {
    231  }
    232  // We don't assert (table && table->IsTable()) here because
    233  // it's possible for this tree walk to yield no table at all
    234  // ex. because a table part has been moved in the tree
    235  // using aria-owns.
    236  return table;
    237 }
    238 
    239 LocalAccessible* nsAccUtils::TableFor(LocalAccessible* aRow) {
    240  Accessible* table = TableFor(static_cast<Accessible*>(aRow));
    241  return table ? table->AsLocal() : nullptr;
    242 }
    243 
    244 HyperTextAccessible* nsAccUtils::GetTextContainer(nsINode* aNode) {
    245  // Get text accessible containing the result node.
    246  DocAccessible* doc = GetAccService()->GetDocAccessible(aNode->OwnerDoc());
    247  LocalAccessible* accessible =
    248      doc ? doc->GetAccessibleOrContainer(aNode) : nullptr;
    249  if (!accessible) return nullptr;
    250 
    251  do {
    252    HyperTextAccessible* textAcc = accessible->AsHyperText();
    253    if (textAcc) return textAcc;
    254 
    255    accessible = accessible->LocalParent();
    256  } while (accessible);
    257 
    258  return nullptr;
    259 }
    260 
    261 LayoutDeviceIntPoint nsAccUtils::ConvertToScreenCoords(
    262    int32_t aX, int32_t aY, uint32_t aCoordinateType, Accessible* aAccessible) {
    263  LayoutDeviceIntPoint coords(aX, aY);
    264 
    265  switch (aCoordinateType) {
    266    // Regardless of coordinate type, the coords returned
    267    // are in dev pixels.
    268    case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
    269      break;
    270 
    271    case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
    272      coords += GetScreenCoordsForWindow(aAccessible);
    273      break;
    274    }
    275 
    276    case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
    277      coords += GetScreenCoordsForParent(aAccessible);
    278      break;
    279    }
    280 
    281    default:
    282      MOZ_ASSERT_UNREACHABLE("invalid coord type!");
    283  }
    284 
    285  return coords;
    286 }
    287 
    288 void nsAccUtils::ConvertScreenCoordsTo(int32_t* aX, int32_t* aY,
    289                                       uint32_t aCoordinateType,
    290                                       Accessible* aAccessible) {
    291  switch (aCoordinateType) {
    292    // Regardless of coordinate type, the values returned for
    293    // aX and aY are in dev pixels.
    294    case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
    295      break;
    296 
    297    case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
    298      LayoutDeviceIntPoint coords = GetScreenCoordsForWindow(aAccessible);
    299      *aX -= coords.x;
    300      *aY -= coords.y;
    301      break;
    302    }
    303 
    304    case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
    305      LayoutDeviceIntPoint coords = GetScreenCoordsForParent(aAccessible);
    306      *aX -= coords.x;
    307      *aY -= coords.y;
    308      break;
    309    }
    310 
    311    default:
    312      MOZ_ASSERT_UNREACHABLE("invalid coord type!");
    313  }
    314 }
    315 
    316 LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForParent(
    317    Accessible* aAccessible) {
    318  if (!aAccessible) return LayoutDeviceIntPoint();
    319 
    320  if (Accessible* parent = aAccessible->Parent()) {
    321    LayoutDeviceIntRect parentBounds = parent->Bounds();
    322    // The rect returned from Bounds() is already in dev
    323    // pixels, so we don't need to do any conversion here.
    324    return parentBounds.TopLeft();
    325  }
    326 
    327  return LayoutDeviceIntPoint();
    328 }
    329 
    330 LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForWindow(
    331    Accessible* aAccessible) {
    332  LayoutDeviceIntPoint coords(0, 0);
    333  a11y::LocalAccessible* localAcc = aAccessible->AsLocal();
    334  if (!localAcc) {
    335    localAcc = aAccessible->AsRemote()->OuterDocOfRemoteBrowser();
    336    if (!localAcc) {
    337      // This could be null if the tab is closing but the document is still
    338      // being shut down.
    339      return coords;
    340    }
    341  }
    342 
    343  nsCOMPtr<nsIDocShellTreeItem> treeItem(
    344      nsCoreUtils::GetDocShellFor(localAcc->GetNode()));
    345  if (!treeItem) return coords;
    346 
    347  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
    348  treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
    349  if (!treeOwner) return coords;
    350 
    351  nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
    352  if (baseWindow) {
    353    baseWindow->GetPosition(&coords.x.value,
    354                            &coords.y.value);  // in device pixels
    355  }
    356 
    357  return coords;
    358 }
    359 
    360 bool nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue) {
    361  switch (aRule) {
    362    case eOffLiveAttr:
    363      aValue = u"off"_ns;
    364      return true;
    365    case ePoliteLiveAttr:
    366      aValue = u"polite"_ns;
    367      return true;
    368    case eAssertiveLiveAttr:
    369      aValue = u"assertive"_ns;
    370      return true;
    371  }
    372 
    373  return false;
    374 }
    375 
    376 #ifdef DEBUG
    377 
    378 bool nsAccUtils::IsTextInterfaceSupportCorrect(LocalAccessible* aAccessible) {
    379  // Don't test for accessible docs, it makes us create accessibles too
    380  // early and fire mutation events before we need to
    381  if (aAccessible->IsDoc()) return true;
    382 
    383  bool foundText = false;
    384  uint32_t childCount = aAccessible->ChildCount();
    385  for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
    386    LocalAccessible* child = aAccessible->LocalChildAt(childIdx);
    387    if (child && child->IsText()) {
    388      foundText = true;
    389      break;
    390    }
    391  }
    392 
    393  return !foundText || aAccessible->IsHyperText();
    394 }
    395 #endif
    396 
    397 uint32_t nsAccUtils::TextLength(Accessible* aAccessible) {
    398  if (!aAccessible->IsText()) {
    399    return 1;
    400  }
    401 
    402  if (LocalAccessible* localAcc = aAccessible->AsLocal()) {
    403    TextLeafAccessible* textLeaf = localAcc->AsTextLeaf();
    404    if (textLeaf) {
    405      return textLeaf->Text().Length();
    406    }
    407  } else if (aAccessible->IsText()) {
    408    RemoteAccessible* remoteAcc = aAccessible->AsRemote();
    409    MOZ_ASSERT(remoteAcc);
    410    return remoteAcc->GetCachedTextLength();
    411  }
    412 
    413  // For list bullets (or anything other accessible which would compute its own
    414  // text. They don't have their own frame.
    415  // XXX In the future, list bullets may have frame and anon content, so
    416  // we should be able to remove this at that point
    417  nsAutoString text;
    418  aAccessible->AppendTextTo(text);  // Get all the text
    419  return text.Length();
    420 }
    421 
    422 bool nsAccUtils::MustPrune(Accessible* aAccessible) {
    423  MOZ_ASSERT(aAccessible);
    424  roles::Role role = aAccessible->Role();
    425 
    426  if (role == roles::SLIDER || role == roles::PROGRESSBAR ||
    427      role == roles::METER) {
    428    // Always prune the tree for sliders and progressbars, as it doesn't make
    429    // sense for either to have descendants. Per the ARIA spec, children of
    430    // these elements are presentational. They also confuse NVDA.
    431    return true;
    432  }
    433 
    434  if (role != roles::MENUITEM && role != roles::COMBOBOX_OPTION &&
    435      role != roles::OPTION && role != roles::ENTRY &&
    436      role != roles::FLAT_EQUATION && role != roles::PASSWORD_TEXT &&
    437      role != roles::PUSHBUTTON && role != roles::TOGGLE_BUTTON &&
    438      role != roles::GRAPHIC && role != roles::SEPARATOR) {
    439    // If it doesn't match any of these roles, don't prune its children.
    440    return false;
    441  }
    442 
    443  if (aAccessible->ChildCount() != 1) {
    444    // If the accessible has more than one child, don't prune it.
    445    return false;
    446  }
    447 
    448  roles::Role childRole = aAccessible->FirstChild()->Role();
    449  // If the accessible's child is a text leaf, prune the accessible.
    450  return childRole == roles::TEXT_LEAF || childRole == roles::STATICTEXT;
    451 }
    452 
    453 void nsAccUtils::GetLiveRegionSetting(Accessible* aAcc, nsAString& aLive) {
    454  MOZ_ASSERT(aAcc);
    455  aAcc->LiveRegionAttributes(&aLive, nullptr, nullptr, nullptr);
    456  // aria-live wasn't explicitly set. See if an aria-live value is implied
    457  // by an ARIA role or markup element.
    458  if (const nsRoleMapEntry* roleMap = aAcc->ARIARoleMap()) {
    459    GetLiveAttrValue(roleMap->liveAttRule, aLive);
    460  } else if (nsStaticAtom* value = GetAccService()
    461                                       ? GetAccService()->MarkupAttribute(
    462                                             aAcc, nsGkAtoms::aria_live)
    463                                       : nullptr) {
    464    value->ToString(aLive);
    465  }
    466 }
    467 
    468 Accessible* nsAccUtils::GetLiveRegionRoot(Accessible* aAcc) {
    469  MOZ_ASSERT(aAcc);
    470  nsAutoString live;
    471  Accessible* acc;
    472  for (acc = aAcc; acc; acc = acc->Parent()) {
    473    GetLiveRegionSetting(acc, live);
    474    if (!live.IsEmpty()) {
    475      break;
    476    }
    477    if (acc->IsDoc()) {
    478      // A document can be the root of a live region, but a live region cannot
    479      // cross document boundaries.
    480      return nullptr;
    481    }
    482  }
    483  if (live.IsEmpty() || live.EqualsLiteral("off")) {
    484    return nullptr;
    485  }
    486  return acc;
    487 }
    488 
    489 Accessible* nsAccUtils::DocumentFor(Accessible* aAcc) {
    490  if (!aAcc) {
    491    return nullptr;
    492  }
    493  if (LocalAccessible* localAcc = aAcc->AsLocal()) {
    494    return localAcc->Document();
    495  }
    496  return aAcc->AsRemote()->Document();
    497 }
    498 
    499 Accessible* nsAccUtils::GetAccessibleByID(Accessible* aDoc, uint64_t aID) {
    500  if (!aDoc) {
    501    return nullptr;
    502  }
    503  if (LocalAccessible* localAcc = aDoc->AsLocal()) {
    504    if (DocAccessible* doc = localAcc->AsDoc()) {
    505      if (!aID) {
    506        // GetAccessibleByUniqueID doesn't treat 0 as the document.
    507        return aDoc;
    508      }
    509      return doc->GetAccessibleByUniqueID(
    510          reinterpret_cast<void*>(static_cast<uintptr_t>(aID)));
    511    }
    512  } else if (DocAccessibleParent* doc = aDoc->AsRemote()->AsDoc()) {
    513    return doc->GetAccessible(aID);
    514  }
    515  return nullptr;
    516 }
    517 
    518 void nsAccUtils::DocumentURL(Accessible* aDoc, nsAString& aURL) {
    519  MOZ_ASSERT(aDoc && aDoc->IsDoc());
    520  if (LocalAccessible* localAcc = aDoc->AsLocal()) {
    521    return localAcc->AsDoc()->URL(aURL);
    522  }
    523  return aDoc->AsRemote()->AsDoc()->URL(aURL);
    524 }
    525 
    526 void nsAccUtils::DocumentMimeType(Accessible* aDoc, nsAString& aMimeType) {
    527  MOZ_ASSERT(aDoc && aDoc->IsDoc());
    528  if (LocalAccessible* localAcc = aDoc->AsLocal()) {
    529    return localAcc->AsDoc()->MimeType(aMimeType);
    530  }
    531  return aDoc->AsRemote()->AsDoc()->MimeType(aMimeType);
    532 }
    533 
    534 // ARIA Accessibility Default Accessors
    535 const AttrArray* nsAccUtils::GetARIADefaults(dom::Element* aElement) {
    536  auto* element = nsGenericHTMLElement::FromNode(aElement);
    537  if (!element) {
    538    return nullptr;
    539  }
    540  auto* internals = element->GetInternals();
    541  if (!internals) {
    542    return nullptr;
    543  }
    544  return &internals->GetAttrs();
    545 }
    546 
    547 bool nsAccUtils::HasARIAAttr(dom::Element* aElement, const nsAtom* aName) {
    548  if (aElement->HasAttr(aName)) {
    549    return true;
    550  }
    551  const auto* defaults = GetARIADefaults(aElement);
    552  if (!defaults) {
    553    return false;
    554  }
    555  return defaults->HasAttr(aName);
    556 }
    557 
    558 bool nsAccUtils::GetARIAAttr(dom::Element* aElement, const nsAtom* aName,
    559                             nsAString& aResult) {
    560  if (aElement->GetAttr(aName, aResult)) {
    561    return true;
    562  }
    563  const auto* defaults = GetARIADefaults(aElement);
    564  if (!defaults) {
    565    return false;
    566  }
    567  return defaults->GetAttr(aName, aResult);
    568 }
    569 
    570 const nsAttrValue* nsAccUtils::GetARIAAttr(dom::Element* aElement,
    571                                           const nsAtom* aName) {
    572  if (const auto* val = aElement->GetParsedAttr(aName, kNameSpaceID_None)) {
    573    return val;
    574  }
    575  const auto* defaults = GetARIADefaults(aElement);
    576  if (!defaults) {
    577    return nullptr;
    578  }
    579  return defaults->GetAttr(aName, kNameSpaceID_None);
    580 }
    581 
    582 bool nsAccUtils::GetARIAElementsAttr(dom::Element* aElement, nsAtom* aName,
    583                                     nsTArray<dom::Element*>& aElements) {
    584  if (aElement->HasAttr(aName)) {
    585    aElement->GetExplicitlySetAttrElements(aName, aElements);
    586    return true;
    587  }
    588 
    589  if (auto* element = nsGenericHTMLElement::FromNode(aElement)) {
    590    if (auto* internals = element->GetInternals()) {
    591      return internals->GetAttrElements(aName, aElements);
    592    }
    593  }
    594 
    595  return false;
    596 }
    597 
    598 bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName,
    599                                 const nsAString& aValue,
    600                                 nsCaseTreatment aCaseSensitive) {
    601  if (aElement->HasAttr(kNameSpaceID_None, aName)) {
    602    return aElement->AttrValueIs(kNameSpaceID_None, aName, aValue,
    603                                 aCaseSensitive);
    604  }
    605  const auto* defaults = GetARIADefaults(aElement);
    606  if (!defaults) {
    607    return false;
    608  }
    609  return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue,
    610                               aCaseSensitive);
    611 }
    612 
    613 bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName,
    614                                 const nsAtom* aValue,
    615                                 nsCaseTreatment aCaseSensitive) {
    616  if (aElement->HasAttr(kNameSpaceID_None, aName)) {
    617    return aElement->AttrValueIs(kNameSpaceID_None, aName, aValue,
    618                                 aCaseSensitive);
    619  }
    620  const auto* defaults = GetARIADefaults(aElement);
    621  if (!defaults) {
    622    return false;
    623  }
    624  return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue,
    625                               aCaseSensitive);
    626 }
    627 
    628 int32_t nsAccUtils::FindARIAAttrValueIn(dom::Element* aElement,
    629                                        const nsAtom* aName,
    630                                        AttrArray::AttrValuesArray* aValues,
    631                                        nsCaseTreatment aCaseSensitive) {
    632  int32_t index = aElement->FindAttrValueIn(kNameSpaceID_None, aName, aValues,
    633                                            aCaseSensitive);
    634  if (index == AttrArray::ATTR_MISSING) {
    635    const auto* defaults = GetARIADefaults(aElement);
    636    if (!defaults) {
    637      return index;
    638    }
    639    index = defaults->FindAttrValueIn(kNameSpaceID_None, aName, aValues,
    640                                      aCaseSensitive);
    641  }
    642  return index;
    643 }
    644 
    645 bool nsAccUtils::IsEditableARIACombobox(const LocalAccessible* aAccessible) {
    646  const nsRoleMapEntry* roleMap = aAccessible->ARIARoleMap();
    647  if (!roleMap || roleMap->role != roles::EDITCOMBOBOX) {
    648    return false;
    649  }
    650 
    651  return aAccessible->IsTextField() ||
    652         aAccessible->Elm()->State().HasState(dom::ElementState::READWRITE);
    653 }
    654 
    655 bool nsAccUtils::IsValidDetailsTargetForAnchor(const Accessible* aTarget,
    656                                               const Accessible* aAnchor) {
    657  if (aAnchor->IsAncestorOf(aTarget)) {
    658    // If the anchor is a parent of the target, the target is not valid
    659    // relation.
    660    return false;
    661  }
    662 
    663  Accessible* nextSibling = aAnchor->NextSibling();
    664  if (nextSibling && nextSibling->IsTextLeaf()) {
    665    nsAutoString text;
    666    nextSibling->Name(text);
    667    if (nsCoreUtils::IsWhitespaceString(text)) {
    668      nextSibling = nextSibling->NextSibling();
    669    }
    670  }
    671 
    672  if (nextSibling == aTarget) {
    673    // If the target is the next sibling of the anchor (ignoring whitespace
    674    // text nodes), the target is not a valid relation.
    675    return false;
    676  }
    677 
    678  return true;
    679 }