tor-browser

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

Accessible.cpp (33757B)


      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 "Accessible.h"
      7 #include "ARIAMap.h"
      8 #include "nsAccUtils.h"
      9 #include "nsIURI.h"
     10 #include "Pivot.h"
     11 #include "Relation.h"
     12 #include "States.h"
     13 #include "mozilla/a11y/FocusManager.h"
     14 #include "mozilla/a11y/HyperTextAccessibleBase.h"
     15 #include "mozilla/BasicEvents.h"
     16 #include "mozilla/Components.h"
     17 #include "mozilla/ProfilerMarkers.h"
     18 #include "nsIStringBundle.h"
     19 
     20 #ifdef A11Y_LOG
     21 #  include "nsAccessibilityService.h"
     22 #endif
     23 
     24 using namespace mozilla;
     25 using namespace mozilla::a11y;
     26 
     27 Accessible::Accessible()
     28    : mType(static_cast<uint32_t>(0)),
     29      mGenericTypes(static_cast<uint32_t>(0)),
     30      mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX) {}
     31 
     32 Accessible::Accessible(AccType aType, AccGenericType aGenericTypes,
     33                       uint8_t aRoleMapEntryIndex)
     34    : mType(static_cast<uint32_t>(aType)),
     35      mGenericTypes(static_cast<uint32_t>(aGenericTypes)),
     36      mRoleMapEntryIndex(aRoleMapEntryIndex) {}
     37 
     38 void Accessible::StaticAsserts() const {
     39  static_assert(eLastAccType <= (1 << kTypeBits) - 1,
     40                "Accessible::mType was oversized by eLastAccType!");
     41  static_assert(
     42      eLastAccGenericType <= (1 << kGenericTypesBits) - 1,
     43      "Accessible::mGenericType was oversized by eLastAccGenericType!");
     44 }
     45 
     46 mozilla::a11y::role Accessible::Role() const {
     47  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
     48  mozilla::a11y::role r =
     49      (!roleMapEntry || roleMapEntry->roleRule != kUseMapRole)
     50          ? NativeRole()
     51          : roleMapEntry->role;
     52  r = ARIATransformRole(r);
     53  return GetMinimumRole(r);
     54 }
     55 
     56 bool Accessible::IsBefore(const Accessible* aAcc) const {
     57  // Build the chain of parents.
     58  const Accessible* thisP = this;
     59  const Accessible* otherP = aAcc;
     60  AutoTArray<const Accessible*, 30> thisParents, otherParents;
     61  do {
     62    thisParents.AppendElement(thisP);
     63    thisP = thisP->Parent();
     64  } while (thisP);
     65  do {
     66    otherParents.AppendElement(otherP);
     67    otherP = otherP->Parent();
     68  } while (otherP);
     69 
     70  // Find where the parent chain differs.
     71  uint32_t thisPos = thisParents.Length(), otherPos = otherParents.Length();
     72  for (uint32_t len = std::min(thisPos, otherPos); len > 0; --len) {
     73    const Accessible* thisChild = thisParents.ElementAt(--thisPos);
     74    const Accessible* otherChild = otherParents.ElementAt(--otherPos);
     75    if (thisChild != otherChild) {
     76      return thisChild->IndexInParent() < otherChild->IndexInParent();
     77    }
     78  }
     79 
     80  // If the ancestries are the same length (both thisPos and otherPos are 0),
     81  // we should have returned by now.
     82  MOZ_ASSERT(thisPos != 0 || otherPos != 0);
     83  // At this point, one of the ancestries is a superset of the other, so one of
     84  // thisPos or otherPos should be 0.
     85  MOZ_ASSERT(thisPos != otherPos);
     86  // If the other Accessible is deeper than this one (otherPos > 0), this
     87  // Accessible comes before the other.
     88  return otherPos > 0;
     89 }
     90 
     91 const Accessible* Accessible::GetClosestCommonInclusiveAncestor(
     92    const Accessible* aAcc) const {
     93  if (aAcc == this) {
     94    return this;
     95  }
     96 
     97  // Build the chain of parents.
     98  const Accessible* thisAnc = this;
     99  const Accessible* otherAnc = aAcc;
    100  AutoTArray<const Accessible*, 30> thisAncs, otherAncs;
    101  do {
    102    thisAncs.AppendElement(thisAnc);
    103    thisAnc = thisAnc->Parent();
    104  } while (thisAnc);
    105  do {
    106    otherAncs.AppendElement(otherAnc);
    107    otherAnc = otherAnc->Parent();
    108  } while (otherAnc);
    109 
    110  // Find where the parent chain differs.
    111  size_t thisPos = thisAncs.Length(), otherPos = otherAncs.Length();
    112  const Accessible* common = nullptr;
    113  for (size_t len = std::min(thisPos, otherPos); len > 0; --len) {
    114    const Accessible* thisChild = thisAncs.ElementAt(--thisPos);
    115    const Accessible* otherChild = otherAncs.ElementAt(--otherPos);
    116    if (thisChild != otherChild) {
    117      break;
    118    }
    119    common = thisChild;
    120  }
    121  return common;
    122 }
    123 
    124 Accessible* Accessible::FocusedChild() {
    125  Accessible* doc = nsAccUtils::DocumentFor(this);
    126  Accessible* child = doc->FocusedChild();
    127  if (child && (child == this || child->Parent() == this)) {
    128    return child;
    129  }
    130 
    131  return nullptr;
    132 }
    133 
    134 const nsRoleMapEntry* Accessible::ARIARoleMap() const {
    135  return aria::GetRoleMapFromIndex(mRoleMapEntryIndex);
    136 }
    137 
    138 bool Accessible::HasARIARole() const {
    139  return mRoleMapEntryIndex != aria::NO_ROLE_MAP_ENTRY_INDEX;
    140 }
    141 
    142 bool Accessible::IsARIARole(nsAtom* aARIARole) const {
    143  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    144  return roleMapEntry && roleMapEntry->Is(aARIARole);
    145 }
    146 
    147 bool Accessible::HasStrongARIARole() const {
    148  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    149  return roleMapEntry && roleMapEntry->roleRule == kUseMapRole;
    150 }
    151 
    152 role Accessible::GetMinimumRole(role aRole) const {
    153  if (aRole != roles::TEXT && aRole != roles::TEXT_CONTAINER &&
    154      aRole != roles::SECTION) {
    155    // This isn't a generic role, so aRole is specific enough.
    156    return aRole;
    157  }
    158 
    159  if (IsPopover()) {
    160    return roles::GROUPING;
    161  }
    162  return aRole;
    163 }
    164 
    165 bool Accessible::HasGenericType(AccGenericType aType) const {
    166  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    167  return (mGenericTypes & aType) ||
    168         (roleMapEntry && roleMapEntry->IsOfType(aType));
    169 }
    170 
    171 nsIntRect Accessible::BoundsInCSSPixels() const {
    172  return BoundsInAppUnits().ToNearestPixels(AppUnitsPerCSSPixel());
    173 }
    174 
    175 LayoutDeviceIntSize Accessible::Size() const { return Bounds().Size(); }
    176 
    177 LayoutDeviceIntPoint Accessible::Position(uint32_t aCoordType) {
    178  LayoutDeviceIntPoint point = Bounds().TopLeft();
    179  nsAccUtils::ConvertScreenCoordsTo(&point.x.value, &point.y.value, aCoordType,
    180                                    this);
    181  return point;
    182 }
    183 
    184 bool Accessible::IsTextRole() {
    185  if (!IsHyperText()) {
    186    return false;
    187  }
    188 
    189  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    190  if (roleMapEntry && (roleMapEntry->role == roles::GRAPHIC ||
    191                       roleMapEntry->role == roles::IMAGE_MAP ||
    192                       roleMapEntry->role == roles::SLIDER ||
    193                       roleMapEntry->role == roles::PROGRESSBAR ||
    194                       roleMapEntry->role == roles::SEPARATOR ||
    195                       roleMapEntry->role == roles::METER)) {
    196    return false;
    197  }
    198 
    199  return true;
    200 }
    201 
    202 bool Accessible::IsEditableRoot() const {
    203  if (IsTextField()) {
    204    // A text field is always an editable root.
    205    return true;
    206  }
    207 
    208  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    209  if (roleMapEntry && (roleMapEntry->role == roles::ENTRY ||
    210                       roleMapEntry->role == roles::SEARCHBOX)) {
    211    // An aria text field is always an editable root.
    212    return true;
    213  }
    214 
    215  if (!IsEditable()) {
    216    return false;
    217  }
    218 
    219  if (IsDoc()) {
    220    return true;
    221  }
    222 
    223  Accessible* parent = Parent();
    224  if (parent && !parent->IsEditable()) {
    225    return true;
    226  }
    227 
    228  return false;
    229 }
    230 
    231 uint32_t Accessible::StartOffset() {
    232  MOZ_ASSERT(IsLink(), "StartOffset is called not on hyper link!");
    233  Accessible* parent = Parent();
    234  HyperTextAccessibleBase* hyperText =
    235      parent ? parent->AsHyperTextBase() : nullptr;
    236  return hyperText ? hyperText->GetChildOffset(this) : 0;
    237 }
    238 
    239 uint32_t Accessible::EndOffset() {
    240  MOZ_ASSERT(IsLink(), "EndOffset is called on not hyper link!");
    241  Accessible* parent = Parent();
    242  HyperTextAccessibleBase* hyperText =
    243      parent ? parent->AsHyperTextBase() : nullptr;
    244  return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0;
    245 }
    246 
    247 GroupPos Accessible::GroupPosition() {
    248  GroupPos groupPos;
    249 
    250  // Try aria-row/colcount/index.
    251  if (IsTableRow()) {
    252    Accessible* table = nsAccUtils::TableFor(this);
    253    if (table) {
    254      if (auto count = table->GetIntARIAAttr(nsGkAtoms::aria_rowcount)) {
    255        if (*count >= 0) {
    256          groupPos.setSize = *count;
    257        }
    258      }
    259    }
    260    if (auto index = GetIntARIAAttr(nsGkAtoms::aria_rowindex)) {
    261      groupPos.posInSet = *index;
    262    }
    263    if (groupPos.setSize && groupPos.posInSet) {
    264      return groupPos;
    265    }
    266  }
    267  if (IsTableCell()) {
    268    Accessible* table;
    269    for (table = Parent(); table; table = table->Parent()) {
    270      if (table->IsTable()) {
    271        break;
    272      }
    273    }
    274    if (table) {
    275      if (auto count = table->GetIntARIAAttr(nsGkAtoms::aria_colcount)) {
    276        if (*count >= 0) {
    277          groupPos.setSize = *count;
    278        }
    279      }
    280    }
    281    if (auto index = GetIntARIAAttr(nsGkAtoms::aria_colindex)) {
    282      groupPos.posInSet = *index;
    283    }
    284    if (groupPos.setSize && groupPos.posInSet) {
    285      return groupPos;
    286    }
    287  }
    288 
    289  // Get group position from ARIA attributes.
    290  ARIAGroupPosition(&groupPos.level, &groupPos.setSize, &groupPos.posInSet);
    291 
    292  // If ARIA is missed and the accessible is visible then calculate group
    293  // position from hierarchy.
    294  if (State() & states::INVISIBLE) return groupPos;
    295 
    296  // Calculate group level if ARIA is missed.
    297  if (groupPos.level == 0) {
    298    groupPos.level = GetLevel(false);
    299  }
    300 
    301  // Calculate position in group and group size if ARIA is missed.
    302  if (groupPos.posInSet == 0 || groupPos.setSize == 0) {
    303    int32_t posInSet = 0, setSize = 0;
    304    GetPositionAndSetSize(&posInSet, &setSize);
    305    if (posInSet != 0 && setSize != 0) {
    306      if (groupPos.posInSet == 0) groupPos.posInSet = posInSet;
    307 
    308      if (groupPos.setSize == 0) groupPos.setSize = setSize;
    309    }
    310  }
    311 
    312  return groupPos;
    313 }
    314 
    315 int32_t Accessible::GetLevel(bool aFast) const {
    316  int32_t level = 0;
    317  if (!Parent()) return level;
    318 
    319  roles::Role role = Role();
    320  if (role == roles::OUTLINEITEM) {
    321    // Always expose 'level' attribute for 'outlineitem' accessible. The number
    322    // of nested 'grouping' accessibles containing 'outlineitem' accessible is
    323    // its level.
    324    level = 1;
    325 
    326    if (!aFast) {
    327      const Accessible* parent = this;
    328      while ((parent = parent->Parent()) && !parent->IsDoc()) {
    329        roles::Role parentRole = parent->Role();
    330 
    331        if (parentRole == roles::OUTLINE) break;
    332        if (parentRole == roles::GROUPING) ++level;
    333      }
    334    }
    335  } else if (role == roles::LISTITEM && !aFast) {
    336    // Expose 'level' attribute on nested lists. We support two hierarchies:
    337    // a) list -> listitem -> list -> listitem (nested list is a last child
    338    //   of listitem of the parent list);
    339    // b) list -> listitem -> group -> listitem (nested listitems are contained
    340    //   by group that is a last child of the parent listitem).
    341 
    342    // Calculate 'level' attribute based on number of parent listitems.
    343    level = 0;
    344    const Accessible* parent = this;
    345    while ((parent = parent->Parent()) && !parent->IsDoc()) {
    346      roles::Role parentRole = parent->Role();
    347 
    348      if (parentRole == roles::LISTITEM) {
    349        ++level;
    350      } else if (parentRole != roles::LIST && parentRole != roles::GROUPING) {
    351        break;
    352      }
    353    }
    354 
    355    if (level == 0) {
    356      // If this listitem is on top of nested lists then expose 'level'
    357      // attribute.
    358      parent = Parent();
    359      uint32_t siblingCount = parent->ChildCount();
    360      for (uint32_t siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) {
    361        Accessible* sibling = parent->ChildAt(siblingIdx);
    362 
    363        Accessible* siblingChild = sibling->LastChild();
    364        if (siblingChild) {
    365          roles::Role lastChildRole = siblingChild->Role();
    366          if (lastChildRole == roles::LIST ||
    367              lastChildRole == roles::GROUPING) {
    368            return 1;
    369          }
    370        }
    371      }
    372    } else {
    373      ++level;  // level is 1-index based
    374    }
    375  } else if (role == roles::OPTION || role == roles::COMBOBOX_OPTION) {
    376    if (const Accessible* parent = Parent()) {
    377      if (parent->IsHTMLOptGroup()) {
    378        return 2;
    379      }
    380 
    381      if (parent->IsListControl() && !parent->ARIARoleMap()) {
    382        // This is for HTML selects only.
    383        if (aFast) {
    384          return 1;
    385        }
    386 
    387        for (uint32_t i = 0, count = parent->ChildCount(); i < count; ++i) {
    388          if (parent->ChildAt(i)->IsHTMLOptGroup()) {
    389            return 1;
    390          }
    391        }
    392      }
    393    }
    394  } else if (role == roles::HEADING) {
    395    nsAtom* tagName = TagName();
    396    if (tagName == nsGkAtoms::h1) {
    397      return 1;
    398    }
    399    if (tagName == nsGkAtoms::h2) {
    400      return 2;
    401    }
    402    if (tagName == nsGkAtoms::h3) {
    403      return 3;
    404    }
    405    if (tagName == nsGkAtoms::h4) {
    406      return 4;
    407    }
    408    if (tagName == nsGkAtoms::h5) {
    409      return 5;
    410    }
    411    if (tagName == nsGkAtoms::h6) {
    412      return 6;
    413    }
    414 
    415    const nsRoleMapEntry* ariaRole = this->ARIARoleMap();
    416    if (ariaRole && ariaRole->Is(nsGkAtoms::heading)) {
    417      // An aria heading with no aria level has a default level of 2.
    418      return 2;
    419    }
    420  } else if (role == roles::COMMENT) {
    421    // For comments, count the ancestor elements with the same role to get the
    422    // level.
    423    level = 1;
    424 
    425    if (!aFast) {
    426      const Accessible* parent = this;
    427      while ((parent = parent->Parent()) && !parent->IsDoc()) {
    428        roles::Role parentRole = parent->Role();
    429        if (parentRole == roles::COMMENT) {
    430          ++level;
    431        }
    432      }
    433    }
    434  } else if (role == roles::ROW) {
    435    // It is a row inside flatten treegrid. Group level is always 1 until it
    436    // is overriden by aria-level attribute.
    437    const Accessible* parent = Parent();
    438    if (parent->Role() == roles::TREE_TABLE) {
    439      return 1;
    440    }
    441  }
    442 
    443  return level;
    444 }
    445 
    446 void Accessible::GetPositionAndSetSize(int32_t* aPosInSet, int32_t* aSetSize) {
    447  auto groupInfo = GetOrCreateGroupInfo();
    448  if (groupInfo) {
    449    *aPosInSet = groupInfo->PosInSet();
    450    *aSetSize = groupInfo->SetSize();
    451  }
    452 }
    453 
    454 bool Accessible::IsLinkValid() {
    455  MOZ_ASSERT(IsLink(), "IsLinkValid is called on not hyper link!");
    456 
    457  // XXX In order to implement this we would need to follow every link
    458  // Perhaps we can get information about invalid links from the cache
    459  // In the mean time authors can use role="link" aria-invalid="true"
    460  // to force it for links they internally know to be invalid
    461  return (0 == (State() & mozilla::a11y::states::INVALID));
    462 }
    463 
    464 uint32_t Accessible::AnchorCount() {
    465  if (IsImageMap()) {
    466    return ChildCount();
    467  }
    468 
    469  MOZ_ASSERT(IsLink(), "AnchorCount is called on not hyper link!");
    470  return 1;
    471 }
    472 
    473 Accessible* Accessible::AnchorAt(uint32_t aAnchorIndex) const {
    474  if (IsImageMap()) {
    475    return ChildAt(aAnchorIndex);
    476  }
    477 
    478  MOZ_ASSERT(IsLink(), "GetAnchor is called on not hyper link!");
    479  return aAnchorIndex == 0 ? const_cast<Accessible*>(this) : nullptr;
    480 }
    481 
    482 already_AddRefed<nsIURI> Accessible::AnchorURIAt(uint32_t aAnchorIndex) const {
    483  Accessible* anchor = nullptr;
    484 
    485  if (IsTextLeaf() || IsImage()) {
    486    for (Accessible* parent = Parent(); parent && !parent->IsOuterDoc();
    487         parent = parent->Parent()) {
    488      if (parent->IsLink()) {
    489        anchor = parent->AnchorAt(aAnchorIndex);
    490      }
    491    }
    492  } else {
    493    anchor = AnchorAt(aAnchorIndex);
    494  }
    495 
    496  if (anchor) {
    497    RefPtr<nsIURI> uri;
    498    nsAutoString spec;
    499    anchor->Value(spec);
    500    nsresult rv = NS_NewURI(getter_AddRefs(uri), spec);
    501    if (NS_SUCCEEDED(rv)) {
    502      return uri.forget();
    503    }
    504  }
    505 
    506  return nullptr;
    507 }
    508 
    509 #ifdef A11Y_LOG
    510 void Accessible::DebugDescription(nsCString& aDesc) const {
    511  aDesc.Truncate();
    512  aDesc.AppendPrintf("%s", IsRemote() ? "Remote" : "Local");
    513  aDesc.AppendPrintf("[%p] ", this);
    514  nsAutoString role;
    515  GetAccService()->GetStringRole(Role(), role);
    516  aDesc.Append(NS_ConvertUTF16toUTF8(role));
    517 
    518  if (nsAtom* tagAtom = TagName()) {
    519    nsAutoCString tag;
    520    tagAtom->ToUTF8String(tag);
    521    aDesc.AppendPrintf(" %s", tag.get());
    522 
    523    nsAutoString id;
    524    DOMNodeID(id);
    525    if (!id.IsEmpty()) {
    526      aDesc.Append("#");
    527      aDesc.Append(NS_ConvertUTF16toUTF8(id));
    528    }
    529  }
    530  nsAutoString id;
    531 
    532  nsAutoString name;
    533  Name(name);
    534  if (!name.IsEmpty()) {
    535    aDesc.Append(" '");
    536    aDesc.Append(NS_ConvertUTF16toUTF8(name));
    537    aDesc.Append("'");
    538  }
    539 }
    540 
    541 void Accessible::DebugPrint(const char* aPrefix,
    542                            const Accessible* aAccessible) {
    543  nsAutoCString desc;
    544  if (aAccessible) {
    545    aAccessible->DebugDescription(desc);
    546  } else {
    547    desc.AssignLiteral("[null]");
    548  }
    549 #  if defined(ANDROID) || defined(MOZ_WIDGET_UIKIT)
    550  printf_stderr("%s %s\n", aPrefix, desc.get());
    551 #  else
    552  printf("%s %s\n", aPrefix, desc.get());
    553 #  endif
    554 }
    555 
    556 #endif
    557 
    558 void Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut,
    559                                 const nsTArray<nsString>& aParams) {
    560  nsCOMPtr<nsIStringBundleService> stringBundleService =
    561      components::StringBundle::Service();
    562  if (!stringBundleService) return;
    563 
    564  nsCOMPtr<nsIStringBundle> stringBundle;
    565  stringBundleService->CreateBundle(
    566      "chrome://global-platform/locale/accessible.properties",
    567      getter_AddRefs(stringBundle));
    568  if (!stringBundle) return;
    569 
    570  nsAutoString xsValue;
    571  nsresult rv = NS_OK;
    572  if (aParams.IsEmpty()) {
    573    rv = stringBundle->GetStringFromName(NS_ConvertUTF16toUTF8(aKey).get(),
    574                                         xsValue);
    575  } else {
    576    rv = stringBundle->FormatStringFromName(NS_ConvertUTF16toUTF8(aKey).get(),
    577                                            aParams, xsValue);
    578  }
    579  if (NS_SUCCEEDED(rv)) aStringOut.Assign(xsValue);
    580 }
    581 
    582 const Accessible* Accessible::ActionAncestor() const {
    583  // We do want to consider a click handler on the document. However, we don't
    584  // want to walk outside of this document, so we stop if we see an OuterDoc.
    585  for (Accessible* parent = Parent(); parent && !parent->IsOuterDoc();
    586       parent = parent->Parent()) {
    587    if (parent->HasPrimaryAction()) {
    588      return parent;
    589    }
    590  }
    591 
    592  return nullptr;
    593 }
    594 
    595 nsStaticAtom* Accessible::LandmarkRole() const {
    596  // For certain cases below (e.g. ARIA region, HTML <header>), whether it is
    597  // actually a landmark is conditional. Rather than duplicating that
    598  // conditional logic here, we check the Gecko role.
    599  if (const nsRoleMapEntry* roleMapEntry = ARIARoleMap()) {
    600    // Explicit ARIA role should take precedence.
    601    if (roleMapEntry->Is(nsGkAtoms::region)) {
    602      if (Role() == roles::REGION) {
    603        return nsGkAtoms::region;
    604      }
    605    } else if (roleMapEntry->Is(nsGkAtoms::form)) {
    606      if (Role() == roles::FORM) {
    607        return nsGkAtoms::form;
    608      }
    609    } else if (roleMapEntry->IsOfType(eLandmark)) {
    610      return roleMapEntry->roleAtom;
    611    }
    612  }
    613 
    614  nsAtom* tagName = TagName();
    615  if (!tagName) {
    616    // Either no associated content, or no cache.
    617    return nullptr;
    618  }
    619 
    620  if (tagName == nsGkAtoms::nav) {
    621    return nsGkAtoms::navigation;
    622  }
    623 
    624  if (tagName == nsGkAtoms::aside) {
    625    return nsGkAtoms::complementary;
    626  }
    627 
    628  if (tagName == nsGkAtoms::main) {
    629    return nsGkAtoms::main;
    630  }
    631 
    632  if (tagName == nsGkAtoms::header) {
    633    if (Role() == roles::LANDMARK) {
    634      return nsGkAtoms::banner;
    635    }
    636  }
    637 
    638  if (tagName == nsGkAtoms::footer) {
    639    if (Role() == roles::LANDMARK) {
    640      return nsGkAtoms::contentinfo;
    641    }
    642  }
    643 
    644  if (tagName == nsGkAtoms::section) {
    645    if (Role() == roles::REGION) {
    646      return nsGkAtoms::region;
    647    }
    648  }
    649 
    650  if (tagName == nsGkAtoms::form) {
    651    if (Role() == roles::FORM_LANDMARK) {
    652      return nsGkAtoms::form;
    653    }
    654  }
    655 
    656  if (tagName == nsGkAtoms::search) {
    657    return nsGkAtoms::search;
    658  }
    659 
    660  return nullptr;
    661 }
    662 
    663 nsStaticAtom* Accessible::ComputedARIARole() const {
    664  const nsRoleMapEntry* roleMap = ARIARoleMap();
    665  if (roleMap && roleMap->IsOfType(eDPub)) {
    666    return roleMap->roleAtom;
    667  }
    668  if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty &&
    669      // region and form have their own Gecko roles and need to be handled
    670      // specially.
    671      roleMap->roleAtom != nsGkAtoms::region &&
    672      roleMap->roleAtom != nsGkAtoms::form &&
    673      (roleMap->roleRule == kUseNativeRole || roleMap->IsOfType(eLandmark) ||
    674       roleMap->roleAtom == nsGkAtoms::alertdialog ||
    675       roleMap->roleAtom == nsGkAtoms::feed)) {
    676    // Explicit ARIA role (e.g. specified via the role attribute) which does not
    677    // map to a unique Gecko role.
    678    return roleMap->roleAtom;
    679  }
    680  role geckoRole = Role();
    681  if (geckoRole == roles::LANDMARK) {
    682    // Landmark role from native markup; e.g. <main>, <nav>.
    683    return LandmarkRole();
    684  }
    685  // Role from native markup or layout.
    686 #define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
    687             msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType,  \
    688             nameRule)                                                       \
    689  case roles::_geckoRole:                                                    \
    690    return ariaRole;
    691  switch (geckoRole) {
    692 #include "RoleMap.h"
    693  }
    694 #undef ROLE
    695  MOZ_ASSERT_UNREACHABLE("Unknown role");
    696  return nullptr;
    697 }
    698 
    699 void Accessible::ApplyImplicitState(uint64_t& aState) const {
    700  // nsAccessibilityService (and thus FocusManager) can be shut down before
    701  // RemoteAccessibles.
    702  if (const auto* focusMgr = FocusMgr()) {
    703    if (focusMgr->IsFocused(this)) {
    704      aState |= states::FOCUSED;
    705    }
    706  }
    707 
    708  // If this is an option, tab or treeitem and if it's focused and not marked
    709  // unselected explicitly (i.e. aria-selected="false") then expose it as
    710  // selected to make ARIA widget authors life easier.
    711  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    712  if (roleMapEntry &&
    713      (roleMapEntry->Is(nsGkAtoms::option) ||
    714       roleMapEntry->Is(nsGkAtoms::tab) ||
    715       roleMapEntry->Is(nsGkAtoms::treeitem)) &&
    716      !(aState & states::SELECTED) && ARIASelected().valueOr(true)) {
    717    if (roleMapEntry->role == roles::PAGETAB && !(aState & states::FOCUSED)) {
    718      // If focus is within the tab panel, this should mean the tab is selected.
    719      // Note that we handle focus on the tab itself below.
    720      Relation rel = RelationByType(RelationType::LABEL_FOR);
    721      Accessible* relTarget = nullptr;
    722      while ((relTarget = rel.Next())) {
    723        if (relTarget->Role() == roles::PROPERTYPAGE &&
    724            FocusMgr()->IsFocusWithin(relTarget)) {
    725          aState |= states::SELECTED;
    726        }
    727      }
    728    } else if (aState & states::FOCUSED) {
    729      Accessible* container = nsAccUtils::GetSelectableContainer(this, aState);
    730      AUTO_PROFILER_MARKER_TEXT(
    731          "Accessible::ApplyImplicitState::ImplicitSelection", A11Y, {}, ""_ns);
    732      auto HasExplicitSelection = [](Accessible* aAcc) {
    733        Pivot p = Pivot(aAcc);
    734        PivotARIASelectedRule rule;
    735        return p.First(rule) != nullptr;
    736      };
    737 
    738      if (container && !(container->State() & states::MULTISELECTABLE) &&
    739          !HasExplicitSelection(container)) {
    740        aState |= states::SELECTED;
    741      }
    742    }
    743  }
    744 
    745  if (Opacity() == 1.0f && !(aState & states::INVISIBLE)) {
    746    aState |= states::OPAQUE1;
    747  }
    748 
    749  if (aState & states::EXPANDABLE && !(aState & states::EXPANDED)) {
    750    aState |= states::COLLAPSED;
    751  }
    752 
    753  if (!(aState & states::UNAVAILABLE)) {
    754    aState |= states::ENABLED | states::SENSITIVE;
    755  }
    756 
    757  if (aState & states::FOCUSABLE && !(aState & states::UNAVAILABLE)) {
    758    // Propagate UNAVAILABLE state from ancestors down to any focusable
    759    // descendant.
    760    for (auto ancestor = Parent(); ancestor; ancestor = ancestor->Parent()) {
    761      if (ancestor->IsDoc() || ancestor->IsOuterDoc()) {
    762        break;
    763      }
    764 
    765      if (ancestor->State() & states::UNAVAILABLE) {
    766        aState |= states::UNAVAILABLE;
    767        break;
    768      }
    769    }
    770  }
    771 }
    772 
    773 bool Accessible::NameIsEmpty() const {
    774  nsAutoString name;
    775  Name(name);
    776  return name.IsEmpty();
    777 }
    778 
    779 ////////////////////////////////////////////////////////////////////////////////
    780 // KeyBinding class
    781 
    782 // static
    783 uint32_t KeyBinding::AccelModifier() {
    784  switch (WidgetInputEvent::AccelModifier()) {
    785    case MODIFIER_ALT:
    786      return kAlt;
    787    case MODIFIER_CONTROL:
    788      return kControl;
    789    case MODIFIER_META:
    790      return kMeta;
    791    default:
    792      MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
    793      return 0;
    794  }
    795 }
    796 
    797 void KeyBinding::ToPlatformFormat(nsAString& aValue) const {
    798  nsCOMPtr<nsIStringBundle> keyStringBundle;
    799  nsCOMPtr<nsIStringBundleService> stringBundleService =
    800      mozilla::components::StringBundle::Service();
    801  if (stringBundleService) {
    802    stringBundleService->CreateBundle(
    803        "chrome://global-platform/locale/platformKeys.properties",
    804        getter_AddRefs(keyStringBundle));
    805  }
    806 
    807  if (!keyStringBundle) return;
    808 
    809  nsAutoString separator;
    810  keyStringBundle->GetStringFromName("MODIFIER_SEPARATOR", separator);
    811 
    812  nsAutoString modifierName;
    813  if (mModifierMask & kControl) {
    814    keyStringBundle->GetStringFromName("VK_CONTROL", modifierName);
    815 
    816    aValue.Append(modifierName);
    817    aValue.Append(separator);
    818  }
    819 
    820  if (mModifierMask & kAlt) {
    821    keyStringBundle->GetStringFromName("VK_ALT", modifierName);
    822 
    823    aValue.Append(modifierName);
    824    aValue.Append(separator);
    825  }
    826 
    827  if (mModifierMask & kShift) {
    828    keyStringBundle->GetStringFromName("VK_SHIFT", modifierName);
    829 
    830    aValue.Append(modifierName);
    831    aValue.Append(separator);
    832  }
    833 
    834  if (mModifierMask & kMeta) {
    835    keyStringBundle->GetStringFromName("VK_META", modifierName);
    836 
    837    aValue.Append(modifierName);
    838    aValue.Append(separator);
    839  }
    840 
    841  aValue.Append(mKey);
    842 }
    843 
    844 void KeyBinding::ToAtkFormat(nsAString& aValue) const {
    845  nsAutoString modifierName;
    846  if (mModifierMask & kControl) aValue.AppendLiteral("<Control>");
    847 
    848  if (mModifierMask & kAlt) aValue.AppendLiteral("<Alt>");
    849 
    850  if (mModifierMask & kShift) aValue.AppendLiteral("<Shift>");
    851 
    852  if (mModifierMask & kMeta) aValue.AppendLiteral("<Meta>");
    853 
    854  aValue.Append(mKey);
    855 }
    856 
    857 role Accessible::FindNextValidARIARole(
    858    std::initializer_list<nsStaticAtom*> aRolesToSkip) const {
    859  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    860  if (roleMapEntry) {
    861    if (!ARIAAttrValueIs(nsGkAtoms::role, roleMapEntry->roleAtom)) {
    862      nsAutoString roles;
    863      GetStringARIAAttr(nsGkAtoms::role, roles);
    864      // Get the next valid token that isn't in the list of roles to skip.
    865      uint8_t roleMapIndex =
    866          aria::GetFirstValidRoleMapIndexExcluding(roles, aRolesToSkip);
    867      // If we don't find a valid token, fall back to the minimum role.
    868      if (roleMapIndex == aria::NO_ROLE_MAP_ENTRY_INDEX ||
    869          roleMapIndex == aria::LANDMARK_ROLE_MAP_ENTRY_INDEX) {
    870        return NativeRole();
    871      }
    872      const nsRoleMapEntry* fallbackRoleMapEntry =
    873          aria::GetRoleMapFromIndex(roleMapIndex);
    874      if (!fallbackRoleMapEntry) {
    875        return NativeRole();
    876      }
    877      // Return the next valid role, but validate that first, too.
    878      return ARIATransformRole(fallbackRoleMapEntry->role);
    879    }
    880  }
    881  // Fall back to the minimum role.
    882  return NativeRole();
    883 }
    884 
    885 role Accessible::ARIATransformRole(role aRole) const {
    886  // Beginning with ARIA 1.1, user agents are expected to use the native host
    887  // language role of the element when the form or region roles are used without
    888  // a name. Says the spec, "the user agent MUST treat such elements as if no
    889  // role had been provided."
    890  // https://w3c.github.io/aria/#document-handling_author-errors_roles
    891  //
    892  // XXX: While the name computation algorithm can be non-trivial in the general
    893  // case, it should not be especially bad here: If the author hasn't used the
    894  // region role, this calculation won't occur. And the region role's name
    895  // calculation rule excludes name from content. That said, this use case is
    896  // another example of why we should consider caching the accessible name. See:
    897  // https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
    898  if (aRole == roles::REGION || aRole == roles::FORM) {
    899    if (NameIsEmpty()) {
    900      // If we have a "form" or "region" role, but no accessible name, we need
    901      // to search for the next valid role. First, we search through the role
    902      // attribute value string - there might be a valid fallback there. Skip
    903      // all "form" or "region" attributes; we know they're not valid since
    904      // there's no accessible name. If we find a valid role that's not "form"
    905      // or "region", fall back to it (but run it through ARIATransformRole
    906      // first). Otherwise, fall back to the element's native role.
    907      return FindNextValidARIARole({nsGkAtoms::region, nsGkAtoms::form});
    908    }
    909    return aRole;
    910  }
    911 
    912  // XXX: these unfortunate exceptions don't fit into the ARIA table. This is
    913  // where the accessible role depends on both the role and ARIA state.
    914  if (aRole == roles::PUSHBUTTON) {
    915    if (HasARIAAttr(nsGkAtoms::aria_pressed)) {
    916      // For simplicity, any existing pressed attribute except "" or "undefined"
    917      // indicates a toggle.
    918      return roles::TOGGLE_BUTTON;
    919    }
    920 
    921    if (ARIAAttrValueIs(nsGkAtoms::aria_haspopup, nsGkAtoms::_true)) {
    922      // For button with aria-haspopup="true".
    923      return roles::BUTTONMENU;
    924    }
    925 
    926  } else if (aRole == roles::LISTBOX) {
    927    // A listbox inside of a combobox needs a special role because of ATK
    928    // mapping to menu.
    929    if (Parent() && Parent()->IsCombobox()) {
    930      return roles::COMBOBOX_LIST;
    931    }
    932 
    933  } else if (aRole == roles::OPTION) {
    934    const Accessible* listbox = FindAncestorIf([](const Accessible& aAcc) {
    935      const role accRole = aAcc.Role();
    936      return (accRole == roles::LISTBOX || accRole == roles::COMBOBOX_LIST)
    937                 ? AncestorSearchOption::Found
    938             : accRole == roles::GROUPING ? AncestorSearchOption::Continue
    939                                          : AncestorSearchOption::NotFound;
    940    });
    941    if (!listbox) {
    942      // Orphaned option outside the context of a listbox.
    943      return NativeRole();
    944    }
    945 
    946    if (listbox->Role() == roles::COMBOBOX_LIST) {
    947      return roles::COMBOBOX_OPTION;
    948    }
    949  } else if (aRole == roles::MENUITEM) {
    950    // Menuitem has a submenu.
    951    if (ARIAAttrValueIs(nsGkAtoms::aria_haspopup, nsGkAtoms::_true)) {
    952      return roles::PARENT_MENUITEM;
    953    }
    954 
    955    // Orphaned menuitem outside the context of a menu/menubar.
    956    const Accessible* menu = FindAncestorIf([](const Accessible& aAcc) {
    957      const role accRole = aAcc.Role();
    958      return (accRole == roles::MENUBAR || accRole == roles::MENUPOPUP)
    959                 ? AncestorSearchOption::Found
    960             : accRole == roles::GROUPING ? AncestorSearchOption::Continue
    961                                          : AncestorSearchOption::NotFound;
    962    });
    963    if (!menu) {
    964      return NativeRole();
    965    }
    966  } else if (aRole == roles::RADIO_MENU_ITEM ||
    967             aRole == roles::CHECK_MENU_ITEM) {
    968    // Orphaned radio/checkbox menuitem outside the context of a menu/menubar.
    969    const Accessible* menu = FindAncestorIf([](const Accessible& aAcc) {
    970      const role accRole = aAcc.Role();
    971      return (accRole == roles::MENUBAR || accRole == roles::MENUPOPUP)
    972                 ? AncestorSearchOption::Found
    973             : accRole == roles::GROUPING ? AncestorSearchOption::Continue
    974                                          : AncestorSearchOption::NotFound;
    975    });
    976    if (!menu) {
    977      return NativeRole();
    978    }
    979  } else if (aRole == roles::CELL) {
    980    // A cell inside an ancestor table element that has a grid role needs a
    981    // gridcell role
    982    // (https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings).
    983    const Accessible* table = nsAccUtils::TableFor(this);
    984    if (table && table->IsARIARole(nsGkAtoms::grid)) {
    985      return roles::GRID_CELL;
    986    }
    987  } else if (aRole == roles::ROW) {
    988    // Orphaned rows outside the context of a table.
    989    const Accessible* table = nsAccUtils::TableFor(this);
    990    if (!table) {
    991      return NativeRole();
    992    }
    993  } else if (aRole == roles::ROWGROUP) {
    994    // Orphaned rowgroups outside the context of a table.
    995    const Accessible* table = FindAncestorIf([](const Accessible& aAcc) {
    996      return aAcc.IsTable() ? AncestorSearchOption::Found
    997                            : AncestorSearchOption::NotFound;
    998    });
    999    if (!table) {
   1000      return NativeRole();
   1001    }
   1002  } else if (aRole == roles::GRID_CELL || aRole == roles::ROWHEADER ||
   1003             aRole == roles::COLUMNHEADER) {
   1004    // Orphaned gridcell/rowheader/columnheader outside the context of a row.
   1005    const Accessible* row = FindAncestorIf([](const Accessible& aAcc) {
   1006      return aAcc.IsTableRow() ? AncestorSearchOption::Found
   1007                               : AncestorSearchOption::NotFound;
   1008    });
   1009    if (!row) {
   1010      return NativeRole();
   1011    }
   1012  } else if (aRole == roles::LISTITEM) {
   1013    // doc-biblioentry and doc-endnote should not be treated as listitems.
   1014    const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   1015    if (!roleMapEntry || (roleMapEntry->roleAtom != nsGkAtoms::docBiblioentry &&
   1016                          roleMapEntry->roleAtom != nsGkAtoms::docEndnote)) {
   1017      // Orphaned listitem outside the context of a list.
   1018      const Accessible* list = FindAncestorIf([](const Accessible& aAcc) {
   1019        return aAcc.IsList() ? AncestorSearchOption::Found
   1020                             : AncestorSearchOption::Continue;
   1021      });
   1022      if (!list) {
   1023        return NativeRole();
   1024      }
   1025    }
   1026  } else if (aRole == roles::PAGETAB) {
   1027    // Orphaned tab outside the context of a tablist.
   1028    const Accessible* tablist = FindAncestorIf([](const Accessible& aAcc) {
   1029      return aAcc.Role() == roles::PAGETABLIST ? AncestorSearchOption::Found
   1030                                               : AncestorSearchOption::NotFound;
   1031    });
   1032    if (!tablist) {
   1033      return NativeRole();
   1034    }
   1035  } else if (aRole == roles::OUTLINEITEM) {
   1036    // Orphaned treeitem outside the context of a tree.
   1037    const Accessible* tree = FindAncestorIf([](const Accessible& aAcc) {
   1038      return aAcc.Role() == roles::OUTLINE ? AncestorSearchOption::Found
   1039                                           : AncestorSearchOption::Continue;
   1040    });
   1041    if (!tree) {
   1042      return NativeRole();
   1043    }
   1044  }
   1045 
   1046  return aRole;
   1047 }