tor-browser

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

RemoteAccessible.cpp (89127B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ARIAMap.h"
      8 #include "CachedTableAccessible.h"
      9 #include "RemoteAccessible.h"
     10 #include "mozilla/a11y/CacheConstants.h"
     11 #include "mozilla/a11y/DocAccessibleParent.h"
     12 #include "mozilla/a11y/DocManager.h"
     13 #include "mozilla/a11y/Platform.h"
     14 #include "mozilla/a11y/TableAccessible.h"
     15 #include "mozilla/a11y/TableCellAccessible.h"
     16 #include "mozilla/dom/Element.h"
     17 #include "mozilla/dom/BrowserParent.h"
     18 #include "mozilla/dom/CanonicalBrowsingContext.h"
     19 #include "mozilla/gfx/Matrix.h"
     20 #include "nsAccessibilityService.h"
     21 #include "nsAccUtils.h"
     22 #include "nsFocusManager.h"
     23 #include "nsTextEquivUtils.h"
     24 #include "Pivot.h"
     25 #include "Relation.h"
     26 #include "mozilla/a11y/RelationType.h"
     27 #include "xpcAccessibleDocument.h"
     28 
     29 #ifdef A11Y_LOG
     30 #  include "Logging.h"
     31 #  define VERIFY_CACHE(domain)                                 \
     32    if (logging::IsEnabled(logging::eCache)) {                 \
     33      (void)mDoc->SendVerifyCache(mID, domain, mCachedFields); \
     34    }
     35 #else
     36 #  define VERIFY_CACHE(domain) \
     37    do {                       \
     38    } while (0)
     39 
     40 #endif
     41 
     42 namespace mozilla {
     43 namespace a11y {
     44 
     45 // Domain sets we need commonly for functions in this file.
     46 static constexpr uint64_t kNecessaryBoundsDomains =
     47    CacheDomain::Bounds | CacheDomain::TransformMatrix | CacheDomain::Style |
     48    CacheDomain::ScrollPosition | CacheDomain::APZ;
     49 static constexpr uint64_t kNecessaryStateDomains =
     50    CacheDomain::State | CacheDomain::Viewport;
     51 
     52 void RemoteAccessible::Shutdown() {
     53  MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
     54  xpcAccessibleDocument* xpcDoc =
     55      GetAccService()->GetCachedXPCDocument(Document());
     56  if (xpcDoc) {
     57    xpcDoc->NotifyOfShutdown(static_cast<RemoteAccessible*>(this));
     58  }
     59 
     60  if (IsTable() || IsTableCell()) {
     61    CachedTableAccessible::Invalidate(this);
     62  }
     63 
     64  // Remove this acc's relation map from the doc's map of
     65  // reverse relations. Prune forward relations associated with this
     66  // acc's reverse relations. This also removes the acc's map of reverse
     67  // rels from the mDoc's mReverseRelations.
     68  PruneRelationsOnShutdown();
     69 
     70  // XXX Ideally  this wouldn't be necessary, but it seems OuterDoc
     71  // accessibles can be destroyed before the doc they own.
     72  uint32_t childCount = mChildren.Length();
     73  if (!IsOuterDoc()) {
     74    for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown();
     75  } else {
     76    if (childCount > 1) {
     77      MOZ_CRASH("outer doc has too many documents!");
     78    } else if (childCount == 1) {
     79      mChildren[0]->AsDoc()->Unbind();
     80    }
     81  }
     82 
     83  mChildren.Clear();
     84  ProxyDestroyed(static_cast<RemoteAccessible*>(this));
     85  // mDoc owns this RemoteAccessible, so RemoveAccessible deletes this.
     86  mDoc->RemoveAccessible(static_cast<RemoteAccessible*>(this));
     87 }
     88 
     89 void RemoteAccessible::SetChildDoc(DocAccessibleParent* aChildDoc) {
     90  MOZ_ASSERT(aChildDoc);
     91  MOZ_ASSERT(mChildren.Length() == 0);
     92  mChildren.AppendElement(aChildDoc);
     93  aChildDoc->mIndexInParent = 0;
     94 }
     95 
     96 void RemoteAccessible::ClearChildDoc(DocAccessibleParent* aChildDoc) {
     97  MOZ_ASSERT(aChildDoc);
     98  // This is possible if we're replacing one document with another: Doc 1
     99  // has not had a chance to remove itself, but was already replaced by Doc 2
    100  // in SetChildDoc(). This could result in two subsequent calls to
    101  // ClearChildDoc() even though mChildren.Length() == 1.
    102  MOZ_ASSERT(mChildren.Length() <= 1);
    103  mChildren.RemoveElement(aChildDoc);
    104 }
    105 
    106 uint32_t RemoteAccessible::EmbeddedChildCount() {
    107  size_t count = 0, kids = mChildren.Length();
    108  for (size_t i = 0; i < kids; i++) {
    109    if (mChildren[i]->IsEmbeddedObject()) {
    110      count++;
    111    }
    112  }
    113 
    114  return count;
    115 }
    116 
    117 int32_t RemoteAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
    118  size_t index = 0, kids = mChildren.Length();
    119  for (size_t i = 0; i < kids; i++) {
    120    if (mChildren[i]->IsEmbeddedObject()) {
    121      if (mChildren[i] == aChild) {
    122        return index;
    123      }
    124 
    125      index++;
    126    }
    127  }
    128 
    129  return -1;
    130 }
    131 
    132 Accessible* RemoteAccessible::EmbeddedChildAt(uint32_t aChildIdx) {
    133  size_t index = 0, kids = mChildren.Length();
    134  for (size_t i = 0; i < kids; i++) {
    135    if (!mChildren[i]->IsEmbeddedObject()) {
    136      continue;
    137    }
    138 
    139    if (index == aChildIdx) {
    140      return mChildren[i];
    141    }
    142 
    143    index++;
    144  }
    145 
    146  return nullptr;
    147 }
    148 
    149 LocalAccessible* RemoteAccessible::OuterDocOfRemoteBrowser() const {
    150  auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager());
    151  dom::Element* frame = tab->GetOwnerElement();
    152  NS_ASSERTION(frame, "why isn't the tab in a frame!");
    153  if (!frame) return nullptr;
    154 
    155  DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
    156 
    157  return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
    158 }
    159 
    160 void RemoteAccessible::SetParent(RemoteAccessible* aParent) {
    161  MOZ_ASSERT(!IsDoc() || !AsDoc()->IsTopLevel(),
    162             "Top level doc should not have remote parent");
    163  MOZ_ASSERT(!IsDoc() || !aParent || !aParent->IsDoc(),
    164             "Doc can't be direct parent of another doc");
    165  MOZ_ASSERT(!IsDoc() || !aParent || aParent->IsOuterDoc(),
    166             "Doc's parent must be OuterDoc");
    167  mParent = aParent;
    168  if (!aParent) {
    169    mIndexInParent = -1;
    170  }
    171 }
    172 
    173 RemoteAccessible* RemoteAccessible::RemoteParent() const {
    174  MOZ_ASSERT(!IsDoc() || !AsDoc()->IsTopLevel() || !mParent,
    175             "Top level doc should not have RemoteParent");
    176  MOZ_ASSERT(!IsDoc() || !mParent || mParent->mDoc != mDoc,
    177             "Doc's parent should be in another doc");
    178  MOZ_ASSERT(!IsDoc() || !mParent || mParent->IsOuterDoc(),
    179             "Doc's parent should be in another doc");
    180  return mParent;
    181 }
    182 
    183 void RemoteAccessible::ApplyCache(CacheUpdateType aUpdateType,
    184                                  AccAttributes* aFields) {
    185  if (!aFields) {
    186    MOZ_ASSERT_UNREACHABLE("ApplyCache called with aFields == null");
    187    return;
    188  }
    189 
    190  const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields);
    191  if (auto maybeViewportCache =
    192          aFields->GetAttribute<nsTArray<uint64_t>>(CacheKey::Viewport)) {
    193    // Updating the viewport cache means the offscreen state of this
    194    // document's accessibles has changed. Update the HashSet we use for
    195    // checking offscreen state here.
    196    MOZ_ASSERT(IsDoc(),
    197               "Fetched the viewport cache from a non-doc accessible?");
    198    AsDoc()->mOnScreenAccessibles.Clear();
    199    for (auto id : *maybeViewportCache) {
    200      AsDoc()->mOnScreenAccessibles.Insert(id);
    201    }
    202  }
    203 
    204  if (aUpdateType == CacheUpdateType::Initial) {
    205    mCachedFields = aFields;
    206  } else {
    207    if (!mCachedFields) {
    208      // The fields cache can be uninitialized if there were no cache-worthy
    209      // fields in the initial cache push.
    210      // We don't do a simple assign because we don't want to store the
    211      // DeleteEntry entries.
    212      mCachedFields = new AccAttributes();
    213    }
    214    mCachedFields->Update(aFields);
    215  }
    216 
    217  if (IsTextLeaf()) {
    218    RemoteAccessible* parent = RemoteParent();
    219    if (parent && parent->IsHyperText()) {
    220      parent->InvalidateCachedHyperTextOffsets();
    221    }
    222  }
    223 
    224  PostProcessRelations(relUpdatesNeeded);
    225 }
    226 
    227 ENameValueFlag RemoteAccessible::Name(nsString& aName) const {
    228  if (RequestDomainsIfInactive(CacheDomain::NameAndDescription |
    229                               CacheDomain::Text | CacheDomain::Relations) ||
    230      !mCachedFields) {
    231    aName.SetIsVoid(true);
    232    return eNameOK;
    233  }
    234 
    235  if (IsText()) {
    236    mCachedFields->GetAttribute(CacheKey::Text, aName);
    237    return eNameOK;
    238  }
    239 
    240  if (mCachedFields->GetAttribute(CacheKey::Name, aName)) {
    241    VERIFY_CACHE(CacheDomain::NameAndDescription);
    242    return eNameOK;
    243  }
    244 
    245  if (auto maybeAriaLabelIds = mCachedFields->GetAttribute<nsTArray<uint64_t>>(
    246          nsGkAtoms::aria_labelledby)) {
    247    RemoteAccIterator iter(*maybeAriaLabelIds, Document());
    248    nsTextEquivUtils::GetTextEquivFromAccIterable(this, &iter, aName);
    249    aName.CompressWhitespace();
    250  }
    251 
    252  if (!aName.IsEmpty()) {
    253    return eNameFromRelations;
    254  }
    255 
    256  if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) {
    257    nsTArray<uint64_t> relationCandidateIds;
    258    for (const auto& data : kRelationTypeAtoms) {
    259      if (data.mAtom != nsGkAtoms::_for || data.mValidTag != nsGkAtoms::label) {
    260        continue;
    261      }
    262 
    263      if (auto labelIds = accRelMapEntry.Data().Lookup(&data)) {
    264        RemoteAccIterator iter(*labelIds, Document());
    265        nsTextEquivUtils::GetTextEquivFromAccIterable(this, &iter, aName);
    266        aName.CompressWhitespace();
    267      }
    268    }
    269    aName.CompressWhitespace();
    270  }
    271 
    272  if (!aName.IsEmpty()) {
    273    return eNameFromRelations;
    274  }
    275 
    276  ArrayAccIterator iter(LegendsOrCaptions());
    277  nsTextEquivUtils::GetTextEquivFromAccIterable(this, &iter, aName);
    278  aName.CompressWhitespace();
    279 
    280  if (!aName.IsEmpty()) {
    281    return eNameFromRelations;
    282  }
    283 
    284  nsTextEquivUtils::GetNameFromSubtree(this, aName);
    285  if (!aName.IsEmpty()) {
    286    return eNameFromSubtree;
    287  }
    288 
    289  if (mCachedFields->GetAttribute(CacheKey::Tooltip, aName)) {
    290    VERIFY_CACHE(CacheDomain::NameAndDescription);
    291    return eNameFromTooltip;
    292  }
    293 
    294  if (mCachedFields->GetAttribute(CacheKey::CssAltContent, aName)) {
    295    VERIFY_CACHE(CacheDomain::NameAndDescription);
    296    return eNameOK;
    297  }
    298 
    299  MOZ_ASSERT(aName.IsEmpty());
    300  aName.SetIsVoid(true);
    301  return eNameOK;
    302 }
    303 
    304 EDescriptionValueFlag RemoteAccessible::Description(
    305    nsString& aDescription) const {
    306  if (RequestDomainsIfInactive(CacheDomain::NameAndDescription)) {
    307    return eDescriptionOK;
    308  }
    309 
    310  EDescriptionValueFlag descFlag = eDescriptionOK;
    311 
    312  if (mCachedFields) {
    313    auto cachedDescriptionFlag =
    314        mCachedFields->GetAttribute<int32_t>(CacheKey::DescriptionValueFlag);
    315    if (cachedDescriptionFlag) {
    316      descFlag = static_cast<EDescriptionValueFlag>(*cachedDescriptionFlag);
    317    }
    318    mCachedFields->GetAttribute(CacheKey::Description, aDescription);
    319    VERIFY_CACHE(CacheDomain::NameAndDescription);
    320  }
    321 
    322  return descFlag;
    323 }
    324 
    325 void RemoteAccessible::Value(nsString& aValue) const {
    326  if (RequestDomainsIfInactive(
    327          CacheDomain::Value |    // CurValue, etc.
    328          CacheDomain::Actions |  // ActionAncestor (HasPrimaryAction)
    329          CacheDomain::State |    // GetSelectedItem
    330          CacheDomain::Viewport   // GetSelectedItem
    331          )) {
    332    return;
    333  }
    334 
    335  if (mCachedFields) {
    336    if (mCachedFields->HasAttribute(CacheKey::TextValue)) {
    337      mCachedFields->GetAttribute(CacheKey::TextValue, aValue);
    338      VERIFY_CACHE(CacheDomain::Value);
    339      return;
    340    }
    341 
    342    if (HasNumericValue()) {
    343      double checkValue = CurValue();
    344      if (!std::isnan(checkValue)) {
    345        aValue.AppendFloat(checkValue);
    346      }
    347      return;
    348    }
    349 
    350    const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    351    // Value of textbox is a textified subtree.
    352    if ((roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) ||
    353        (IsGeneric() && IsEditableRoot())) {
    354      nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
    355      return;
    356    }
    357 
    358    if (IsCombobox()) {
    359      // For combo boxes, rely on selection state to determine the value.
    360      const Accessible* option =
    361          const_cast<RemoteAccessible*>(this)->GetSelectedItem(0);
    362      if (option) {
    363        option->Name(aValue);
    364      } else {
    365        // If no selected item, determine the value from descendant elements.
    366        nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
    367      }
    368      return;
    369    }
    370 
    371    if (IsTextLeaf() || IsImage()) {
    372      if (const Accessible* actionAcc = ActionAncestor()) {
    373        if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) {
    374          // Text and image descendants of links expose the link URL as the
    375          // value.
    376          return actionAcc->Value(aValue);
    377        }
    378      }
    379    }
    380  }
    381 }
    382 
    383 double RemoteAccessible::CurValue() const {
    384  if (RequestDomainsIfInactive(CacheDomain::Value)) {
    385    return UnspecifiedNaN<double>();
    386  }
    387 
    388  if (mCachedFields) {
    389    if (auto value =
    390            mCachedFields->GetAttribute<double>(CacheKey::NumericValue)) {
    391      VERIFY_CACHE(CacheDomain::Value);
    392      return *value;
    393    }
    394  }
    395 
    396  return UnspecifiedNaN<double>();
    397 }
    398 
    399 double RemoteAccessible::MinValue() const {
    400  if (RequestDomainsIfInactive(CacheDomain::Value)) {
    401    return UnspecifiedNaN<double>();
    402  }
    403 
    404  if (mCachedFields) {
    405    if (auto min = mCachedFields->GetAttribute<double>(CacheKey::MinValue)) {
    406      VERIFY_CACHE(CacheDomain::Value);
    407      return *min;
    408    }
    409  }
    410 
    411  return UnspecifiedNaN<double>();
    412 }
    413 
    414 double RemoteAccessible::MaxValue() const {
    415  if (RequestDomainsIfInactive(CacheDomain::Value)) {
    416    return UnspecifiedNaN<double>();
    417  }
    418 
    419  if (mCachedFields) {
    420    if (auto max = mCachedFields->GetAttribute<double>(CacheKey::MaxValue)) {
    421      VERIFY_CACHE(CacheDomain::Value);
    422      return *max;
    423    }
    424  }
    425 
    426  return UnspecifiedNaN<double>();
    427 }
    428 
    429 double RemoteAccessible::Step() const {
    430  if (RequestDomainsIfInactive(CacheDomain::Value)) {
    431    return UnspecifiedNaN<double>();
    432  }
    433 
    434  if (mCachedFields) {
    435    if (auto step = mCachedFields->GetAttribute<double>(CacheKey::Step)) {
    436      VERIFY_CACHE(CacheDomain::Value);
    437      return *step;
    438    }
    439  }
    440 
    441  return UnspecifiedNaN<double>();
    442 }
    443 
    444 bool RemoteAccessible::SetCurValue(double aValue) {
    445  if (RequestDomainsIfInactive(CacheDomain::Value |   // MinValue, MaxValue
    446                               CacheDomain::State |   // State
    447                               CacheDomain::Viewport  // State
    448                               )) {
    449    return false;
    450  }
    451 
    452  if (!HasNumericValue() || IsProgress()) {
    453    return false;
    454  }
    455 
    456  const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
    457  if (State() & kValueCannotChange) {
    458    return false;
    459  }
    460 
    461  double checkValue = MinValue();
    462  if (!std::isnan(checkValue) && aValue < checkValue) {
    463    return false;
    464  }
    465 
    466  checkValue = MaxValue();
    467  if (!std::isnan(checkValue) && aValue > checkValue) {
    468    return false;
    469  }
    470 
    471  (void)mDoc->SendSetCurValue(mID, aValue);
    472  return true;
    473 }
    474 
    475 bool RemoteAccessible::ContainsPoint(int32_t aX, int32_t aY) {
    476  ASSERT_DOMAINS_ACTIVE(CacheDomain::TextBounds |  // GetCachedTextLines
    477                        kNecessaryBoundsDomains);
    478 
    479  if (!BoundsWithOffset(Nothing(), true).Contains(aX, aY)) {
    480    return false;
    481  }
    482  if (!IsTextLeaf()) {
    483    if (IsImage() || IsImageMap() || !HasChildren() ||
    484        RefPtr{DisplayStyle()} != nsGkAtoms::inlinevalue) {
    485      // This isn't an inline element that might contain text, so we don't need
    486      // to walk lines. It's enough that our rect contains the point.
    487      return true;
    488    }
    489    // Non-image inline elements with children can wrap across lines just like
    490    // text leaves; see below.
    491    // Walk the children, which will walk the lines of text in any text leaves.
    492    uint32_t count = ChildCount();
    493    for (uint32_t c = 0; c < count; ++c) {
    494      RemoteAccessible* child = RemoteChildAt(c);
    495      if (child->Role() == roles::TEXT_CONTAINER && child->IsClipped()) {
    496        // There is a clipped child. This is a candidate for fuzzy hit testing.
    497        // See RemoteAccessible::DoFuzzyHittesting.
    498        return true;
    499      }
    500      if (child->ContainsPoint(aX, aY)) {
    501        return true;
    502      }
    503    }
    504    // None of our descendants contain the point, so nor do we.
    505    return false;
    506  }
    507  // This is a text leaf. The text might wrap across lines, which means our
    508  // rect might cover a wider area than the actual text. For example, if the
    509  // text begins in the middle of the first line and wraps on to the second,
    510  // the rect will cover the start of the first line and the end of the second.
    511  auto lines = GetCachedTextLines();
    512  if (!lines) {
    513    // This means the text is empty or occupies a single line (but does not
    514    // begin the line). In that case, the Bounds check above is sufficient,
    515    // since there's only one rect.
    516    return true;
    517  }
    518  uint32_t length = lines->Length();
    519  MOZ_ASSERT(length > 0,
    520             "Line starts shouldn't be in cache if there aren't any");
    521  if (length == 0 || (length == 1 && (*lines)[0] == 0)) {
    522    // This means the text begins and occupies a single line. Again, the Bounds
    523    // check above is sufficient.
    524    return true;
    525  }
    526  // Walk the lines of the text. Even if this text doesn't start at the
    527  // beginning of a line (i.e. lines[0] > 0), we always want to consider its
    528  // first line.
    529  int32_t lineStart = 0;
    530  for (uint32_t index = 0; index <= length; ++index) {
    531    int32_t lineEnd;
    532    if (index < length) {
    533      int32_t nextLineStart = (*lines)[index];
    534      if (nextLineStart == 0) {
    535        // This Accessible starts at the beginning of a line. Here, we always
    536        // treat 0 as the first line start anyway.
    537        MOZ_ASSERT(index == 0);
    538        continue;
    539      }
    540      lineEnd = nextLineStart - 1;
    541    } else {
    542      // This is the last line.
    543      lineEnd = static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1;
    544    }
    545    MOZ_ASSERT(lineEnd >= lineStart);
    546    nsRect lineRect = GetCachedCharRect(lineStart);
    547    if (lineEnd > lineStart) {
    548      nsRect lineEndRect = GetCachedCharRect(lineEnd);
    549      if (lineEndRect.IsEmpty() && lineEnd - 1 > lineStart) {
    550        // The line feed character at the end of a line in pre-formatted text
    551        // doesn't have a useful rect. Use the previous character. Otherwise,
    552        // lineRect won't span the line of text and we'll miss characters.
    553        lineEndRect = GetCachedCharRect(lineEnd - 1);
    554      }
    555      lineRect.UnionRect(lineRect, lineEndRect);
    556    }
    557    if (BoundsWithOffset(Some(lineRect), true).Contains(aX, aY)) {
    558      return true;
    559    }
    560    lineStart = lineEnd + 1;
    561  }
    562  return false;
    563 }
    564 
    565 RemoteAccessible* RemoteAccessible::DoFuzzyHittesting() {
    566  ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
    567 
    568  uint32_t childCount = ChildCount();
    569  if (!childCount) {
    570    return nullptr;
    571  }
    572  // Check if this match has a clipped child.
    573  // This usually indicates invisible text, and we're
    574  // interested in returning the inner text content
    575  // even if it doesn't contain the point we're hittesting.
    576  RemoteAccessible* clippedContainer = nullptr;
    577  for (uint32_t i = 0; i < childCount; i++) {
    578    RemoteAccessible* child = RemoteChildAt(i);
    579    if (child->Role() == roles::TEXT_CONTAINER) {
    580      if (child->IsClipped()) {
    581        clippedContainer = child;
    582        break;
    583      }
    584    }
    585  }
    586  // If we found a clipped container, descend it in search of
    587  // meaningful text leaves. Ignore non-text-leaf/text-container
    588  // siblings.
    589  RemoteAccessible* container = clippedContainer;
    590  while (container) {
    591    RemoteAccessible* textLeaf = nullptr;
    592    bool continueSearch = false;
    593    childCount = container->ChildCount();
    594    for (uint32_t i = 0; i < childCount; i++) {
    595      RemoteAccessible* child = container->RemoteChildAt(i);
    596      if (child->Role() == roles::TEXT_CONTAINER) {
    597        container = child;
    598        continueSearch = true;
    599        break;
    600      }
    601      if (child->IsTextLeaf()) {
    602        textLeaf = child;
    603        // Don't break here -- it's possible a text container
    604        // exists as another sibling, and we should descend as
    605        // deep as possible.
    606      }
    607    }
    608    if (textLeaf) {
    609      return textLeaf;
    610    }
    611    if (!continueSearch) {
    612      // We didn't find anything useful in this set of siblings.
    613      // Don't keep searching
    614      break;
    615    }
    616  }
    617  return nullptr;
    618 }
    619 
    620 Accessible* RemoteAccessible::ChildAtPoint(
    621    int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) {
    622  if (RequestDomainsIfInactive(
    623          kNecessaryBoundsDomains |
    624          CacheDomain::TextBounds  // GetCachedTextLines (via ContainsPoint)
    625          )) {
    626    return nullptr;
    627  }
    628 
    629  // Elements that are partially on-screen should have their bounds masked by
    630  // their containing scroll area so hittesting yields results that are
    631  // consistent with the content's visual representation. Pass this value to
    632  // bounds calculation functions to indicate that we're hittesting.
    633  const bool hitTesting = true;
    634 
    635  if (IsOuterDoc() && aWhichChild == EWhichChildAtPoint::DirectChild) {
    636    // This is an iframe, which is as deep as the viewport cache goes. The
    637    // caller wants a direct child, which can only be the embedded document.
    638    if (BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
    639      return RemoteFirstChild();
    640    }
    641    return nullptr;
    642  }
    643 
    644  RemoteAccessible* lastMatch = nullptr;
    645  // If `this` is a document, use its viewport cache instead of
    646  // the cache of its parent document.
    647  if (DocAccessibleParent* doc = IsDoc() ? AsDoc() : mDoc) {
    648    if (!doc->mCachedFields) {
    649      // A client call might arrive after we've constructed doc but before we
    650      // get a cache push for it.
    651      return nullptr;
    652    }
    653    if (auto maybeViewportCache =
    654            doc->mCachedFields->GetAttribute<nsTArray<uint64_t>>(
    655                CacheKey::Viewport)) {
    656      // The retrieved viewport cache contains acc IDs in hittesting order.
    657      // That is, items earlier in the list have z-indexes that are larger than
    658      // those later in the list. If you were to build a tree by z-index, where
    659      // chilren have larger z indices than their parents, iterating this list
    660      // is essentially a postorder tree traversal.
    661      const nsTArray<uint64_t>& viewportCache = *maybeViewportCache;
    662 
    663      for (auto id : viewportCache) {
    664        RemoteAccessible* acc = doc->GetAccessible(id);
    665        if (!acc) {
    666          // This can happen if the acc died in between
    667          // pushing the viewport cache and doing this hittest
    668          continue;
    669        }
    670 
    671        if (acc->IsOuterDoc() &&
    672            aWhichChild == EWhichChildAtPoint::DeepestChild &&
    673            acc->BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
    674          // acc is an iframe, which is as deep as the viewport cache goes. This
    675          // iframe contains the requested point.
    676          RemoteAccessible* innerDoc = acc->RemoteFirstChild();
    677          if (innerDoc) {
    678            MOZ_ASSERT(innerDoc->IsDoc());
    679            // Search the embedded document's viewport cache so we return the
    680            // deepest descendant in that embedded document.
    681            Accessible* deepestAcc = innerDoc->ChildAtPoint(
    682                aX, aY, EWhichChildAtPoint::DeepestChild);
    683            MOZ_ASSERT(!deepestAcc || deepestAcc->IsRemote());
    684            lastMatch = deepestAcc ? deepestAcc->AsRemote() : nullptr;
    685            break;
    686          }
    687          // If there is no embedded document, the iframe itself is the deepest
    688          // descendant.
    689          lastMatch = acc;
    690          break;
    691        }
    692 
    693        if (acc == this) {
    694          MOZ_ASSERT(!acc->IsOuterDoc());
    695          // Even though we're searching from the doc's cache
    696          // this call shouldn't pass the boundary defined by
    697          // the acc this call originated on. If we hit `this`,
    698          // return our most recent match.
    699          if (!lastMatch &&
    700              BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
    701            // If we haven't found a match, but `this` contains the point we're
    702            // looking for, set it as our temp last match so we can
    703            // (potentially) do fuzzy hittesting on it below.
    704            lastMatch = acc;
    705          }
    706          break;
    707        }
    708 
    709        if (acc->ContainsPoint(aX, aY)) {
    710          // Because our rects are in hittesting order, the
    711          // first match we encounter is guaranteed to be the
    712          // deepest match.
    713          lastMatch = acc;
    714          break;
    715        }
    716      }
    717      if (lastMatch) {
    718        RemoteAccessible* fuzzyMatch = lastMatch->DoFuzzyHittesting();
    719        lastMatch = fuzzyMatch ? fuzzyMatch : lastMatch;
    720      }
    721    }
    722  }
    723 
    724  if (aWhichChild == EWhichChildAtPoint::DirectChild && lastMatch) {
    725    // lastMatch is the deepest match. Walk up to the direct child of this.
    726    RemoteAccessible* parent = lastMatch->RemoteParent();
    727    for (;;) {
    728      if (parent == this) {
    729        break;
    730      }
    731      if (!parent || parent->IsDoc()) {
    732        // `this` is not an ancestor of lastMatch. Ignore lastMatch.
    733        lastMatch = nullptr;
    734        break;
    735      }
    736      lastMatch = parent;
    737      parent = parent->RemoteParent();
    738    }
    739  } else if (aWhichChild == EWhichChildAtPoint::DeepestChild && lastMatch &&
    740             !IsDoc() && !IsAncestorOf(lastMatch)) {
    741    // If we end up with a match that is not in the ancestor chain
    742    // of the accessible this call originated on, we should ignore it.
    743    // This can happen when the aX, aY given are outside `this`.
    744    lastMatch = nullptr;
    745  }
    746 
    747  if (!lastMatch && BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
    748    // Even though the hit target isn't inside `this`, the point is still
    749    // within our bounds, so fall back to `this`.
    750    return this;
    751  }
    752 
    753  return lastMatch;
    754 }
    755 
    756 Maybe<nsRect> RemoteAccessible::RetrieveCachedBounds() const {
    757  if (!mCachedFields) {
    758    return Nothing();
    759  }
    760 
    761  ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
    762  Maybe<const UniquePtr<nsRect>&> maybeRect =
    763      mCachedFields->GetAttribute<UniquePtr<nsRect>>(
    764          CacheKey::ParentRelativeBounds);
    765  if (maybeRect) {
    766    return Some(*(*maybeRect));
    767  }
    768 
    769  return Nothing();
    770 }
    771 
    772 void RemoteAccessible::ApplyCrossDocOffset(nsRect& aBounds) const {
    773  if (!IsDoc()) {
    774    // We should only apply cross-doc offsets to documents. If we're anything
    775    // else, return early here.
    776    return;
    777  }
    778 
    779  RemoteAccessible* parentAcc = RemoteParent();
    780  if (!parentAcc || !parentAcc->IsOuterDoc()) {
    781    return;
    782  }
    783 
    784  ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
    785  Maybe<const nsTArray<int32_t>&> maybeOffset =
    786      parentAcc->mCachedFields->GetAttribute<nsTArray<int32_t>>(
    787          CacheKey::CrossDocOffset);
    788  if (!maybeOffset) {
    789    return;
    790  }
    791 
    792  MOZ_ASSERT(maybeOffset->Length() == 2);
    793  const nsTArray<int32_t>& offset = *maybeOffset;
    794  // Our retrieved value is in app units, so we don't need to do any
    795  // unit conversion here.
    796  aBounds.MoveBy(offset[0], offset[1]);
    797 }
    798 
    799 bool RemoteAccessible::ApplyTransform(nsRect& aCumulativeBounds) const {
    800  ASSERT_DOMAINS_ACTIVE(CacheDomain::TransformMatrix);
    801 
    802  // First, attempt to retrieve the transform from the cache.
    803  Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform =
    804      mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>(
    805          CacheKey::TransformMatrix);
    806  if (!maybeTransform) {
    807    return false;
    808  }
    809 
    810  auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix(
    811      *(*maybeTransform));
    812 
    813  // Our matrix is in CSS Pixels, so we need our rect to be in CSS
    814  // Pixels too. Convert before applying.
    815  auto boundsInPixels = CSSRect::FromAppUnits(aCumulativeBounds);
    816  boundsInPixels = mtxInPixels.TransformBounds(boundsInPixels);
    817  aCumulativeBounds = CSSRect::ToAppUnits(boundsInPixels);
    818 
    819  return true;
    820 }
    821 
    822 bool RemoteAccessible::ApplyScrollOffset(nsRect& aBounds,
    823                                         float aResolution) const {
    824  ASSERT_DOMAINS_ACTIVE(CacheDomain::ScrollPosition);
    825  Maybe<const nsTArray<int32_t>&> maybeScrollPosition =
    826      mCachedFields->GetAttribute<nsTArray<int32_t>>(CacheKey::ScrollPosition);
    827 
    828  if (!maybeScrollPosition || maybeScrollPosition->Length() != 2) {
    829    return false;
    830  }
    831  // Our retrieved value is in app units, so we don't need to do any
    832  // unit conversion here.
    833  const nsTArray<int32_t>& scrollPosition = *maybeScrollPosition;
    834 
    835  // Scroll position is an inverse representation of scroll offset (since the
    836  // further the scroll bar moves down the page, the further the page content
    837  // moves up/closer to the origin).
    838  nsPoint scrollOffset(-scrollPosition[0], -scrollPosition[1]);
    839 
    840  aBounds.MoveBy(scrollOffset.x * aResolution * aResolution,
    841                 scrollOffset.y * aResolution * aResolution);
    842 
    843  // Return true here even if the scroll offset was 0,0 because the RV is used
    844  // as a scroll container indicator. Non-scroll containers won't have cached
    845  // scroll position.
    846  return true;
    847 }
    848 
    849 void RemoteAccessible::ApplyVisualViewportOffset(nsRect& aBounds) const {
    850  ASSERT_DOMAINS_ACTIVE(CacheDomain::APZ);
    851  MOZ_ASSERT(IsDoc(), "Attempting to get visual viewport data from non-doc?");
    852  Maybe<const nsTArray<int32_t>&> maybeViewportOffset =
    853      mCachedFields->GetAttribute<nsTArray<int32_t>>(
    854          CacheKey::VisualViewportOffset);
    855 
    856  if (!maybeViewportOffset || maybeViewportOffset->Length() != 2) {
    857    return;
    858  }
    859  // Our retrieved value is in app units, so we don't need to do any
    860  // unit conversion here.
    861  const nsTArray<int32_t>& viewportOffset = *maybeViewportOffset;
    862 
    863  // Like scroll position, this offset is an inverse representation: the
    864  // further the visual viewport moves, the further the page content
    865  // moves up/closer to the origin
    866  aBounds.MoveBy(-viewportOffset[0], -viewportOffset[1]);
    867 }
    868 
    869 nsRect RemoteAccessible::BoundsInAppUnits() const {
    870  if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) {
    871    return {};
    872  }
    873  if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()->Top()) {
    874    if (dom::BrowserParent* bp = cbc->GetBrowserParent()) {
    875      DocAccessibleParent* topDoc = bp->GetTopLevelDocAccessible();
    876      if (topDoc && topDoc->mCachedFields) {
    877        auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
    878            CacheKey::AppUnitsPerDevPixel);
    879        MOZ_ASSERT(appUnitsPerDevPixel);
    880        return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel);
    881      }
    882    }
    883  }
    884  return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel());
    885 }
    886 
    887 bool RemoteAccessible::IsFixedPos() const {
    888  ASSERT_DOMAINS_ACTIVE(CacheDomain::Style);
    889  MOZ_ASSERT(mCachedFields);
    890  if (auto maybePosition =
    891          mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CssPosition)) {
    892    return *maybePosition == nsGkAtoms::fixed;
    893  }
    894 
    895  return false;
    896 }
    897 
    898 bool RemoteAccessible::IsOverflowHidden() const {
    899  ASSERT_DOMAINS_ACTIVE(CacheDomain::Style);
    900  MOZ_ASSERT(mCachedFields);
    901  if (auto maybeOverflow =
    902          mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSOverflow)) {
    903    return *maybeOverflow == nsGkAtoms::hidden;
    904  }
    905 
    906  return false;
    907 }
    908 
    909 bool RemoteAccessible::IsClipped() const {
    910  ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
    911  MOZ_ASSERT(mCachedFields);
    912  if (mCachedFields->GetAttribute<bool>(CacheKey::IsClipped)) {
    913    return true;
    914  }
    915 
    916  return false;
    917 }
    918 
    919 LayoutDeviceIntRect RemoteAccessible::BoundsWithOffset(
    920    Maybe<nsRect> aOffset, bool aBoundsAreForHittesting) const {
    921  if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) {
    922    return LayoutDeviceIntRect{};
    923  }
    924 
    925  Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
    926  if (maybeBounds) {
    927    nsRect bounds = *maybeBounds;
    928    // maybeBounds is parent-relative. However, the transform matrix we cache
    929    // (if any) is meant to operate on self-relative rects. Therefore, make
    930    // bounds self-relative until after we transform.
    931    bounds.MoveTo(0, 0);
    932    const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr;
    933 
    934    if (aOffset.isSome()) {
    935      // The rect we've passed in is in app units, so no conversion needed.
    936      nsRect internalRect = *aOffset;
    937      bounds.SetRectX(bounds.x + internalRect.x, internalRect.width);
    938      bounds.SetRectY(bounds.y + internalRect.y, internalRect.height);
    939    }
    940 
    941    (void)ApplyTransform(bounds);
    942    // Now apply the parent-relative offset.
    943    bounds.MoveBy(maybeBounds->TopLeft());
    944 
    945    ApplyCrossDocOffset(bounds);
    946 
    947    Maybe<float> res =
    948        mDoc->mCachedFields->GetAttribute<float>(CacheKey::Resolution);
    949    MOZ_ASSERT(res, "No cached document resolution found.");
    950    const float resolution = res.valueOr(1.0f);
    951 
    952    LayoutDeviceIntRect devPxBounds;
    953    const Accessible* acc = Parent();
    954    bool encounteredFixedContainer = IsFixedPos();
    955    while (acc && acc->IsRemote()) {
    956      // Return early if we're hit testing and our cumulative bounds are empty,
    957      // since walking the ancestor chain won't produce any hits.
    958      if (aBoundsAreForHittesting && bounds.IsEmpty()) {
    959        return LayoutDeviceIntRect{};
    960      }
    961 
    962      RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote();
    963 
    964      if (Maybe<nsRect> maybeRemoteBounds = remoteAcc->RetrieveCachedBounds()) {
    965        nsRect remoteBounds = *maybeRemoteBounds;
    966        // We need to take into account a non-1 resolution set on the
    967        // presshell. This happens with async pinch zooming, among other
    968        // things. We can't reliably query this value in the parent process,
    969        // so we retrieve it from the document's cache.
    970        if (remoteAcc->IsDoc()) {
    971          // Apply our visual viewport offset, which is non-zero when
    972          // pinch zoom has been applied. Do this before we scale by
    973          // resolution as this offset is unscaled.
    974          remoteAcc->ApplyVisualViewportOffset(bounds);
    975          // Apply the document's resolution to the bounds we've gathered
    976          // thus far. We do this before applying the document's offset
    977          // because document accs should not have their bounds scaled by
    978          // their own resolution. They should be scaled by the resolution
    979          // of their containing document (if any).
    980          Maybe<float> res =
    981              remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>(
    982                  CacheKey::Resolution);
    983          MOZ_ASSERT(res, "No cached document resolution found.");
    984          bounds.ScaleRoundOut(res.valueOr(1.0f));
    985 
    986          topDoc = remoteAcc->AsDoc();
    987        }
    988 
    989        // We don't account for the document offset of iframes when
    990        // computing parent-relative bounds. Instead, we store this value
    991        // separately on all iframes and apply it here. See the comments in
    992        // LocalAccessible::BundleFieldsForCache where we set the
    993        // nsGkAtoms::crossorigin attribute.
    994        remoteAcc->ApplyCrossDocOffset(remoteBounds);
    995        if (!encounteredFixedContainer) {
    996          // Apply scroll offset, if applicable. Only the contents of an
    997          // element are affected by its scroll offset, which is why this call
    998          // happens in this loop instead of both inside and outside of
    999          // the loop (like ApplyTransform).
   1000          // Never apply scroll offsets past a fixed container.
   1001          const bool hasScrollArea =
   1002              remoteAcc->ApplyScrollOffset(bounds, resolution);
   1003 
   1004          // If we are hit testing and the Accessible has a scroll area, ensure
   1005          // that the bounds we've calculated so far are constrained to the
   1006          // bounds of the scroll area. Without this, we'll "hit" the off-screen
   1007          // portions of accs that are are partially (but not fully) within the
   1008          // scroll area. This is also a problem for accs with overflow:hidden;
   1009          if (aBoundsAreForHittesting &&
   1010              (hasScrollArea || remoteAcc->IsOverflowHidden())) {
   1011            nsRect selfRelativeVisibleBounds(0, 0, remoteBounds.width,
   1012                                             remoteBounds.height);
   1013            bounds = bounds.SafeIntersect(selfRelativeVisibleBounds);
   1014          }
   1015        }
   1016        if (remoteAcc->IsDoc()) {
   1017          // Fixed elements are document relative, so if we've hit a
   1018          // document we're now subject to that document's styling
   1019          // (including scroll offsets that operate on it).
   1020          // This ordering is important, we don't want to apply scroll
   1021          // offsets on this doc's content.
   1022          encounteredFixedContainer = false;
   1023        }
   1024        if (!encounteredFixedContainer) {
   1025          // The transform matrix we cache (if any) is meant to operate on
   1026          // self-relative rects. Therefore, we must apply the transform before
   1027          // we make bounds parent-relative.
   1028          (void)remoteAcc->ApplyTransform(bounds);
   1029          // Regardless of whether this is a doc, we should offset `bounds`
   1030          // by the bounds retrieved here. This is how we build screen
   1031          // coordinates from relative coordinates.
   1032          bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
   1033        }
   1034 
   1035        if (remoteAcc->IsFixedPos()) {
   1036          encounteredFixedContainer = true;
   1037        }
   1038        // we can't just break here if we're scroll suppressed because we still
   1039        // need to find the top doc.
   1040      }
   1041      acc = acc->Parent();
   1042    }
   1043 
   1044    MOZ_ASSERT(topDoc);
   1045    if (topDoc) {
   1046      // We use the top documents app-units-per-dev-pixel even though
   1047      // theoretically nested docs can have different values. Practically,
   1048      // that isn't likely since we only offer zoom controls for the top
   1049      // document and all subdocuments inherit from it.
   1050      auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
   1051          CacheKey::AppUnitsPerDevPixel);
   1052      MOZ_ASSERT(appUnitsPerDevPixel);
   1053      if (appUnitsPerDevPixel) {
   1054        // Convert our existing `bounds` rect from app units to dev pixels
   1055        devPxBounds = LayoutDeviceIntRect::FromAppUnitsToNearest(
   1056            bounds, *appUnitsPerDevPixel);
   1057      }
   1058    }
   1059 
   1060 #if !defined(ANDROID)
   1061    // This block is not thread safe because it queries a LocalAccessible.
   1062    // It is also not needed in Android since the only local accessible is
   1063    // the outer doc browser that has an offset of 0.
   1064    // acc could be null if the OuterDocAccessible died before the top level
   1065    // DocAccessibleParent.
   1066    if (LocalAccessible* localAcc =
   1067            acc ? const_cast<Accessible*>(acc)->AsLocal() : nullptr) {
   1068      // LocalAccessible::Bounds returns screen-relative bounds in
   1069      // dev pixels.
   1070      LayoutDeviceIntRect localBounds = localAcc->Bounds();
   1071 
   1072      // The root document will always have an APZ resolution of 1,
   1073      // so we don't factor in its scale here. We also don't scale
   1074      // by GetFullZoom because LocalAccessible::Bounds already does
   1075      // that.
   1076      devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
   1077    }
   1078 #endif
   1079 
   1080    return devPxBounds;
   1081  }
   1082 
   1083  return LayoutDeviceIntRect();
   1084 }
   1085 
   1086 LayoutDeviceIntRect RemoteAccessible::Bounds() const {
   1087  if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) {
   1088    return {};
   1089  }
   1090  return BoundsWithOffset(Nothing());
   1091 }
   1092 
   1093 Relation RemoteAccessible::RelationByType(RelationType aType) const {
   1094  if (RequestDomainsIfInactive(
   1095          CacheDomain::Relations |          // relations info, DOMName attribute
   1096          CacheDomain::Value |              // Value
   1097          CacheDomain::DOMNodeIDAndClass |  // DOMNodeID
   1098          CacheDomain::GroupInfo            // GetOrCreateGroupInfo
   1099          )) {
   1100    return Relation();
   1101  }
   1102 
   1103  // We are able to handle some relations completely in the
   1104  // parent process, without the help of the cache. Those
   1105  // relations are enumerated here. Other relations, whose
   1106  // types are stored in kRelationTypeAtoms, are processed
   1107  // below using the cache.
   1108  if (aType == RelationType::CONTAINING_TAB_PANE) {
   1109    if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()) {
   1110      if (dom::CanonicalBrowsingContext* topCbc = cbc->Top()) {
   1111        if (dom::BrowserParent* bp = topCbc->GetBrowserParent()) {
   1112          return Relation(bp->GetTopLevelDocAccessible());
   1113        }
   1114      }
   1115    }
   1116    return Relation();
   1117  }
   1118 
   1119  if (aType == RelationType::LINKS_TO && Role() == roles::LINK) {
   1120    Pivot p = Pivot(mDoc);
   1121    nsString href;
   1122    Value(href);
   1123    int32_t i = href.FindChar('#');
   1124    int32_t len = static_cast<int32_t>(href.Length());
   1125    if (i != -1 && i < (len - 1)) {
   1126      nsDependentSubstring anchorName = Substring(href, i + 1, len);
   1127      MustPruneSameDocRule rule;
   1128      Accessible* nameMatch = nullptr;
   1129      for (Accessible* match = p.Next(mDoc, rule); match;
   1130           match = p.Next(match, rule)) {
   1131        nsString currID;
   1132        match->DOMNodeID(currID);
   1133        MOZ_ASSERT(match->IsRemote());
   1134        if (anchorName.Equals(currID)) {
   1135          return Relation(match->AsRemote());
   1136        }
   1137        if (!nameMatch) {
   1138          nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute();
   1139          if (match->TagName() == nsGkAtoms::a && anchorName.Equals(currName)) {
   1140            // If we find an element with a matching ID, we should return
   1141            // that, but if we don't we should return the first anchor with
   1142            // a matching name. To avoid doing two traversals, store the first
   1143            // name match here.
   1144            nameMatch = match;
   1145          }
   1146        }
   1147      }
   1148      return nameMatch ? Relation(nameMatch->AsRemote()) : Relation();
   1149    }
   1150 
   1151    return Relation();
   1152  }
   1153 
   1154  // Handle ARIA tree, treegrid parent/child relations. Each of these cases
   1155  // relies on cached group info. To find the parent of an accessible, use the
   1156  // unified conceptual parent.
   1157  if (aType == RelationType::NODE_CHILD_OF) {
   1158    const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   1159    if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
   1160                         roleMapEntry->role == roles::LISTITEM ||
   1161                         roleMapEntry->role == roles::ROW)) {
   1162      if (const AccGroupInfo* groupInfo =
   1163              const_cast<RemoteAccessible*>(this)->GetOrCreateGroupInfo()) {
   1164        return Relation(groupInfo->ConceptualParent());
   1165      }
   1166    }
   1167    return Relation();
   1168  }
   1169 
   1170  // To find the children of a parent, provide an iterator through its items.
   1171  if (aType == RelationType::NODE_PARENT_OF) {
   1172    const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   1173    if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
   1174                         roleMapEntry->role == roles::LISTITEM ||
   1175                         roleMapEntry->role == roles::ROW ||
   1176                         roleMapEntry->role == roles::OUTLINE ||
   1177                         roleMapEntry->role == roles::LIST ||
   1178                         roleMapEntry->role == roles::TREE_TABLE)) {
   1179      return Relation(new ItemIterator(this));
   1180    }
   1181    return Relation();
   1182  }
   1183 
   1184  if (aType == RelationType::MEMBER_OF) {
   1185    Relation rel = Relation();
   1186    // HTML radio buttons with cached names should be grouped.
   1187    if (IsHTMLRadioButton()) {
   1188      nsString name = GetCachedHTMLNameAttribute();
   1189      if (name.IsEmpty()) {
   1190        return rel;
   1191      }
   1192 
   1193      RemoteAccessible* ancestor = RemoteParent();
   1194      while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) {
   1195        ancestor = ancestor->RemoteParent();
   1196      }
   1197      if (ancestor) {
   1198        // Sometimes we end up with an unparented acc here, potentially
   1199        // because the acc is being moved. See bug 1807639.
   1200        // Pivot expects to be created with a non-null mRoot.
   1201        Pivot p = Pivot(ancestor);
   1202        PivotRadioNameRule rule(name);
   1203        Accessible* match = p.Next(ancestor, rule);
   1204        while (match) {
   1205          rel.AppendTarget(match->AsRemote());
   1206          match = p.Next(match, rule);
   1207        }
   1208      }
   1209      return rel;
   1210    }
   1211 
   1212    if (IsARIARole(nsGkAtoms::radio)) {
   1213      // ARIA radio buttons should be grouped by their radio group
   1214      // parent, if one exists.
   1215      RemoteAccessible* currParent = RemoteParent();
   1216      while (currParent && currParent->Role() != roles::RADIO_GROUP) {
   1217        currParent = currParent->RemoteParent();
   1218      }
   1219 
   1220      if (currParent && currParent->Role() == roles::RADIO_GROUP) {
   1221        // If we found a radiogroup parent, search for all
   1222        // roles::RADIOBUTTON children and add them to our relation.
   1223        // This search will include the radio button this method
   1224        // was called from, which is expected.
   1225        Pivot p = Pivot(currParent);
   1226        PivotRoleRule rule(roles::RADIOBUTTON);
   1227        Accessible* match = p.Next(currParent, rule);
   1228        while (match) {
   1229          MOZ_ASSERT(match->IsRemote(),
   1230                     "We should only be traversing the remote tree.");
   1231          rel.AppendTarget(match->AsRemote());
   1232          match = p.Next(match, rule);
   1233        }
   1234      }
   1235    }
   1236    // By webkit's standard, aria radio buttons do not get grouped
   1237    // if they lack a group parent, so we return an empty
   1238    // relation here if the above check fails.
   1239    return rel;
   1240  }
   1241 
   1242  Relation rel;
   1243  if (!mCachedFields) {
   1244    return rel;
   1245  }
   1246 
   1247  auto GetDirectRelationFromCache =
   1248      [](const RemoteAccessible* aAcc,
   1249         RelationType aRelType) -> Maybe<const nsTArray<uint64_t>&> {
   1250    for (const auto& data : kRelationTypeAtoms) {
   1251      if (data.mType != aRelType ||
   1252          (data.mValidTag && aAcc->TagName() != data.mValidTag)) {
   1253        continue;
   1254      }
   1255 
   1256      if (auto maybeIds = aAcc->mCachedFields->GetAttribute<nsTArray<uint64_t>>(
   1257              data.mAtom)) {
   1258        if (data.mAtom == nsGkAtoms::target) {
   1259          if (!maybeIds->IsEmpty() &&
   1260              !nsAccUtils::IsValidDetailsTargetForAnchor(
   1261                  aAcc->mDoc->GetAccessible(maybeIds->ElementAt(0)), aAcc)) {
   1262            continue;
   1263          }
   1264        }
   1265 
   1266        // Relations can have several cached attributes in order of precedence,
   1267        // if one is found we use it.
   1268        return maybeIds;
   1269      }
   1270    }
   1271 
   1272    return Nothing();
   1273  };
   1274 
   1275  if (auto maybeIds = GetDirectRelationFromCache(this, aType)) {
   1276    rel.AppendIter(new RemoteAccIterator(*maybeIds, Document()));
   1277  }
   1278 
   1279  if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) {
   1280    // A list of candidate IDs that might have a relation of type aType
   1281    // pointing to us. We keep a list so we don't evaluate or add the same
   1282    // target more than once.
   1283    nsTArray<uint64_t> relationCandidateIds;
   1284    for (const auto& data : kRelationTypeAtoms) {
   1285      if (data.mReverseType != aType) {
   1286        continue;
   1287      }
   1288 
   1289      auto reverseIdsEntry = accRelMapEntry.Data().Lookup(&data);
   1290      if (!reverseIdsEntry) {
   1291        continue;
   1292      }
   1293 
   1294      for (auto id : *reverseIdsEntry) {
   1295        if (relationCandidateIds.Contains(id)) {
   1296          // If multiple attributes point to the same target, only
   1297          // include it once. Our assumption is that there is a 1:1
   1298          // relationship between relation and reverse relation *types*.
   1299          continue;
   1300        }
   1301        relationCandidateIds.AppendElement(id);
   1302 
   1303        RemoteAccessible* relatedAcc = mDoc->GetAccessible(id);
   1304        if (!relatedAcc) {
   1305          continue;
   1306        }
   1307 
   1308        if (auto maybeIds =
   1309                GetDirectRelationFromCache(relatedAcc, data.mType)) {
   1310          if (maybeIds->Contains(ID())) {
   1311            // The candidate target has the forward relation type pointing
   1312            // to us, so we can add it as a target.
   1313            rel.AppendTarget(relatedAcc);
   1314          }
   1315        }
   1316      }
   1317    }
   1318  }
   1319 
   1320  // We handle these relations here rather than before cached relations because
   1321  // the cached relations need to take precedence. For example, a <figure> with
   1322  // both aria-labelledby and a <figcaption> must return two LABELLED_BY
   1323  // targets: the aria-labelledby and then the <figcaption>.
   1324  if (aType == RelationType::LABELLED_BY) {
   1325    rel.AppendIter(new ArrayAccIterator(LegendsOrCaptions()));
   1326  } else if (aType == RelationType::LABEL_FOR) {
   1327    if (RemoteAccessible* labelTarget = LegendOrCaptionFor()) {
   1328      rel.AppendTarget(labelTarget);
   1329    }
   1330  }
   1331 
   1332  return rel;
   1333 }
   1334 
   1335 nsTArray<Accessible*> RemoteAccessible::LegendsOrCaptions() const {
   1336  nsTArray<Accessible*> children;
   1337  auto AddChildWithTag = [this, &children](nsAtom* aTarget) {
   1338    uint32_t count = ChildCount();
   1339    for (uint32_t c = 0; c < count; ++c) {
   1340      Accessible* child = ChildAt(c);
   1341      MOZ_ASSERT(child);
   1342      if (child->TagName() == aTarget) {
   1343        children.AppendElement(child);
   1344      }
   1345    }
   1346  };
   1347 
   1348  auto tag = TagName();
   1349  if (tag == nsGkAtoms::figure) {
   1350    AddChildWithTag(nsGkAtoms::figcaption);
   1351  } else if (tag == nsGkAtoms::fieldset) {
   1352    AddChildWithTag(nsGkAtoms::legend);
   1353  } else if (tag == nsGkAtoms::table) {
   1354    AddChildWithTag(nsGkAtoms::caption);
   1355  }
   1356 
   1357  return children;
   1358 }
   1359 
   1360 RemoteAccessible* RemoteAccessible::LegendOrCaptionFor() const {
   1361  auto tag = TagName();
   1362  if (tag == nsGkAtoms::figcaption) {
   1363    if (RemoteAccessible* parent = RemoteParent()) {
   1364      if (parent->TagName() == nsGkAtoms::figure) {
   1365        return parent;
   1366      }
   1367    }
   1368  } else if (tag == nsGkAtoms::legend) {
   1369    if (RemoteAccessible* parent = RemoteParent()) {
   1370      if (parent->TagName() == nsGkAtoms::fieldset) {
   1371        return parent;
   1372      }
   1373    }
   1374  } else if (tag == nsGkAtoms::caption) {
   1375    if (RemoteAccessible* parent = RemoteParent()) {
   1376      if (parent->TagName() == nsGkAtoms::table) {
   1377        return parent;
   1378      }
   1379    }
   1380  }
   1381 
   1382  return nullptr;
   1383 }
   1384 
   1385 void RemoteAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
   1386                                    uint32_t aLength) {
   1387  if (IsText()) {
   1388    if (RequestDomainsIfInactive(CacheDomain::Text)) {
   1389      return;
   1390    }
   1391    if (mCachedFields) {
   1392      if (auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text)) {
   1393        aText.Append(Substring(*text, aStartOffset, aLength));
   1394      }
   1395      VERIFY_CACHE(CacheDomain::Text);
   1396    }
   1397    return;
   1398  }
   1399 
   1400  if (aStartOffset != 0 || aLength == 0) {
   1401    return;
   1402  }
   1403 
   1404  if (IsHTMLBr()) {
   1405    aText += kForcedNewLineChar;
   1406  } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) {
   1407    // Expose the embedded object accessible as imaginary embedded object
   1408    // character if its parent hypertext accessible doesn't expose children to
   1409    // AT.
   1410    aText += kImaginaryEmbeddedObjectChar;
   1411  } else {
   1412    aText += kEmbeddedObjectChar;
   1413  }
   1414 }
   1415 
   1416 nsTArray<bool> RemoteAccessible::PreProcessRelations(AccAttributes* aFields) {
   1417  if (!DomainsAreActive(CacheDomain::Relations)) {
   1418    return {};
   1419  }
   1420  nsTArray<bool> updateTracker(std::size(kRelationTypeAtoms));
   1421  for (auto const& data : kRelationTypeAtoms) {
   1422    if (data.mValidTag) {
   1423      // The relation we're currently processing only applies to particular
   1424      // elements. Check to see if we're one of them.
   1425      nsAtom* tag = TagName();
   1426      if (!tag) {
   1427        // TagName() returns null on an initial cache push -- check aFields
   1428        // for a tag name instead.
   1429        if (auto maybeTag =
   1430                aFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
   1431          tag = *maybeTag;
   1432        }
   1433      }
   1434      MOZ_ASSERT(
   1435          tag || IsTextLeaf() || IsDoc(),
   1436          "Could not fetch tag via TagName() or from initial cache push!");
   1437      if (tag != data.mValidTag) {
   1438        // If this rel doesn't apply to us, do no pre-processing. Also,
   1439        // note in our updateTracker that we should do no post-processing.
   1440        updateTracker.AppendElement(false);
   1441        continue;
   1442      }
   1443    }
   1444 
   1445    nsStaticAtom* const relAtom = data.mAtom;
   1446    auto newRelationTargets =
   1447        aFields->GetAttribute<nsTArray<uint64_t>>(relAtom);
   1448    bool shouldAddNewImplicitRels =
   1449        newRelationTargets && newRelationTargets->Length();
   1450 
   1451    // Remove existing implicit relations if we need to perform an update, or
   1452    // if we've received a DeleteEntry(). Only do this if mCachedFields is
   1453    // initialized. If mCachedFields is not initialized, we still need to
   1454    // construct the update array so we correctly handle reverse rels in
   1455    // PostProcessRelations.
   1456    if ((shouldAddNewImplicitRels ||
   1457         aFields->GetAttribute<DeleteEntry>(relAtom)) &&
   1458        mCachedFields) {
   1459      ASSERT_DOMAINS_ACTIVE(CacheDomain::Relations);
   1460      if (auto maybeOldIDs =
   1461              mCachedFields->GetAttribute<nsTArray<uint64_t>>(relAtom)) {
   1462        for (uint64_t id : *maybeOldIDs) {
   1463          // For each target, fetch its reverse relation map
   1464          // We need to call `Lookup` here instead of `LookupOrInsert` because
   1465          // it's possible the ID we're querying is from an acc that has since
   1466          // been Shutdown(), and so has intentionally removed its reverse rels
   1467          // from the doc's reverse rel cache.
   1468          if (auto reverseRels = Document()->mReverseRelations.Lookup(id)) {
   1469            // Then fetch its reverse relation's ID list. This should be safe
   1470            // to do via LookupOrInsert because by the time we've gotten here,
   1471            // we know the acc and `this` are still alive in the doc. If we hit
   1472            // the following assert, we don't have parity on implicit/explicit
   1473            // rels and something is wrong.
   1474            nsTArray<uint64_t>& reverseRelIDs =
   1475                reverseRels->LookupOrInsert(&data);
   1476            //  There might be other reverse relations stored for this acc, so
   1477            //  remove our ID instead of deleting the array entirely.
   1478            DebugOnly<bool> removed = reverseRelIDs.RemoveElement(ID());
   1479            MOZ_ASSERT(removed, "Can't find old reverse relation");
   1480          }
   1481        }
   1482      }
   1483    }
   1484 
   1485    updateTracker.AppendElement(shouldAddNewImplicitRels);
   1486  }
   1487 
   1488  return updateTracker;
   1489 }
   1490 
   1491 void RemoteAccessible::PostProcessRelations(const nsTArray<bool>& aToUpdate) {
   1492  if (!DomainsAreActive(CacheDomain::Relations)) {
   1493    return;
   1494  }
   1495  size_t updateCount = aToUpdate.Length();
   1496  MOZ_ASSERT(updateCount == std::size(kRelationTypeAtoms),
   1497             "Did not note update status for every relation type!");
   1498  for (size_t i = 0; i < updateCount; i++) {
   1499    if (aToUpdate.ElementAt(i)) {
   1500      // Since kRelationTypeAtoms was used to generate aToUpdate, we
   1501      // know the ith entry of aToUpdate corresponds to the relation type in
   1502      // the ith entry of kRelationTypeAtoms. Fetch the related data here.
   1503      auto const& data = kRelationTypeAtoms[i];
   1504 
   1505      const nsTArray<uint64_t>& newIDs =
   1506          *mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom);
   1507      for (uint64_t id : newIDs) {
   1508        auto& relations = Document()->mReverseRelations.LookupOrInsert(id);
   1509        nsTArray<uint64_t>& ids = relations.LookupOrInsert(&data);
   1510        ids.AppendElement(ID());
   1511      }
   1512    }
   1513  }
   1514 }
   1515 
   1516 void RemoteAccessible::PruneRelationsOnShutdown() {
   1517  auto reverseRels = mDoc->mReverseRelations.Lookup(ID());
   1518  if (!reverseRels) {
   1519    return;
   1520  }
   1521  for (auto const& data : kRelationTypeAtoms) {
   1522    // Fetch the list of targets for this reverse relation
   1523    auto reverseTargetList = reverseRels->Lookup(&data);
   1524    if (!reverseTargetList) {
   1525      continue;
   1526    }
   1527    for (uint64_t id : *reverseTargetList) {
   1528      // For each target, retrieve its corresponding forward relation target
   1529      // list
   1530      RemoteAccessible* affectedAcc = mDoc->GetAccessible(id);
   1531      if (!affectedAcc) {
   1532        // It's possible the affect acc also shut down, in which case
   1533        // we don't have anything to update.
   1534        continue;
   1535      }
   1536      if (auto forwardTargetList =
   1537              affectedAcc->mCachedFields
   1538                  ->GetMutableAttribute<nsTArray<uint64_t>>(data.mAtom)) {
   1539        forwardTargetList->RemoveElement(ID());
   1540        if (!forwardTargetList->Length()) {
   1541          // The ID we removed was the only thing in the list, so remove the
   1542          // entry from the cache entirely -- don't leave an empty array.
   1543          affectedAcc->mCachedFields->Remove(data.mAtom);
   1544        }
   1545      }
   1546    }
   1547  }
   1548  // Remove this ID from the document's map of reverse relations.
   1549  reverseRels.Remove();
   1550 }
   1551 
   1552 uint32_t RemoteAccessible::GetCachedTextLength() {
   1553  if (RequestDomainsIfInactive(CacheDomain::Text)) {
   1554    return 0;
   1555  }
   1556  MOZ_ASSERT(!HasChildren());
   1557  if (!mCachedFields) {
   1558    return 0;
   1559  }
   1560  VERIFY_CACHE(CacheDomain::Text);
   1561  auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text);
   1562  if (!text) {
   1563    return 0;
   1564  }
   1565  return text->Length();
   1566 }
   1567 
   1568 Maybe<const nsTArray<int32_t>&> RemoteAccessible::GetCachedTextLines() {
   1569  if (RequestDomainsIfInactive(CacheDomain::TextBounds)) {
   1570    return Nothing();
   1571  }
   1572 
   1573  MOZ_ASSERT(!HasChildren());
   1574  if (!mCachedFields) {
   1575    return Nothing();
   1576  }
   1577  VERIFY_CACHE(CacheDomain::TextBounds);
   1578  return mCachedFields->GetAttribute<nsTArray<int32_t>>(
   1579      CacheKey::TextLineStarts);
   1580 }
   1581 
   1582 nsRect RemoteAccessible::GetCachedCharRect(int32_t aOffset) {
   1583  ASSERT_DOMAINS_ACTIVE(CacheDomain::TextBounds);
   1584  MOZ_ASSERT(IsText());
   1585  if (!mCachedFields) {
   1586    return nsRect();
   1587  }
   1588 
   1589  if (Maybe<const nsTArray<int32_t>&> maybeCharData =
   1590          mCachedFields->GetAttribute<nsTArray<int32_t>>(
   1591              CacheKey::TextBounds)) {
   1592    const nsTArray<int32_t>& charData = *maybeCharData;
   1593    const int32_t index = aOffset * kNumbersInRect;
   1594    if (index < static_cast<int32_t>(charData.Length())) {
   1595      return nsRect(charData[index], charData[index + 1], charData[index + 2],
   1596                    charData[index + 3]);
   1597    }
   1598    // It is valid for a client to call this with an offset 1 after the last
   1599    // character because of the insertion point at the end of text boxes.
   1600    MOZ_ASSERT(index == static_cast<int32_t>(charData.Length()));
   1601  }
   1602 
   1603  return nsRect();
   1604 }
   1605 
   1606 void RemoteAccessible::DOMNodeID(nsString& aID) const {
   1607  if (RequestDomainsIfInactive(CacheDomain::DOMNodeIDAndClass)) {
   1608    return;
   1609  }
   1610  if (mCachedFields) {
   1611    mCachedFields->GetAttribute(CacheKey::DOMNodeID, aID);
   1612    VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
   1613  }
   1614 }
   1615 
   1616 void RemoteAccessible::DOMNodeClass(nsString& aClass) const {
   1617  if (mCachedFields) {
   1618    mCachedFields->GetAttribute(CacheKey::DOMNodeClass, aClass);
   1619    VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
   1620  }
   1621 }
   1622 
   1623 void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX,
   1624                                     int32_t aY) {
   1625  (void)mDoc->SendScrollToPoint(mID, aScrollType, aX, aY);
   1626 }
   1627 
   1628 bool RemoteAccessible::IsScrollable() const {
   1629  if (RequestDomainsIfInactive(CacheDomain::ScrollPosition)) {
   1630    return false;
   1631  }
   1632  return mCachedFields && mCachedFields->HasAttribute(CacheKey::ScrollPosition);
   1633 }
   1634 
   1635 bool RemoteAccessible::IsPopover() const {
   1636  return mCachedFields && mCachedFields->HasAttribute(CacheKey::PopupType);
   1637 }
   1638 
   1639 bool RemoteAccessible::IsEditable() const {
   1640  if (RequestDomainsIfInactive(CacheDomain::State)) {
   1641    return false;
   1642  }
   1643 
   1644  if (mCachedFields) {
   1645    if (auto rawState =
   1646            mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) {
   1647      VERIFY_CACHE(CacheDomain::State);
   1648      return (*rawState & states::EDITABLE) != 0;
   1649    }
   1650  }
   1651 
   1652  return false;
   1653 }
   1654 
   1655 #if !defined(XP_WIN)
   1656 void RemoteAccessible::Announce(const nsString& aAnnouncement,
   1657                                uint16_t aPriority) {
   1658  (void)mDoc->SendAnnounce(mID, aAnnouncement, aPriority);
   1659 }
   1660 #endif  // !defined(XP_WIN)
   1661 
   1662 int32_t RemoteAccessible::ValueRegion() const {
   1663  MOZ_ASSERT(TagName() == nsGkAtoms::meter,
   1664             "Accessing value region on non-meter element?");
   1665  if (mCachedFields) {
   1666    if (auto region =
   1667            mCachedFields->GetAttribute<int32_t>(CacheKey::ValueRegion)) {
   1668      return *region;
   1669    }
   1670  }
   1671  // Expose sub-optimal (but not critical) as the value region, as a fallback.
   1672  return 0;
   1673 }
   1674 
   1675 void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
   1676                                              int32_t aEndOffset,
   1677                                              uint32_t aCoordinateType,
   1678                                              int32_t aX, int32_t aY) {
   1679  (void)mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset,
   1680                                         aCoordinateType, aX, aY);
   1681 }
   1682 
   1683 RefPtr<const AccAttributes> RemoteAccessible::GetCachedTextAttributes() {
   1684  if (RequestDomainsIfInactive(CacheDomain::Text)) {
   1685    return nullptr;
   1686  }
   1687  MOZ_ASSERT(IsText() || IsHyperText());
   1688  if (mCachedFields) {
   1689    auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
   1690        CacheKey::TextAttributes);
   1691    VERIFY_CACHE(CacheDomain::Text);
   1692    return attrs;
   1693  }
   1694  return nullptr;
   1695 }
   1696 
   1697 std::pair<LayoutDeviceIntRect, nsIWidget*> RemoteAccessible::GetCaretRect() {
   1698  nsIWidget* widget = nullptr;
   1699  LocalAccessible* outerDoc = OuterDocOfRemoteBrowser();
   1700  if (outerDoc) {
   1701    widget = nsContentUtils::WidgetForContent(outerDoc->GetContent());
   1702  }
   1703 
   1704  return {mDoc->GetCachedCaretRect(), widget};
   1705 }
   1706 
   1707 already_AddRefed<AccAttributes> RemoteAccessible::DefaultTextAttributes() {
   1708  if (RequestDomainsIfInactive(CacheDomain::Text)) {
   1709    return nullptr;
   1710  }
   1711 
   1712  RefPtr<AccAttributes> result = new AccAttributes();
   1713  for (RemoteAccessible* parent = this; parent;
   1714       parent = parent->RemoteParent()) {
   1715    if (!parent->IsHyperText()) {
   1716      // We are only interested in hypertext nodes for defaults, not in text
   1717      // leafs or non hypertext nodes.
   1718      continue;
   1719    }
   1720 
   1721    if (RefPtr<const AccAttributes> parentAttrs =
   1722            parent->GetCachedTextAttributes()) {
   1723      // Update our text attributes with any parent entries we don't have.
   1724      parentAttrs->CopyTo(result, true);
   1725    }
   1726  }
   1727 
   1728  return result.forget();
   1729 }
   1730 
   1731 const AccAttributes* RemoteAccessible::GetCachedARIAAttributes() const {
   1732  ASSERT_DOMAINS_ACTIVE(CacheDomain::ARIA);
   1733  if (mCachedFields) {
   1734    auto attrs = mCachedFields->GetAttributeWeakPtr<AccAttributes>(
   1735        CacheKey::ARIAAttributes);
   1736    VERIFY_CACHE(CacheDomain::ARIA);
   1737    return attrs;
   1738  }
   1739  return nullptr;
   1740 }
   1741 
   1742 nsString RemoteAccessible::GetCachedHTMLNameAttribute() const {
   1743  ASSERT_DOMAINS_ACTIVE(CacheDomain::Relations);
   1744  if (mCachedFields) {
   1745    if (auto maybeName =
   1746            mCachedFields->GetAttribute<nsString>(CacheKey::DOMName)) {
   1747      return *maybeName;
   1748    }
   1749  }
   1750  return nsString();
   1751 }
   1752 
   1753 uint64_t RemoteAccessible::State() {
   1754  if (RequestDomainsIfInactive(
   1755          CacheDomain::State |   // State attributes
   1756          CacheDomain::Style |   // for Opacity (via ApplyImplicitState)
   1757          CacheDomain::Viewport  // necessary to build mOnScreenAccessibles
   1758          )) {
   1759    return 0;
   1760  }
   1761  uint64_t state = 0;
   1762  if (mCachedFields) {
   1763    if (auto rawState =
   1764            mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) {
   1765      VERIFY_CACHE(CacheDomain::State);
   1766      state = *rawState;
   1767    }
   1768 
   1769    ApplyImplicitState(state);
   1770 
   1771    auto* cbc = mDoc->GetBrowsingContext();
   1772    if (cbc && !cbc->IsActive()) {
   1773      // If our browsing context is _not_ active, we're in a background tab
   1774      // and inherently offscreen.
   1775      state |= states::OFFSCREEN;
   1776    } else {
   1777      // If we're in an active browsing context, there are a few scenarios we
   1778      // need to address:
   1779      // - We are an iframe document in the visual viewport
   1780      // - We are an iframe document out of the visual viewport
   1781      // - We are non-iframe content in the visual viewport
   1782      // - We are non-iframe content out of the visual viewport
   1783      // We assume top level tab docs are on screen if their BC is active, so
   1784      // we don't need additional handling for them here.
   1785      if (!mDoc->IsTopLevel()) {
   1786        // Here we handle iframes and iframe content.
   1787        // We use an iframe's outer doc's position in the embedding document's
   1788        // viewport to determine if the iframe has been scrolled offscreen.
   1789        Accessible* docParent = mDoc->Parent();
   1790        // In rare cases, we might not have an outer doc yet. Return if that's
   1791        // the case.
   1792        if (NS_WARN_IF(!docParent || !docParent->IsRemote())) {
   1793          return state;
   1794        }
   1795 
   1796        RemoteAccessible* outerDoc = docParent->AsRemote();
   1797        DocAccessibleParent* embeddingDocument = outerDoc->Document();
   1798        if (embeddingDocument &&
   1799            !embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) {
   1800          // Our embedding document's viewport cache doesn't contain the ID of
   1801          // our outer doc, so this iframe (and any of its content) is
   1802          // offscreen.
   1803          state |= states::OFFSCREEN;
   1804        } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
   1805          // Our embedding document's viewport cache contains the ID of our
   1806          // outer doc, but the iframe's viewport cache doesn't contain our ID.
   1807          // We are offscreen.
   1808          state |= states::OFFSCREEN;
   1809        }
   1810      } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
   1811        // We are top level tab content (but not a top level tab doc).
   1812        // If our tab doc's viewport cache doesn't contain our ID, we're
   1813        // offscreen.
   1814        state |= states::OFFSCREEN;
   1815      }
   1816    }
   1817  }
   1818 
   1819  return state;
   1820 }
   1821 
   1822 already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
   1823  RefPtr<AccAttributes> attributes = new AccAttributes();
   1824  if (RequestDomainsIfInactive(CacheDomain::ARIA |  // GetCachedARIAAttributes
   1825                               CacheDomain::NameAndDescription |  // Name
   1826                               CacheDomain::Text |                // Name
   1827                               CacheDomain::Value |               // Value
   1828                               CacheDomain::Actions |             // Value
   1829                               CacheDomain::Style |      // DisplayStyle
   1830                               CacheDomain::GroupInfo |  // GroupPosition
   1831                               CacheDomain::State |      // State
   1832                               CacheDomain::Viewport |   // State
   1833                               CacheDomain::Table |  // TableIsProbablyForLayout
   1834                               CacheDomain::DOMNodeIDAndClass |  // DOMNodeID
   1835                               CacheDomain::Relations)) {
   1836    return attributes.forget();
   1837  }
   1838 
   1839  nsAccessibilityService* accService = GetAccService();
   1840  if (!accService) {
   1841    // The service can be shut down before RemoteAccessibles. If it is shut
   1842    // down, we can't calculate some attributes. We're about to die anyway.
   1843    return attributes.forget();
   1844  }
   1845 
   1846  if (mCachedFields) {
   1847    // We use GetAttribute instead of GetAttributeRefPtr because we need
   1848    // nsAtom, not const nsAtom.
   1849    if (auto tag =
   1850            mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
   1851      attributes->SetAttribute(nsGkAtoms::tag, *tag);
   1852    }
   1853 
   1854    bool hierarchical = false;
   1855    uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
   1856    if (itemCount) {
   1857      attributes->SetAttribute(nsGkAtoms::child_item_count,
   1858                               static_cast<int32_t>(itemCount));
   1859    }
   1860 
   1861    if (hierarchical) {
   1862      attributes->SetAttribute(nsGkAtoms::tree, true);
   1863    }
   1864 
   1865    if (auto inputType =
   1866            mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
   1867      attributes->SetAttribute(nsGkAtoms::textInputType, *inputType);
   1868    }
   1869 
   1870    if (RefPtr<nsAtom> display = DisplayStyle()) {
   1871      attributes->SetAttribute(nsGkAtoms::display, display);
   1872    }
   1873 
   1874    if (TableCellAccessible* cell = AsTableCell()) {
   1875      TableAccessible* table = cell->Table();
   1876      uint32_t row = cell->RowIdx();
   1877      uint32_t col = cell->ColIdx();
   1878      int32_t cellIdx = table->CellIndexAt(row, col);
   1879      if (cellIdx != -1) {
   1880        attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx);
   1881      }
   1882    }
   1883 
   1884    if (bool layoutGuess = TableIsProbablyForLayout()) {
   1885      attributes->SetAttribute(nsGkAtoms::layout_guess, layoutGuess);
   1886    }
   1887 
   1888    accService->MarkupAttributes(this, attributes);
   1889 
   1890    const nsRoleMapEntry* roleMap = ARIARoleMap();
   1891    nsAutoString role;
   1892    mCachedFields->GetAttribute(CacheKey::ARIARole, role);
   1893    if (role.IsEmpty()) {
   1894      if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) {
   1895        // Single, known role.
   1896        attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom);
   1897      } else if (nsAtom* landmark = LandmarkRole()) {
   1898        // Landmark role from markup; e.g. HTML <main>.
   1899        attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
   1900      }
   1901    } else {
   1902      // Unknown role or multiple roles.
   1903      attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role));
   1904    }
   1905 
   1906    if (roleMap) {
   1907      nsAutoString live;
   1908      if (nsAccUtils::GetLiveAttrValue(roleMap->liveAttRule, live)) {
   1909        attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
   1910      }
   1911    }
   1912 
   1913    if (auto ariaAttrs = GetCachedARIAAttributes()) {
   1914      ariaAttrs->CopyTo(attributes);
   1915    }
   1916 
   1917    nsAccUtils::SetLiveContainerAttributes(attributes, this);
   1918 
   1919    nsString id;
   1920    DOMNodeID(id);
   1921    if (!id.IsEmpty()) {
   1922      attributes->SetAttribute(nsGkAtoms::id, std::move(id));
   1923    }
   1924 
   1925    nsString className;
   1926    DOMNodeClass(className);
   1927    if (!className.IsEmpty()) {
   1928      attributes->SetAttribute(nsGkAtoms::_class, std::move(className));
   1929    }
   1930 
   1931    if (IsImage()) {
   1932      nsString src;
   1933      mCachedFields->GetAttribute(CacheKey::SrcURL, src);
   1934      if (!src.IsEmpty()) {
   1935        attributes->SetAttribute(nsGkAtoms::src, std::move(src));
   1936      }
   1937    }
   1938 
   1939    if (IsTextField()) {
   1940      nsString placeholder;
   1941      mCachedFields->GetAttribute(CacheKey::HTMLPlaceholder, placeholder);
   1942      if (!placeholder.IsEmpty()) {
   1943        attributes->SetAttribute(nsGkAtoms::placeholder,
   1944                                 std::move(placeholder));
   1945        attributes->Remove(nsGkAtoms::aria_placeholder);
   1946      }
   1947    }
   1948 
   1949    nsString popupType;
   1950    mCachedFields->GetAttribute(CacheKey::PopupType, popupType);
   1951    if (!popupType.IsEmpty()) {
   1952      attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType));
   1953    }
   1954 
   1955    if (HasCustomActions()) {
   1956      attributes->SetAttribute(nsGkAtoms::hasActions, true);
   1957    }
   1958 
   1959    nsString detailsFrom;
   1960    if (mCachedFields->HasAttribute(nsGkAtoms::aria_details)) {
   1961      detailsFrom.AssignLiteral("aria-details");
   1962    } else if (mCachedFields->HasAttribute(nsGkAtoms::commandfor)) {
   1963      detailsFrom.AssignLiteral("command-for");
   1964    } else if (mCachedFields->HasAttribute(nsGkAtoms::popovertarget)) {
   1965      detailsFrom.AssignLiteral("popover-target");
   1966    } else if (mCachedFields->HasAttribute(nsGkAtoms::target)) {
   1967      detailsFrom.AssignLiteral("css-anchor");
   1968    }
   1969 
   1970    if (!detailsFrom.IsEmpty()) {
   1971      attributes->SetAttribute(nsGkAtoms::details_from, std::move(detailsFrom));
   1972    }
   1973  }
   1974 
   1975  nsAutoString name;
   1976  if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
   1977    attributes->SetAttribute(nsGkAtoms::explicit_name, true);
   1978  }
   1979 
   1980  // Expose the string value via the valuetext attribute. We test for the value
   1981  // interface because we don't want to expose traditional Value() information
   1982  // such as URLs on links and documents, or text in an input.
   1983  // XXX This is only needed for ATK, since other APIs have native ways to
   1984  // retrieve value text. We should probably move this into ATK specific code.
   1985  // For now, we do this because LocalAccessible does it.
   1986  if (HasNumericValue()) {
   1987    nsString valuetext;
   1988    Value(valuetext);
   1989    attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
   1990  }
   1991 
   1992  return attributes.forget();
   1993 }
   1994 
   1995 nsAtom* RemoteAccessible::TagName() const {
   1996  if (mCachedFields) {
   1997    if (auto tag =
   1998            mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
   1999      return *tag;
   2000    }
   2001  }
   2002 
   2003  return nullptr;
   2004 }
   2005 
   2006 already_AddRefed<nsAtom> RemoteAccessible::DisplayStyle() const {
   2007  if (RequestDomainsIfInactive(CacheDomain::Style)) {
   2008    return nullptr;
   2009  }
   2010  if (mCachedFields) {
   2011    if (auto display =
   2012            mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSDisplay)) {
   2013      RefPtr<nsAtom> result = *display;
   2014      return result.forget();
   2015    }
   2016  }
   2017  return nullptr;
   2018 }
   2019 
   2020 float RemoteAccessible::Opacity() const {
   2021  if (RequestDomainsIfInactive(CacheDomain::Style)) {
   2022    return 1.0f;
   2023  }
   2024 
   2025  if (mCachedFields) {
   2026    if (auto opacity = mCachedFields->GetAttribute<float>(CacheKey::Opacity)) {
   2027      return *opacity;
   2028    }
   2029  }
   2030 
   2031  return 1.0f;
   2032 }
   2033 
   2034 WritingMode RemoteAccessible::GetWritingMode() const {
   2035  if (RequestDomainsIfInactive(CacheDomain::Style)) {
   2036    return WritingMode();
   2037  }
   2038 
   2039  if (mCachedFields) {
   2040    if (auto wm =
   2041            mCachedFields->GetAttribute<WritingMode>(CacheKey::WritingMode)) {
   2042      return *wm;
   2043    }
   2044  }
   2045 
   2046  return WritingMode();
   2047 }
   2048 
   2049 void RemoteAccessible::LiveRegionAttributes(nsAString* aLive,
   2050                                            nsAString* aRelevant,
   2051                                            Maybe<bool>* aAtomic,
   2052                                            nsAString* aBusy) const {
   2053  if (RequestDomainsIfInactive(CacheDomain::ARIA)) {
   2054    return;
   2055  }
   2056  if (!mCachedFields) {
   2057    return;
   2058  }
   2059  auto attrs = GetCachedARIAAttributes();
   2060  if (!attrs) {
   2061    return;
   2062  }
   2063  if (aLive) {
   2064    attrs->GetAttribute(nsGkAtoms::aria_live, *aLive);
   2065  }
   2066  if (aRelevant) {
   2067    attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant);
   2068  }
   2069  if (aAtomic) {
   2070    if (auto value =
   2071            attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) {
   2072      *aAtomic = Some(*value == nsGkAtoms::_true);
   2073    }
   2074  }
   2075  if (aBusy) {
   2076    attrs->GetAttribute(nsGkAtoms::aria_busy, *aBusy);
   2077  }
   2078 }
   2079 
   2080 Maybe<bool> RemoteAccessible::ARIASelected() const {
   2081  if (RequestDomainsIfInactive(CacheDomain::State)) {
   2082    return Nothing();
   2083  }
   2084 
   2085  if (mCachedFields) {
   2086    return mCachedFields->GetAttribute<bool>(CacheKey::ARIASelected);
   2087  }
   2088  return Nothing();
   2089 }
   2090 
   2091 nsAtom* RemoteAccessible::GetPrimaryAction() const {
   2092  if (mCachedFields) {
   2093    ASSERT_DOMAINS_ACTIVE(CacheDomain::Actions);
   2094    if (auto action = mCachedFields->GetAttribute<RefPtr<nsAtom>>(
   2095            CacheKey::PrimaryAction)) {
   2096      return *action;
   2097    }
   2098  }
   2099 
   2100  return nullptr;
   2101 }
   2102 
   2103 uint8_t RemoteAccessible::ActionCount() const {
   2104  uint8_t actionCount = 0;
   2105  if (RequestDomainsIfInactive(CacheDomain::Actions)) {
   2106    return actionCount;
   2107  }
   2108  if (mCachedFields) {
   2109    if (HasPrimaryAction() || ActionAncestor()) {
   2110      actionCount++;
   2111    }
   2112 
   2113    if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
   2114      actionCount++;
   2115    }
   2116    VERIFY_CACHE(CacheDomain::Actions);
   2117  }
   2118 
   2119  return actionCount;
   2120 }
   2121 
   2122 void RemoteAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
   2123  if (RequestDomainsIfInactive(CacheDomain::Actions)) {
   2124    return;
   2125  }
   2126 
   2127  if (mCachedFields) {
   2128    aName.Truncate();
   2129    nsAtom* action = GetPrimaryAction();
   2130    bool hasActionAncestor = !action && ActionAncestor();
   2131 
   2132    switch (aIndex) {
   2133      case 0:
   2134        if (action) {
   2135          action->ToString(aName);
   2136        } else if (hasActionAncestor) {
   2137          aName.AssignLiteral("clickAncestor");
   2138        } else if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
   2139          aName.AssignLiteral("showlongdesc");
   2140        }
   2141        break;
   2142      case 1:
   2143        if ((action || hasActionAncestor) &&
   2144            mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
   2145          aName.AssignLiteral("showlongdesc");
   2146        }
   2147        break;
   2148      default:
   2149        break;
   2150    }
   2151  }
   2152  VERIFY_CACHE(CacheDomain::Actions);
   2153 }
   2154 
   2155 bool RemoteAccessible::DoAction(uint8_t aIndex) const {
   2156  if (RequestDomainsIfInactive(CacheDomain::Actions)) {
   2157    return false;
   2158  }
   2159 
   2160  if (ActionCount() < aIndex + 1) {
   2161    return false;
   2162  }
   2163 
   2164  (void)mDoc->SendDoActionAsync(mID, aIndex);
   2165  return true;
   2166 }
   2167 
   2168 KeyBinding RemoteAccessible::AccessKey() const {
   2169  if (RequestDomainsIfInactive(CacheDomain::Actions)) {
   2170    return {};
   2171  }
   2172 
   2173  if (mCachedFields) {
   2174    if (auto value =
   2175            mCachedFields->GetAttribute<uint64_t>(CacheKey::AccessKey)) {
   2176      return KeyBinding(*value);
   2177    }
   2178  }
   2179  return KeyBinding();
   2180 }
   2181 
   2182 void RemoteAccessible::SelectionRanges(nsTArray<TextRange>* aRanges) const {
   2183  Document()->SelectionRanges(aRanges);
   2184 }
   2185 
   2186 bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) {
   2187  MOZ_ASSERT(IsHyperText());
   2188  if (SelectionCount() <= aSelectionNum) {
   2189    return false;
   2190  }
   2191 
   2192  (void)mDoc->SendRemoveTextSelection(mID, aSelectionNum);
   2193 
   2194  return true;
   2195 }
   2196 
   2197 void RemoteAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
   2198                                         int32_t* aPosInSet) const {
   2199  if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) {
   2200    return;
   2201  }
   2202 
   2203  if (!mCachedFields) {
   2204    return;
   2205  }
   2206 
   2207  if (aLevel) {
   2208    if (auto level =
   2209            mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) {
   2210      *aLevel = *level;
   2211    }
   2212  }
   2213  if (aSetSize) {
   2214    if (auto setsize =
   2215            mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) {
   2216      *aSetSize = *setsize;
   2217    }
   2218  }
   2219  if (aPosInSet) {
   2220    if (auto posinset =
   2221            mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) {
   2222      *aPosInSet = *posinset;
   2223    }
   2224  }
   2225 }
   2226 
   2227 AccGroupInfo* RemoteAccessible::GetGroupInfo() const {
   2228  // Interpret a call to GetGroupInfo as a signal that the AT will want group
   2229  // info information. CacheKey::GroupInfo is not in CacheDomain::GroupInfo, so
   2230  // this isn't strictly necessary, but is likely helpful.
   2231  if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) {
   2232    return nullptr;
   2233  }
   2234 
   2235  if (!mCachedFields) {
   2236    return nullptr;
   2237  }
   2238 
   2239  if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>(
   2240          CacheKey::GroupInfo)) {
   2241    return groupInfo->get();
   2242  }
   2243 
   2244  return nullptr;
   2245 }
   2246 
   2247 AccGroupInfo* RemoteAccessible::GetOrCreateGroupInfo() {
   2248  if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) {
   2249    return nullptr;
   2250  }
   2251 
   2252  AccGroupInfo* groupInfo = GetGroupInfo();
   2253  if (groupInfo) {
   2254    return groupInfo;
   2255  }
   2256 
   2257  groupInfo = AccGroupInfo::CreateGroupInfo(this);
   2258  if (groupInfo) {
   2259    if (!mCachedFields) {
   2260      mCachedFields = new AccAttributes();
   2261    }
   2262 
   2263    mCachedFields->SetAttribute(CacheKey::GroupInfo, groupInfo);
   2264  }
   2265 
   2266  return groupInfo;
   2267 }
   2268 
   2269 void RemoteAccessible::InvalidateGroupInfo() {
   2270  if (mCachedFields) {
   2271    mCachedFields->Remove(CacheKey::GroupInfo);
   2272  }
   2273 }
   2274 
   2275 void RemoteAccessible::GetPositionAndSetSize(int32_t* aPosInSet,
   2276                                             int32_t* aSetSize) {
   2277  // Note: Required domains come from requirements of RelationByType.
   2278  if (RequestDomainsIfInactive(CacheDomain::Relations | CacheDomain::Value |
   2279                               CacheDomain::DOMNodeIDAndClass |
   2280                               CacheDomain::GroupInfo)) {
   2281    return;
   2282  }
   2283 
   2284  if (IsHTMLRadioButton()) {
   2285    *aSetSize = 0;
   2286    Relation rel = RelationByType(RelationType::MEMBER_OF);
   2287    while (Accessible* radio = rel.Next()) {
   2288      ++*aSetSize;
   2289      if (radio == this) {
   2290        *aPosInSet = *aSetSize;
   2291      }
   2292    }
   2293    return;
   2294  }
   2295 
   2296  Accessible::GetPositionAndSetSize(aPosInSet, aSetSize);
   2297 }
   2298 
   2299 bool RemoteAccessible::HasPrimaryAction() const {
   2300  if (RequestDomainsIfInactive(CacheDomain::Actions)) {
   2301    return false;
   2302  }
   2303  return mCachedFields && mCachedFields->HasAttribute(CacheKey::PrimaryAction);
   2304 }
   2305 
   2306 void RemoteAccessible::TakeFocus() const {
   2307  (void)mDoc->SendTakeFocus(mID);
   2308  auto* bp = static_cast<dom::BrowserParent*>(mDoc->Manager());
   2309  MOZ_ASSERT(bp);
   2310  if (nsFocusManager::GetFocusedElementStatic() == bp->GetOwnerElement()) {
   2311    // This remote document tree is already focused. We don't need to do
   2312    // anything else.
   2313    return;
   2314  }
   2315  // Otherwise, we need to focus the <browser> or <iframe> element embedding the
   2316  // remote document in the parent process. If `this` is in an OOP iframe, we
   2317  // first need to focus the embedder iframe (and any ancestor OOP iframes). If
   2318  // the parent process embedder element were already focused, that would happen
   2319  // automatically, but it isn't. We can't simply focus the parent process
   2320  // embedder element before calling mDoc->SendTakeFocus because that would
   2321  // cause the remote document to restore focus to the last focused element,
   2322  // which we don't want.
   2323  DocAccessibleParent* embeddedDoc = mDoc;
   2324  Accessible* embedder = mDoc->Parent();
   2325  while (embedder) {
   2326    MOZ_ASSERT(embedder->IsOuterDoc());
   2327    RemoteAccessible* embedderRemote = embedder->AsRemote();
   2328    if (!embedderRemote) {
   2329      // This is the element in the parent process which embeds the remote
   2330      // document.
   2331      embedder->TakeFocus();
   2332      break;
   2333    }
   2334    // This is a remote <iframe>.
   2335    if (embeddedDoc->IsTopLevelInContentProcess()) {
   2336      // We only need to focus OOP iframes because these are where we cross
   2337      // process boundaries.
   2338      (void)embedderRemote->mDoc->SendTakeFocus(embedderRemote->mID);
   2339    }
   2340    embeddedDoc = embedderRemote->mDoc;
   2341    embedder = embeddedDoc->Parent();
   2342  }
   2343 }
   2344 
   2345 void RemoteAccessible::ScrollTo(uint32_t aHow) const {
   2346  (void)mDoc->SendScrollTo(mID, aHow);
   2347 }
   2348 
   2349 ////////////////////////////////////////////////////////////////////////////////
   2350 // SelectAccessible
   2351 
   2352 void RemoteAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
   2353  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2354    return;
   2355  }
   2356  Pivot p = Pivot(this);
   2357  PivotStateRule rule(states::SELECTED);
   2358  for (Accessible* selected = p.First(rule); selected;
   2359       selected = p.Next(selected, rule)) {
   2360    aItems->AppendElement(selected);
   2361  }
   2362 }
   2363 
   2364 uint32_t RemoteAccessible::SelectedItemCount() {
   2365  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2366    return 0;
   2367  }
   2368  uint32_t count = 0;
   2369  Pivot p = Pivot(this);
   2370  PivotStateRule rule(states::SELECTED);
   2371  for (Accessible* selected = p.First(rule); selected;
   2372       selected = p.Next(selected, rule)) {
   2373    count++;
   2374  }
   2375 
   2376  return count;
   2377 }
   2378 
   2379 Accessible* RemoteAccessible::GetSelectedItem(uint32_t aIndex) {
   2380  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2381    return nullptr;
   2382  }
   2383  uint32_t index = 0;
   2384  Accessible* selected = nullptr;
   2385  Pivot p = Pivot(this);
   2386  PivotStateRule rule(states::SELECTED);
   2387  for (selected = p.First(rule); selected && index < aIndex;
   2388       selected = p.Next(selected, rule)) {
   2389    index++;
   2390  }
   2391 
   2392  return selected;
   2393 }
   2394 
   2395 bool RemoteAccessible::IsItemSelected(uint32_t aIndex) {
   2396  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2397    return false;
   2398  }
   2399  uint32_t index = 0;
   2400  Accessible* selectable = nullptr;
   2401  Pivot p = Pivot(this);
   2402  PivotStateRule rule(states::SELECTABLE);
   2403  for (selectable = p.First(rule); selectable && index < aIndex;
   2404       selectable = p.Next(selectable, rule)) {
   2405    index++;
   2406  }
   2407 
   2408  return selectable && selectable->State() & states::SELECTED;
   2409 }
   2410 
   2411 bool RemoteAccessible::AddItemToSelection(uint32_t aIndex) {
   2412  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2413    return false;
   2414  }
   2415  uint32_t index = 0;
   2416  Accessible* selectable = nullptr;
   2417  Pivot p = Pivot(this);
   2418  PivotStateRule rule(states::SELECTABLE);
   2419  for (selectable = p.First(rule); selectable && index < aIndex;
   2420       selectable = p.Next(selectable, rule)) {
   2421    index++;
   2422  }
   2423 
   2424  if (selectable) selectable->SetSelected(true);
   2425 
   2426  return static_cast<bool>(selectable);
   2427 }
   2428 
   2429 bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex) {
   2430  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2431    return false;
   2432  }
   2433  uint32_t index = 0;
   2434  Accessible* selectable = nullptr;
   2435  Pivot p = Pivot(this);
   2436  PivotStateRule rule(states::SELECTABLE);
   2437  for (selectable = p.First(rule); selectable && index < aIndex;
   2438       selectable = p.Next(selectable, rule)) {
   2439    index++;
   2440  }
   2441 
   2442  if (selectable) selectable->SetSelected(false);
   2443 
   2444  return static_cast<bool>(selectable);
   2445 }
   2446 
   2447 bool RemoteAccessible::SelectAll() {
   2448  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2449    return false;
   2450  }
   2451  if ((State() & states::MULTISELECTABLE) == 0) {
   2452    return false;
   2453  }
   2454 
   2455  bool success = false;
   2456  Accessible* selectable = nullptr;
   2457  Pivot p = Pivot(this);
   2458  PivotStateRule rule(states::SELECTABLE);
   2459  for (selectable = p.First(rule); selectable;
   2460       selectable = p.Next(selectable, rule)) {
   2461    success = true;
   2462    selectable->SetSelected(true);
   2463  }
   2464  return success;
   2465 }
   2466 
   2467 bool RemoteAccessible::UnselectAll() {
   2468  if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
   2469    return false;
   2470  }
   2471  if ((State() & states::MULTISELECTABLE) == 0) {
   2472    return false;
   2473  }
   2474 
   2475  bool success = false;
   2476  Accessible* selectable = nullptr;
   2477  Pivot p = Pivot(this);
   2478  PivotStateRule rule(states::SELECTABLE);
   2479  for (selectable = p.First(rule); selectable;
   2480       selectable = p.Next(selectable, rule)) {
   2481    success = true;
   2482    selectable->SetSelected(false);
   2483  }
   2484  return success;
   2485 }
   2486 
   2487 void RemoteAccessible::TakeSelection() { (void)mDoc->SendTakeSelection(mID); }
   2488 
   2489 void RemoteAccessible::SetSelected(bool aSelect) {
   2490  (void)mDoc->SendSetSelected(mID, aSelect);
   2491 }
   2492 
   2493 TableAccessible* RemoteAccessible::AsTable() {
   2494  if (IsTable()) {
   2495    return CachedTableAccessible::GetFrom(this);
   2496  }
   2497  return nullptr;
   2498 }
   2499 
   2500 TableCellAccessible* RemoteAccessible::AsTableCell() {
   2501  if (IsTableCell()) {
   2502    return CachedTableCellAccessible::GetFrom(this);
   2503  }
   2504  return nullptr;
   2505 }
   2506 
   2507 bool RemoteAccessible::TableIsProbablyForLayout() {
   2508  if (RequestDomainsIfInactive(CacheDomain::Table)) {
   2509    return false;
   2510  }
   2511  if (mCachedFields) {
   2512    if (auto layoutGuess =
   2513            mCachedFields->GetAttribute<bool>(CacheKey::TableLayoutGuess)) {
   2514      return *layoutGuess;
   2515    }
   2516  }
   2517  return false;
   2518 }
   2519 
   2520 nsTArray<int32_t>& RemoteAccessible::GetCachedHyperTextOffsets() {
   2521  if (mCachedFields) {
   2522    if (auto offsets = mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
   2523            CacheKey::HyperTextOffsets)) {
   2524      return *offsets;
   2525    }
   2526  }
   2527  nsTArray<int32_t> newOffsets;
   2528  if (!mCachedFields) {
   2529    mCachedFields = new AccAttributes();
   2530  }
   2531  mCachedFields->SetAttribute(CacheKey::HyperTextOffsets,
   2532                              std::move(newOffsets));
   2533  return *mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
   2534      CacheKey::HyperTextOffsets);
   2535 }
   2536 
   2537 Maybe<int32_t> RemoteAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
   2538  if (RequestDomainsIfInactive(CacheDomain::ARIA)) {
   2539    return Nothing();
   2540  }
   2541  if (auto attrs = GetCachedARIAAttributes()) {
   2542    if (auto val = attrs->GetAttribute<int32_t>(aAttrName)) {
   2543      return val;
   2544    }
   2545  }
   2546  return Nothing();
   2547 }
   2548 
   2549 bool RemoteAccessible::GetStringARIAAttr(nsAtom* aAttrName,
   2550                                         nsAString& aAttrValue) const {
   2551  if (RequestDomainsIfInactive(CacheDomain::ARIA)) {
   2552    return false;
   2553  }
   2554 
   2555  if (aAttrName == nsGkAtoms::role) {
   2556    if (mCachedFields->GetAttribute(CacheKey::ARIARole, aAttrValue)) {
   2557      // Unknown, or multiple roles.
   2558      return true;
   2559    }
   2560 
   2561    if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
   2562      if (roleMap->roleAtom != nsGkAtoms::_empty) {
   2563        // Non-empty rolemap, stringify it and return true.
   2564        roleMap->roleAtom->ToString(aAttrValue);
   2565        return true;
   2566      }
   2567    }
   2568  }
   2569 
   2570  if (auto attrs = GetCachedARIAAttributes()) {
   2571    return attrs->GetAttribute(aAttrName, aAttrValue);
   2572  }
   2573 
   2574  return false;
   2575 }
   2576 
   2577 bool RemoteAccessible::ARIAAttrValueIs(nsAtom* aAttrName,
   2578                                       nsAtom* aAttrValue) const {
   2579  if (RequestDomainsIfInactive(CacheDomain::ARIA)) {
   2580    return false;
   2581  }
   2582 
   2583  if (aAttrName == nsGkAtoms::role) {
   2584    nsAutoString roleStr;
   2585    if (mCachedFields->GetAttribute(CacheKey::ARIARole, roleStr)) {
   2586      return aAttrValue->Equals(roleStr);
   2587    }
   2588 
   2589    if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
   2590      return roleMap->roleAtom == aAttrValue;
   2591    }
   2592  }
   2593 
   2594  if (auto attrs = GetCachedARIAAttributes()) {
   2595    if (auto val = attrs->GetAttribute<RefPtr<nsAtom>>(aAttrName)) {
   2596      return *val == aAttrValue;
   2597    }
   2598  }
   2599 
   2600  return false;
   2601 }
   2602 
   2603 bool RemoteAccessible::HasARIAAttr(nsAtom* aAttrName) const {
   2604  if (RequestDomainsIfInactive(CacheDomain::ARIA)) {
   2605    return false;
   2606  }
   2607 
   2608  if (aAttrName == nsGkAtoms::role) {
   2609    if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
   2610      if (roleMap->roleAtom != nsGkAtoms::_empty) {
   2611        return true;
   2612      }
   2613    }
   2614 
   2615    return mCachedFields->HasAttribute(CacheKey::ARIARole);
   2616  }
   2617 
   2618  if (auto attrs = GetCachedARIAAttributes()) {
   2619    return attrs->HasAttribute(aAttrName);
   2620  }
   2621 
   2622  return false;
   2623 }
   2624 
   2625 void RemoteAccessible::Language(nsAString& aLocale) {
   2626  if (RequestDomainsIfInactive(CacheDomain::Text)) {
   2627    return;
   2628  }
   2629 
   2630  if (IsHyperText() || IsText()) {
   2631    for (RemoteAccessible* parent = this; parent;
   2632         parent = parent->RemoteParent()) {
   2633      // Climb up the tree to find where the nearest language attribute is.
   2634      if (RefPtr<const AccAttributes> attrs =
   2635              parent->GetCachedTextAttributes()) {
   2636        if (attrs->GetAttribute(nsGkAtoms::language, aLocale)) {
   2637          return;
   2638        }
   2639      }
   2640    }
   2641  } else if (mCachedFields) {
   2642    mCachedFields->GetAttribute(CacheKey::Language, aLocale);
   2643  }
   2644 }
   2645 
   2646 void RemoteAccessible::ReplaceText(const nsAString& aText) {
   2647  (void)mDoc->SendReplaceText(mID, aText);
   2648 }
   2649 
   2650 void RemoteAccessible::InsertText(const nsAString& aText, int32_t aPosition) {
   2651  (void)mDoc->SendInsertText(mID, aText, aPosition);
   2652 }
   2653 
   2654 void RemoteAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) {
   2655  (void)mDoc->SendCopyText(mID, aStartPos, aEndPos);
   2656 }
   2657 
   2658 void RemoteAccessible::CutText(int32_t aStartPos, int32_t aEndPos) {
   2659  (void)mDoc->SendCutText(mID, aStartPos, aEndPos);
   2660 }
   2661 
   2662 void RemoteAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) {
   2663  (void)mDoc->SendDeleteText(mID, aStartPos, aEndPos);
   2664 }
   2665 
   2666 void RemoteAccessible::PasteText(int32_t aPosition) {
   2667  (void)mDoc->SendPasteText(mID, aPosition);
   2668 }
   2669 
   2670 bool RemoteAccessible::HasCustomActions() const {
   2671  if (RequestDomainsIfInactive(CacheDomain::ARIA) || !mCachedFields) {
   2672    return false;
   2673  }
   2674  auto hasActions = mCachedFields->GetAttribute<bool>(CacheKey::HasActions);
   2675  return hasActions && *hasActions;
   2676 }
   2677 
   2678 size_t RemoteAccessible::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
   2679  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   2680 }
   2681 
   2682 size_t RemoteAccessible::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
   2683  size_t size = 0;
   2684 
   2685  // Count attributes.
   2686  if (mCachedFields) {
   2687    size += mCachedFields->SizeOfIncludingThis(aMallocSizeOf);
   2688  }
   2689 
   2690  // We don't recurse into mChildren because they're already counted in their
   2691  // document's mAccessibles.
   2692  size += mChildren.ShallowSizeOfExcludingThis(aMallocSizeOf);
   2693 
   2694  return size;
   2695 }
   2696 
   2697 }  // namespace a11y
   2698 }  // namespace mozilla