tor-browser

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

LocalAccessible.cpp (170932B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "AccEvent.h"
      7 #include "LocalAccessible-inl.h"
      8 
      9 #include "EmbeddedObjCollector.h"
     10 #include "AccGroupInfo.h"
     11 #include "AccIterator.h"
     12 #include "CachedTableAccessible.h"
     13 #include "CssAltContent.h"
     14 #include "DocAccessible-inl.h"
     15 #include "mozilla/a11y/AccAttributes.h"
     16 #include "mozilla/a11y/DocAccessibleChild.h"
     17 #include "mozilla/a11y/Platform.h"
     18 #include "mozilla/FocusModel.h"
     19 #include "nsAccUtils.h"
     20 #include "nsMenuPopupFrame.h"
     21 #include "nsAccessibilityService.h"
     22 #include "ApplicationAccessible.h"
     23 #include "nsGenericHTMLElement.h"
     24 #include "NotificationController.h"
     25 #include "nsEventShell.h"
     26 #include "nsTextEquivUtils.h"
     27 #include "EventTree.h"
     28 #include "OuterDocAccessible.h"
     29 #include "Pivot.h"
     30 #include "Relation.h"
     31 #include "mozilla/a11y/Role.h"
     32 #include "RootAccessible.h"
     33 #include "States.h"
     34 #include "TextLeafAccessible.h"
     35 #include "TextLeafRange.h"
     36 #include "TextRange.h"
     37 #include "HTMLElementAccessibles.h"
     38 #include "HTMLSelectAccessible.h"
     39 #include "HTMLTableAccessible.h"
     40 #include "ImageAccessible.h"
     41 
     42 #include "nsComputedDOMStyle.h"
     43 #include "nsGkAtoms.h"
     44 #include "nsIDOMXULButtonElement.h"
     45 #include "nsIDOMXULSelectCntrlEl.h"
     46 #include "nsIDOMXULSelectCntrlItemEl.h"
     47 #include "nsIMutationObserver.h"
     48 #include "nsINodeList.h"
     49 
     50 #include "mozilla/dom/Document.h"
     51 #include "mozilla/dom/HTMLFormElement.h"
     52 #include "mozilla/dom/HTMLAnchorElement.h"
     53 #include "mozilla/gfx/Matrix.h"
     54 #include "nsIContent.h"
     55 #include "nsIFormControl.h"
     56 
     57 #include "nsDisplayList.h"
     58 #include "nsLayoutUtils.h"
     59 #include "nsPresContext.h"
     60 #include "nsIFrame.h"
     61 #include "nsTextFrame.h"
     62 #include "nsIDocShellTreeItem.h"
     63 #include "nsStyleStructInlines.h"
     64 #include "nsFocusManager.h"
     65 
     66 #include "nsString.h"
     67 #include "nsAtom.h"
     68 #include "nsContainerFrame.h"
     69 
     70 #include "mozilla/Assertions.h"
     71 #include "mozilla/BasicEvents.h"
     72 #include "mozilla/ErrorResult.h"
     73 #include "mozilla/FloatingPoint.h"
     74 #include "mozilla/PerfStats.h"
     75 #include "mozilla/PresShell.h"
     76 #include "mozilla/ProfilerMarkers.h"
     77 #include "mozilla/ScrollContainerFrame.h"
     78 #include "mozilla/StaticPrefs_accessibility.h"
     79 #include "mozilla/StaticPrefs_dom.h"
     80 #include "mozilla/StaticPrefs_ui.h"
     81 #include "mozilla/dom/Element.h"
     82 #include "mozilla/dom/HTMLLabelElement.h"
     83 #include "mozilla/dom/KeyboardEventBinding.h"
     84 #include "mozilla/dom/TreeWalker.h"
     85 #include "mozilla/dom/UserActivation.h"
     86 
     87 using namespace mozilla;
     88 using namespace mozilla::a11y;
     89 
     90 ////////////////////////////////////////////////////////////////////////////////
     91 // LocalAccessible: nsISupports and cycle collection
     92 
     93 NS_IMPL_CYCLE_COLLECTION_CLASS(LocalAccessible)
     94 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalAccessible)
     95  tmp->Shutdown();
     96 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     97 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalAccessible)
     98  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)
     99 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    100 
    101 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalAccessible)
    102  NS_INTERFACE_MAP_ENTRY_CONCRETE(LocalAccessible)
    103  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LocalAccessible)
    104 NS_INTERFACE_MAP_END
    105 
    106 NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalAccessible)
    107 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(LocalAccessible, LastRelease())
    108 
    109 LocalAccessible::LocalAccessible(nsIContent* aContent, DocAccessible* aDoc)
    110    : mContent(aContent),
    111      mDoc(aDoc),
    112      mParent(nullptr),
    113      mIndexInParent(-1),
    114      mFirstLineStart(-1),
    115      mStateFlags(0),
    116      mContextFlags(0),
    117      mReorderEventTarget(false),
    118      mShowEventTarget(false),
    119      mHideEventTarget(false),
    120      mIndexOfEmbeddedChild(-1),
    121      mGroupInfo(nullptr) {}
    122 
    123 LocalAccessible::~LocalAccessible() {
    124  NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
    125 }
    126 
    127 ENameValueFlag LocalAccessible::DirectName(nsString& aName) const {
    128  if (!HasOwnContent()) return eNameOK;
    129 
    130  ENameValueFlag nameFlag = ARIAName(aName);
    131  if (!aName.IsEmpty()) return nameFlag;
    132 
    133  nameFlag = NativeName(aName);
    134  aName.CompressWhitespace();
    135 
    136  return nameFlag;
    137 }
    138 
    139 ENameValueFlag LocalAccessible::Name(nsString& aName) const {
    140  aName.Truncate();
    141 
    142  ENameValueFlag nameFlag = DirectName(aName);
    143  if (!aName.IsEmpty()) return nameFlag;
    144 
    145  nsTextEquivUtils::GetNameFromSubtree(this, aName);
    146  if (!aName.IsEmpty()) return eNameFromSubtree;
    147 
    148  // In the end get the name from tooltip.
    149  if (Tooltip(aName)) {
    150    return eNameFromTooltip;
    151  }
    152 
    153  if (auto cssAlt = CssAltContent(mContent)) {
    154    cssAlt.AppendToString(aName);
    155    return eNameOK;
    156  }
    157 
    158  aName.SetIsVoid(true);
    159 
    160  return eNameOK;
    161 }
    162 
    163 EDescriptionValueFlag LocalAccessible::Description(
    164    nsString& aDescription) const {
    165  // There are 4 conditions that make an accessible have no accDescription:
    166  // 1. it's a text node; or
    167  // 2. It has no ARIA describedby or description property
    168  // 3. it doesn't have an accName; or
    169  // 4. its title attribute already equals to its accName nsAutoString name;
    170 
    171  EDescriptionValueFlag descFlag = eDescriptionOK;
    172  aDescription.Truncate();
    173 
    174  if (!HasOwnContent() || mContent->IsText()) {
    175    return descFlag;
    176  }
    177 
    178  if (ARIADescription(aDescription)) {
    179    descFlag = eDescriptionFromARIA;
    180  }
    181 
    182  if (aDescription.IsEmpty()) {
    183    NativeDescription(aDescription);
    184    aDescription.CompressWhitespace();
    185  }
    186 
    187  if (aDescription.IsEmpty()) {
    188    Tooltip(aDescription);
    189  }
    190 
    191  if (!aDescription.IsEmpty()) {
    192    nsAutoString name;
    193    Name(name);
    194    // Don't expose a description if it is the same as the name.
    195    if (aDescription.Equals(name)) aDescription.Truncate();
    196  }
    197 
    198  return descFlag;
    199 }
    200 
    201 KeyBinding LocalAccessible::AccessKey() const {
    202  if (!HasOwnContent()) return KeyBinding();
    203 
    204  uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
    205  if (!key && mContent->IsElement()) {
    206    LocalAccessible* label = nullptr;
    207 
    208    // Copy access key from label node.
    209    if (mContent->IsHTMLElement()) {
    210      // Unless it is labeled via an ancestor <label>, in which case that would
    211      // be redundant.
    212      HTMLLabelIterator iter(Document(), this,
    213                             HTMLLabelIterator::eSkipAncestorLabel);
    214      label = iter.Next();
    215    }
    216    if (!label) {
    217      XULLabelIterator iter(Document(), mContent);
    218      label = iter.Next();
    219    }
    220 
    221    if (label) key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
    222  }
    223 
    224  if (!key) return KeyBinding();
    225 
    226  // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
    227  switch (StaticPrefs::ui_key_generalAccessKey()) {
    228    case -1:
    229      break;
    230    case dom::KeyboardEvent_Binding::DOM_VK_SHIFT:
    231      return KeyBinding(key, KeyBinding::kShift);
    232    case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
    233      return KeyBinding(key, KeyBinding::kControl);
    234    case dom::KeyboardEvent_Binding::DOM_VK_ALT:
    235      return KeyBinding(key, KeyBinding::kAlt);
    236    case dom::KeyboardEvent_Binding::DOM_VK_META:
    237      return KeyBinding(key, KeyBinding::kMeta);
    238    default:
    239      return KeyBinding();
    240  }
    241 
    242  // Determine the access modifier used in this context.
    243  dom::Document* document = mContent->GetComposedDoc();
    244  if (!document) return KeyBinding();
    245 
    246  nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
    247  if (!treeItem) return KeyBinding();
    248 
    249  nsresult rv = NS_ERROR_FAILURE;
    250  int32_t modifierMask = 0;
    251  switch (treeItem->ItemType()) {
    252    case nsIDocShellTreeItem::typeChrome:
    253      modifierMask = StaticPrefs::ui_key_chromeAccess();
    254      rv = NS_OK;
    255      break;
    256    case nsIDocShellTreeItem::typeContent:
    257      modifierMask = StaticPrefs::ui_key_contentAccess();
    258      rv = NS_OK;
    259      break;
    260  }
    261 
    262  return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
    263 }
    264 
    265 KeyBinding LocalAccessible::KeyboardShortcut() const { return KeyBinding(); }
    266 
    267 uint64_t LocalAccessible::VisibilityState() const {
    268  if (IPCAccessibilityActive()) {
    269    // Visibility states must be calculated by RemoteAccessible, so there's no
    270    // point calculating them here.
    271    return 0;
    272  }
    273  nsIFrame* frame = GetFrame();
    274  if (!frame) {
    275    // Element having display:contents is considered visible semantically,
    276    // despite it doesn't have a visually visible box.
    277    if (nsCoreUtils::IsDisplayContents(mContent)) {
    278      return states::OFFSCREEN;
    279    }
    280    return states::INVISIBLE;
    281  }
    282 
    283  if (!frame->StyleVisibility()->IsVisible()) return states::INVISIBLE;
    284 
    285  // It's invisible if the presshell is hidden by a visibility:hidden element in
    286  // an ancestor document.
    287  if (frame->PresShell()->IsUnderHiddenEmbedderElement()) {
    288    return states::INVISIBLE;
    289  }
    290 
    291  // Offscreen state if the document's visibility state is not visible.
    292  if (Document()->IsHidden()) return states::OFFSCREEN;
    293 
    294  // Walk the parent frame chain to see if the frame is in background tab or
    295  // scrolled out.
    296  nsIFrame* curFrame = frame;
    297  do {
    298    if (nsMenuPopupFrame* popup = do_QueryFrame(curFrame)) {
    299      return popup->IsOpen() ? 0 : states::INVISIBLE;
    300    }
    301 
    302    if (curFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
    303      // Offscreen state for background tab content.
    304      return states::OFFSCREEN;
    305    }
    306 
    307    nsIFrame* parentFrame = curFrame->GetParent();
    308    // If contained by scrollable frame then check that at least 12 pixels
    309    // around the object is visible, otherwise the object is offscreen.
    310    const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
    311    if (ScrollContainerFrame* scrollContainerFrame =
    312            do_QueryFrame(parentFrame)) {
    313      nsRect scrollPortRect = scrollContainerFrame->GetScrollPortRect();
    314      nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
    315          frame, frame->GetRectRelativeToSelf(), parentFrame);
    316      if (!scrollPortRect.Contains(frameRect)) {
    317        scrollPortRect.Deflate(kMinPixels, kMinPixels);
    318        if (!scrollPortRect.Intersects(frameRect)) return states::OFFSCREEN;
    319      }
    320    }
    321 
    322    if (!parentFrame) {
    323      parentFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(curFrame);
    324      // Even if we couldn't find the parent frame, it might mean we are in an
    325      // out-of-process iframe, try to see if |frame| is scrolled out in an
    326      // scrollable frame in a cross-process ancestor document.
    327      if (!parentFrame &&
    328          nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
    329              frame, kMinPixels)) {
    330        return states::OFFSCREEN;
    331      }
    332    }
    333 
    334    curFrame = parentFrame;
    335  } while (curFrame);
    336 
    337  // Zero area rects can occur in the first frame of a multi-frame text flow,
    338  // in which case the rendered text is not empty and the frame should not be
    339  // marked invisible.
    340  // XXX Can we just remove this check? Why do we need to mark empty
    341  // text invisible?
    342  if (frame->IsTextFrame() && !frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
    343      frame->GetRect().IsEmpty()) {
    344    nsIFrame::RenderedText text = frame->GetRenderedText(
    345        0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
    346        nsIFrame::TrailingWhitespace::DontTrim);
    347    if (text.mString.IsEmpty()) {
    348      return states::INVISIBLE;
    349    }
    350  }
    351 
    352  return 0;
    353 }
    354 
    355 uint64_t LocalAccessible::NativeState() const {
    356  uint64_t state = 0;
    357 
    358  if (!IsInDocument()) state |= states::STALE;
    359 
    360  if (HasOwnContent() && mContent->IsElement()) {
    361    dom::ElementState elementState = mContent->AsElement()->State();
    362 
    363    if (elementState.HasState(dom::ElementState::INVALID)) {
    364      state |= states::INVALID;
    365    }
    366 
    367    if (elementState.HasState(dom::ElementState::REQUIRED)) {
    368      state |= states::REQUIRED;
    369    }
    370 
    371    state |= NativeInteractiveState();
    372  }
    373 
    374  // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
    375  state |= VisibilityState();
    376 
    377  nsIFrame* frame = GetFrame();
    378  if (frame && frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
    379    state |= states::FLOATING;
    380  }
    381 
    382  // Check if a XUL element has the popup attribute (an attached popup menu).
    383  if (HasOwnContent() && mContent->IsXULElement() &&
    384      mContent->AsElement()->HasAttr(nsGkAtoms::popup)) {
    385    state |= states::HASPOPUP;
    386  }
    387 
    388  // Bypass the link states specialization for non links.
    389  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
    390  if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
    391      roleMapEntry->role == roles::LINK) {
    392    state |= NativeLinkState();
    393  }
    394 
    395  return state;
    396 }
    397 
    398 uint64_t LocalAccessible::NativeInteractiveState() const {
    399  if (!mContent->IsElement()) return 0;
    400 
    401  if (NativelyUnavailable()) return states::UNAVAILABLE;
    402 
    403  nsIFrame* frame = GetFrame();
    404  auto flags = IsFocusableFlags(0);
    405  // If we're caching this remote document in the parent process, we
    406  // need to cache focusability irrespective of visibility. Otherwise,
    407  // if this document is invisible when it first loads, we'll cache that
    408  // all descendants are unfocusable and this won't get updated when the
    409  // document becomes visible. Even if we did get notified when the
    410  // document becomes visible, it would be wasteful to walk the entire
    411  // tree to figure out what is now focusable and push cache updates.
    412  // Although ignoring visibility means IsFocusable will return true for
    413  // visibility: hidden, etc., this isn't a problem because we don't include
    414  // those hidden elements in the a11y tree anyway.
    415  if (mDoc->IPCDoc()) {
    416    flags |= IsFocusableFlags::IgnoreVisibility;
    417  }
    418  if (frame && frame->IsFocusable(flags)) {
    419    return states::FOCUSABLE;
    420  }
    421  return 0;
    422 }
    423 
    424 uint64_t LocalAccessible::NativeLinkState() const { return 0; }
    425 
    426 bool LocalAccessible::NativelyUnavailable() const {
    427  if (mContent->IsHTMLElement()) return mContent->AsElement()->IsDisabled();
    428 
    429  return mContent->IsElement() &&
    430         mContent->AsElement()->GetBoolAttr(nsGkAtoms::disabled);
    431 }
    432 
    433 Accessible* LocalAccessible::ChildAtPoint(int32_t aX, int32_t aY,
    434                                          EWhichChildAtPoint aWhichChild) {
    435  Accessible* child = LocalChildAtPoint(aX, aY, aWhichChild);
    436  if (aWhichChild != EWhichChildAtPoint::DirectChild && child &&
    437      child->IsOuterDoc()) {
    438    child = child->ChildAtPoint(aX, aY, aWhichChild);
    439  }
    440 
    441  return child;
    442 }
    443 
    444 LocalAccessible* LocalAccessible::LocalChildAtPoint(
    445    int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) {
    446  // If we can't find the point in a child, we will return the fallback answer:
    447  // we return |this| if the point is within it, otherwise nullptr.
    448  LocalAccessible* fallbackAnswer = nullptr;
    449  LayoutDeviceIntRect rect = Bounds();
    450  if (rect.Contains(aX, aY)) fallbackAnswer = this;
    451 
    452  if (nsAccUtils::MustPrune(this)) {  // Do not dig any further
    453    return fallbackAnswer;
    454  }
    455 
    456  // Search an accessible at the given point starting from accessible document
    457  // because containing block (see CSS2) for out of flow element (for example,
    458  // absolutely positioned element) may be different from its DOM parent and
    459  // therefore accessible for containing block may be different from accessible
    460  // for DOM parent but GetFrameForPoint() should be called for containing block
    461  // to get an out of flow element.
    462  DocAccessible* accDocument = Document();
    463  NS_ENSURE_TRUE(accDocument, nullptr);
    464 
    465  nsIFrame* rootFrame = accDocument->GetFrame();
    466  NS_ENSURE_TRUE(rootFrame, nullptr);
    467 
    468  nsIFrame* startFrame = rootFrame;
    469 
    470  // Check whether the point is at popup content.
    471  nsIWidget* rootWidget = rootFrame->GetNearestWidget();
    472  NS_ENSURE_TRUE(rootWidget, nullptr);
    473 
    474  LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
    475 
    476  auto point = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y());
    477 
    478  nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
    479      accDocument->PresContext()->GetRootPresContext(), rootWidget, point);
    480  if (popupFrame) {
    481    // If 'this' accessible is not inside the popup then ignore the popup when
    482    // searching an accessible at point.
    483    DocAccessible* popupDoc =
    484        GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
    485    LocalAccessible* popupAcc =
    486        popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
    487    LocalAccessible* popupChild = this;
    488    while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) {
    489      popupChild = popupChild->LocalParent();
    490    }
    491 
    492    if (popupChild == popupAcc) startFrame = popupFrame;
    493  }
    494 
    495  nsPresContext* presContext = startFrame->PresContext();
    496  nsRect screenRect = startFrame->GetScreenRectInAppUnits();
    497  nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(),
    498                 presContext->DevPixelsToAppUnits(aY) - screenRect.Y());
    499 
    500  nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(
    501      RelativeTo{startFrame, ViewportType::Visual}, offset);
    502 
    503  nsIContent* content = nullptr;
    504  if (!foundFrame || !(content = foundFrame->GetContent())) {
    505    return fallbackAnswer;
    506  }
    507 
    508  // Get accessible for the node with the point or the first accessible in
    509  // the DOM parent chain.
    510  DocAccessible* contentDocAcc =
    511      GetAccService()->GetDocAccessible(content->OwnerDoc());
    512 
    513  // contentDocAcc in some circumstances can be nullptr. See bug 729861
    514  NS_ASSERTION(contentDocAcc, "could not get the document accessible");
    515  if (!contentDocAcc) return fallbackAnswer;
    516 
    517  LocalAccessible* accessible =
    518      contentDocAcc->GetAccessibleOrContainer(content);
    519  if (!accessible) return fallbackAnswer;
    520 
    521  // Hurray! We have an accessible for the frame that layout gave us.
    522  // Since DOM node of obtained accessible may be out of flow then we should
    523  // ensure obtained accessible is a child of this accessible.
    524  LocalAccessible* child = accessible;
    525  while (child != this) {
    526    LocalAccessible* parent = child->LocalParent();
    527    if (!parent) {
    528      // Reached the top of the hierarchy. These bounds were inside an
    529      // accessible that is not a descendant of this one.
    530      return fallbackAnswer;
    531    }
    532 
    533    // If we landed on a legitimate child of |this|, and we want the direct
    534    // child, return it here.
    535    if (parent == this && aWhichChild == EWhichChildAtPoint::DirectChild) {
    536      return child;
    537    }
    538 
    539    child = parent;
    540  }
    541 
    542  // Manually walk through accessible children and see if the are within this
    543  // point. Skip offscreen or invisible accessibles. This takes care of cases
    544  // where layout won't walk into things for us, such as image map areas and
    545  // sub documents (XXX: subdocuments should be handled by methods of
    546  // OuterDocAccessibles).
    547  uint32_t childCount = accessible->ChildCount();
    548  if (childCount == 1 && accessible->IsOuterDoc() &&
    549      accessible->FirstChild()->IsRemote()) {
    550    // No local children.
    551    return accessible;
    552  }
    553  for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
    554    LocalAccessible* child = accessible->LocalChildAt(childIdx);
    555 
    556    LayoutDeviceIntRect childRect = child->Bounds();
    557    if (childRect.Contains(aX, aY) &&
    558        (child->State() & states::INVISIBLE) == 0) {
    559      if (aWhichChild == EWhichChildAtPoint::DeepestChild) {
    560        return child->LocalChildAtPoint(aX, aY,
    561                                        EWhichChildAtPoint::DeepestChild);
    562      }
    563 
    564      return child;
    565    }
    566  }
    567 
    568  return accessible;
    569 }
    570 
    571 nsIFrame* LocalAccessible::FindNearestAccessibleAncestorFrame() {
    572  nsIFrame* frame = GetFrame();
    573  if (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
    574      nsLayoutUtils::IsReallyFixedPos(frame)) {
    575    return mDoc->PresShellPtr()->GetRootFrame();
    576  }
    577 
    578  if (IsDoc()) {
    579    // We bound documents by their own frame, which is their PresShell's root
    580    // frame. We cache the document offset elsewhere in BundleFieldsForCache
    581    // using the nsGkAtoms::crossorigin attribute.
    582    MOZ_ASSERT(frame, "DocAccessibles should always have a frame");
    583    return frame;
    584  }
    585 
    586  // Iterate through accessible's ancestors to find one with a frame.
    587  LocalAccessible* ancestor = mParent;
    588  while (ancestor) {
    589    if (nsIFrame* boundingFrame = ancestor->GetFrame()) {
    590      return boundingFrame;
    591    }
    592    ancestor = ancestor->LocalParent();
    593  }
    594 
    595  MOZ_ASSERT_UNREACHABLE("No ancestor with frame?");
    596  return nsLayoutUtils::GetContainingBlockForClientRect(frame);
    597 }
    598 
    599 nsRect LocalAccessible::ParentRelativeBounds() {
    600  nsIFrame* frame = GetFrame();
    601  if (frame && mContent) {
    602    nsIFrame* boundingFrame = FindNearestAccessibleAncestorFrame();
    603    nsRect result = nsLayoutUtils::GetAllInFlowRectsUnion(frame, boundingFrame);
    604 
    605    if (result.IsEmpty()) {
    606      // If we end up with a 0x0 rect from above (or one with negative
    607      // height/width) we should try using the ink overflow rect instead. If we
    608      // use this rect, our relative bounds will match the bounds of what
    609      // appears visually. We do this because some web authors (icloud.com for
    610      // example) employ things like 0x0 buttons with visual overflow. Without
    611      // this, such frames aren't navigable by screen readers.
    612      result = frame->InkOverflowRectRelativeToSelf();
    613      result.MoveBy(frame->GetOffsetTo(boundingFrame));
    614    }
    615 
    616    if (boundingFrame->GetRect().IsEmpty() ||
    617        nsLayoutUtils::GetNextContinuationOrIBSplitSibling(boundingFrame)) {
    618      // Constructing a bounding box across a frame that has an IB split means
    619      // the origin is likely be different from that of boundingFrame.
    620      // Descendants will need their parent-relative bounds adjusted
    621      // accordingly, since parent-relative bounds are constructed to the
    622      // bounding box of the entire element and not each individual IB split
    623      // frame. In the case that boundingFrame's rect is empty,
    624      // GetAllInFlowRectsUnion might exclude its origin. For example, if
    625      // boundingFrame is empty with an origin of (0, -840) but has a non-empty
    626      // ib-split-sibling with (0, 0), the union rect will originate at (0, 0).
    627      // This means the bounds returned for our parent Accessible might be
    628      // offset from boundingFrame's rect. Since result is currently relative to
    629      // boundingFrame's rect, we might need to adjust it to make it parent
    630      // relative.
    631      nsRect boundingUnion =
    632          nsLayoutUtils::GetAllInFlowRectsUnion(boundingFrame, boundingFrame);
    633      if (!boundingUnion.IsEmpty()) {
    634        // The origin of boundingUnion is relative to boundingFrame, meaning
    635        // when we call MoveBy on result with this value we're offsetting
    636        // `result` by the distance boundingFrame's origin was moved to
    637        // construct its bounding box.
    638        result.MoveBy(-boundingUnion.TopLeft());
    639      } else {
    640        // Since GetAllInFlowRectsUnion returned an empty rect on our parent
    641        // Accessible, we would have used the ink overflow rect. However,
    642        // GetAllInFlowRectsUnion calculates relative to the bounding frame's
    643        // main rect, not its ink overflow rect. We need to adjust for the ink
    644        // overflow offset to make our result parent relative.
    645        nsRect boundingOverflow =
    646            boundingFrame->InkOverflowRectRelativeToSelf();
    647        result.MoveBy(-boundingOverflow.TopLeft());
    648      }
    649    }
    650 
    651    if (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
    652        nsLayoutUtils::IsReallyFixedPos(frame)) {
    653      // If we're dealing with a fixed position frame, we've already made it
    654      // relative to the document which should have gotten rid of its scroll
    655      // offset.
    656      return result;
    657    }
    658 
    659    if (ScrollContainerFrame* sf =
    660            mParent == mDoc
    661                ? mDoc->PresShellPtr()->GetRootScrollContainerFrame()
    662                : boundingFrame->GetScrollTargetFrame()) {
    663      // If boundingFrame has a scroll position, result is currently relative
    664      // to that. Instead, we want result to remain the same regardless of
    665      // scrolling. We then subtract the scroll position later when
    666      // calculating absolute bounds. We do this because we don't want to push
    667      // cache updates for the bounds of all descendants every time we scroll.
    668      nsPoint scrollPos = sf->GetScrollPosition().ApplyResolution(
    669          mDoc->PresShellPtr()->GetResolution());
    670      result.MoveBy(scrollPos.x, scrollPos.y);
    671    }
    672 
    673    return result;
    674  }
    675 
    676  return nsRect();
    677 }
    678 
    679 nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const {
    680  nsIFrame* frame = GetFrame();
    681  if (frame && mContent) {
    682    *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
    683    nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
    684        frame, *aBoundingFrame,
    685        nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
    686 
    687    if (unionRect.IsEmpty()) {
    688      // If we end up with a 0x0 rect from above (or one with negative
    689      // height/width) we should try using the ink overflow rect instead. If we
    690      // use this rect, our relative bounds will match the bounds of what
    691      // appears visually. We do this because some web authors (icloud.com for
    692      // example) employ things like 0x0 buttons with visual overflow. Without
    693      // this, such frames aren't navigable by screen readers.
    694      nsRect overflow = frame->InkOverflowRectRelativeToSelf();
    695      nsLayoutUtils::TransformRect(frame, *aBoundingFrame, overflow);
    696      return overflow;
    697    }
    698 
    699    return unionRect;
    700  }
    701 
    702  return nsRect();
    703 }
    704 
    705 nsRect LocalAccessible::BoundsInAppUnits() const {
    706  nsIFrame* boundingFrame = nullptr;
    707  nsRect unionRectTwips = RelativeBounds(&boundingFrame);
    708  if (!boundingFrame) {
    709    return nsRect();
    710  }
    711 
    712  PresShell* presShell = mDoc->PresContext()->PresShell();
    713 
    714  // We need to inverse translate with the offset of the edge of the visual
    715  // viewport from top edge of the layout viewport.
    716  nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
    717                           presShell->GetLayoutViewportOffset();
    718  unionRectTwips.MoveBy(-viewportOffset);
    719 
    720  // We need to take into account a non-1 resolution set on the presshell.
    721  // This happens with async pinch zooming. Here we scale the bounds before
    722  // adding the screen-relative offset.
    723  unionRectTwips.ScaleRoundOut(presShell->GetResolution());
    724  // We have the union of the rectangle, now we need to put it in absolute
    725  // screen coords.
    726  nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits();
    727  unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y());
    728 
    729  return unionRectTwips;
    730 }
    731 
    732 LayoutDeviceIntRect LocalAccessible::Bounds() const {
    733  return LayoutDeviceIntRect::FromAppUnitsToNearest(
    734      BoundsInAppUnits(), mDoc->PresContext()->AppUnitsPerDevPixel());
    735 }
    736 
    737 void LocalAccessible::SetSelected(bool aSelect) {
    738  if (!HasOwnContent()) return;
    739 
    740  if (nsAccUtils::GetSelectableContainer(this, State()) && aSelect) {
    741    TakeFocus();
    742  }
    743 }
    744 
    745 void LocalAccessible::TakeSelection() {
    746  LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State());
    747  if (select) {
    748    if (select->State() & states::MULTISELECTABLE) select->UnselectAll();
    749    SetSelected(true);
    750  }
    751 }
    752 
    753 void LocalAccessible::TakeFocus() const {
    754  nsIFrame* frame = GetFrame();
    755  if (!frame) return;
    756 
    757  nsIContent* focusContent = mContent;
    758 
    759  // If the accessible focus is managed by container widget then focus the
    760  // widget and set the accessible as its current item.
    761  if (!frame->IsFocusable()) {
    762    LocalAccessible* widget = ContainerWidget();
    763    if (widget && widget->AreItemsOperable()) {
    764      nsIContent* widgetElm = widget->GetContent();
    765      nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
    766      if (widgetFrame && widgetFrame->IsFocusable()) {
    767        focusContent = widgetElm;
    768        widget->SetCurrentItem(this);
    769      }
    770    }
    771  }
    772 
    773  if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
    774    dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
    775    // XXXbz: Can we actually have a non-element content here?
    776    RefPtr<dom::Element> element = dom::Element::FromNodeOrNull(focusContent);
    777    fm->SetFocus(element, 0);
    778  }
    779 }
    780 
    781 void LocalAccessible::NameFromAssociatedXULLabel(DocAccessible* aDocument,
    782                                                 nsIContent* aElm,
    783                                                 nsString& aName) {
    784  LocalAccessible* label = nullptr;
    785  XULLabelIterator iter(aDocument, aElm);
    786  while ((label = iter.Next())) {
    787    // Check if label's value attribute is used
    788    label->Elm()->GetAttr(nsGkAtoms::value, aName);
    789    if (aName.IsEmpty()) {
    790      // If no value attribute, a non-empty label must contain
    791      // children that define its text -- possibly using HTML
    792      nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName);
    793    }
    794  }
    795  aName.CompressWhitespace();
    796 }
    797 
    798 void LocalAccessible::XULElmName(DocAccessible* aDocument, nsIContent* aElm,
    799                                 nsString& aName) {
    800  /**
    801   * 3 main cases for XUL Controls to be labeled
    802   *   1 - control contains label="foo"
    803   *   2 - non-child label contains control="controlID"
    804   *        - label has either value="foo" or children
    805   *   3 - name from subtree; e.g. a child label element
    806   * Cases 1 and 2 are handled here.
    807   * Case 3 is handled by GetNameFromSubtree called in NativeName.
    808   * Once a label is found, the search is discontinued, so a control
    809   *  that has a label attribute as well as having a label external to
    810   *  the control that uses the control="controlID" syntax will use
    811   *  the label attribute for its Name.
    812   */
    813 
    814  // CASE #1 (via label attribute) -- great majority of the cases
    815  // Only do this if this is not a select control element, which uses label
    816  // attribute to indicate, which option is selected.
    817  nsCOMPtr<nsIDOMXULSelectControlElement> select =
    818      aElm->AsElement()->AsXULSelectControl();
    819  if (!select) {
    820    aElm->AsElement()->GetAttr(nsGkAtoms::label, aName);
    821  }
    822 
    823  // CASE #2 -- label as <label control="id" ... ></label>
    824  if (aName.IsEmpty()) {
    825    NameFromAssociatedXULLabel(aDocument, aElm, aName);
    826  }
    827 
    828  aName.CompressWhitespace();
    829 }
    830 
    831 nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) {
    832  NS_ENSURE_ARG_POINTER(aEvent);
    833 
    834  if (profiler_thread_is_being_profiled_for_markers()) {
    835    nsAutoCString strEventType;
    836    GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
    837    nsAutoCString strMarker;
    838    strMarker.AppendLiteral("A11y Event - ");
    839    strMarker.Append(strEventType);
    840    PROFILER_MARKER_UNTYPED(strMarker, A11Y);
    841  }
    842 
    843  if (IPCAccessibilityActive() && Document()) {
    844    DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
    845    // If ipcDoc is null, we can't fire the event to the client. We shouldn't
    846    // have fired the event in the first place, since this makes events
    847    // inconsistent for local and remote documents. To avoid this, don't call
    848    // nsEventShell::FireEvent on a DocAccessible for which
    849    // HasLoadState(eTreeConstructed) is false.
    850    MOZ_ASSERT(ipcDoc);
    851    if (ipcDoc) {
    852      uint64_t id = aEvent->GetAccessible()->ID();
    853 
    854      auto getCaretRect = [aEvent] {
    855        HyperTextAccessible* ht = aEvent->GetAccessible()->AsHyperText();
    856        if (ht) {
    857          auto [rect, widget] = ht->GetCaretRect();
    858          // Remove doc offset and reapply in parent.
    859          LayoutDeviceIntRect docBounds = ht->Document()->Bounds();
    860          rect.MoveBy(-docBounds.X(), -docBounds.Y());
    861          return rect;
    862        }
    863        return LayoutDeviceIntRect();
    864      };
    865 
    866      switch (aEvent->GetEventType()) {
    867        case nsIAccessibleEvent::EVENT_SHOW:
    868          ipcDoc->ShowEvent(downcast_accEvent(aEvent));
    869          break;
    870 
    871        case nsIAccessibleEvent::EVENT_HIDE:
    872          ipcDoc->PushMutationEventData(
    873              HideEventData{id, aEvent->IsFromUserInput()});
    874          break;
    875 
    876        case nsIAccessibleEvent::EVENT_INNER_REORDER:
    877        case nsIAccessibleEvent::EVENT_REORDER:
    878          if (IsTable()) {
    879            SendCache(CacheDomain::Table, CacheUpdateType::Update,
    880                      /*aAppendEventData*/ true);
    881          }
    882 
    883 #if defined(XP_WIN)
    884          if (HasOwnContent() && mContent->IsMathMLElement()) {
    885            // For any change in a MathML subtree, update the innerHTML cache on
    886            // the root math element.
    887            for (LocalAccessible* acc = this; acc; acc = acc->LocalParent()) {
    888              if (acc->HasOwnContent() &&
    889                  acc->mContent->IsMathMLElement(nsGkAtoms::math)) {
    890                mDoc->QueueCacheUpdate(acc, CacheDomain::InnerHTML);
    891              }
    892            }
    893          }
    894 #endif  // defined(XP_WIN)
    895 
    896          // reorder events on the application acc aren't necessary to tell the
    897          // parent about new top level documents.
    898          if (!aEvent->GetAccessible()->IsApplication()) {
    899            ipcDoc->PushMutationEventData(
    900                ReorderEventData{id, aEvent->GetEventType()});
    901          }
    902          break;
    903        case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
    904          AccStateChangeEvent* event = downcast_accEvent(aEvent);
    905          ipcDoc->SendStateChangeEvent(id, event->GetState(),
    906                                       event->IsStateEnabled());
    907          break;
    908        }
    909        case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
    910          AccCaretMoveEvent* event = downcast_accEvent(aEvent);
    911          ipcDoc->SendCaretMoveEvent(
    912              id, getCaretRect(), event->GetCaretOffset(),
    913              event->IsSelectionCollapsed(), event->IsAtEndOfLine(),
    914              event->GetGranularity(), event->IsFromUserInput());
    915          break;
    916        }
    917        case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
    918        case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
    919          AccTextChangeEvent* event = downcast_accEvent(aEvent);
    920          ipcDoc->PushMutationEventData(TextChangeEventData{
    921              id, event->ModifiedText(), event->GetStartOffset(),
    922              event->GetLength(), event->IsTextInserted(),
    923              event->IsFromUserInput()});
    924          break;
    925        }
    926        case nsIAccessibleEvent::EVENT_SELECTION:
    927        case nsIAccessibleEvent::EVENT_SELECTION_ADD:
    928        case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
    929          AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
    930          ipcDoc->SendSelectionEvent(id, selEvent->Widget()->ID(),
    931                                     aEvent->GetEventType());
    932          break;
    933        }
    934        case nsIAccessibleEvent::EVENT_FOCUS:
    935          ipcDoc->SendFocusEvent(id, getCaretRect());
    936          break;
    937        case nsIAccessibleEvent::EVENT_SCROLLING_END:
    938        case nsIAccessibleEvent::EVENT_SCROLLING: {
    939          AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
    940          ipcDoc->SendScrollingEvent(
    941              id, aEvent->GetEventType(), scrollingEvent->ScrollX(),
    942              scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
    943              scrollingEvent->MaxScrollY());
    944          break;
    945        }
    946 #if !defined(XP_WIN)
    947        case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
    948          AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
    949          ipcDoc->SendAnnouncementEvent(id, announcementEvent->Announcement(),
    950                                        announcementEvent->Priority());
    951          break;
    952        }
    953 #endif  // !defined(XP_WIN)
    954        case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
    955          AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
    956          AutoTArray<TextRange, 1> ranges;
    957          textSelChangeEvent->SelectionRanges(&ranges);
    958          nsTArray<TextRangeData> textRangeData(ranges.Length());
    959          for (size_t i = 0; i < ranges.Length(); i++) {
    960            const TextRange& range = ranges.ElementAt(i);
    961            LocalAccessible* start = range.StartContainer()->AsLocal();
    962            LocalAccessible* end = range.EndContainer()->AsLocal();
    963            textRangeData.AppendElement(TextRangeData(start->ID(), end->ID(),
    964                                                      range.StartOffset(),
    965                                                      range.EndOffset()));
    966          }
    967          ipcDoc->SendTextSelectionChangeEvent(id, textRangeData);
    968          break;
    969        }
    970        case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
    971        case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
    972          SendCache(CacheDomain::NameAndDescription, CacheUpdateType::Update);
    973          ipcDoc->SendEvent(id, aEvent->GetEventType());
    974          break;
    975        }
    976        case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
    977        case nsIAccessibleEvent::EVENT_VALUE_CHANGE: {
    978          SendCache(CacheDomain::Value, CacheUpdateType::Update);
    979          ipcDoc->SendEvent(id, aEvent->GetEventType());
    980          break;
    981        }
    982        default:
    983          ipcDoc->SendEvent(id, aEvent->GetEventType());
    984      }
    985    }
    986  }
    987 
    988  if (nsCoreUtils::AccEventObserversExist()) {
    989    nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
    990  }
    991 
    992  if (IPCAccessibilityActive()) {
    993    return NS_OK;
    994  }
    995 
    996  if (IsDefunct()) {
    997    // This could happen if there is an XPCOM observer, since script might run
    998    // which mutates the tree.
    999    return NS_OK;
   1000  }
   1001 
   1002  LocalAccessible* target = aEvent->GetAccessible();
   1003  switch (aEvent->GetEventType()) {
   1004    case nsIAccessibleEvent::EVENT_SHOW: {
   1005      // Scope for PerfStats
   1006      AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns);
   1007      PerfStats::AutoMetricRecording<
   1008          PerfStats::Metric::A11Y_PlatformShowHideEvent>
   1009          autoRecording;
   1010      // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
   1011      // THIS CODE IS MEASURING TIMINGS.
   1012      PlatformShowHideEvent(target, target->LocalParent(), true,
   1013                            aEvent->IsFromUserInput());
   1014      break;
   1015    }
   1016    case nsIAccessibleEvent::EVENT_HIDE: {
   1017      // Scope for PerfStats
   1018      AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns);
   1019      PerfStats::AutoMetricRecording<
   1020          PerfStats::Metric::A11Y_PlatformShowHideEvent>
   1021          autoRecording;
   1022      // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
   1023      // THIS CODE IS MEASURING TIMINGS.
   1024      PlatformShowHideEvent(target, target->LocalParent(), false,
   1025                            aEvent->IsFromUserInput());
   1026      break;
   1027    }
   1028    case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
   1029      AccStateChangeEvent* event = downcast_accEvent(aEvent);
   1030      PlatformStateChangeEvent(target, event->GetState(),
   1031                               event->IsStateEnabled());
   1032      break;
   1033    }
   1034    case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
   1035      AccCaretMoveEvent* event = downcast_accEvent(aEvent);
   1036      PlatformCaretMoveEvent(target, event->GetCaretOffset(),
   1037                             event->IsSelectionCollapsed(),
   1038                             event->GetGranularity(), event->IsFromUserInput());
   1039      break;
   1040    }
   1041    case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
   1042    case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
   1043      AccTextChangeEvent* event = downcast_accEvent(aEvent);
   1044      const nsString& text = event->ModifiedText();
   1045      PlatformTextChangeEvent(target, text, event->GetStartOffset(),
   1046                              event->GetLength(), event->IsTextInserted(),
   1047                              event->IsFromUserInput());
   1048      break;
   1049    }
   1050    case nsIAccessibleEvent::EVENT_SELECTION:
   1051    case nsIAccessibleEvent::EVENT_SELECTION_ADD:
   1052    case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
   1053      AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
   1054      PlatformSelectionEvent(target, selEvent->Widget(),
   1055                             aEvent->GetEventType());
   1056      break;
   1057    }
   1058    case nsIAccessibleEvent::EVENT_FOCUS: {
   1059      PlatformFocusEvent(target);
   1060      break;
   1061    }
   1062 #if defined(ANDROID)
   1063    case nsIAccessibleEvent::EVENT_SCROLLING_END:
   1064    case nsIAccessibleEvent::EVENT_SCROLLING: {
   1065      AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
   1066      PlatformScrollingEvent(
   1067          target, aEvent->GetEventType(), scrollingEvent->ScrollX(),
   1068          scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
   1069          scrollingEvent->MaxScrollY());
   1070      break;
   1071    }
   1072    case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
   1073      AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
   1074      PlatformAnnouncementEvent(target, announcementEvent->Announcement(),
   1075                                announcementEvent->Priority());
   1076      break;
   1077    }
   1078 #endif  // defined(ANDROID)
   1079 #if defined(MOZ_WIDGET_COCOA)
   1080    case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
   1081      AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
   1082      AutoTArray<TextRange, 1> ranges;
   1083      textSelChangeEvent->SelectionRanges(&ranges);
   1084      PlatformTextSelectionChangeEvent(target, ranges);
   1085      break;
   1086    }
   1087 #endif  // defined(MOZ_WIDGET_COCOA)
   1088    default:
   1089      PlatformEvent(target, aEvent->GetEventType());
   1090  }
   1091 
   1092  return NS_OK;
   1093 }
   1094 
   1095 already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
   1096  RefPtr<AccAttributes> attributes = NativeAttributes();
   1097  if (!HasOwnContent() || !mContent->IsElement()) return attributes.forget();
   1098 
   1099  // 'xml-roles' attribute coming from ARIA.
   1100  nsString xmlRoles;
   1101  if (nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::role,
   1102                              xmlRoles) &&
   1103      !xmlRoles.IsEmpty()) {
   1104    attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(xmlRoles));
   1105  } else if (nsAtom* landmark = LandmarkRole()) {
   1106    // 'xml-roles' attribute for landmark.
   1107    attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
   1108  }
   1109 
   1110  // Expose object attributes from ARIA attributes.
   1111  aria::AttrIterator attribIter(mContent);
   1112  while (attribIter.Next()) {
   1113    if (attribIter.AttrName() == nsGkAtoms::aria_placeholder &&
   1114        attributes->HasAttribute(nsGkAtoms::placeholder)) {
   1115      // If there is an HTML placeholder attribute exposed by
   1116      // HTMLTextFieldAccessible::NativeAttributes, don't expose
   1117      // aria-placeholder.
   1118      continue;
   1119    }
   1120    attribIter.ExposeAttr(attributes);
   1121  }
   1122 
   1123  if (HasCustomActions()) {
   1124    attributes->SetAttribute(nsGkAtoms::hasActions, true);
   1125  }
   1126 
   1127  // If there is no aria-live attribute then expose default value of 'live'
   1128  // object attribute used for ARIA role of this accessible.
   1129  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   1130  if (roleMapEntry) {
   1131    if (roleMapEntry->Is(nsGkAtoms::searchbox)) {
   1132      attributes->SetAttribute(nsGkAtoms::textInputType, nsGkAtoms::search);
   1133    }
   1134 
   1135    if (!attributes->HasAttribute(nsGkAtoms::aria_live)) {
   1136      nsString live;
   1137      if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live)) {
   1138        attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
   1139      }
   1140    }
   1141  }
   1142 
   1143  nsString detailsFrom;
   1144  AssociatedElementsIterator iter(mDoc, Elm(), nsGkAtoms::aria_details);
   1145  if (iter.Next()) {
   1146    detailsFrom.AssignLiteral("aria-details");
   1147  } else if (GetCommandForDetailsRelation()) {
   1148    detailsFrom.AssignLiteral("command-for");
   1149  } else if (GetPopoverTargetDetailsRelation()) {
   1150    detailsFrom.AssignLiteral("popover-target");
   1151  } else if (GetAnchorPositionTargetDetailsRelation()) {
   1152    detailsFrom.AssignLiteral("css-anchor");
   1153  }
   1154 
   1155  if (!detailsFrom.IsEmpty()) {
   1156    attributes->SetAttribute(nsGkAtoms::details_from, std::move(detailsFrom));
   1157  }
   1158 
   1159  return attributes.forget();
   1160 }
   1161 
   1162 already_AddRefed<AccAttributes> LocalAccessible::NativeAttributes() {
   1163  RefPtr<AccAttributes> attributes = new AccAttributes();
   1164 
   1165  // We support values, so expose the string value as well, via the valuetext
   1166  // object attribute. We test for the value interface because we don't want
   1167  // to expose traditional Value() information such as URL's on links and
   1168  // documents, or text in an input.
   1169  if (HasNumericValue()) {
   1170    nsString valuetext;
   1171    Value(valuetext);
   1172    attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
   1173  }
   1174 
   1175  // Expose checkable object attribute if the accessible has checkable state
   1176  if (State() & states::CHECKABLE) {
   1177    attributes->SetAttribute(nsGkAtoms::checkable, true);
   1178  }
   1179 
   1180  // Expose 'explicit-name' attribute.
   1181  nsAutoString name;
   1182  if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
   1183    attributes->SetAttribute(nsGkAtoms::explicit_name, true);
   1184  }
   1185 
   1186  bool hierarchical = false;
   1187  uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
   1188  if (itemCount) {
   1189    attributes->SetAttribute(nsGkAtoms::child_item_count,
   1190                             static_cast<int32_t>(itemCount));
   1191  }
   1192 
   1193  if (hierarchical) {
   1194    attributes->SetAttribute(nsGkAtoms::tree, true);
   1195  }
   1196 
   1197  // If the accessible doesn't have own content (such as list item bullet or
   1198  // xul tree item) then don't calculate content based attributes.
   1199  if (!HasOwnContent()) return attributes.forget();
   1200 
   1201  // Get container-foo computed live region properties based on the closest
   1202  // container with the live region attribute. Inner nodes override outer nodes
   1203  // within the same document. The inner nodes can be used to override live
   1204  // region behavior on more general outer nodes.
   1205  nsAccUtils::SetLiveContainerAttributes(attributes, this);
   1206 
   1207  if (!mContent->IsElement()) return attributes.forget();
   1208 
   1209  nsString id;
   1210  if (nsCoreUtils::GetID(mContent, id)) {
   1211    attributes->SetAttribute(nsGkAtoms::id, std::move(id));
   1212  }
   1213 
   1214  // Expose class because it may have useful microformat information.
   1215  nsString _class;
   1216  if (mContent->AsElement()->GetAttr(nsGkAtoms::_class, _class)) {
   1217    attributes->SetAttribute(nsGkAtoms::_class, std::move(_class));
   1218  }
   1219 
   1220  // Expose tag.
   1221  attributes->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom());
   1222 
   1223  if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
   1224    // Expose draggable object attribute.
   1225    if (htmlElement->Draggable()) {
   1226      attributes->SetAttribute(nsGkAtoms::draggable, true);
   1227    }
   1228    nsString popover;
   1229    htmlElement->GetPopover(popover);
   1230    if (!popover.IsEmpty()) {
   1231      attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popover));
   1232    }
   1233  }
   1234 
   1235  // Don't calculate CSS-based object attributes when:
   1236  // 1. There is no frame (e.g. the accessible is unattached from the tree).
   1237  // 2. This is an image map area. CSS is irrelevant here. Furthermore, we won't
   1238  // be able to get the computed style if the map is unslotted in a shadow host.
   1239  nsIFrame* f = mContent->GetPrimaryFrame();
   1240  if (!f || mContent->IsHTMLElement(nsGkAtoms::area)) {
   1241    return attributes.forget();
   1242  }
   1243 
   1244  // Expose 'display' attribute.
   1245  if (RefPtr<nsAtom> display = DisplayStyle()) {
   1246    attributes->SetAttribute(nsGkAtoms::display, display);
   1247  }
   1248 
   1249  const ComputedStyle& style = *f->Style();
   1250  auto Atomize = [&](NonCustomCSSPropertyId aId) -> RefPtr<nsAtom> {
   1251    nsAutoCString value;
   1252    style.GetComputedPropertyValue(aId, value);
   1253    return NS_Atomize(value);
   1254  };
   1255 
   1256  // Expose 'text-align' attribute.
   1257  attributes->SetAttribute(nsGkAtoms::textAlign,
   1258                           Atomize(eCSSProperty_text_align));
   1259 
   1260  // Expose 'text-indent' attribute.
   1261  attributes->SetAttribute(nsGkAtoms::textIndent,
   1262                           Atomize(eCSSProperty_text_indent));
   1263 
   1264  auto GetMargin = [&](mozilla::Side aSide) -> CSSCoord {
   1265    // This is here only to guarantee that we do the same as getComputedStyle
   1266    // does, so that we don't hit precision errors in tests.
   1267    const auto margin =
   1268        f->StyleMargin()->GetMargin(aSide, AnchorPosResolutionParams::From(f));
   1269    if (margin->ConvertsToLength()) {
   1270      return margin->AsLengthPercentage().ToLengthInCSSPixels();
   1271    }
   1272 
   1273    nscoord coordVal = f->GetUsedMargin().Side(aSide);
   1274    return CSSPixel::FromAppUnits(coordVal);
   1275  };
   1276 
   1277  // Expose 'margin-left' attribute.
   1278  attributes->SetAttribute(nsGkAtoms::marginLeft, GetMargin(eSideLeft));
   1279 
   1280  // Expose 'margin-right' attribute.
   1281  attributes->SetAttribute(nsGkAtoms::marginRight, GetMargin(eSideRight));
   1282 
   1283  // Expose 'margin-top' attribute.
   1284  attributes->SetAttribute(nsGkAtoms::marginTop, GetMargin(eSideTop));
   1285 
   1286  // Expose 'margin-bottom' attribute.
   1287  attributes->SetAttribute(nsGkAtoms::marginBottom, GetMargin(eSideBottom));
   1288 
   1289  // Expose data-at-shortcutkeys attribute for web applications and virtual
   1290  // cursors. Currently mostly used by JAWS.
   1291  nsString atShortcutKeys;
   1292  if (mContent->AsElement()->GetAttr(
   1293          kNameSpaceID_None, nsGkAtoms::dataAtShortcutkeys, atShortcutKeys)) {
   1294    attributes->SetAttribute(nsGkAtoms::dataAtShortcutkeys,
   1295                             std::move(atShortcutKeys));
   1296  }
   1297 
   1298  return attributes.forget();
   1299 }
   1300 
   1301 bool LocalAccessible::AttributeChangesState(nsAtom* aAttribute) {
   1302  return aAttribute == nsGkAtoms::aria_disabled ||
   1303         // The HTML element disabled state gets handled in
   1304         // DocAccessible::ElementStateChanged. This matches
   1305         // LocalAccessible::NativelyUnavailable.
   1306         (aAttribute == nsGkAtoms::disabled && !mContent->IsHTMLElement()) ||
   1307         aAttribute == nsGkAtoms::tabindex ||
   1308         aAttribute == nsGkAtoms::aria_required ||
   1309         aAttribute == nsGkAtoms::aria_invalid ||
   1310         aAttribute == nsGkAtoms::aria_expanded ||
   1311         aAttribute == nsGkAtoms::aria_checked ||
   1312         (aAttribute == nsGkAtoms::aria_pressed && IsButton()) ||
   1313         aAttribute == nsGkAtoms::aria_readonly ||
   1314         aAttribute == nsGkAtoms::aria_current ||
   1315         aAttribute == nsGkAtoms::aria_haspopup ||
   1316         aAttribute == nsGkAtoms::aria_busy ||
   1317         aAttribute == nsGkAtoms::aria_multiline ||
   1318         aAttribute == nsGkAtoms::aria_multiselectable ||
   1319         // We track this for focusable state update
   1320         aAttribute == nsGkAtoms::commandfor ||
   1321         aAttribute == nsGkAtoms::contenteditable ||
   1322         aAttribute == nsGkAtoms::popovertarget;
   1323 }
   1324 
   1325 void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
   1326                                          nsAtom* aAttribute,
   1327                                          AttrModType aModType,
   1328                                          const nsAttrValue* aOldValue,
   1329                                          uint64_t aOldState) {
   1330  // Fire accessible event after short timer, because we need to wait for
   1331  // DOM attribute & resulting layout to actually change. Otherwise,
   1332  // assistive technology will retrieve the wrong state/value/selection info.
   1333 
   1334  CssAltContent::HandleAttributeChange(mContent, aNameSpaceID, aAttribute);
   1335 
   1336  // XXX todo
   1337  // We still need to handle special HTML cases here
   1338  // For example, if an <img>'s usemap attribute is modified
   1339  // Otherwise it may just be a state change, for example an object changing
   1340  // its visibility
   1341  //
   1342  // XXX todo: report aria state changes for "undefined" literal value changes
   1343  // filed as bug 472142
   1344  //
   1345  // XXX todo:  invalidate accessible when aria state changes affect exposed
   1346  // role filed as bug 472143
   1347 
   1348  if (AttributeChangesState(aAttribute)) {
   1349    uint64_t currState = State();
   1350    uint64_t diffState = currState ^ aOldState;
   1351    if (diffState) {
   1352      for (uint64_t state = 1; state <= states::LAST_ENTRY; state <<= 1) {
   1353        if (diffState & state) {
   1354          RefPtr<AccEvent> stateChangeEvent =
   1355              new AccStateChangeEvent(this, state, (currState & state));
   1356          mDoc->FireDelayedEvent(stateChangeEvent);
   1357        }
   1358      }
   1359    }
   1360  }
   1361 
   1362  if (aAttribute == nsGkAtoms::_class) {
   1363    mDoc->QueueCacheUpdate(this, CacheDomain::DOMNodeIDAndClass);
   1364    return;
   1365  }
   1366 
   1367  // When a details object has its open attribute changed
   1368  // we should fire a state-change event on the accessible of
   1369  // its main summary
   1370  if (aAttribute == nsGkAtoms::open) {
   1371    // FromDetails checks if the given accessible belongs to
   1372    // a details frame and also locates the accessible of its
   1373    // main summary.
   1374    if (HTMLSummaryAccessible* summaryAccessible =
   1375            HTMLSummaryAccessible::FromDetails(this)) {
   1376      RefPtr<AccEvent> expandedChangeEvent =
   1377          new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
   1378      mDoc->FireDelayedEvent(expandedChangeEvent);
   1379      return;
   1380    }
   1381  }
   1382 
   1383  // Check for namespaced ARIA attribute
   1384  if (aNameSpaceID == kNameSpaceID_None) {
   1385    // Check for hyphenated aria-foo property?
   1386    if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
   1387      uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
   1388      if (!(attrFlags & ATTR_BYPASSOBJ)) {
   1389        mDoc->QueueCacheUpdate(this, CacheDomain::ARIA);
   1390        // For aria attributes like drag and drop changes we fire a generic
   1391        // attribute change event; at least until native API comes up with a
   1392        // more meaningful event.
   1393        RefPtr<AccEvent> event =
   1394            new AccObjectAttrChangedEvent(this, aAttribute);
   1395        mDoc->FireDelayedEvent(event);
   1396      }
   1397    }
   1398  }
   1399 
   1400  if (aAttribute == nsGkAtoms::aria_actions && IsAdditionOrRemoval(aModType)) {
   1401    // We only care about the presence of aria-actions, not its value.
   1402    mDoc->QueueCacheUpdate(this, CacheDomain::ARIA);
   1403    RefPtr<AccEvent> event =
   1404        new AccObjectAttrChangedEvent(this, nsGkAtoms::hasActions);
   1405    mDoc->FireDelayedEvent(event);
   1406  }
   1407 
   1408  dom::Element* elm = Elm();
   1409 
   1410  if (HasNumericValue() &&
   1411      (aAttribute == nsGkAtoms::aria_valuemax ||
   1412       aAttribute == nsGkAtoms::aria_valuemin || aAttribute == nsGkAtoms::min ||
   1413       aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step)) {
   1414    mDoc->QueueCacheUpdate(this, CacheDomain::Value);
   1415    return;
   1416  }
   1417 
   1418  // Fire text value change event whenever aria-valuetext is changed.
   1419  if (aAttribute == nsGkAtoms::aria_valuetext) {
   1420    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, this);
   1421    return;
   1422  }
   1423 
   1424  if (aAttribute == nsGkAtoms::aria_valuenow) {
   1425    if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_valuetext) ||
   1426        nsAccUtils::ARIAAttrValueIs(elm, nsGkAtoms::aria_valuetext,
   1427                                    nsGkAtoms::_empty, eCaseMatters)) {
   1428      // Fire numeric value change event when aria-valuenow is changed and
   1429      // aria-valuetext is empty
   1430      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
   1431    } else {
   1432      // We need to update the cache here since we won't get an event if
   1433      // aria-valuenow is shadowed by aria-valuetext.
   1434      mDoc->QueueCacheUpdate(this, CacheDomain::Value);
   1435    }
   1436    return;
   1437  }
   1438 
   1439  if (aAttribute == nsGkAtoms::aria_owns) {
   1440    mDoc->Controller()->ScheduleRelocation(this);
   1441  }
   1442 
   1443  // Fire name change and description change events.
   1444  if (aAttribute == nsGkAtoms::aria_label || aAttribute == nsGkAtoms::label) {
   1445    // A valid aria-labelledby would take precedence over an aria-label or a xul
   1446    // label attribute. So if that relation exists the name won't change.
   1447    AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
   1448    if (!iter.NextElem()) {
   1449      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
   1450    }
   1451    return;
   1452  }
   1453 
   1454  if (aAttribute == nsGkAtoms::aria_description) {
   1455    // A valid aria-describedby would take precedence so an aria-description
   1456    // change won't change the description.
   1457    AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
   1458    if (!iter.NextElem()) {
   1459      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
   1460                             this);
   1461    }
   1462    return;
   1463  }
   1464 
   1465  if (aAttribute == nsGkAtoms::aria_describedby) {
   1466    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   1467    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, this);
   1468    if (IsAdditionOrModification(aModType)) {
   1469      // The subtrees of the new aria-describedby targets might be used to
   1470      // compute the description for this. Therefore, we need to set
   1471      // the eHasDescriptionDependent flag on all Accessibles in these subtrees.
   1472      AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
   1473      while (LocalAccessible* target = iter.Next()) {
   1474        target->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
   1475      }
   1476    }
   1477    return;
   1478  }
   1479 
   1480  if (aAttribute == nsGkAtoms::aria_labelledby) {
   1481    // We only queue cache updates for explicit relations. Implicit, reverse
   1482    // relations are handled in ApplyCache and stored in a map on the remote
   1483    // document itself.
   1484    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   1485    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
   1486    if (IsAdditionOrModification(aModType)) {
   1487      // The subtrees of the new aria-labelledby targets might be used to
   1488      // compute the name for this. Therefore, we need to set
   1489      // the eHasNameDependent flag on all Accessibles in these subtrees.
   1490      AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
   1491      while (LocalAccessible* target = iter.Next()) {
   1492        target->ModifySubtreeContextFlags(eHasNameDependent, true);
   1493      }
   1494    }
   1495    return;
   1496  }
   1497 
   1498  if ((aAttribute == nsGkAtoms::aria_expanded ||
   1499       aAttribute == nsGkAtoms::href) &&
   1500      IsAdditionOrRemoval(aModType)) {
   1501    // The presence of aria-expanded adds an expand/collapse action.
   1502    mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
   1503  }
   1504 
   1505  if (aAttribute == nsGkAtoms::href || aAttribute == nsGkAtoms::src) {
   1506    mDoc->QueueCacheUpdate(this, CacheDomain::Value);
   1507  }
   1508 
   1509  if (aAttribute == nsGkAtoms::aria_details) {
   1510    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   1511    // If an aria-details attribute is added or removed from an anchored
   1512    // accessible, it will change the validity of its anchor's relation.
   1513    mDoc->RefreshAnchorRelationCacheForTarget(this);
   1514  }
   1515 
   1516  if (aAttribute == nsGkAtoms::aria_controls ||
   1517      aAttribute == nsGkAtoms::aria_flowto ||
   1518      aAttribute == nsGkAtoms::aria_errormessage ||
   1519      aAttribute == nsGkAtoms::aria_actions) {
   1520    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   1521  }
   1522 
   1523  if (aAttribute == nsGkAtoms::popovertarget) {
   1524    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   1525    return;
   1526  }
   1527 
   1528  if (aAttribute == nsGkAtoms::commandfor) {
   1529    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   1530    return;
   1531  }
   1532 
   1533  if (aAttribute == nsGkAtoms::alt &&
   1534      !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label) &&
   1535      !elm->HasAttr(nsGkAtoms::aria_labelledby)) {
   1536    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
   1537    return;
   1538  }
   1539 
   1540  if (aAttribute == nsGkAtoms::title) {
   1541    nsAutoString name;
   1542    if (Name(name) == eNameFromTooltip || name.IsVoid()) {
   1543      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
   1544      return;
   1545    }
   1546 
   1547    if (!elm->HasAttr(nsGkAtoms::aria_describedby)) {
   1548      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
   1549                             this);
   1550    }
   1551 
   1552    return;
   1553  }
   1554 
   1555  // ARIA or XUL selection
   1556  if ((mContent->IsXULElement() && aAttribute == nsGkAtoms::selected) ||
   1557      aAttribute == nsGkAtoms::aria_selected) {
   1558    LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State());
   1559    if (widget) {
   1560      AccSelChangeEvent::SelChangeType selChangeType;
   1561      if (aNameSpaceID != kNameSpaceID_None) {
   1562        selChangeType = elm->AttrValueIs(aNameSpaceID, aAttribute,
   1563                                         nsGkAtoms::_true, eCaseMatters)
   1564                            ? AccSelChangeEvent::eSelectionAdd
   1565                            : AccSelChangeEvent::eSelectionRemove;
   1566      } else {
   1567        selChangeType = nsAccUtils::ARIAAttrValueIs(
   1568                            elm, aAttribute, nsGkAtoms::_true, eCaseMatters)
   1569                            ? AccSelChangeEvent::eSelectionAdd
   1570                            : AccSelChangeEvent::eSelectionRemove;
   1571      }
   1572 
   1573      RefPtr<AccEvent> event =
   1574          new AccSelChangeEvent(widget, this, selChangeType);
   1575      mDoc->FireDelayedEvent(event);
   1576      if (aAttribute == nsGkAtoms::aria_selected) {
   1577        mDoc->QueueCacheUpdate(this, CacheDomain::State);
   1578      }
   1579    }
   1580 
   1581    return;
   1582  }
   1583 
   1584  if (aAttribute == nsGkAtoms::aria_level ||
   1585      aAttribute == nsGkAtoms::aria_setsize ||
   1586      aAttribute == nsGkAtoms::aria_posinset) {
   1587    mDoc->QueueCacheUpdate(this, CacheDomain::GroupInfo);
   1588    return;
   1589  }
   1590 
   1591  if (aAttribute == nsGkAtoms::accesskey) {
   1592    mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
   1593  }
   1594 
   1595  if (aAttribute == nsGkAtoms::name &&
   1596      (mContent && mContent->IsHTMLElement(nsGkAtoms::a))) {
   1597    // If an anchor's name changed, it's possible a LINKS_TO relation
   1598    // also changed. Push a cache update for Relations.
   1599    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   1600  }
   1601 }
   1602 
   1603 void LocalAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
   1604                                        int32_t* aPosInSet) const {
   1605  if (!mContent) {
   1606    return;
   1607  }
   1608 
   1609  if (aLevel) {
   1610    nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, aLevel);
   1611  }
   1612  if (aSetSize) {
   1613    nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, aSetSize);
   1614  }
   1615  if (aPosInSet) {
   1616    nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, aPosInSet);
   1617  }
   1618 }
   1619 
   1620 uint64_t LocalAccessible::ExplicitState() const {
   1621  if (IsDefunct()) return states::DEFUNCT;
   1622 
   1623  uint64_t state = NativeState();
   1624  // Apply ARIA states to be sure accessible states will be overridden.
   1625  ApplyARIAState(&state);
   1626 
   1627  if (!(state & states::UNAVAILABLE)) {
   1628    // If the object is a current item of container widget then mark it as
   1629    // ACTIVE. This allows screen reader virtual buffer modes to know which
   1630    // descendant is the current one that would get focus if the user navigates
   1631    // to the container widget.
   1632    LocalAccessible* widget = ContainerWidget();
   1633    if (widget && widget->CurrentItem() == this) state |= states::ACTIVE;
   1634  }
   1635 
   1636  return state;
   1637 }
   1638 
   1639 uint64_t LocalAccessible::State() {
   1640  uint64_t state = ExplicitState();
   1641 
   1642  ApplyImplicitState(state);
   1643  return state;
   1644 }
   1645 
   1646 void LocalAccessible::ApplyARIAState(uint64_t* aState) const {
   1647  if (!mContent->IsElement()) return;
   1648 
   1649  dom::Element* element = mContent->AsElement();
   1650 
   1651  // Test for universal states first
   1652  *aState |= aria::UniversalStatesFor(element);
   1653 
   1654  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   1655  if (!roleMapEntry && IsHTMLTableCell() && Role() == roles::GRID_CELL) {
   1656    // This is a <td> inside a role="grid", so it gets an implicit role of
   1657    // GRID_CELL in ARIATransformRole. However, because it's implicit, we
   1658    // don't have a role map entry, and without that, we can't apply ARIA states
   1659    // below. Therefore, we get the role map entry here.
   1660    roleMapEntry = aria::GetRoleMap(nsGkAtoms::gridcell);
   1661    MOZ_ASSERT(roleMapEntry, "Should have role map entry for gridcell");
   1662  }
   1663  if (roleMapEntry) {
   1664    // We only force the readonly bit off if we have a real mapping for the aria
   1665    // role. This preserves the ability for screen readers to use readonly
   1666    // (primarily on the document) as the hint for creating a virtual buffer.
   1667    if (roleMapEntry->role != roles::NOTHING) *aState &= ~states::READONLY;
   1668 
   1669    if (mContent->HasID()) {
   1670      // If has a role & ID and aria-activedescendant on the container, assume
   1671      // focusable.
   1672      const LocalAccessible* ancestor = this;
   1673      while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
   1674        dom::Element* el = ancestor->Elm();
   1675        if (el && el->HasAttr(nsGkAtoms::aria_activedescendant)) {
   1676          *aState |= states::FOCUSABLE;
   1677          break;
   1678        }
   1679      }
   1680    }
   1681  }
   1682 
   1683  if (!(*aState & states::FOCUSABLE)) {
   1684    // Sometimes, we use aria-activedescendant targeting something which isn't
   1685    // actually a descendant. This is technically a spec violation, but it's a
   1686    // useful hack which makes certain things much easier. For example, we use
   1687    // this for "fake focus" for multi select browser tabs and Quantumbar
   1688    // autocomplete suggestions.
   1689    // In these cases, the aria-activedescendant code above won't make the
   1690    // active item focusable. It doesn't make sense for something to have
   1691    // focus when it isn't focusable, so fix that here.
   1692    if (FocusMgr()->IsActiveItem(this)) {
   1693      *aState |= states::FOCUSABLE;
   1694    }
   1695  }
   1696 
   1697  // special case: A native button element whose role got transformed by ARIA to
   1698  // a toggle button Also applies to togglable button menus, like in the Dev
   1699  // Tools Web Console.
   1700  if (IsButton() || IsMenuButton()) {
   1701    aria::MapToState(aria::eARIAPressed, element, aState);
   1702  }
   1703 
   1704  if (!IsTextField() && IsEditableRoot()) {
   1705    // HTML text fields will have their own multi/single line calcuation in
   1706    // NativeState.
   1707    aria::MapToState(aria::eARIAMultilineByDefault, element, aState);
   1708  }
   1709 
   1710  if (!roleMapEntry) return;
   1711 
   1712  *aState |= roleMapEntry->state;
   1713 
   1714  if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
   1715      aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
   1716      aria::MapToState(roleMapEntry->attributeMap3, element, aState)) {
   1717    aria::MapToState(roleMapEntry->attributeMap4, element, aState);
   1718  }
   1719 
   1720  // ARIA gridcell inherits readonly state from the grid until it's overridden.
   1721  if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
   1722       roleMapEntry->Is(nsGkAtoms::columnheader) ||
   1723       roleMapEntry->Is(nsGkAtoms::rowheader)) &&
   1724      // Don't recurse infinitely for an authoring error like
   1725      // <table role="gridcell">. Without this check, we'd call TableFor(this)
   1726      // below, which would return this.
   1727      !IsTable() &&
   1728      !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) {
   1729    if (const LocalAccessible* grid = nsAccUtils::TableFor(this)) {
   1730      uint64_t gridState = 0;
   1731      grid->ApplyARIAState(&gridState);
   1732      *aState |= gridState & states::READONLY;
   1733    }
   1734  }
   1735 }
   1736 
   1737 void LocalAccessible::Value(nsString& aValue) const {
   1738  if (HasNumericValue()) {
   1739    // aria-valuenow is a number, and aria-valuetext is the optional text
   1740    // equivalent. For the string value, we will try the optional text
   1741    // equivalent first.
   1742    if (!mContent->IsElement()) {
   1743      return;
   1744    }
   1745 
   1746    if (!nsAccUtils::GetARIAAttr(mContent->AsElement(),
   1747                                 nsGkAtoms::aria_valuetext, aValue)) {
   1748      if (!NativeHasNumericValue()) {
   1749        double checkValue = CurValue();
   1750        if (!std::isnan(checkValue)) {
   1751          aValue.AppendFloat(checkValue);
   1752        }
   1753      }
   1754    }
   1755    return;
   1756  }
   1757 
   1758  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   1759 
   1760  // Value of textbox is a textified subtree.
   1761  if ((roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) ||
   1762      (IsGeneric() && IsEditableRoot())) {
   1763    nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
   1764    return;
   1765  }
   1766 
   1767  // Value of combobox is a text of current or selected item.
   1768  if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
   1769    LocalAccessible* option = CurrentItem();
   1770    if (!option) {
   1771      uint32_t childCount = ChildCount();
   1772      for (uint32_t idx = 0; idx < childCount; idx++) {
   1773        LocalAccessible* child = mChildren.ElementAt(idx);
   1774        if (child->IsListControl()) {
   1775          Accessible* acc = child->GetSelectedItem(0);
   1776          option = acc ? acc->AsLocal() : nullptr;
   1777          break;
   1778        }
   1779      }
   1780    }
   1781 
   1782    // If there's a selected item, get the value from it. Otherwise, determine
   1783    // the value from descendant elements.
   1784    nsTextEquivUtils::GetTextEquivFromSubtree(option ? option : this, aValue);
   1785  }
   1786 }
   1787 
   1788 double LocalAccessible::MaxValue() const {
   1789  double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemax);
   1790  if (std::isnan(checkValue) && !NativeHasNumericValue()) {
   1791    // aria-valuemax isn't present and this element doesn't natively provide a
   1792    // maximum value. Use the ARIA default.
   1793    const nsRoleMapEntry* roleMap = ARIARoleMap();
   1794    if (roleMap && roleMap->role == roles::SPINBUTTON) {
   1795      return UnspecifiedNaN<double>();
   1796    }
   1797    return 100;
   1798  }
   1799  return checkValue;
   1800 }
   1801 
   1802 double LocalAccessible::MinValue() const {
   1803  double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemin);
   1804  if (std::isnan(checkValue) && !NativeHasNumericValue()) {
   1805    // aria-valuemin isn't present and this element doesn't natively provide a
   1806    // minimum value. Use the ARIA default.
   1807    const nsRoleMapEntry* roleMap = ARIARoleMap();
   1808    if (roleMap && roleMap->role == roles::SPINBUTTON) {
   1809      return UnspecifiedNaN<double>();
   1810    }
   1811    return 0;
   1812  }
   1813  return checkValue;
   1814 }
   1815 
   1816 double LocalAccessible::Step() const {
   1817  return UnspecifiedNaN<double>();  // no mimimum increment (step) in ARIA.
   1818 }
   1819 
   1820 double LocalAccessible::CurValue() const {
   1821  double checkValue = AttrNumericValue(nsGkAtoms::aria_valuenow);
   1822  if (std::isnan(checkValue) && !NativeHasNumericValue()) {
   1823    // aria-valuenow isn't present and this element doesn't natively provide a
   1824    // current value. Use the ARIA default.
   1825    const nsRoleMapEntry* roleMap = ARIARoleMap();
   1826    if (roleMap && roleMap->role == roles::SPINBUTTON) {
   1827      return UnspecifiedNaN<double>();
   1828    }
   1829    double minValue = MinValue();
   1830    return minValue + ((MaxValue() - minValue) / 2);
   1831  }
   1832 
   1833  return checkValue;
   1834 }
   1835 
   1836 bool LocalAccessible::SetCurValue(double aValue) { return false; }
   1837 
   1838 role LocalAccessible::NativeRole() const { return roles::NOTHING; }
   1839 
   1840 uint8_t LocalAccessible::ActionCount() const {
   1841  return HasPrimaryAction() || ActionAncestor() ? 1 : 0;
   1842 }
   1843 
   1844 void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
   1845  aName.Truncate();
   1846 
   1847  if (aIndex != 0) return;
   1848 
   1849  uint32_t actionRule = GetActionRule();
   1850 
   1851  switch (actionRule) {
   1852    case eActivateAction:
   1853      aName.AssignLiteral("activate");
   1854      return;
   1855 
   1856    case eClickAction:
   1857      aName.AssignLiteral("click");
   1858      return;
   1859 
   1860    case ePressAction:
   1861      aName.AssignLiteral("press");
   1862      return;
   1863 
   1864    case eCheckUncheckAction: {
   1865      uint64_t state = State();
   1866      if (state & states::CHECKED) {
   1867        aName.AssignLiteral("uncheck");
   1868      } else if (state & states::MIXED) {
   1869        aName.AssignLiteral("cycle");
   1870      } else {
   1871        aName.AssignLiteral("check");
   1872      }
   1873      return;
   1874    }
   1875 
   1876    case eJumpAction:
   1877      aName.AssignLiteral("jump");
   1878      return;
   1879 
   1880    case eOpenCloseAction:
   1881      if (State() & states::EXPANDED) {
   1882        aName.AssignLiteral("close");
   1883      } else {
   1884        aName.AssignLiteral("open");
   1885      }
   1886      return;
   1887 
   1888    case eSelectAction:
   1889      aName.AssignLiteral("select");
   1890      return;
   1891 
   1892    case eSwitchAction:
   1893      aName.AssignLiteral("switch");
   1894      return;
   1895 
   1896    case eSortAction:
   1897      aName.AssignLiteral("sort");
   1898      return;
   1899 
   1900    case eExpandAction:
   1901      if (State() & states::EXPANDED) {
   1902        aName.AssignLiteral("collapse");
   1903      } else {
   1904        aName.AssignLiteral("expand");
   1905      }
   1906      return;
   1907  }
   1908 
   1909  if (ActionAncestor()) {
   1910    aName.AssignLiteral("clickAncestor");
   1911    return;
   1912  }
   1913 }
   1914 
   1915 bool LocalAccessible::DoAction(uint8_t aIndex) const {
   1916  if (aIndex != 0) return false;
   1917 
   1918  if (HasPrimaryAction() || ActionAncestor()) {
   1919    DoCommand();
   1920    return true;
   1921  }
   1922 
   1923  return false;
   1924 }
   1925 
   1926 bool LocalAccessible::HasPrimaryAction() const {
   1927  return GetActionRule() != eNoAction;
   1928 }
   1929 
   1930 nsIContent* LocalAccessible::GetAtomicRegion() const {
   1931  nsIContent* loopContent = mContent;
   1932  nsAutoString atomic;
   1933  while (loopContent &&
   1934         (!loopContent->IsElement() ||
   1935          !nsAccUtils::GetARIAAttr(loopContent->AsElement(),
   1936                                   nsGkAtoms::aria_atomic, atomic))) {
   1937    loopContent = loopContent->GetParent();
   1938  }
   1939 
   1940  return atomic.EqualsLiteral("true") ? loopContent : nullptr;
   1941 }
   1942 
   1943 LocalAccessible* LocalAccessible::GetCommandForDetailsRelation() const {
   1944  dom::Element* targetEl = mContent->GetEffectiveCommandForElement();
   1945  if (!targetEl) {
   1946    return nullptr;
   1947  }
   1948  LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
   1949  if (!targetAcc) {
   1950    return nullptr;
   1951  }
   1952  // Relations on Command/CommandFor should only be for ShowPopover &
   1953  // TogglePopover commands.
   1954  if (const nsAttrValue* actionVal = Elm()->GetParsedAttr(nsGkAtoms::command)) {
   1955    if (actionVal && actionVal->Type() != nsAttrValue::eEnum) {
   1956      return nullptr;
   1957    }
   1958    auto command =
   1959        static_cast<dom::Element::Command>(actionVal->GetEnumValue());
   1960    if (command != dom::Element::Command::ShowPopover &&
   1961        command != dom::Element::Command::TogglePopover) {
   1962      return nullptr;
   1963    }
   1964  }
   1965  if (targetAcc->NextSibling() == this || targetAcc->PrevSibling() == this) {
   1966    return nullptr;
   1967  }
   1968  return targetAcc;
   1969 }
   1970 
   1971 LocalAccessible* LocalAccessible::GetPopoverTargetDetailsRelation() const {
   1972  dom::Element* targetEl = mContent->GetEffectivePopoverTargetElement();
   1973  if (!targetEl) {
   1974    return nullptr;
   1975  }
   1976  LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
   1977  if (!targetAcc) {
   1978    return nullptr;
   1979  }
   1980  // Even if the popovertarget is valid, there are a few cases where we must not
   1981  // expose it via the details relation.
   1982  if (const nsAttrValue* actionVal =
   1983          Elm()->GetParsedAttr(nsGkAtoms::popovertargetaction)) {
   1984    if (static_cast<PopoverTargetAction>(actionVal->GetEnumValue()) ==
   1985        PopoverTargetAction::Hide) {
   1986      return nullptr;
   1987    }
   1988  }
   1989  if (targetAcc->NextSibling() == this || targetAcc->PrevSibling() == this) {
   1990    return nullptr;
   1991  }
   1992  return targetAcc;
   1993 }
   1994 
   1995 LocalAccessible* LocalAccessible::GetAnchorPositionTargetDetailsRelation()
   1996    const {
   1997  if (!StaticPrefs::accessibility_anchorPositionedAsDetails_enabled()) {
   1998    return nullptr;
   1999  }
   2000  nsIFrame* positionedFrame = nsCoreUtils::GetPositionedFrameForAnchor(
   2001      mDoc->PresShellPtr(), GetFrame());
   2002  if (!positionedFrame) {
   2003    return nullptr;
   2004  }
   2005 
   2006  if (!nsCoreUtils::GetAnchorForPositionedFrame(mDoc->PresShellPtr(),
   2007                                                positionedFrame)) {
   2008    // There is no reciprocal, 1:1, anchor for this positioned frame.
   2009    return nullptr;
   2010  }
   2011 
   2012  LocalAccessible* targetAcc =
   2013      mDoc->GetAccessible(positionedFrame->GetContent());
   2014 
   2015  if (!targetAcc) {
   2016    return nullptr;
   2017  }
   2018 
   2019  if (targetAcc->Role() == roles::TOOLTIP) {
   2020    // A tooltip is never a valid target for details relation.
   2021    return nullptr;
   2022  }
   2023 
   2024  AssociatedElementsIterator describedby(mDoc, GetContent(),
   2025                                         nsGkAtoms::aria_describedby);
   2026  while (LocalAccessible* target = describedby.Next()) {
   2027    if (target == targetAcc) {
   2028      // An explicit description relation exists, so we don't want to create a
   2029      // details relation.
   2030      return nullptr;
   2031    }
   2032  }
   2033 
   2034  AssociatedElementsIterator labelledby(mDoc, GetContent(),
   2035                                        nsGkAtoms::aria_labelledby);
   2036  while (LocalAccessible* target = labelledby.Next()) {
   2037    if (target == targetAcc) {
   2038      // An explicit label relation exists, so we don't want to create a details
   2039      // relation.
   2040      return nullptr;
   2041    }
   2042  }
   2043 
   2044  dom::Element* anchorEl = targetAcc->Elm();
   2045  if (anchorEl && anchorEl->HasAttr(nsGkAtoms::aria_details)) {
   2046    // If the anchor has an explicit aria-details attribute, then we don't want
   2047    // to create a details relation.
   2048    return nullptr;
   2049  }
   2050 
   2051  dom::Element* targetEl = Elm();
   2052  if (targetEl && targetEl->HasAttr(nsGkAtoms::aria_details)) {
   2053    // If the target has an explicit aria-details attribute, then we don't want
   2054    // to create a details relation.
   2055    return nullptr;
   2056  }
   2057 
   2058  return targetAcc;
   2059 }
   2060 
   2061 Relation LocalAccessible::RelationByType(RelationType aType) const {
   2062  if (!HasOwnContent()) return Relation();
   2063 
   2064  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   2065 
   2066  // Relationships are defined on the same content node that the role would be
   2067  // defined on.
   2068  switch (aType) {
   2069    case RelationType::LABELLED_BY: {
   2070      Relation rel(new AssociatedElementsIterator(mDoc, mContent,
   2071                                                  nsGkAtoms::aria_labelledby));
   2072      if (mContent->IsHTMLElement()) {
   2073        rel.AppendIter(new HTMLLabelIterator(Document(), this));
   2074      }
   2075      rel.AppendIter(new XULLabelIterator(Document(), mContent));
   2076 
   2077      return rel;
   2078    }
   2079 
   2080    case RelationType::LABEL_FOR: {
   2081      Relation rel(new RelatedAccIterator(Document(), mContent,
   2082                                          nsGkAtoms::aria_labelledby));
   2083      if (mContent->IsXULElement(nsGkAtoms::label)) {
   2084        rel.AppendIter(
   2085            new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control));
   2086      }
   2087 
   2088      return rel;
   2089    }
   2090 
   2091    case RelationType::DESCRIBED_BY: {
   2092      Relation rel(new AssociatedElementsIterator(mDoc, mContent,
   2093                                                  nsGkAtoms::aria_describedby));
   2094      if (mContent->IsXULElement()) {
   2095        rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
   2096      }
   2097 
   2098      return rel;
   2099    }
   2100 
   2101    case RelationType::DESCRIPTION_FOR: {
   2102      Relation rel(new RelatedAccIterator(Document(), mContent,
   2103                                          nsGkAtoms::aria_describedby));
   2104 
   2105      // This affectively adds an optional control attribute to xul:description,
   2106      // which only affects accessibility, by allowing the description to be
   2107      // tied to a control.
   2108      if (mContent->IsXULElement(nsGkAtoms::description)) {
   2109        rel.AppendIter(
   2110            new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control));
   2111      }
   2112 
   2113      return rel;
   2114    }
   2115 
   2116    case RelationType::NODE_CHILD_OF: {
   2117      Relation rel;
   2118      // This is an ARIA tree or treegrid that doesn't use owns, so we need to
   2119      // get the parent the hard way.
   2120      if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
   2121                           roleMapEntry->role == roles::LISTITEM ||
   2122                           roleMapEntry->role == roles::ROW)) {
   2123        AccGroupInfo* groupInfo =
   2124            const_cast<LocalAccessible*>(this)->GetOrCreateGroupInfo();
   2125        if (groupInfo) {
   2126          Accessible* parent = groupInfo->ConceptualParent();
   2127          if (parent) {
   2128            MOZ_ASSERT(parent->IsLocal());
   2129            rel.AppendTarget(parent->AsLocal());
   2130          }
   2131        }
   2132      }
   2133 
   2134      // If this is an OOP iframe document, we can't support NODE_CHILD_OF
   2135      // here, since the iframe resides in a different process. This is fine
   2136      // because the client will then request the parent instead, which will be
   2137      // correctly handled by platform code.
   2138      if (XRE_IsContentProcess() && IsRoot()) {
   2139        dom::Document* doc =
   2140            const_cast<LocalAccessible*>(this)->AsDoc()->DocumentNode();
   2141        dom::BrowsingContext* bc = doc->GetBrowsingContext();
   2142        MOZ_ASSERT(bc);
   2143        if (!bc->Top()->IsInProcess()) {
   2144          return rel;
   2145        }
   2146      }
   2147 
   2148      // If accessible is in its own Window, or is the root of a document,
   2149      // then we should provide NODE_CHILD_OF relation so that MSAA clients
   2150      // can easily get to true parent instead of getting to oleacc's
   2151      // ROLE_WINDOW accessible which will prevent us from going up further
   2152      // (because it is system generated and has no idea about the hierarchy
   2153      // above it).
   2154      if (nsIFrame* frame = GetFrame()) {
   2155        ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
   2156        if (scrollContainerFrame || frame->GetOwnWidget() ||
   2157            !frame->GetParent()) {
   2158          rel.AppendTarget(LocalParent());
   2159        }
   2160      }
   2161 
   2162      return rel;
   2163    }
   2164 
   2165    case RelationType::NODE_PARENT_OF: {
   2166      // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
   2167      // also can be organized by groups.
   2168      if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
   2169                           roleMapEntry->role == roles::LISTITEM ||
   2170                           roleMapEntry->role == roles::ROW ||
   2171                           roleMapEntry->role == roles::OUTLINE ||
   2172                           roleMapEntry->role == roles::LIST ||
   2173                           roleMapEntry->role == roles::TREE_TABLE)) {
   2174        return Relation(new ItemIterator(this));
   2175      }
   2176 
   2177      return Relation();
   2178    }
   2179 
   2180    case RelationType::CONTROLLED_BY: {
   2181      Relation rel(new RelatedAccIterator(Document(), mContent,
   2182                                          nsGkAtoms::aria_controls));
   2183 
   2184      RelatedAccIterator owners(Document(), mContent, nsGkAtoms::aria_owns);
   2185      if (LocalAccessible* owner = owners.Next()) {
   2186        if (nsAccUtils::IsEditableARIACombobox(owner)) {
   2187          MOZ_ASSERT(!IsRelocated(),
   2188                     "Child is not relocated to editable combobox");
   2189          rel.AppendTarget(owner);
   2190        }
   2191      }
   2192 
   2193      return rel;
   2194    }
   2195    case RelationType::CONTROLLER_FOR: {
   2196      Relation rel(new AssociatedElementsIterator(mDoc, mContent,
   2197                                                  nsGkAtoms::aria_controls));
   2198      rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
   2199      if (nsAccUtils::IsEditableARIACombobox(this)) {
   2200        AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::aria_owns);
   2201        while (Accessible* owned_child = iter.Next()) {
   2202          MOZ_ASSERT(!owned_child->AsLocal()->IsRelocated());
   2203          rel.AppendTarget(owned_child->AsLocal());
   2204        }
   2205      }
   2206      return rel;
   2207    }
   2208 
   2209    case RelationType::FLOWS_TO:
   2210      return Relation(new AssociatedElementsIterator(mDoc, mContent,
   2211                                                     nsGkAtoms::aria_flowto));
   2212 
   2213    case RelationType::FLOWS_FROM:
   2214      return Relation(
   2215          new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_flowto));
   2216 
   2217    case RelationType::MEMBER_OF: {
   2218      if (Role() == roles::RADIOBUTTON) {
   2219        /* If we see a radio button role here, we're dealing with an aria
   2220         * radio button (because input=radio buttons are
   2221         * HTMLRadioButtonAccessibles) */
   2222        Relation rel = Relation();
   2223        LocalAccessible* currParent = LocalParent();
   2224        while (currParent && currParent->Role() != roles::RADIO_GROUP) {
   2225          currParent = currParent->LocalParent();
   2226        }
   2227 
   2228        if (currParent && currParent->Role() == roles::RADIO_GROUP) {
   2229          /* If we found a radiogroup parent, search for all
   2230           * roles::RADIOBUTTON children and add them to our relation.
   2231           * This search will include the radio button this method
   2232           * was called from, which is expected. */
   2233          Pivot p = Pivot(currParent);
   2234          PivotRoleRule rule(roles::RADIOBUTTON);
   2235          Accessible* match = p.Next(currParent, rule);
   2236          while (match) {
   2237            MOZ_ASSERT(match->IsLocal(),
   2238                       "We shouldn't find any remote accs while building our "
   2239                       "relation!");
   2240            rel.AppendTarget(match->AsLocal());
   2241            match = p.Next(match, rule);
   2242          }
   2243        }
   2244 
   2245        /* By webkit's standard, aria radio buttons do not get grouped
   2246         * if they lack a group parent, so we return an empty
   2247         * relation here if the above check fails. */
   2248 
   2249        return rel;
   2250      }
   2251 
   2252      return Relation(mDoc, GetAtomicRegion());
   2253    }
   2254 
   2255    case RelationType::LINKS_TO: {
   2256      Relation rel = Relation();
   2257      if (Role() == roles::LINK) {
   2258        dom::HTMLAnchorElement* anchor =
   2259            dom::HTMLAnchorElement::FromNode(mContent);
   2260        if (!anchor) {
   2261          return rel;
   2262        }
   2263        // If this node is an anchor element, query its hash to find the
   2264        // target.
   2265        nsAutoCString hash;
   2266        anchor->GetHash(hash);
   2267        if (hash.IsEmpty()) {
   2268          return rel;
   2269        }
   2270 
   2271        // GetHash returns an ID or name with a leading '#', trim it so we can
   2272        // search the doc by ID or name alone.
   2273        NS_ConvertUTF8toUTF16 hash16(Substring(hash, 1));
   2274        if (dom::Element* elm = mContent->OwnerDoc()->GetElementById(hash16)) {
   2275          rel.AppendTarget(mDoc->GetAccessibleOrContainer(elm));
   2276        } else if (nsCOMPtr<nsINodeList> list =
   2277                       mContent->OwnerDoc()->GetElementsByName(hash16)) {
   2278          // Loop through the named nodes looking for the first anchor
   2279          uint32_t length = list->Length();
   2280          for (uint32_t i = 0; i < length; i++) {
   2281            nsIContent* node = list->Item(i);
   2282            if (node->IsHTMLElement(nsGkAtoms::a)) {
   2283              rel.AppendTarget(mDoc->GetAccessibleOrContainer(node));
   2284              break;
   2285            }
   2286          }
   2287        }
   2288      }
   2289 
   2290      return rel;
   2291    }
   2292 
   2293    case RelationType::SUBWINDOW_OF:
   2294    case RelationType::EMBEDS:
   2295    case RelationType::EMBEDDED_BY:
   2296    case RelationType::POPUP_FOR:
   2297    case RelationType::PARENT_WINDOW_OF:
   2298      return Relation();
   2299 
   2300    case RelationType::DEFAULT_BUTTON: {
   2301      if (mContent->IsHTMLElement()) {
   2302        // HTML form controls implements nsIFormControl interface.
   2303        if (auto* control = nsIFormControl::FromNode(mContent)) {
   2304          if (dom::HTMLFormElement* form = control->GetForm()) {
   2305            return Relation(mDoc, form->GetDefaultSubmitElement());
   2306          }
   2307        }
   2308      } else {
   2309        // In XUL, use first <button default="true" .../> in the document
   2310        dom::Document* doc = mContent->OwnerDoc();
   2311        nsIContent* buttonEl = nullptr;
   2312        if (doc->AllowXULXBL()) {
   2313          nsCOMPtr<nsIHTMLCollection> possibleDefaultButtons =
   2314              doc->GetElementsByAttribute(u"default"_ns, u"true"_ns);
   2315          if (possibleDefaultButtons) {
   2316            uint32_t length = possibleDefaultButtons->Length();
   2317            // Check for button in list of default="true" elements
   2318            for (uint32_t count = 0; count < length && !buttonEl; count++) {
   2319              nsIContent* item = possibleDefaultButtons->Item(count);
   2320              RefPtr<nsIDOMXULButtonElement> button =
   2321                  item->IsElement() ? item->AsElement()->AsXULButton()
   2322                                    : nullptr;
   2323              if (button) {
   2324                buttonEl = item;
   2325              }
   2326            }
   2327          }
   2328          return Relation(mDoc, buttonEl);
   2329        }
   2330      }
   2331      return Relation();
   2332    }
   2333 
   2334    case RelationType::CONTAINING_DOCUMENT:
   2335      return Relation(mDoc);
   2336 
   2337    case RelationType::CONTAINING_TAB_PANE: {
   2338      nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
   2339      if (docShell) {
   2340        // Walk up the parent chain without crossing the boundary at which item
   2341        // types change, preventing us from walking up out of tab content.
   2342        nsCOMPtr<nsIDocShellTreeItem> root;
   2343        docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
   2344        if (root) {
   2345          // If the item type is typeContent, we assume we are in browser tab
   2346          // content. Note, this includes content such as about:addons,
   2347          // for consistency.
   2348          if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
   2349            return Relation(nsAccUtils::GetDocAccessibleFor(root));
   2350          }
   2351        }
   2352      }
   2353      return Relation();
   2354    }
   2355 
   2356    case RelationType::CONTAINING_APPLICATION:
   2357      return Relation(ApplicationAcc());
   2358 
   2359    case RelationType::DETAILS: {
   2360      if (mContent->IsElement() &&
   2361          nsAccUtils::HasARIAAttr(mContent->AsElement(),
   2362                                  nsGkAtoms::aria_details)) {
   2363        return Relation(new AssociatedElementsIterator(
   2364            mDoc, mContent, nsGkAtoms::aria_details));
   2365      }
   2366      if (LocalAccessible* target = GetCommandForDetailsRelation()) {
   2367        return Relation(target);
   2368      }
   2369      if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) {
   2370        return Relation(target);
   2371      }
   2372      if (LocalAccessible* target = GetAnchorPositionTargetDetailsRelation()) {
   2373        if (nsAccUtils::IsValidDetailsTargetForAnchor(target, this)) {
   2374          return Relation(target);
   2375        }
   2376      }
   2377 
   2378      return Relation();
   2379    }
   2380 
   2381    case RelationType::DETAILS_FOR: {
   2382      Relation rel(
   2383          new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
   2384      RelatedAccIterator popover_invokers(mDoc, mContent,
   2385                                          nsGkAtoms::popovertarget);
   2386      while (Accessible* invoker = popover_invokers.Next()) {
   2387        // We should only expose DETAILS_FOR if DETAILS was exposed on the
   2388        // invoker. However, DETAILS exposure on popover invokers is
   2389        // conditional.
   2390        if (invoker->AsLocal()->GetPopoverTargetDetailsRelation()) {
   2391          MOZ_ASSERT(invoker->AsLocal()->GetPopoverTargetDetailsRelation() ==
   2392                     this);
   2393          rel.AppendTarget(invoker);
   2394        }
   2395      }
   2396      RelatedAccIterator command_invokers(mDoc, mContent,
   2397                                          nsGkAtoms::commandfor);
   2398      while (Accessible* invoker = command_invokers.Next()) {
   2399        // We should only expose DETAILS_FOR if DETAILS was exposed on the
   2400        // invoker. However, DETAILS exposure on popover invokers is
   2401        // conditional.
   2402        if (invoker->AsLocal()->GetCommandForDetailsRelation()) {
   2403          MOZ_ASSERT(invoker->AsLocal()->GetCommandForDetailsRelation() ==
   2404                     this);
   2405          rel.AppendTarget(invoker);
   2406        }
   2407      }
   2408 
   2409      // Check early if the accessible is a tooltip. If so, it can never be a
   2410      // valid target for an anchor's details relation.
   2411      if (StaticPrefs::accessibility_anchorPositionedAsDetails_enabled() &&
   2412          Role() != roles::TOOLTIP) {
   2413        if (const nsIFrame* anchorFrame =
   2414                nsCoreUtils::GetAnchorForPositionedFrame(mDoc->PresShellPtr(),
   2415                                                         GetFrame())) {
   2416          LocalAccessible* anchorAcc =
   2417              mDoc->GetAccessible(anchorFrame->GetContent());
   2418          if (anchorAcc &&
   2419              anchorAcc->GetAnchorPositionTargetDetailsRelation() == this &&
   2420              nsAccUtils::IsValidDetailsTargetForAnchor(this, anchorAcc)) {
   2421            rel.AppendTarget(anchorAcc);
   2422          }
   2423        }
   2424      }
   2425 
   2426      return rel;
   2427    }
   2428 
   2429    case RelationType::ERRORMSG:
   2430      return Relation(new AssociatedElementsIterator(
   2431          mDoc, mContent, nsGkAtoms::aria_errormessage));
   2432 
   2433    case RelationType::ERRORMSG_FOR:
   2434      return Relation(
   2435          new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
   2436 
   2437    case RelationType::ACTION:
   2438      return Relation(new AssociatedElementsIterator(mDoc, mContent,
   2439                                                     nsGkAtoms::aria_actions));
   2440 
   2441    case RelationType::ACTION_FOR:
   2442      return Relation(
   2443          new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_actions));
   2444 
   2445    default:
   2446      return Relation();
   2447  }
   2448 }
   2449 
   2450 void LocalAccessible::GetNativeInterface(void** aNativeAccessible) {}
   2451 
   2452 void LocalAccessible::DoCommand(uint32_t aActionIndex) const {
   2453  NS_DispatchToMainThread(NS_NewRunnableFunction(
   2454      "LocalAccessible::DispatchClickEvent",
   2455      [aActionIndex, acc = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   2456        acc->DispatchClickEvent(aActionIndex);
   2457      }));
   2458 }
   2459 
   2460 void LocalAccessible::DispatchClickEvent(uint32_t aActionIndex) const {
   2461  if (IsDefunct()) return;
   2462  MOZ_ASSERT(mContent);
   2463 
   2464  RefPtr<PresShell> presShell = mDoc->PresShellPtr();
   2465 
   2466  // Scroll into view.
   2467  presShell->ScrollContentIntoView(mContent, ScrollAxis(), ScrollAxis(),
   2468                                   ScrollFlags::ScrollOverflowHidden);
   2469 
   2470  AutoWeakFrame frame = GetFrame();
   2471  if (!frame) {
   2472    return;
   2473  }
   2474 
   2475  // We use RelativeBounds rather than querying the frame directly because of
   2476  // special cases like image map areas which don't have their own frame.
   2477  // RelativeBounds overrides handle these special cases.
   2478  nsIFrame* boundingFrame = nullptr;
   2479  nsRect rect = RelativeBounds(&boundingFrame);
   2480  MOZ_ASSERT(boundingFrame);
   2481 
   2482  // Compute x and y coordinates in dev pixels relative to the widget.
   2483  nsPoint offsetToWidget;
   2484  nsCOMPtr<nsIWidget> widget = boundingFrame->GetNearestWidget(offsetToWidget);
   2485  if (!widget) {
   2486    return;
   2487  }
   2488 
   2489  rect += offsetToWidget;
   2490  RefPtr<nsPresContext> presContext = presShell->GetPresContext();
   2491  int32_t x = presContext->AppUnitsToDevPixels(rect.x + rect.width / 2);
   2492  int32_t y = presContext->AppUnitsToDevPixels(rect.y + rect.height / 2);
   2493 
   2494  // Simulate a touch interaction by dispatching touch events with mouse events.
   2495  // Even though we calculated x and y using the bounding frame, we must use the
   2496  // primary frame for the event. In most cases, these two frames will be
   2497  // different, but they are the same for special cases such as image map areas
   2498  // which don't have their own frame.
   2499  nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, mContent, frame, presShell,
   2500                                  widget);
   2501 
   2502  // This isn't needed once bug 1924790 is fixed.
   2503  mContent->OwnerDoc()->NotifyUserGestureActivation();
   2504 
   2505  nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, mContent, frame, presShell,
   2506                                  widget);
   2507  nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, mContent, frame, presShell,
   2508                                  widget);
   2509  nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, mContent, frame, presShell,
   2510                                  widget);
   2511 }
   2512 
   2513 void LocalAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX,
   2514                                    int32_t aY) {
   2515  nsIFrame* frame = GetFrame();
   2516  if (!frame) return;
   2517 
   2518  LayoutDeviceIntPoint coords =
   2519      nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
   2520 
   2521  nsIFrame* parentFrame = frame;
   2522  while ((parentFrame = parentFrame->GetParent())) {
   2523    nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
   2524  }
   2525 }
   2526 
   2527 bool LocalAccessible::IsScrollable() const {
   2528  const auto [scrollPosition, scrollRange] = mDoc->ComputeScrollData(this);
   2529  return scrollRange.width > 0 || scrollRange.height > 0;
   2530 }
   2531 
   2532 bool LocalAccessible::IsPopover() const {
   2533  dom::Element* el = Elm();
   2534  return el && el->IsHTMLElement() && el->HasAttr(nsGkAtoms::popover);
   2535 }
   2536 
   2537 bool LocalAccessible::IsEditable() const {
   2538  dom::Element* el = Elm();
   2539  return el && el->State().HasState(dom::ElementState::READWRITE);
   2540 }
   2541 
   2542 void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
   2543                                   uint32_t aLength) {
   2544  // Return text representation of non-text accessible within hypertext
   2545  // accessible. Text accessible overrides this method to return enclosed text.
   2546  if (aStartOffset != 0 || aLength == 0) return;
   2547 
   2548  MOZ_ASSERT(mParent,
   2549             "Called on accessible unbound from tree. Result can be wrong.");
   2550  nsIFrame* frame = GetFrame();
   2551  // We handle something becoming display: none async, which means we won't have
   2552  // a frame when we're queuing text removed events. Thus, it's important that
   2553  // we produce text here even if there's no frame. Otherwise, we won't fire a
   2554  // text removed event at all, which might leave client caches (e.g. NVDA
   2555  // virtual buffers) with dead nodes.
   2556  if (IsHTMLBr() || (frame && frame->IsBrFrame())) {
   2557    aText += kForcedNewLineChar;
   2558  } else if (mParent && nsAccUtils::MustPrune(mParent)) {
   2559    // Expose the embedded object accessible as imaginary embedded object
   2560    // character if its parent hypertext accessible doesn't expose children to
   2561    // AT.
   2562    aText += kImaginaryEmbeddedObjectChar;
   2563  } else {
   2564    aText += kEmbeddedObjectChar;
   2565  }
   2566 }
   2567 
   2568 void LocalAccessible::Shutdown() {
   2569  // Mark the accessible as defunct, invalidate the child count and pointers to
   2570  // other accessibles, also make sure none of its children point to this
   2571  // parent
   2572  mStateFlags |= eIsDefunct;
   2573 
   2574  int32_t childCount = mChildren.Length();
   2575  for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
   2576    mChildren.ElementAt(childIdx)->UnbindFromParent();
   2577  }
   2578  mChildren.Clear();
   2579 
   2580  mEmbeddedObjCollector = nullptr;
   2581 
   2582  if (mParent) mParent->RemoveChild(this);
   2583 
   2584  mContent = nullptr;
   2585  mDoc = nullptr;
   2586  if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) {
   2587    SelectionMgr()->ResetCaretOffset();
   2588  }
   2589 }
   2590 
   2591 // LocalAccessible protected
   2592 ENameValueFlag LocalAccessible::ARIAName(nsString& aName) const {
   2593  // 'slot' elements should ignore aria-label and aria-labelledby.
   2594  if (mContent->IsHTMLElement(nsGkAtoms::slot)) {
   2595    return eNameOK;
   2596  }
   2597  // aria-labelledby now takes precedence over aria-label
   2598  bool notSimpleRelation = nsTextEquivUtils::GetTextEquivFromIDRefs(
   2599      this, nsGkAtoms::aria_labelledby, aName);
   2600  aName.CompressWhitespace();
   2601 
   2602  if (!aName.IsEmpty()) {
   2603    return notSimpleRelation ? eNameOK : eNameFromRelations;
   2604  }
   2605 
   2606  if (mContent->IsElement() &&
   2607      nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::aria_label,
   2608                              aName)) {
   2609    aName.CompressWhitespace();
   2610  }
   2611 
   2612  return eNameOK;
   2613 }
   2614 
   2615 // LocalAccessible protected
   2616 bool LocalAccessible::ARIADescription(nsString& aDescription) const {
   2617  // aria-describedby takes precedence over aria-description
   2618  nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
   2619                                           aDescription);
   2620  aDescription.CompressWhitespace();
   2621 
   2622  if (aDescription.IsEmpty() && mContent->IsElement() &&
   2623      nsAccUtils::GetARIAAttr(mContent->AsElement(),
   2624                              nsGkAtoms::aria_description, aDescription)) {
   2625    aDescription.CompressWhitespace();
   2626  }
   2627 
   2628  return !aDescription.IsEmpty();
   2629 }
   2630 
   2631 // LocalAccessible protected
   2632 bool LocalAccessible::Tooltip(nsString& aTooltip) const {
   2633  if (!HasOwnContent()) {
   2634    return false;
   2635  }
   2636 
   2637  if (mContent->IsHTMLElement()) {
   2638    mContent->AsElement()->GetAttr(nsGkAtoms::title, aTooltip);
   2639    aTooltip.CompressWhitespace();
   2640    return !aTooltip.IsEmpty();
   2641  } else if (mContent->IsXULElement()) {
   2642    mContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, aTooltip);
   2643    aTooltip.CompressWhitespace();
   2644    return !aTooltip.IsEmpty();
   2645  } else if (mContent->IsSVGElement()) {
   2646    // If user agents need to choose among multiple 'desc' or 'title'
   2647    // elements for processing, the user agent shall choose the first one.
   2648    for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
   2649         childElm = childElm->GetNextSibling()) {
   2650      if (childElm->IsSVGElement(nsGkAtoms::desc)) {
   2651        nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aTooltip);
   2652        aTooltip.CompressWhitespace();
   2653        return !aTooltip.IsEmpty();
   2654      }
   2655    }
   2656  }
   2657  return false;
   2658 }
   2659 
   2660 // LocalAccessible protected
   2661 ENameValueFlag LocalAccessible::NativeName(nsString& aName) const {
   2662  if (mContent->IsHTMLElement()) {
   2663    LocalAccessible* label = nullptr;
   2664    HTMLLabelIterator iter(Document(), this);
   2665    bool notSimpleRelation = false;
   2666    while ((label = iter.Next())) {
   2667      notSimpleRelation |= nsTextEquivUtils::AppendTextEquivFromContent(
   2668          this, label->GetContent(), &aName);
   2669      aName.CompressWhitespace();
   2670    }
   2671 
   2672    if (!aName.IsEmpty()) {
   2673      return notSimpleRelation ? eNameOK : eNameFromRelations;
   2674    }
   2675 
   2676    NameFromAssociatedXULLabel(mDoc, mContent, aName);
   2677    if (!aName.IsEmpty()) {
   2678      return eNameOK;
   2679    }
   2680 
   2681    // We return eNameFromSubtree here to indicate that if there is no native
   2682    // name we will calculate the name from the subtree next. This is useful for
   2683    // noting where the name will come from in cases like name change
   2684    // notifications.
   2685    return eNameFromSubtree;
   2686  }
   2687 
   2688  if (mContent->IsXULElement()) {
   2689    XULElmName(mDoc, mContent, aName);
   2690    if (!aName.IsEmpty()) return eNameOK;
   2691 
   2692    // We return eNameFromSubtree here to indicate that if there is no native
   2693    // name we will calculate the name from the subtree next. See above.
   2694    return eNameFromSubtree;
   2695  }
   2696 
   2697  if (mContent->IsSVGElement()) {
   2698    // If user agents need to choose among multiple 'desc' or 'title'
   2699    // elements for processing, the user agent shall choose the first one.
   2700    for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
   2701         childElm = childElm->GetNextSibling()) {
   2702      if (childElm->IsSVGElement(nsGkAtoms::title)) {
   2703        nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
   2704        return eNameOK;
   2705      }
   2706    }
   2707  }
   2708 
   2709  return eNameOK;
   2710 }
   2711 
   2712 // LocalAccessible protected
   2713 void LocalAccessible::NativeDescription(nsString& aDescription) const {
   2714  bool isXUL = mContent->IsXULElement();
   2715  if (isXUL) {
   2716    // Try XUL <description control="[id]">description text</description>
   2717    XULDescriptionIterator iter(Document(), mContent);
   2718    LocalAccessible* descr = nullptr;
   2719    while ((descr = iter.Next())) {
   2720      nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
   2721                                                   &aDescription);
   2722    }
   2723  }
   2724 }
   2725 
   2726 // LocalAccessible protected
   2727 void LocalAccessible::BindToParent(LocalAccessible* aParent,
   2728                                   uint32_t aIndexInParent) {
   2729  MOZ_ASSERT(aParent, "This method isn't used to set null parent");
   2730  MOZ_ASSERT(!mParent, "The child was expected to be moved");
   2731 
   2732 #ifdef A11Y_LOG
   2733  if (mParent) {
   2734    logging::TreeInfo("BindToParent: stealing accessible", 0, "old parent",
   2735                      mParent, "new parent", aParent, "child", this, nullptr);
   2736  }
   2737 #endif
   2738 
   2739  mParent = aParent;
   2740  mIndexInParent = aIndexInParent;
   2741 
   2742  if (mParent->HasNameDependent() || mParent->IsXULListItem() ||
   2743      RelationByType(RelationType::LABEL_FOR).Next() ||
   2744      nsTextEquivUtils::HasNameRule(mParent, eNameFromSubtreeRule)) {
   2745    mContextFlags |= eHasNameDependent;
   2746  } else {
   2747    mContextFlags &= ~eHasNameDependent;
   2748  }
   2749  if (mParent->HasDescriptionDependent() ||
   2750      RelationByType(RelationType::DESCRIPTION_FOR).Next()) {
   2751    mContextFlags |= eHasDescriptionDependent;
   2752  } else {
   2753    mContextFlags &= ~eHasDescriptionDependent;
   2754  }
   2755 
   2756  // Add name/description dependent flags for dependent content once
   2757  // a name/description provider is added to doc.
   2758  Relation rel = RelationByType(RelationType::LABELLED_BY);
   2759  LocalAccessible* relTarget = nullptr;
   2760  while ((relTarget = rel.LocalNext())) {
   2761    if (!relTarget->HasNameDependent()) {
   2762      relTarget->ModifySubtreeContextFlags(eHasNameDependent, true);
   2763    }
   2764  }
   2765 
   2766  rel = RelationByType(RelationType::DESCRIBED_BY);
   2767  while ((relTarget = rel.LocalNext())) {
   2768    if (!relTarget->HasDescriptionDependent()) {
   2769      relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
   2770    }
   2771  }
   2772 
   2773  mContextFlags |=
   2774      static_cast<uint32_t>((mParent->IsAlert() || mParent->IsInsideAlert())) &
   2775      eInsideAlert;
   2776 
   2777  if (IsTableCell()) {
   2778    CachedTableAccessible::Invalidate(this);
   2779  }
   2780 }
   2781 
   2782 // LocalAccessible protected
   2783 void LocalAccessible::UnbindFromParent() {
   2784  // We do this here to handle document shutdown and an Accessible being moved.
   2785  // We do this for subtree removal in DocAccessible::UncacheChildrenInSubtree.
   2786  if (IsTable() || IsTableCell()) {
   2787    CachedTableAccessible::Invalidate(this);
   2788  }
   2789 
   2790  mParent = nullptr;
   2791  mIndexInParent = -1;
   2792  mIndexOfEmbeddedChild = -1;
   2793 
   2794  delete mGroupInfo;
   2795  mGroupInfo = nullptr;
   2796  mContextFlags &= ~eHasNameDependent & ~eInsideAlert;
   2797 }
   2798 
   2799 ////////////////////////////////////////////////////////////////////////////////
   2800 // LocalAccessible public methods
   2801 
   2802 RootAccessible* LocalAccessible::RootAccessible() const {
   2803  nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
   2804  NS_ASSERTION(docShell, "No docshell for mContent");
   2805  if (!docShell) {
   2806    return nullptr;
   2807  }
   2808 
   2809  nsCOMPtr<nsIDocShellTreeItem> root;
   2810  docShell->GetInProcessRootTreeItem(getter_AddRefs(root));
   2811  NS_ASSERTION(root, "No root content tree item");
   2812  if (!root) {
   2813    return nullptr;
   2814  }
   2815 
   2816  DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
   2817  return docAcc ? docAcc->AsRoot() : nullptr;
   2818 }
   2819 
   2820 nsIFrame* LocalAccessible::GetFrame() const {
   2821  return mContent ? mContent->GetPrimaryFrame() : nullptr;
   2822 }
   2823 
   2824 nsINode* LocalAccessible::GetNode() const { return mContent; }
   2825 
   2826 dom::Element* LocalAccessible::Elm() const {
   2827  return dom::Element::FromNodeOrNull(mContent);
   2828 }
   2829 
   2830 void LocalAccessible::Language(nsAString& aLanguage) {
   2831  aLanguage.Truncate();
   2832 
   2833  if (!mDoc) return;
   2834 
   2835  nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
   2836  if (aLanguage.IsEmpty()) {  // Nothing found, so use document's language
   2837    mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
   2838                                        aLanguage);
   2839  }
   2840 }
   2841 
   2842 bool LocalAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) {
   2843  if (!aChild) return false;
   2844 
   2845  if (aIndex == mChildren.Length()) {
   2846    // XXX(Bug 1631371) Check if this should use a fallible operation as it
   2847    // pretended earlier.
   2848    mChildren.AppendElement(aChild);
   2849  } else {
   2850    // XXX(Bug 1631371) Check if this should use a fallible operation as it
   2851    // pretended earlier.
   2852    mChildren.InsertElementAt(aIndex, aChild);
   2853 
   2854    MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change");
   2855 
   2856    for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
   2857      mChildren[idx]->mIndexInParent = idx;
   2858    }
   2859  }
   2860 
   2861  if (aChild->IsText()) {
   2862    mStateFlags |= eHasTextKids;
   2863  }
   2864 
   2865  aChild->BindToParent(this, aIndex);
   2866  return true;
   2867 }
   2868 
   2869 bool LocalAccessible::RemoveChild(LocalAccessible* aChild) {
   2870  MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
   2871  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent");
   2872  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent");
   2873  MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,
   2874                        "Unbound child was given");
   2875  MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() ||
   2876                            aChild->IsDoc() || IsApplication(),
   2877                        "Illicit children change");
   2878 
   2879  int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
   2880  if (mChildren.SafeElementAt(index) != aChild) {
   2881    MOZ_ASSERT_UNREACHABLE("A wrong child index");
   2882    index = mChildren.IndexOf(aChild);
   2883    if (index == -1) {
   2884      MOZ_ASSERT_UNREACHABLE("No child was found");
   2885      return false;
   2886    }
   2887  }
   2888 
   2889  aChild->UnbindFromParent();
   2890  mChildren.RemoveElementAt(index);
   2891 
   2892  for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
   2893    mChildren[idx]->mIndexInParent = idx;
   2894  }
   2895 
   2896  return true;
   2897 }
   2898 
   2899 void LocalAccessible::RelocateChild(uint32_t aNewIndex,
   2900                                    LocalAccessible* aChild) {
   2901  MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
   2902  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this,
   2903                        "A child from different subtree was given");
   2904  MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,
   2905                        "Unbound child was given");
   2906  MOZ_DIAGNOSTIC_ASSERT(
   2907      aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild,
   2908      "Wrong index in parent");
   2909  MOZ_DIAGNOSTIC_ASSERT(
   2910      static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,
   2911      "No move, same index");
   2912  MOZ_DIAGNOSTIC_ASSERT(aNewIndex <= mChildren.Length(),
   2913                        "Wrong new index was given");
   2914 
   2915  RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false);
   2916  if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
   2917    aChild->SetHideEventTarget(true);
   2918  }
   2919 
   2920  mEmbeddedObjCollector = nullptr;
   2921  mChildren.RemoveElementAt(aChild->mIndexInParent);
   2922 
   2923  uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
   2924 
   2925  // If the child is moved after its current position.
   2926  if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
   2927    startIdx = aChild->mIndexInParent;
   2928    if (aNewIndex == mChildren.Length() + 1) {
   2929      // The child is moved to the end.
   2930      mChildren.AppendElement(aChild);
   2931      endIdx = mChildren.Length() - 1;
   2932    } else {
   2933      mChildren.InsertElementAt(aNewIndex - 1, aChild);
   2934      endIdx = aNewIndex;
   2935    }
   2936  } else {
   2937    // The child is moved prior its current position.
   2938    mChildren.InsertElementAt(aNewIndex, aChild);
   2939  }
   2940 
   2941  for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
   2942    mChildren[idx]->mIndexInParent = idx;
   2943    mChildren[idx]->mIndexOfEmbeddedChild = -1;
   2944  }
   2945 
   2946  for (uint32_t idx = 0; idx < mChildren.Length(); idx++) {
   2947    mChildren[idx]->mStateFlags |= eGroupInfoDirty;
   2948  }
   2949 
   2950  RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild);
   2951  DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
   2952  MOZ_ASSERT(added);
   2953  aChild->SetShowEventTarget(true);
   2954 }
   2955 
   2956 LocalAccessible* LocalAccessible::LocalChildAt(uint32_t aIndex) const {
   2957  LocalAccessible* child = mChildren.SafeElementAt(aIndex, nullptr);
   2958  if (!child) return nullptr;
   2959 
   2960 #ifdef DEBUG
   2961  LocalAccessible* realParent = child->mParent;
   2962  NS_ASSERTION(!realParent || realParent == this,
   2963               "Two accessibles have the same first child accessible!");
   2964 #endif
   2965 
   2966  return child;
   2967 }
   2968 
   2969 uint32_t LocalAccessible::ChildCount() const { return mChildren.Length(); }
   2970 
   2971 int32_t LocalAccessible::IndexInParent() const { return mIndexInParent; }
   2972 
   2973 uint32_t LocalAccessible::EmbeddedChildCount() {
   2974  if (mStateFlags & eHasTextKids) {
   2975    if (!mEmbeddedObjCollector) {
   2976      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
   2977    }
   2978    return mEmbeddedObjCollector->Count();
   2979  }
   2980 
   2981  return ChildCount();
   2982 }
   2983 
   2984 Accessible* LocalAccessible::EmbeddedChildAt(uint32_t aIndex) {
   2985  if (mStateFlags & eHasTextKids) {
   2986    if (!mEmbeddedObjCollector) {
   2987      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
   2988    }
   2989    return mEmbeddedObjCollector.get()
   2990               ? mEmbeddedObjCollector->GetAccessibleAt(aIndex)
   2991               : nullptr;
   2992  }
   2993 
   2994  return ChildAt(aIndex);
   2995 }
   2996 
   2997 int32_t LocalAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
   2998  MOZ_ASSERT(aChild->IsLocal());
   2999  if (mStateFlags & eHasTextKids) {
   3000    if (!mEmbeddedObjCollector) {
   3001      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
   3002    }
   3003    return mEmbeddedObjCollector.get()
   3004               ? mEmbeddedObjCollector->GetIndexAt(aChild->AsLocal())
   3005               : -1;
   3006  }
   3007 
   3008  return GetIndexOf(aChild->AsLocal());
   3009 }
   3010 
   3011 ////////////////////////////////////////////////////////////////////////////////
   3012 // HyperLinkAccessible methods
   3013 
   3014 bool LocalAccessible::IsLink() const {
   3015  // Every embedded accessible within hypertext accessible implements
   3016  // hyperlink interface.
   3017  return mParent && mParent->IsHyperText() && !IsText();
   3018 }
   3019 
   3020 ////////////////////////////////////////////////////////////////////////////////
   3021 // SelectAccessible
   3022 
   3023 void LocalAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
   3024  AccIterator iter(this, filters::GetSelected);
   3025  LocalAccessible* selected = nullptr;
   3026  while ((selected = iter.Next())) aItems->AppendElement(selected);
   3027 }
   3028 
   3029 uint32_t LocalAccessible::SelectedItemCount() {
   3030  uint32_t count = 0;
   3031  AccIterator iter(this, filters::GetSelected);
   3032  LocalAccessible* selected = nullptr;
   3033  while ((selected = iter.Next())) ++count;
   3034 
   3035  return count;
   3036 }
   3037 
   3038 Accessible* LocalAccessible::GetSelectedItem(uint32_t aIndex) {
   3039  AccIterator iter(this, filters::GetSelected);
   3040  LocalAccessible* selected = nullptr;
   3041 
   3042  uint32_t index = 0;
   3043  while ((selected = iter.Next()) && index < aIndex) index++;
   3044 
   3045  return selected;
   3046 }
   3047 
   3048 bool LocalAccessible::IsItemSelected(uint32_t aIndex) {
   3049  uint32_t index = 0;
   3050  AccIterator iter(this, filters::GetSelectable);
   3051  LocalAccessible* selected = nullptr;
   3052  while ((selected = iter.Next()) && index < aIndex) index++;
   3053 
   3054  return selected && selected->State() & states::SELECTED;
   3055 }
   3056 
   3057 bool LocalAccessible::AddItemToSelection(uint32_t aIndex) {
   3058  uint32_t index = 0;
   3059  AccIterator iter(this, filters::GetSelectable);
   3060  LocalAccessible* selected = nullptr;
   3061  while ((selected = iter.Next()) && index < aIndex) index++;
   3062 
   3063  if (selected) selected->SetSelected(true);
   3064 
   3065  return static_cast<bool>(selected);
   3066 }
   3067 
   3068 bool LocalAccessible::RemoveItemFromSelection(uint32_t aIndex) {
   3069  uint32_t index = 0;
   3070  AccIterator iter(this, filters::GetSelectable);
   3071  LocalAccessible* selected = nullptr;
   3072  while ((selected = iter.Next()) && index < aIndex) index++;
   3073 
   3074  if (selected) selected->SetSelected(false);
   3075 
   3076  return static_cast<bool>(selected);
   3077 }
   3078 
   3079 bool LocalAccessible::SelectAll() {
   3080  bool success = false;
   3081  LocalAccessible* selectable = nullptr;
   3082 
   3083  AccIterator iter(this, filters::GetSelectable);
   3084  while ((selectable = iter.Next())) {
   3085    success = true;
   3086    selectable->SetSelected(true);
   3087  }
   3088  return success;
   3089 }
   3090 
   3091 bool LocalAccessible::UnselectAll() {
   3092  bool success = false;
   3093  LocalAccessible* selected = nullptr;
   3094 
   3095  AccIterator iter(this, filters::GetSelected);
   3096  while ((selected = iter.Next())) {
   3097    success = true;
   3098    selected->SetSelected(false);
   3099  }
   3100  return success;
   3101 }
   3102 
   3103 ////////////////////////////////////////////////////////////////////////////////
   3104 // Widgets
   3105 
   3106 bool LocalAccessible::IsWidget() const { return false; }
   3107 
   3108 bool LocalAccessible::IsActiveWidget() const {
   3109  if (FocusMgr()->HasDOMFocus(mContent)) return true;
   3110 
   3111  // If text entry of combobox widget has a focus then the combobox widget is
   3112  // active.
   3113  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   3114  if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
   3115    uint32_t childCount = ChildCount();
   3116    for (uint32_t idx = 0; idx < childCount; idx++) {
   3117      LocalAccessible* child = mChildren.ElementAt(idx);
   3118      if (child->Role() == roles::ENTRY) {
   3119        return FocusMgr()->HasDOMFocus(child->GetContent());
   3120      }
   3121    }
   3122  }
   3123 
   3124  return false;
   3125 }
   3126 
   3127 bool LocalAccessible::AreItemsOperable() const {
   3128  return HasOwnContent() && mContent->IsElement() &&
   3129         mContent->AsElement()->HasAttr(nsGkAtoms::aria_activedescendant);
   3130 }
   3131 
   3132 LocalAccessible* LocalAccessible::CurrentItem() const {
   3133  // Check for aria-activedescendant, which changes which element has focus.
   3134  // For activedescendant, the ARIA spec does not require that the user agent
   3135  // checks whether pointed node is actually a DOM descendant of the element
   3136  // with the aria-activedescendant attribute.
   3137  if (HasOwnContent() && mContent->IsElement()) {
   3138    if (dom::Element* activeDescendantElm =
   3139            nsCoreUtils::GetAriaActiveDescendantElement(
   3140                mContent->AsElement())) {
   3141      if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) {
   3142        // Don't want a cyclical descendant relationship. That would be bad.
   3143        return nullptr;
   3144      }
   3145 
   3146      DocAccessible* document = Document();
   3147      if (document) return document->GetAccessible(activeDescendantElm);
   3148    }
   3149  }
   3150  return nullptr;
   3151 }
   3152 
   3153 void LocalAccessible::SetCurrentItem(const LocalAccessible* aItem) {}
   3154 
   3155 LocalAccessible* LocalAccessible::ContainerWidget() const {
   3156  if (HasARIARole() && mContent->HasID()) {
   3157    for (LocalAccessible* parent = LocalParent(); parent;
   3158         parent = parent->LocalParent()) {
   3159      nsIContent* parentContent = parent->GetContent();
   3160      if (parentContent && parentContent->IsElement() &&
   3161          nsCoreUtils::GetAriaActiveDescendantElement(
   3162              parentContent->AsElement())) {
   3163        return parent;
   3164      }
   3165 
   3166      // Don't cross DOM document boundaries.
   3167      if (parent->IsDoc()) break;
   3168    }
   3169  }
   3170  return nullptr;
   3171 }
   3172 
   3173 bool LocalAccessible::IsActiveDescendant(LocalAccessible** aWidget) const {
   3174  RelatedAccIterator widgets(mDoc, mContent, nsGkAtoms::aria_activedescendant);
   3175  if (LocalAccessible* widget = widgets.Next()) {
   3176    if (aWidget) {
   3177      *aWidget = widget;
   3178    }
   3179    return true;
   3180  }
   3181 
   3182  return false;
   3183 }
   3184 
   3185 void LocalAccessible::Announce(const nsAString& aAnnouncement,
   3186                               uint16_t aPriority) {
   3187  RefPtr<AccAnnouncementEvent> event =
   3188      new AccAnnouncementEvent(this, aAnnouncement, aPriority);
   3189  nsEventShell::FireEvent(event);
   3190 }
   3191 
   3192 ////////////////////////////////////////////////////////////////////////////////
   3193 // LocalAccessible protected methods
   3194 
   3195 void LocalAccessible::LastRelease() {
   3196  // First cleanup if needed...
   3197  if (mDoc) {
   3198    Shutdown();
   3199    NS_ASSERTION(!mDoc,
   3200                 "A Shutdown() impl forgot to call its parent's Shutdown?");
   3201  }
   3202  // ... then die.
   3203  delete this;
   3204 }
   3205 
   3206 LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset,
   3207                                                     nsresult* aError) const {
   3208  if (!mParent || mIndexInParent == -1) {
   3209    if (aError) *aError = NS_ERROR_UNEXPECTED;
   3210 
   3211    return nullptr;
   3212  }
   3213 
   3214  if (aError &&
   3215      mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
   3216    *aError = NS_OK;  // fail peacefully
   3217    return nullptr;
   3218  }
   3219 
   3220  LocalAccessible* child = mParent->LocalChildAt(mIndexInParent + aOffset);
   3221  if (aError && !child) *aError = NS_ERROR_UNEXPECTED;
   3222 
   3223  return child;
   3224 }
   3225 
   3226 void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags,
   3227                                                bool aAdd) {
   3228  Pivot pivot(this);
   3229  LocalAccInSameDocRule rule;
   3230  for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) {
   3231    MOZ_ASSERT(anchor->IsLocal());
   3232    LocalAccessible* acc = anchor->AsLocal();
   3233    if (aAdd) {
   3234      acc->mContextFlags |= aContextFlags;
   3235    } else {
   3236      acc->mContextFlags &= ~aContextFlags;
   3237    }
   3238  }
   3239 }
   3240 
   3241 double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const {
   3242  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   3243  if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) {
   3244    return UnspecifiedNaN<double>();
   3245  }
   3246 
   3247  nsAutoString attrValue;
   3248  if (!mContent->IsElement() ||
   3249      !nsAccUtils::GetARIAAttr(mContent->AsElement(), aAttr, attrValue)) {
   3250    return UnspecifiedNaN<double>();
   3251  }
   3252 
   3253  nsresult error = NS_OK;
   3254  double value = attrValue.ToDouble(&error);
   3255  return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
   3256 }
   3257 
   3258 uint32_t LocalAccessible::GetActionRule() const {
   3259  if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) {
   3260    return eNoAction;
   3261  }
   3262 
   3263  // Return "click" action on elements that have an attached popup menu.
   3264  if (mContent->IsXULElement()) {
   3265    if (mContent->AsElement()->HasAttr(nsGkAtoms::popup)) {
   3266      return eClickAction;
   3267    }
   3268  }
   3269 
   3270  // Has registered 'click' event handler.
   3271  bool isOnclick = nsCoreUtils::HasClickListener(mContent);
   3272 
   3273  if (isOnclick) return eClickAction;
   3274 
   3275  // Get an action based on ARIA role.
   3276  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
   3277  if (roleMapEntry && roleMapEntry->actionRule != eNoAction) {
   3278    return roleMapEntry->actionRule;
   3279  }
   3280 
   3281  // Get an action based on ARIA attribute.
   3282  if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) {
   3283    return eExpandAction;
   3284  }
   3285 
   3286  return eNoAction;
   3287 }
   3288 
   3289 AccGroupInfo* LocalAccessible::GetGroupInfo() const {
   3290  if (mGroupInfo && !(mStateFlags & eGroupInfoDirty)) {
   3291    return mGroupInfo;
   3292  }
   3293 
   3294  return nullptr;
   3295 }
   3296 
   3297 AccGroupInfo* LocalAccessible::GetOrCreateGroupInfo() {
   3298  if (mGroupInfo) {
   3299    if (mStateFlags & eGroupInfoDirty) {
   3300      mGroupInfo->Update();
   3301      mStateFlags &= ~eGroupInfoDirty;
   3302    }
   3303 
   3304    return mGroupInfo;
   3305  }
   3306 
   3307  mGroupInfo = AccGroupInfo::CreateGroupInfo(this);
   3308  mStateFlags &= ~eGroupInfoDirty;
   3309  return mGroupInfo;
   3310 }
   3311 
   3312 void LocalAccessible::SendCache(uint64_t aCacheDomain,
   3313                                CacheUpdateType aUpdateType,
   3314                                bool aAppendEventData) {
   3315  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_SendCache>
   3316      autoRecording;
   3317  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
   3318 
   3319  if (!IPCAccessibilityActive() || !Document()) {
   3320    return;
   3321  }
   3322 
   3323  // Only send cache updates for domains that are active.
   3324  const uint64_t domainsToSend =
   3325      nsAccessibilityService::GetActiveCacheDomains() & aCacheDomain;
   3326 
   3327  // Avoid sending cache updates if we have no domains to update.
   3328  if (domainsToSend == CacheDomain::None) {
   3329    return;
   3330  }
   3331 
   3332  DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
   3333  if (!ipcDoc) {
   3334    // This means DocAccessible::DoInitialUpdate hasn't been called yet, which
   3335    // means the a11y tree hasn't been built yet. Therefore, this should only
   3336    // be possible if this is a DocAccessible.
   3337    MOZ_ASSERT(IsDoc(), "Called on a non-DocAccessible but IPCDoc is null");
   3338    return;
   3339  }
   3340 
   3341  RefPtr<AccAttributes> fields =
   3342      BundleFieldsForCache(domainsToSend, aUpdateType);
   3343  if (!fields->Count()) {
   3344    return;
   3345  }
   3346  nsTArray<CacheData> data;
   3347  data.AppendElement(CacheData(ID(), fields));
   3348  if (aAppendEventData) {
   3349    ipcDoc->PushMutationEventData(
   3350        CacheEventData{std::move(aUpdateType), std::move(data)});
   3351  } else {
   3352    ipcDoc->SendCache(aUpdateType, data);
   3353  }
   3354 
   3355  if (profiler_thread_is_being_profiled_for_markers()) {
   3356    nsAutoCString updateTypeStr;
   3357    if (aUpdateType == CacheUpdateType::Initial) {
   3358      updateTypeStr = "Initial";
   3359    } else if (aUpdateType == CacheUpdateType::Update) {
   3360      updateTypeStr = "Update";
   3361    } else {
   3362      updateTypeStr = "Other";
   3363    }
   3364    PROFILER_MARKER_TEXT("LocalAccessible::SendCache", A11Y, {}, updateTypeStr);
   3365  }
   3366 }
   3367 
   3368 already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
   3369    uint64_t aCacheDomain, CacheUpdateType aUpdateType,
   3370    uint64_t aInitialDomains) {
   3371  MOZ_ASSERT((~aCacheDomain & aInitialDomains) == CacheDomain::None,
   3372             "Initial domain pushes without domains requested!");
   3373  RefPtr<AccAttributes> fields = new AccAttributes();
   3374 
   3375  if (aUpdateType == CacheUpdateType::Initial) {
   3376    aInitialDomains = CacheDomain::All;
   3377  }
   3378  // Pass a single cache domain in to query whether this is the initial push for
   3379  // this domain.
   3380  auto IsInitialPush = [aInitialDomains](uint64_t aCacheDomain) {
   3381    return (aCacheDomain & aInitialDomains) == aCacheDomain;
   3382  };
   3383  auto IsUpdatePush = [aInitialDomains](uint64_t aCacheDomain) {
   3384    return (aCacheDomain & aInitialDomains) == CacheDomain::None;
   3385  };
   3386 
   3387  // Caching name for text leaf Accessibles is redundant, since their name is
   3388  // always their text. Text gets handled below.
   3389  if (aCacheDomain & CacheDomain::NameAndDescription && !IsText()) {
   3390    nsString name;
   3391    ENameValueFlag nameFlag = DirectName(name);
   3392 
   3393    if (IsTextField()) {
   3394      MOZ_ASSERT(mContent);
   3395      nsString placeholder;
   3396      // Only cache the placeholder separately if it isn't used as the name.
   3397      if (Elm()->GetAttr(nsGkAtoms::placeholder, placeholder) &&
   3398          name != placeholder) {
   3399        fields->SetAttribute(CacheKey::HTMLPlaceholder, std::move(placeholder));
   3400      } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
   3401        fields->SetAttribute(CacheKey::HTMLPlaceholder, DeleteEntry());
   3402      }
   3403    }
   3404 
   3405    if (nameFlag != eNameFromRelations && !name.IsEmpty()) {
   3406      fields->SetAttribute(CacheKey::Name, std::move(name));
   3407    } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
   3408      fields->SetAttribute(CacheKey::Name, DeleteEntry());
   3409    }
   3410 
   3411    nsString tooltip;
   3412    if (Tooltip(tooltip)) {
   3413      fields->SetAttribute(CacheKey::Tooltip, std::move(tooltip));
   3414    } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
   3415      fields->SetAttribute(CacheKey::Tooltip, DeleteEntry());
   3416    }
   3417 
   3418    nsString cssAltContent;
   3419    if (auto cssAlt = CssAltContent(mContent)) {
   3420      cssAlt.AppendToString(cssAltContent);
   3421      fields->SetAttribute(CacheKey::CssAltContent, std::move(cssAltContent));
   3422    } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
   3423      fields->SetAttribute(CacheKey::CssAltContent, DeleteEntry());
   3424    }
   3425 
   3426    nsString description;
   3427    int32_t descFlag = Description(description);
   3428    if (!description.IsEmpty()) {
   3429      fields->SetAttribute(CacheKey::Description, std::move(description));
   3430    } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
   3431      fields->SetAttribute(CacheKey::Description, DeleteEntry());
   3432    }
   3433 
   3434    if (descFlag != eDescriptionOK) {
   3435      fields->SetAttribute(CacheKey::DescriptionValueFlag, descFlag);
   3436    } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
   3437      fields->SetAttribute(CacheKey::DescriptionValueFlag, DeleteEntry());
   3438    }
   3439  }
   3440 
   3441  if (aCacheDomain & CacheDomain::Value) {
   3442    // We cache the text value in 3 cases:
   3443    // 1. Accessible is an HTML input type that holds a number.
   3444    // 2. Accessible has a numeric value and an aria-valuetext.
   3445    // 3. Accessible is an HTML input type that holds text.
   3446    // 4. Accessible is a link, in which case value is the target URL.
   3447    // ... for all other cases we divine the value remotely.
   3448    bool cacheValueText = false;
   3449    if (HasNumericValue()) {
   3450      fields->SetAttribute(CacheKey::NumericValue, CurValue());
   3451      fields->SetAttribute(CacheKey::MaxValue, MaxValue());
   3452      fields->SetAttribute(CacheKey::MinValue, MinValue());
   3453      fields->SetAttribute(CacheKey::Step, Step());
   3454      cacheValueText = NativeHasNumericValue() ||
   3455                       (mContent->IsElement() &&
   3456                        nsAccUtils::HasARIAAttr(mContent->AsElement(),
   3457                                                nsGkAtoms::aria_valuetext));
   3458    } else {
   3459      cacheValueText = IsTextField() || IsHTMLLink();
   3460    }
   3461 
   3462    if (cacheValueText) {
   3463      nsString value;
   3464      Value(value);
   3465      if (!value.IsEmpty()) {
   3466        fields->SetAttribute(CacheKey::TextValue, std::move(value));
   3467      } else if (IsUpdatePush(CacheDomain::Value)) {
   3468        fields->SetAttribute(CacheKey::TextValue, DeleteEntry());
   3469      }
   3470    }
   3471 
   3472    if (IsImage()) {
   3473      // Cache the src of images. This is used by some clients to help remediate
   3474      // inaccessible images.
   3475      MOZ_ASSERT(mContent, "Image must have mContent");
   3476      nsString src;
   3477      mContent->AsElement()->GetAttr(nsGkAtoms::src, src);
   3478      if (!src.IsEmpty()) {
   3479        fields->SetAttribute(CacheKey::SrcURL, std::move(src));
   3480      } else if (IsUpdatePush(CacheDomain::Value)) {
   3481        fields->SetAttribute(CacheKey::SrcURL, DeleteEntry());
   3482      }
   3483    }
   3484 
   3485    if (TagName() == nsGkAtoms::meter) {
   3486      // We should only cache value region for HTML meter elements. A meter
   3487      // should always have a value region, so this attribute should never
   3488      // be empty (i.e. there is no DeleteEntry() clause here).
   3489      HTMLMeterAccessible* meter = static_cast<HTMLMeterAccessible*>(this);
   3490      fields->SetAttribute(CacheKey::ValueRegion, meter->ValueRegion());
   3491    }
   3492  }
   3493 
   3494  if (aCacheDomain & CacheDomain::Viewport && IsDoc()) {
   3495    // Construct the viewport cache for this document. This cache domain will
   3496    // only be requested after we finish painting.
   3497    DocAccessible* doc = AsDoc();
   3498    PresShell* presShell = doc->PresShellPtr();
   3499 
   3500    if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
   3501      nsTArray<nsIFrame*> frames;
   3502      ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame();
   3503      nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
   3504 
   3505      nsLayoutUtils::GetFramesForArea(
   3506          RelativeTo{rootFrame}, scrollPort, frames,
   3507          {{// We don't add the ::OnlyVisible option here, because
   3508            // it means we always consider frames with pointer-events: none.
   3509            // See usage of HitTestIsForVisibility in nsDisplayList::HitTest.
   3510            // This flag ensures the display lists are built, even if
   3511            // the page hasn't finished loading.
   3512            nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
   3513            // Each doc should have its own viewport cache, so we can
   3514            // ignore cross-doc content as an optimization.
   3515            nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc}});
   3516 
   3517      nsTHashSet<LocalAccessible*> inViewAccs;
   3518      nsTArray<uint64_t> viewportCache(frames.Length());
   3519      // Layout considers table rows fully occluded by their containing cells.
   3520      // This means they don't have their own display list items, and they won't
   3521      // show up in the list returned from GetFramesForArea. To prevent table
   3522      // rows from appearing offscreen, we manually add any rows for which we
   3523      // have on-screen cells.
   3524      LocalAccessible* prevParentRow = nullptr;
   3525      for (nsIFrame* frame : frames) {
   3526        if (frame->IsInlineFrame() && !frame->IsPrimaryFrame()) {
   3527          // This is a line other than the first line in an inline element. Even
   3528          // though there are multiple frames for this element (one per line),
   3529          // there is only a single Accessible with bounds encompassing all the
   3530          // frames. We don't have any additional information about the
   3531          // individual continuation frames in our cache. Thus, we don't want
   3532          // this Accessible to appear before leaves on other lines which are
   3533          // later in the `frames` array. Otherwise, when hit testing, this
   3534          // Accessible will match instead of those leaves. We will add this
   3535          // Accessible when we get to its primary frame later.
   3536          continue;
   3537        }
   3538        nsIContent* content = frame->GetContent();
   3539        if (!content) {
   3540          continue;
   3541        }
   3542 
   3543        LocalAccessible* acc = doc->GetAccessible(content);
   3544        // The document should always be present at the end of the list, so
   3545        // including it is unnecessary and wasteful. We skip the document here
   3546        // and handle it as a fallback when hit testing.
   3547        if (!acc || acc == mDoc) {
   3548          continue;
   3549        }
   3550 
   3551        if (acc->IsTextLeaf() && nsAccUtils::MustPrune(acc->LocalParent())) {
   3552          acc = acc->LocalParent();
   3553        }
   3554        if (acc->IsTableCell()) {
   3555          LocalAccessible* parent = acc->LocalParent();
   3556          if (parent && parent->IsTableRow() && parent != prevParentRow) {
   3557            // If we've entered a new row since the last cell we saw, add the
   3558            // previous parent row to our viewport cache here to maintain
   3559            // hittesting order. Keep track of the current parent row.
   3560            if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
   3561              viewportCache.AppendElement(prevParentRow->ID());
   3562            }
   3563            prevParentRow = parent;
   3564          }
   3565        } else if (acc->IsTable()) {
   3566          // If we've encountered a table, we know we've already
   3567          // handled all of this table's content (because we're traversing
   3568          // in hittesting order). Add our table's final row to the viewport
   3569          // cache before adding the table itself. Reset our marker for the next
   3570          // table.
   3571          if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
   3572            viewportCache.AppendElement(prevParentRow->ID());
   3573          }
   3574          prevParentRow = nullptr;
   3575        } else if (acc->IsImageMap()) {
   3576          // Layout doesn't walk image maps, so we do that
   3577          // manually here. We do this before adding the map itself
   3578          // so the children come earlier in the hittesting order.
   3579          for (uint32_t i = 0; i < acc->ChildCount(); i++) {
   3580            LocalAccessible* child = acc->LocalChildAt(i);
   3581            MOZ_ASSERT(child);
   3582            if (inViewAccs.EnsureInserted(child)) {
   3583              MOZ_ASSERT(!child->IsDoc());
   3584              viewportCache.AppendElement(child->ID());
   3585            }
   3586          }
   3587        } else if (acc->IsHTMLCombobox()) {
   3588          // Layout doesn't consider combobox lists (or their
   3589          // currently selected items) to be onscreen, but we do.
   3590          // Add those things manually here.
   3591          HTMLComboboxAccessible* combobox =
   3592              static_cast<HTMLComboboxAccessible*>(acc);
   3593          HTMLComboboxListAccessible* list = combobox->List();
   3594          LocalAccessible* currItem = combobox->SelectedOption();
   3595          // Preserve hittesting order by adding the item, then
   3596          // the list, and finally the combobox itself.
   3597          if (currItem && inViewAccs.EnsureInserted(currItem)) {
   3598            viewportCache.AppendElement(currItem->ID());
   3599          }
   3600          if (list && inViewAccs.EnsureInserted(list)) {
   3601            viewportCache.AppendElement(list->ID());
   3602          }
   3603        }
   3604 
   3605        if (inViewAccs.EnsureInserted(acc)) {
   3606          MOZ_ASSERT(!acc->IsDoc());
   3607          viewportCache.AppendElement(acc->ID());
   3608        }
   3609      }
   3610 
   3611      // Always send the viewport cache, even if we have no accessibles
   3612      // in it. We don't want to send a delete entry because the viewport
   3613      // cache _does_ exist, it is simply representing an empty screen.
   3614      fields->SetAttribute(CacheKey::Viewport, std::move(viewportCache));
   3615    }
   3616  }
   3617 
   3618  if (aCacheDomain & CacheDomain::APZ && IsDoc()) {
   3619    PresShell* presShell = AsDoc()->PresShellPtr();
   3620    MOZ_ASSERT(presShell, "Can't get APZ factor for null presShell");
   3621    nsPoint viewportOffset =
   3622        presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
   3623    if (viewportOffset.x || viewportOffset.y) {
   3624      nsTArray<int32_t> offsetArray(2);
   3625      offsetArray.AppendElement(viewportOffset.x);
   3626      offsetArray.AppendElement(viewportOffset.y);
   3627      fields->SetAttribute(CacheKey::VisualViewportOffset,
   3628                           std::move(offsetArray));
   3629    } else if (IsUpdatePush(CacheDomain::APZ)) {
   3630      fields->SetAttribute(CacheKey::VisualViewportOffset, DeleteEntry());
   3631    }
   3632  }
   3633 
   3634  bool boundsChanged = false;
   3635  nsIFrame* frame = GetFrame();
   3636  if (aCacheDomain & CacheDomain::Bounds) {
   3637    nsRect newBoundsRect = ParentRelativeBounds();
   3638 
   3639    // 1. Layout might notify us of a possible bounds change when the bounds
   3640    // haven't really changed. Therefore, we cache the last  bounds we sent
   3641    // and don't send an update if they haven't changed.
   3642    // 2. For an initial cache push, we ignore 1)  and always send the bounds.
   3643    // This handles the case where this LocalAccessible was moved (but not
   3644    // re-created). In that case, we will have cached bounds, but we currently
   3645    // do an initial cache push.
   3646    MOZ_ASSERT(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome(),
   3647               "Incremental cache push but mBounds is not set!");
   3648 
   3649    if (OuterDocAccessible* doc = AsOuterDoc()) {
   3650      if (nsIFrame* docFrame = doc->GetFrame()) {
   3651        const nsMargin& newOffset = docFrame->GetUsedBorderAndPadding();
   3652        Maybe<nsMargin> currOffset = doc->GetCrossDocOffset();
   3653        if (!currOffset || *currOffset != newOffset) {
   3654          // OOP iframe docs can't compute their position within their
   3655          // cross-proc parent, so we have to manually cache that offset
   3656          // on the parent (outer doc) itself. For simplicity and consistency,
   3657          // we do this here for both OOP and in-process iframes. For in-process
   3658          // iframes, this also avoids the need to push a cache update for the
   3659          // embedded document when the iframe changes its padding, gets
   3660          // re-created, etc. Similar to bounds, we maintain a local cache and a
   3661          // remote cache to avoid sending redundant updates.
   3662          doc->SetCrossDocOffset(newOffset);
   3663          nsTArray<int32_t> offsetArray(2);
   3664          offsetArray.AppendElement(newOffset.Side(eSideLeft));  // X offset
   3665          offsetArray.AppendElement(newOffset.Side(eSideTop));   // Y offset
   3666          fields->SetAttribute(CacheKey::CrossDocOffset,
   3667                               std::move(offsetArray));
   3668        }
   3669      }
   3670    }
   3671 
   3672    // mBounds should never be Nothing, but sometimes it is (see Bug 1922691).
   3673    // We null check mBounds here to avoid a crash while we figure out how this
   3674    // can happen.
   3675    boundsChanged = IsInitialPush(CacheDomain::Bounds) || !mBounds ||
   3676                    !newBoundsRect.IsEqualEdges(mBounds.value());
   3677    if (boundsChanged) {
   3678      mBounds = Some(newBoundsRect);
   3679 
   3680      UniquePtr<nsRect> ptr = MakeUnique<nsRect>(newBoundsRect);
   3681      fields->SetAttribute(CacheKey::ParentRelativeBounds, std::move(ptr));
   3682    }
   3683 
   3684    if (frame && frame->ScrollableOverflowRect().IsEmpty()) {
   3685      fields->SetAttribute(CacheKey::IsClipped, true);
   3686    } else if (IsUpdatePush(CacheDomain::Bounds)) {
   3687      fields->SetAttribute(CacheKey::IsClipped, DeleteEntry());
   3688    }
   3689  }
   3690 
   3691  if (aCacheDomain & CacheDomain::Text) {
   3692    if (!HasChildren()) {
   3693      // We only cache text and line offsets on leaf Accessibles.
   3694      // Only text Accessibles can have actual text.
   3695      if (IsText()) {
   3696        nsString text;
   3697        AppendTextTo(text);
   3698        fields->SetAttribute(CacheKey::Text, std::move(text));
   3699        TextLeafPoint point(this, 0);
   3700        RefPtr<AccAttributes> attrs = point.GetTextAttributesLocalAcc(
   3701            /* aIncludeDefaults */ false);
   3702        if (attrs->Count()) {
   3703          fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
   3704        } else if (IsUpdatePush(CacheDomain::Text)) {
   3705          fields->SetAttribute(CacheKey::TextAttributes, DeleteEntry());
   3706        }
   3707      }
   3708    }
   3709    if (HyperTextAccessible* ht = AsHyperText()) {
   3710      RefPtr<AccAttributes> attrs = ht->DefaultTextAttributes();
   3711      LocalAccessible* parent = LocalParent();
   3712      if (HyperTextAccessible* htParent =
   3713              parent ? parent->AsHyperText() : nullptr) {
   3714        if (RefPtr<AccAttributes> parentAttrs =
   3715                htParent->DefaultTextAttributes()) {
   3716          // Discard any entries that our parent already has.
   3717          attrs->RemoveIdentical(parentAttrs);
   3718        }
   3719      }
   3720      if (attrs->Count()) {
   3721        fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
   3722      } else if (IsUpdatePush(CacheDomain::Text)) {
   3723        fields->SetAttribute(CacheKey::TextAttributes, DeleteEntry());
   3724      }
   3725    } else if (!IsText()) {
   3726      // Language is normally cached in text attributes, but Accessibles that
   3727      // aren't HyperText or Text (e.g. <img>, <input type="radio">) don't have
   3728      // text attributes. The Text domain isn't a great fit, but the kinds of
   3729      // clients (e.g. screen readers) that care about language are likely to
   3730      // care about text as well.
   3731      nsString language;
   3732      Language(language);
   3733      if (!language.IsEmpty()) {
   3734        fields->SetAttribute(CacheKey::Language, std::move(language));
   3735      } else if (IsUpdatePush(CacheDomain::Text)) {
   3736        fields->SetAttribute(CacheKey::Language, DeleteEntry());
   3737      }
   3738    }
   3739  }
   3740 
   3741  // If text changes, we must also update text offset attributes.
   3742  if (aCacheDomain & (CacheDomain::TextOffsetAttributes | CacheDomain::Text) &&
   3743      IsTextLeaf()) {
   3744    auto offsetAttrs = TextLeafPoint::GetTextOffsetAttributes(this);
   3745    if (!offsetAttrs.IsEmpty()) {
   3746      fields->SetAttribute(CacheKey::TextOffsetAttributes,
   3747                           std::move(offsetAttrs));
   3748    } else if (IsUpdatePush(CacheDomain::TextOffsetAttributes) ||
   3749               IsUpdatePush(CacheDomain::Text)) {
   3750      fields->SetAttribute(CacheKey::TextOffsetAttributes, DeleteEntry());
   3751    }
   3752  }
   3753 
   3754  if (aCacheDomain & (CacheDomain::TextBounds) && !HasChildren()) {
   3755    // We cache line start offsets for both text and non-text leaf Accessibles
   3756    // because non-text leaf Accessibles can still start a line.
   3757    TextLeafPoint lineStart =
   3758        TextLeafPoint(this, 0).FindNextLineStartSameLocalAcc(
   3759            /* aIncludeOrigin */ true);
   3760    int32_t lineStartOffset = lineStart ? lineStart.mOffset : -1;
   3761    // We push line starts and text bounds in two cases:
   3762    // 1. TextBounds is pushed initially.
   3763    // 2. CacheDomain::Bounds was requested (indicating that the frame was
   3764    // reflowed) but the bounds  didn't actually change. This can happen when
   3765    // the spanned text is non-rectangular. For example, an Accessible might
   3766    // cover two characters on one line and a single character on another line.
   3767    // An insertion in a previous text node might cause it to shift such that it
   3768    // now covers a single character on the first line and two characters on the
   3769    // second line. Its bounding rect will be the same both before and after the
   3770    // insertion. In this case, we use the first line start to determine whether
   3771    // there was a change. This should be safe because the text didn't change in
   3772    // this Accessible, so if the first line start doesn't shift, none of them
   3773    // should shift.
   3774    if (IsInitialPush(CacheDomain::TextBounds) ||
   3775        aCacheDomain & CacheDomain::Text || boundsChanged ||
   3776        mFirstLineStart != lineStartOffset) {
   3777      mFirstLineStart = lineStartOffset;
   3778      nsTArray<int32_t> lineStarts;
   3779      for (; lineStart;
   3780           lineStart = lineStart.FindNextLineStartSameLocalAcc(false)) {
   3781        lineStarts.AppendElement(lineStart.mOffset);
   3782      }
   3783      if (!lineStarts.IsEmpty()) {
   3784        fields->SetAttribute(CacheKey::TextLineStarts, std::move(lineStarts));
   3785      } else if (IsUpdatePush(CacheDomain::TextBounds)) {
   3786        fields->SetAttribute(CacheKey::TextLineStarts, DeleteEntry());
   3787      }
   3788 
   3789      if (frame && frame->IsTextFrame()) {
   3790        if (nsTextFrame* currTextFrame = do_QueryFrame(frame)) {
   3791          nsTArray<int32_t> charData(nsAccUtils::TextLength(this) *
   3792                                     kNumbersInRect);
   3793          // Continuation offsets are calculated relative to the primary frame.
   3794          // However, the acc's bounds are calculated using
   3795          // GetAllInFlowRectsUnion. For wrapped text which starts part way
   3796          // through a line, this might mean the top left of the acc is
   3797          // different to the top left of the primary frame. This also happens
   3798          // when the primary frame is empty (e.g. a blank line at the start of
   3799          // pre-formatted text), since the union rect will exclude the origin
   3800          // in that case. Calculate the offset from the acc's rect to the
   3801          // primary frame's rect.
   3802          nsRect accOffset =
   3803              nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
   3804          while (currTextFrame) {
   3805            nsPoint contOffset = currTextFrame->GetOffsetTo(frame);
   3806            contOffset -= accOffset.TopLeft();
   3807            int32_t length = currTextFrame->GetContentLength();
   3808            nsTArray<nsRect> charBounds(length);
   3809            currTextFrame->GetCharacterRectsInRange(
   3810                currTextFrame->GetContentOffset(), length, charBounds);
   3811            for (nsRect& charRect : charBounds) {
   3812              if (charRect.width == 0 &&
   3813                  !currTextFrame->StyleText()->WhiteSpaceIsSignificant()) {
   3814                // GetCharacterRectsInRange gives us one rect per content
   3815                // offset. However, TextLeafAccessibles use rendered offsets;
   3816                // e.g. they might exclude some content white space. If we get
   3817                // a 0 width rect and it's white space, skip this rect, since
   3818                // this character isn't in the rendered text. We do have
   3819                // a way to convert between content and rendered offsets, but
   3820                // doing this for every character is expensive.
   3821                const char16_t contentChar =
   3822                    mContent->GetCharacterDataBuffer()->CharAt(
   3823                        charData.Length() / kNumbersInRect);
   3824                if (contentChar == u' ' || contentChar == u'\t' ||
   3825                    contentChar == u'\n') {
   3826                  continue;
   3827                }
   3828              }
   3829              // We expect each char rect to be relative to the text leaf
   3830              // acc this text lives in. Unfortunately, GetCharacterRectsInRange
   3831              // returns rects relative to their continuation. Add the
   3832              // continuation's relative position here to make our final
   3833              // rect relative to the text leaf acc.
   3834              charRect.MoveBy(contOffset);
   3835              charData.AppendElement(charRect.x);
   3836              charData.AppendElement(charRect.y);
   3837              charData.AppendElement(charRect.width);
   3838              charData.AppendElement(charRect.height);
   3839            }
   3840            currTextFrame = currTextFrame->GetNextContinuation();
   3841          }
   3842          if (charData.Length()) {
   3843            fields->SetAttribute(CacheKey::TextBounds, std::move(charData));
   3844          }
   3845        }
   3846      }
   3847    }
   3848  }
   3849 
   3850  if (aCacheDomain & CacheDomain::TransformMatrix) {
   3851    bool transformed = false;
   3852    if (frame && frame->IsTransformed()) {
   3853      // This matrix is only valid when applied to CSSPixel points/rects
   3854      // in the coordinate space of `frame`.
   3855      gfx::Matrix4x4 mtx = nsDisplayTransform::GetResultingTransformMatrix(
   3856          frame, nsPoint(0, 0), AppUnitsPerCSSPixel(),
   3857          nsDisplayTransform::INCLUDE_PERSPECTIVE);
   3858      // We might get back the identity matrix. This can happen if there is no
   3859      // actual transform. For example, if an element has
   3860      // will-change: transform, nsIFrame::IsTransformed will return true, but
   3861      // this doesn't necessarily mean there is a transform right now.
   3862      // Applying the identity matrix is effectively a no-op, so there's no
   3863      // point caching it.
   3864      transformed = !mtx.IsIdentity();
   3865      if (transformed) {
   3866        UniquePtr<gfx::Matrix4x4> ptr = MakeUnique<gfx::Matrix4x4>(mtx);
   3867        fields->SetAttribute(CacheKey::TransformMatrix, std::move(ptr));
   3868      }
   3869    }
   3870    if (!transformed && IsUpdatePush(CacheDomain::TransformMatrix)) {
   3871      // Otherwise, if we're bundling a transform update but this
   3872      // frame isn't transformed (or doesn't exist), we need
   3873      // to send a DeleteEntry() to remove any
   3874      // transform that was previously cached for this frame.
   3875      fields->SetAttribute(CacheKey::TransformMatrix, DeleteEntry());
   3876    }
   3877  }
   3878 
   3879  if (aCacheDomain & CacheDomain::ScrollPosition && frame) {
   3880    // We request these values unscaled when caching because the scaled values
   3881    // have resolution multiplied in. We can encounter a race condition when
   3882    // using APZ where the resolution is not propogated back to content in time
   3883    // for it to be multipled into the scroll position calculation. Even if we
   3884    // end up with the correct resolution cached in parent, our final bounds
   3885    // will be incorrect. Instead of scaling here, we scale in parent with our
   3886    // cached resolution so any incorrectness will be consistent and dependent
   3887    // on a single cache update (Resolution) instead of two (Resolution and
   3888    // ScrollPosition).
   3889    const auto [scrollPosition, scrollRange] =
   3890        mDoc->ComputeScrollData(this, /* aShouldScaleByResolution */ false);
   3891    if (scrollRange.width || scrollRange.height) {
   3892      // If the scroll range is 0 by 0, this acc is not scrollable. We
   3893      // can't simply check scrollPosition != 0, since it's valid for scrollable
   3894      // frames to have a (0, 0) position. We also can't check IsEmpty or
   3895      // ZeroArea because frames with only one scrollable dimension will return
   3896      // a height/width of zero for the non-scrollable dimension, yielding zero
   3897      // area even if the width/height for the scrollable dimension is nonzero.
   3898      // We also cache (0, 0) for accs with overflow:auto or overflow:scroll,
   3899      // even if the content is not currently large enough to be scrollable
   3900      // right now -- these accs have a non-zero scroll range.
   3901      nsTArray<int32_t> positionArr(2);
   3902      positionArr.AppendElement(scrollPosition.x);
   3903      positionArr.AppendElement(scrollPosition.y);
   3904      fields->SetAttribute(CacheKey::ScrollPosition, std::move(positionArr));
   3905    } else if (IsUpdatePush(CacheDomain::ScrollPosition)) {
   3906      fields->SetAttribute(CacheKey::ScrollPosition, DeleteEntry());
   3907    }
   3908  }
   3909 
   3910  if (aCacheDomain & CacheDomain::DOMNodeIDAndClass && mContent) {
   3911    nsAtom* id = mContent->GetID();
   3912    if (id) {
   3913      fields->SetAttribute(CacheKey::DOMNodeID, id);
   3914    } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) {
   3915      fields->SetAttribute(CacheKey::DOMNodeID, DeleteEntry());
   3916    }
   3917 
   3918    if (dom::Element* el = Elm()) {
   3919      nsTArray<RefPtr<nsAtom>> classes;
   3920      if (const nsAttrValue* attr = el->GetClasses()) {
   3921        for (uint32_t i = 0; i < attr->GetAtomCount(); i++) {
   3922          classes.AppendElement(attr->AtomAt(i));
   3923        }
   3924      }
   3925      if (!classes.IsEmpty()) {
   3926        fields->SetAttribute(CacheKey::DOMNodeClass, std::move(classes));
   3927      } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) {
   3928        fields->SetAttribute(CacheKey::DOMNodeClass, DeleteEntry());
   3929      }
   3930    }
   3931  }
   3932 
   3933  // State is only included in the initial push. Thereafter, cached state is
   3934  // updated via events.
   3935  if (aCacheDomain & CacheDomain::State) {
   3936    if (IsInitialPush(CacheDomain::State)) {
   3937      // Most states are updated using state change events, so we only send
   3938      // these for the initial cache push.
   3939      uint64_t state = ExplicitState();
   3940      // Exclude states which must be calculated by RemoteAccessible.
   3941      state &= ~kRemoteCalculatedStates;
   3942      fields->SetAttribute(CacheKey::State, state);
   3943    }
   3944    // If aria-selected isn't specified, there may be no SELECTED state.
   3945    // However, aria-selected can be implicit in some cases when an item is
   3946    // focused. We don't want to do this if aria-selected is explicitly
   3947    // set to "false", so we need to differentiate between false and unset.
   3948    if (auto ariaSelected = ARIASelected()) {
   3949      fields->SetAttribute(CacheKey::ARIASelected, *ariaSelected);
   3950    } else if (IsUpdatePush(CacheDomain::State)) {
   3951      fields->SetAttribute(CacheKey::ARIASelected, DeleteEntry());  // Unset.
   3952    }
   3953  }
   3954 
   3955  if (aCacheDomain & CacheDomain::GroupInfo && mContent) {
   3956    for (nsAtom* attr : {nsGkAtoms::aria_level, nsGkAtoms::aria_setsize,
   3957                         nsGkAtoms::aria_posinset}) {
   3958      int32_t value = 0;
   3959      if (nsCoreUtils::GetUIntAttr(mContent, attr, &value)) {
   3960        fields->SetAttribute(attr, value);
   3961      } else if (IsUpdatePush(CacheDomain::GroupInfo)) {
   3962        fields->SetAttribute(attr, DeleteEntry());
   3963      }
   3964    }
   3965  }
   3966 
   3967  if (aCacheDomain & CacheDomain::Actions) {
   3968    if (HasPrimaryAction()) {
   3969      // Here we cache the primary action.
   3970      nsAutoString actionName;
   3971      ActionNameAt(0, actionName);
   3972      RefPtr<nsAtom> actionAtom = NS_Atomize(actionName);
   3973      fields->SetAttribute(CacheKey::PrimaryAction, actionAtom);
   3974    } else if (IsUpdatePush(CacheDomain::Actions)) {
   3975      fields->SetAttribute(CacheKey::PrimaryAction, DeleteEntry());
   3976    }
   3977 
   3978    if (ImageAccessible* imgAcc = AsImage()) {
   3979      // Here we cache the showlongdesc action.
   3980      if (imgAcc->HasLongDesc()) {
   3981        fields->SetAttribute(CacheKey::HasLongdesc, true);
   3982      } else if (IsUpdatePush(CacheDomain::Actions)) {
   3983        fields->SetAttribute(CacheKey::HasLongdesc, DeleteEntry());
   3984      }
   3985    }
   3986 
   3987    KeyBinding accessKey = AccessKey();
   3988    if (!accessKey.IsEmpty()) {
   3989      fields->SetAttribute(CacheKey::AccessKey, accessKey.Serialize());
   3990    } else if (IsUpdatePush(CacheDomain::Actions)) {
   3991      fields->SetAttribute(CacheKey::AccessKey, DeleteEntry());
   3992    }
   3993  }
   3994 
   3995  if (aCacheDomain & CacheDomain::Style) {
   3996    if (RefPtr<nsAtom> display = DisplayStyle()) {
   3997      fields->SetAttribute(CacheKey::CSSDisplay, display);
   3998    }
   3999 
   4000    float opacity = Opacity();
   4001    if (opacity != 1.0f) {
   4002      fields->SetAttribute(CacheKey::Opacity, opacity);
   4003    } else if (IsUpdatePush(CacheDomain::Style)) {
   4004      fields->SetAttribute(CacheKey::Opacity, DeleteEntry());
   4005    }
   4006 
   4007    WritingMode wm = GetWritingMode();
   4008    if (wm.GetBits()) {
   4009      fields->SetAttribute(CacheKey::WritingMode, wm);
   4010    } else if (IsUpdatePush(CacheDomain::Style)) {
   4011      fields->SetAttribute(CacheKey::WritingMode, DeleteEntry());
   4012    }
   4013 
   4014    if (frame &&
   4015        frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
   4016        nsLayoutUtils::IsReallyFixedPos(frame)) {
   4017      fields->SetAttribute(CacheKey::CssPosition, nsGkAtoms::fixed);
   4018    } else if (IsUpdatePush(CacheDomain::Style)) {
   4019      fields->SetAttribute(CacheKey::CssPosition, DeleteEntry());
   4020    }
   4021 
   4022    if (frame) {
   4023      nsAutoCString overflow;
   4024      frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow);
   4025      RefPtr<nsAtom> overflowAtom = NS_Atomize(overflow);
   4026      if (overflowAtom == nsGkAtoms::hidden) {
   4027        fields->SetAttribute(CacheKey::CSSOverflow, nsGkAtoms::hidden);
   4028      } else if (IsUpdatePush(CacheDomain::Style)) {
   4029        fields->SetAttribute(CacheKey::CSSOverflow, DeleteEntry());
   4030      }
   4031    }
   4032  }
   4033 
   4034  if (aCacheDomain & CacheDomain::Table) {
   4035    if (auto* table = HTMLTableAccessible::GetFrom(this)) {
   4036      if (table->IsProbablyLayoutTable()) {
   4037        fields->SetAttribute(CacheKey::TableLayoutGuess, true);
   4038      } else if (IsUpdatePush(CacheDomain::Table)) {
   4039        fields->SetAttribute(CacheKey::TableLayoutGuess, DeleteEntry());
   4040      }
   4041    } else if (auto* cell = HTMLTableCellAccessible::GetFrom(this)) {
   4042      // For HTML table cells, we must use the HTMLTableCellAccessible
   4043      // GetRow/ColExtent methods rather than using the DOM attributes directly.
   4044      // This is because of things like rowspan="0" which depend on knowing
   4045      // about thead, tbody, etc., which is info we don't have in the a11y tree.
   4046      int32_t value = static_cast<int32_t>(cell->RowExtent());
   4047      MOZ_ASSERT(value > 0);
   4048      if (value > 1) {
   4049        fields->SetAttribute(CacheKey::RowSpan, value);
   4050      } else if (IsUpdatePush(CacheDomain::Table)) {
   4051        fields->SetAttribute(CacheKey::RowSpan, DeleteEntry());
   4052      }
   4053      value = static_cast<int32_t>(cell->ColExtent());
   4054      MOZ_ASSERT(value > 0);
   4055      if (value > 1) {
   4056        fields->SetAttribute(CacheKey::ColSpan, value);
   4057      } else if (IsUpdatePush(CacheDomain::Table)) {
   4058        fields->SetAttribute(CacheKey::ColSpan, DeleteEntry());
   4059      }
   4060      if (mContent->AsElement()->HasAttr(nsGkAtoms::headers)) {
   4061        nsTArray<uint64_t> headers;
   4062        AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::headers);
   4063        while (LocalAccessible* cell = iter.Next()) {
   4064          if (cell->IsTableCell()) {
   4065            headers.AppendElement(cell->ID());
   4066          }
   4067        }
   4068        fields->SetAttribute(CacheKey::CellHeaders, std::move(headers));
   4069      } else if (IsUpdatePush(CacheDomain::Table)) {
   4070        fields->SetAttribute(CacheKey::CellHeaders, DeleteEntry());
   4071      }
   4072    }
   4073  }
   4074 
   4075  if (aCacheDomain & CacheDomain::ARIA && mContent && mContent->IsElement()) {
   4076    // We use a nested AccAttributes to make cache updates simpler. Rather than
   4077    // managing individual removals, we just replace or remove the entire set of
   4078    // ARIA attributes.
   4079    RefPtr<AccAttributes> ariaAttrs;
   4080    aria::AttrIterator attrIt(mContent);
   4081    while (attrIt.Next()) {
   4082      if (!ariaAttrs) {
   4083        ariaAttrs = new AccAttributes();
   4084      }
   4085      attrIt.ExposeAttr(ariaAttrs);
   4086    }
   4087    if (ariaAttrs) {
   4088      fields->SetAttribute(CacheKey::ARIAAttributes, std::move(ariaAttrs));
   4089    } else if (IsUpdatePush(CacheDomain::ARIA)) {
   4090      fields->SetAttribute(CacheKey::ARIAAttributes, DeleteEntry());
   4091    }
   4092 
   4093    if (HasCustomActions()) {
   4094      fields->SetAttribute(CacheKey::HasActions, true);
   4095    } else if (IsUpdatePush(CacheDomain::ARIA)) {
   4096      fields->SetAttribute(CacheKey::HasActions, DeleteEntry());
   4097    }
   4098  }
   4099 
   4100  if (aCacheDomain & CacheDomain::Relations && mContent) {
   4101    if (IsHTMLRadioButton() ||
   4102        (mContent->IsElement() &&
   4103         mContent->AsElement()->IsHTMLElement(nsGkAtoms::a))) {
   4104      // HTML radio buttons with the same name should be grouped
   4105      // and returned together when their MEMBER_OF relation is
   4106      // requested. Computing LINKS_TO also requires we cache `name` on
   4107      // anchor elements.
   4108      nsString name;
   4109      mContent->AsElement()->GetAttr(nsGkAtoms::name, name);
   4110      if (!name.IsEmpty()) {
   4111        fields->SetAttribute(CacheKey::DOMName, std::move(name));
   4112      } else if (IsUpdatePush(CacheDomain::Relations)) {
   4113        // It's possible we used to have a name and it's since been
   4114        // removed. Send a delete entry.
   4115        fields->SetAttribute(CacheKey::DOMName, DeleteEntry());
   4116      }
   4117    }
   4118 
   4119    for (auto const& data : kRelationTypeAtoms) {
   4120      nsTArray<uint64_t> ids;
   4121      nsStaticAtom* const relAtom = data.mAtom;
   4122      if (data.mValidTag && !mContent->IsHTMLElement(data.mValidTag)) {
   4123        continue;
   4124      }
   4125 
   4126      Relation rel;
   4127      if (data.mType == RelationType::LABEL_FOR) {
   4128        // Labels are a special case -- we need to validate that the target of
   4129        // their `for` attribute is in fact labelable. DOM checks this when we
   4130        // call GetControl(). If a label contains an element we will return it
   4131        // here.
   4132        if (dom::HTMLLabelElement* labelEl =
   4133                dom::HTMLLabelElement::FromNode(mContent)) {
   4134          rel.AppendTarget(mDoc, labelEl->GetControl());
   4135        }
   4136      } else if (data.mType == RelationType::DETAILS) {
   4137        if (relAtom == nsGkAtoms::aria_details) {
   4138          rel.AppendIter(
   4139              new AssociatedElementsIterator(mDoc, mContent, relAtom));
   4140        } else if (relAtom == nsGkAtoms::commandfor) {
   4141          if (LocalAccessible* target = GetCommandForDetailsRelation()) {
   4142            rel.AppendTarget(target);
   4143          }
   4144        } else if (relAtom == nsGkAtoms::popovertarget) {
   4145          if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) {
   4146            rel.AppendTarget(target);
   4147          }
   4148        } else if (relAtom == nsGkAtoms::target) {
   4149          if (LocalAccessible* target =
   4150                  GetAnchorPositionTargetDetailsRelation()) {
   4151            rel.AppendTarget(target);
   4152          }
   4153        } else {
   4154          MOZ_ASSERT_UNREACHABLE("Unknown details relAtom");
   4155        }
   4156      } else if (data.mType == RelationType::CONTROLLER_FOR) {
   4157        // We need to use RelationByType for controls because it might include
   4158        // failed aria-owned relocations or it may be an output element.
   4159        // Nothing exposes an implicit reverse controls relation, so using
   4160        // RelationByType here is fine.
   4161        rel = RelationByType(data.mType);
   4162      } else {
   4163        // We use an AssociatedElementsIterator here instead of calling
   4164        // RelationByType directly because we only want to cache explicit
   4165        // relations. Implicit relations (e.g. LABEL_FOR exposed on the target
   4166        // of aria-labelledby) will be computed and stored separately in the
   4167        // parent process.
   4168        rel.AppendIter(new AssociatedElementsIterator(mDoc, mContent, relAtom));
   4169      }
   4170 
   4171      while (LocalAccessible* acc = rel.LocalNext()) {
   4172        ids.AppendElement(acc->ID());
   4173      }
   4174      if (ids.Length()) {
   4175        fields->SetAttribute(relAtom, std::move(ids));
   4176      } else if (IsUpdatePush(CacheDomain::Relations)) {
   4177        fields->SetAttribute(relAtom, DeleteEntry());
   4178      }
   4179    }
   4180  }
   4181 
   4182 #if defined(XP_WIN)
   4183  if (aCacheDomain & CacheDomain::InnerHTML && HasOwnContent() &&
   4184      mContent->IsMathMLElement(nsGkAtoms::math)) {
   4185    nsString innerHTML;
   4186    mContent->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
   4187    fields->SetAttribute(CacheKey::InnerHTML, std::move(innerHTML));
   4188  }
   4189 #endif  // defined(XP_WIN)
   4190 
   4191  if (aUpdateType == CacheUpdateType::Initial) {
   4192    // Add fields which never change and thus only need to be included in the
   4193    // initial cache push.
   4194    if (mContent && mContent->IsElement()) {
   4195      fields->SetAttribute(CacheKey::TagName, mContent->NodeInfo()->NameAtom());
   4196 
   4197      dom::Element* el = mContent->AsElement();
   4198      if (IsTextField() || IsDateTimeField()) {
   4199        // Cache text input types. Accessible is recreated if this changes,
   4200        // so it is considered immutable.
   4201        if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) {
   4202          RefPtr<nsAtom> inputType = attr->GetAsAtom();
   4203          if (inputType) {
   4204            fields->SetAttribute(CacheKey::InputType, inputType);
   4205          }
   4206        }
   4207      }
   4208 
   4209      // Changing the role attribute currently re-creates the Accessible, so
   4210      // it's immutable in the cache.
   4211      if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
   4212        // Most of the time, the role attribute is a single, known role. We
   4213        // already send the map index, so we don't need to double up.
   4214        if (!nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::role, roleMap->roleAtom,
   4215                                         eIgnoreCase)) {
   4216          // Multiple roles or unknown roles are rare, so just send them as a
   4217          // string.
   4218          nsAutoString role;
   4219          nsAccUtils::GetARIAAttr(el, nsGkAtoms::role, role);
   4220          fields->SetAttribute(CacheKey::ARIARole, std::move(role));
   4221        }
   4222      }
   4223 
   4224      if (auto* htmlEl = nsGenericHTMLElement::FromNode(mContent)) {
   4225        // Changing popover recreates the Accessible, so it's immutable in the
   4226        // cache.
   4227        nsAutoString popover;
   4228        htmlEl->GetPopover(popover);
   4229        if (!popover.IsEmpty()) {
   4230          fields->SetAttribute(CacheKey::PopupType,
   4231                               RefPtr{NS_Atomize(popover)});
   4232        }
   4233      }
   4234    }
   4235 
   4236    if (frame) {
   4237      // Note our frame's current computed style so we can track style changes
   4238      // later on.
   4239      mOldComputedStyle = frame->Style();
   4240      if (frame->IsTransformed()) {
   4241        mStateFlags |= eOldFrameHasValidTransformStyle;
   4242      } else {
   4243        mStateFlags &= ~eOldFrameHasValidTransformStyle;
   4244      }
   4245    }
   4246 
   4247    if (IsDoc()) {
   4248      if (PresShell* presShell = AsDoc()->PresShellPtr()) {
   4249        // Send the initial resolution of the document. When this changes, we
   4250        // will ne notified via nsAS::NotifyOfResolutionChange
   4251        float resolution = presShell->GetResolution();
   4252        fields->SetAttribute(CacheKey::Resolution, resolution);
   4253        int32_t appUnitsPerDevPixel =
   4254            presShell->GetPresContext()->AppUnitsPerDevPixel();
   4255        fields->SetAttribute(CacheKey::AppUnitsPerDevPixel,
   4256                             appUnitsPerDevPixel);
   4257      }
   4258 
   4259      nsString mimeType;
   4260      AsDoc()->MimeType(mimeType);
   4261      fields->SetAttribute(CacheKey::MimeType, std::move(mimeType));
   4262    }
   4263  }
   4264 
   4265  if ((aCacheDomain & (CacheDomain::Text | CacheDomain::ScrollPosition |
   4266                       CacheDomain::APZ) ||
   4267       boundsChanged) &&
   4268      mDoc) {
   4269    mDoc->SetViewportCacheDirty(true);
   4270  }
   4271 
   4272  return fields.forget();
   4273 }
   4274 
   4275 void LocalAccessible::MaybeQueueCacheUpdateForStyleChanges() {
   4276  // mOldComputedStyle might be null if the initial cache hasn't been sent yet.
   4277  // In that case, there is nothing to do here.
   4278  if (!IPCAccessibilityActive() || !mOldComputedStyle) {
   4279    return;
   4280  }
   4281 
   4282  if (nsIFrame* frame = GetFrame()) {
   4283    const ComputedStyle* newStyle = frame->Style();
   4284 
   4285    const auto overflowProps =
   4286        nsCSSPropertyIDSet({eCSSProperty_overflow_x, eCSSProperty_overflow_y});
   4287 
   4288    for (NonCustomCSSPropertyId overflowProp : overflowProps) {
   4289      nsAutoCString oldOverflow, newOverflow;
   4290      mOldComputedStyle->GetComputedPropertyValue(overflowProp, oldOverflow);
   4291      newStyle->GetComputedPropertyValue(overflowProp, newOverflow);
   4292 
   4293      if (oldOverflow != newOverflow) {
   4294        if (oldOverflow.Equals("hidden"_ns) ||
   4295            newOverflow.Equals("hidden"_ns)) {
   4296          mDoc->QueueCacheUpdate(this, CacheDomain::Style);
   4297        }
   4298        if (oldOverflow.Equals("auto"_ns) || newOverflow.Equals("auto"_ns) ||
   4299            oldOverflow.Equals("scroll"_ns) ||
   4300            newOverflow.Equals("scroll"_ns)) {
   4301          // We cache a (0,0) scroll position for frames that have overflow
   4302          // styling which means they _could_ become scrollable, even if the
   4303          // content within them doesn't currently scroll.
   4304          mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition);
   4305        }
   4306      } else {
   4307        ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
   4308        if (!scrollContainerFrame && (newOverflow.Equals("auto"_ns) ||
   4309                                      newOverflow.Equals("scroll"_ns))) {
   4310          // A document's body element can lose its scroll frame if the root
   4311          // element (eg. <html>) is restyled to overflow scroll/auto. In that
   4312          // case we will not get any useful notifications for the body element
   4313          // except for a reframe to a non-scrolling frame.
   4314          mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition);
   4315        }
   4316      }
   4317    }
   4318 
   4319    nsAutoCString oldDisplay, newDisplay;
   4320    mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_display,
   4321                                                oldDisplay);
   4322    newStyle->GetComputedPropertyValue(eCSSProperty_display, newDisplay);
   4323 
   4324    nsAutoCString oldOpacity, newOpacity;
   4325    mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_opacity,
   4326                                                oldOpacity);
   4327    newStyle->GetComputedPropertyValue(eCSSProperty_opacity, newOpacity);
   4328 
   4329    if (oldDisplay != newDisplay || oldOpacity != newOpacity) {
   4330      // CacheDomain::Style covers both display and opacity, so if
   4331      // either property has changed, send an update for the entire domain.
   4332      mDoc->QueueCacheUpdate(this, CacheDomain::Style);
   4333    }
   4334 
   4335    if (mOldComputedStyle->StyleDisplay()->mAnchorName !=
   4336        newStyle->StyleDisplay()->mAnchorName) {
   4337      // Anchor name changes can affect the result of
   4338      // details relations.
   4339      mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
   4340    }
   4341 
   4342    if (mOldComputedStyle->MaybeAnchorPosReferencesDiffer(newStyle)) {
   4343      // Refresh the cache for details on current target (ie. the old style)
   4344      mDoc->RefreshAnchorRelationCacheForTarget(this);
   4345      // Refresh the cache for details on new target asynchronously after the
   4346      // next layout tick for new style.
   4347      mDoc->Controller()->ScheduleNotification<DocAccessible>(
   4348          mDoc, &DocAccessible::RefreshAnchorRelationCacheForTarget, this);
   4349    }
   4350 
   4351    nsAutoCString oldPosition, newPosition;
   4352    mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_position,
   4353                                                oldPosition);
   4354    newStyle->GetComputedPropertyValue(eCSSProperty_position, newPosition);
   4355 
   4356    if (oldPosition != newPosition) {
   4357      RefPtr<nsAtom> oldAtom = NS_Atomize(oldPosition);
   4358      RefPtr<nsAtom> newAtom = NS_Atomize(newPosition);
   4359      if (oldAtom == nsGkAtoms::fixed || newAtom == nsGkAtoms::fixed) {
   4360        mDoc->QueueCacheUpdate(this, CacheDomain::Style);
   4361      }
   4362    }
   4363 
   4364    bool newHasValidTransformStyle =
   4365        newStyle->StyleDisplay()->HasTransform(frame);
   4366    bool oldHasValidTransformStyle =
   4367        (mStateFlags & eOldFrameHasValidTransformStyle) != 0;
   4368 
   4369    // We should send a transform update if we're adding or
   4370    // removing transform styling altogether.
   4371    bool sendTransformUpdate =
   4372        newHasValidTransformStyle || oldHasValidTransformStyle;
   4373 
   4374    if (newHasValidTransformStyle && oldHasValidTransformStyle) {
   4375      // If we continue to have transform styling, verify
   4376      // our transform has actually changed.
   4377      nsChangeHint transformHint =
   4378          newStyle->StyleDisplay()->CalcTransformPropertyDifference(
   4379              *mOldComputedStyle->StyleDisplay());
   4380      // If this hint exists, it implies we found a property difference
   4381      sendTransformUpdate = !!transformHint;
   4382    }
   4383 
   4384    if (sendTransformUpdate) {
   4385      // If our transform matrix has changed, it's possible our
   4386      // viewport cache has also changed.
   4387      mDoc->SetViewportCacheDirty(true);
   4388      // Queuing a cache update for the TransformMatrix domain doesn't
   4389      // necessarily mean we'll send the matrix itself, we may
   4390      // send a DeleteEntry() instead. See BundleFieldsForCache for
   4391      // more information.
   4392      mDoc->QueueCacheUpdate(this, CacheDomain::TransformMatrix);
   4393    }
   4394 
   4395    mOldComputedStyle = newStyle;
   4396    if (newHasValidTransformStyle) {
   4397      mStateFlags |= eOldFrameHasValidTransformStyle;
   4398    } else {
   4399      mStateFlags &= ~eOldFrameHasValidTransformStyle;
   4400    }
   4401  }
   4402 }
   4403 
   4404 nsAtom* LocalAccessible::TagName() const {
   4405  return mContent && mContent->IsElement() ? mContent->NodeInfo()->NameAtom()
   4406                                           : nullptr;
   4407 }
   4408 
   4409 already_AddRefed<nsAtom> LocalAccessible::DisplayStyle() const {
   4410  dom::Element* elm = Elm();
   4411  if (!elm) {
   4412    return nullptr;
   4413  }
   4414  if (elm->IsHTMLElement(nsGkAtoms::area)) {
   4415    // This is an image map area. CSS is irrelevant here.
   4416    return nullptr;
   4417  }
   4418  static const dom::Element::AttrValuesArray presentationRoles[] = {
   4419      nsGkAtoms::none, nsGkAtoms::presentation, nullptr};
   4420  if (nsAccUtils::FindARIAAttrValueIn(elm, nsGkAtoms::role, presentationRoles,
   4421                                      eIgnoreCase) != AttrArray::ATTR_MISSING &&
   4422      IsGeneric()) {
   4423    // This Accessible has been marked presentational, but we forced a generic
   4424    // Accessible for some reason; e.g. CSS transform. Don't expose display in
   4425    // this case, as the author might be explicitly trying to avoid said
   4426    // exposure.
   4427    return nullptr;
   4428  }
   4429  RefPtr<const ComputedStyle> style =
   4430      nsComputedDOMStyle::GetComputedStyleNoFlush(elm);
   4431  if (!style) {
   4432    // The element is not styled, maybe not in the flat tree?
   4433    return nullptr;
   4434  }
   4435  nsAutoCString value;
   4436  style->GetComputedPropertyValue(eCSSProperty_display, value);
   4437  return NS_Atomize(value);
   4438 }
   4439 
   4440 float LocalAccessible::Opacity() const {
   4441  if (nsIFrame* frame = GetFrame()) {
   4442    return frame->StyleEffects()->mOpacity;
   4443  }
   4444 
   4445  return 1.0f;
   4446 }
   4447 
   4448 WritingMode LocalAccessible::GetWritingMode() const {
   4449  if (nsIFrame* frame = GetFrame()) {
   4450    return WritingMode(frame->Style());
   4451  }
   4452 
   4453  return WritingMode();
   4454 }
   4455 
   4456 void LocalAccessible::DOMNodeID(nsString& aID) const {
   4457  aID.Truncate();
   4458  if (mContent) {
   4459    if (nsAtom* id = mContent->GetID()) {
   4460      id->ToString(aID);
   4461    }
   4462  }
   4463 }
   4464 
   4465 void LocalAccessible::DOMNodeClass(nsString& aClass) const {
   4466  aClass.Truncate();
   4467  if (auto* el = dom::Element::FromNodeOrNull(mContent)) {
   4468    el->GetClassName(aClass);
   4469  }
   4470 }
   4471 
   4472 void LocalAccessible::LiveRegionAttributes(nsAString* aLive,
   4473                                           nsAString* aRelevant,
   4474                                           Maybe<bool>* aAtomic,
   4475                                           nsAString* aBusy) const {
   4476  dom::Element* el = Elm();
   4477  if (!el) {
   4478    return;
   4479  }
   4480  if (aLive) {
   4481    nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_live, *aLive);
   4482  }
   4483  if (aRelevant) {
   4484    nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_relevant, *aRelevant);
   4485  }
   4486  if (aAtomic) {
   4487    // XXX We ignore aria-atomic="false", but this probably doesn't conform to
   4488    // the spec.
   4489    if (nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::aria_atomic,
   4490                                    nsGkAtoms::_true, eCaseMatters)) {
   4491      *aAtomic = Some(true);
   4492    }
   4493  }
   4494  if (aBusy) {
   4495    nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_busy, *aBusy);
   4496  }
   4497 }
   4498 
   4499 Maybe<bool> LocalAccessible::ARIASelected() const {
   4500  if (dom::Element* el = Elm()) {
   4501    nsStaticAtom* atom =
   4502        nsAccUtils::NormalizeARIAToken(el, nsGkAtoms::aria_selected);
   4503    if (atom == nsGkAtoms::_true) {
   4504      return Some(true);
   4505    }
   4506    if (atom == nsGkAtoms::_false) {
   4507      return Some(false);
   4508    }
   4509  }
   4510  return Nothing();
   4511 }
   4512 
   4513 void LocalAccessible::StaticAsserts() const {
   4514  static_assert(
   4515      eLastStateFlag <= (1 << kStateFlagsBits) - 1,
   4516      "LocalAccessible::mStateFlags was oversized by eLastStateFlag!");
   4517  static_assert(
   4518      eLastContextFlag <= (1 << kContextFlagsBits) - 1,
   4519      "LocalAccessible::mContextFlags was oversized by eLastContextFlag!");
   4520 }
   4521 
   4522 TableAccessible* LocalAccessible::AsTable() {
   4523  if (IsTable() && !mContent->IsXULElement()) {
   4524    return CachedTableAccessible::GetFrom(this);
   4525  }
   4526  return nullptr;
   4527 }
   4528 
   4529 TableCellAccessible* LocalAccessible::AsTableCell() {
   4530  if (IsTableCell() && !mContent->IsXULElement()) {
   4531    return CachedTableCellAccessible::GetFrom(this);
   4532  }
   4533  return nullptr;
   4534 }
   4535 
   4536 Maybe<int32_t> LocalAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
   4537  if (mContent) {
   4538    int32_t val;
   4539    if (nsCoreUtils::GetUIntAttr(mContent, aAttrName, &val)) {
   4540      return Some(val);
   4541    }
   4542    // XXX Handle attributes that allow -1; e.g. aria-row/colcount.
   4543  }
   4544  return Nothing();
   4545 }
   4546 
   4547 bool LocalAccessible::GetStringARIAAttr(nsAtom* aAttrName,
   4548                                        nsAString& aAttrValue) const {
   4549  if (dom::Element* elm = Elm()) {
   4550    return nsAccUtils::GetARIAAttr(elm, aAttrName, aAttrValue);
   4551  }
   4552  return false;
   4553 }
   4554 
   4555 bool LocalAccessible::ARIAAttrValueIs(nsAtom* aAttrName,
   4556                                      nsAtom* aAttrValue) const {
   4557  if (dom::Element* elm = Elm()) {
   4558    return nsAccUtils::ARIAAttrValueIs(elm, aAttrName, aAttrValue,
   4559                                       eCaseMatters);
   4560  }
   4561  return false;
   4562 }
   4563 
   4564 bool LocalAccessible::HasARIAAttr(nsAtom* aAttrName) const {
   4565  return mContent ? nsAccUtils::HasDefinedARIAToken(mContent, aAttrName)
   4566                  : false;
   4567 }
   4568 
   4569 bool LocalAccessible::HasCustomActions() const {
   4570  dom::Element* el = Elm();
   4571  return el && nsAccUtils::HasARIAAttr(el, nsGkAtoms::aria_actions);
   4572 }