tor-browser

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

DocAccessible.cpp (116686B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "LocalAccessible-inl.h"
      8 #include "AccIterator.h"
      9 #include "AccAttributes.h"
     10 #include "ARIAMap.h"
     11 #include "CachedTableAccessible.h"
     12 #include "DocAccessible-inl.h"
     13 #include "EventTree.h"
     14 #include "HTMLImageMapAccessible.h"
     15 #include "Relation.h"
     16 #include "mozilla/ProfilerMarkers.h"
     17 #include "nsAccUtils.h"
     18 #include "nsEventShell.h"
     19 #include "nsIIOService.h"
     20 #include "nsLayoutUtils.h"
     21 #include "nsTextEquivUtils.h"
     22 #include "mozilla/a11y/Role.h"
     23 #include "TreeWalker.h"
     24 #include "xpcAccessibleDocument.h"
     25 
     26 #include "AnchorPositioningUtils.h"
     27 #include "nsIDocShell.h"
     28 #include "mozilla/dom/Document.h"
     29 #include "nsPIDOMWindow.h"
     30 #include "nsIContentInlines.h"
     31 #include "nsIEditingSession.h"
     32 #include "nsIFrame.h"
     33 #include "nsIInterfaceRequestorUtils.h"
     34 #include "nsImageFrame.h"
     35 #include "nsIMutationObserver.h"
     36 #include "nsIURI.h"
     37 #include "nsIWebNavigation.h"
     38 #include "nsFocusManager.h"
     39 #include "mozilla/AppShutdown.h"
     40 #include "mozilla/Assertions.h"
     41 #include "mozilla/Components.h"  // for mozilla::components
     42 #include "mozilla/EditorBase.h"
     43 #include "mozilla/HTMLEditor.h"
     44 #include "mozilla/PerfStats.h"
     45 #include "mozilla/PresShell.h"
     46 #include "mozilla/ScrollContainerFrame.h"
     47 #include "nsAccessibilityService.h"
     48 #include "mozilla/a11y/DocAccessibleChild.h"
     49 #include "mozilla/dom/AncestorIterator.h"
     50 #include "mozilla/dom/BrowserChild.h"
     51 #include "mozilla/dom/DocumentType.h"
     52 #include "mozilla/dom/Element.h"
     53 #include "mozilla/dom/ElementInlines.h"
     54 #include "mozilla/dom/HTMLSelectElement.h"
     55 #include "mozilla/dom/UserActivation.h"
     56 
     57 using namespace mozilla;
     58 using namespace mozilla::a11y;
     59 
     60 ////////////////////////////////////////////////////////////////////////////////
     61 // Static member initialization
     62 
     63 static nsStaticAtom* const kRelationAttrs[] = {
     64    nsGkAtoms::aria_labelledby,   nsGkAtoms::aria_describedby,
     65    nsGkAtoms::aria_details,      nsGkAtoms::aria_owns,
     66    nsGkAtoms::aria_controls,     nsGkAtoms::aria_flowto,
     67    nsGkAtoms::aria_errormessage, nsGkAtoms::_for,
     68    nsGkAtoms::control,           nsGkAtoms::popovertarget,
     69    nsGkAtoms::commandfor,        nsGkAtoms::aria_activedescendant,
     70    nsGkAtoms::aria_actions};
     71 
     72 static const uint32_t kRelationAttrsLen = std::size(kRelationAttrs);
     73 
     74 static nsStaticAtom* const kSingleElementRelationIdlAttrs[] = {
     75    nsGkAtoms::popovertarget, nsGkAtoms::commandfor};
     76 
     77 ////////////////////////////////////////////////////////////////////////////////
     78 // Constructor/desctructor
     79 
     80 DocAccessible::DocAccessible(dom::Document* aDocument,
     81                             PresShell* aPresShell)
     82    :  // XXX don't pass a document to the LocalAccessible constructor so that
     83       // we don't set mDoc until our vtable is fully setup.  If we set mDoc
     84       // before setting up the vtable we will call LocalAccessible::AddRef()
     85       // but not the overrides of it for subclasses.  It is important to call
     86       // those overrides to avoid confusing leak checking machinary.
     87      HyperTextAccessible(nullptr, nullptr),
     88      // XXX aaronl should we use an algorithm for the initial cache size?
     89      mAccessibleCache(kDefaultCacheLength),
     90      mNodeToAccessibleMap(kDefaultCacheLength),
     91      mDocumentNode(aDocument),
     92      mLoadState(eTreeConstructionPending),
     93      mDocFlags(0),
     94      mViewportCacheDirty(false),
     95      mLoadEventType(0),
     96      mPrevStateBits(0),
     97      mPresShell(aPresShell),
     98      mIPCDoc(nullptr) {
     99  mGenericTypes |= eDocument;
    100  mStateFlags |= eNotNodeMapEntry;
    101  mDoc = this;
    102 
    103  MOZ_ASSERT(mPresShell, "should have been given a pres shell");
    104  mPresShell->SetDocAccessible(this);
    105 }
    106 
    107 DocAccessible::~DocAccessible() {
    108  NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
    109 }
    110 
    111 ////////////////////////////////////////////////////////////////////////////////
    112 // nsISupports
    113 
    114 NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
    115 
    116 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible,
    117                                                  LocalAccessible)
    118  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
    119  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
    120  for (const auto& hashEntry : tmp->mDependentIDsHashes.Values()) {
    121    for (const auto& providers : hashEntry->Values()) {
    122      for (int32_t provIdx = providers->Length() - 1; provIdx >= 0; provIdx--) {
    123        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
    124            cb, "content of dependent ids hash entry of document accessible");
    125 
    126        const auto& provider = (*providers)[provIdx];
    127        cb.NoteXPCOMChild(provider->mContent);
    128      }
    129    }
    130  }
    131  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
    132  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
    133  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
    134  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingUpdates)
    135  for (const auto& ar : tmp->mARIAOwnsHash.Values()) {
    136    for (uint32_t i = 0; i < ar->Length(); i++) {
    137      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mARIAOwnsHash entry item");
    138      cb.NoteXPCOMChild(ar->ElementAt(i));
    139    }
    140  }
    141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    142 
    143 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, LocalAccessible)
    144  NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
    145  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
    146  tmp->mDependentIDsHashes.Clear();
    147  tmp->mNodeToAccessibleMap.Clear();
    148  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
    149  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
    150  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
    151  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingUpdates)
    152  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
    153  tmp->mARIAOwnsHash.Clear();
    154 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    155 
    156 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocAccessible)
    157  NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
    158  NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
    159  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
    160 NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
    161 
    162 NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
    163 NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
    164 
    165 ////////////////////////////////////////////////////////////////////////////////
    166 // nsIAccessible
    167 
    168 ENameValueFlag DocAccessible::DirectName(nsString& aName) const {
    169  aName.Truncate();
    170 
    171  if (mParent) {
    172    mParent->Name(aName);  // Allow owning iframe to override the name
    173  }
    174  if (aName.IsEmpty()) {
    175    // Allow name via aria-labelledby or title attribute
    176    LocalAccessible::DirectName(aName);
    177  }
    178  if (aName.IsEmpty()) {
    179    Title(aName);  // Try title element
    180  }
    181  if (aName.IsEmpty()) {  // Last resort: use URL
    182    URL(aName);
    183  }
    184 
    185  if (aName.IsEmpty()) {
    186    aName.SetIsVoid(true);
    187  }
    188 
    189  return eNameOK;
    190 }
    191 
    192 // LocalAccessible public method
    193 role DocAccessible::NativeRole() const {
    194  nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
    195  if (docShell) {
    196    nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
    197    docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
    198    int32_t itemType = docShell->ItemType();
    199    if (sameTypeRoot == docShell) {
    200      // Root of content or chrome tree
    201      if (itemType == nsIDocShellTreeItem::typeChrome) {
    202        return roles::CHROME_WINDOW;
    203      }
    204 
    205      if (itemType == nsIDocShellTreeItem::typeContent) {
    206        return roles::DOCUMENT;
    207      }
    208    } else if (itemType == nsIDocShellTreeItem::typeContent) {
    209      return roles::DOCUMENT;
    210    }
    211  }
    212 
    213  return roles::PANE;  // Fall back;
    214 }
    215 
    216 EDescriptionValueFlag DocAccessible::Description(nsString& aDescription) const {
    217  if (mParent) mParent->Description(aDescription);
    218 
    219  if (HasOwnContent() && aDescription.IsEmpty()) {
    220    nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
    221                                             aDescription);
    222  }
    223  return eDescriptionFromARIA;
    224 }
    225 
    226 // LocalAccessible public method
    227 uint64_t DocAccessible::NativeState() const {
    228  // Document is always focusable.
    229  uint64_t state =
    230      states::FOCUSABLE;  // keep in sync with NativeInteractiveState() impl
    231  if (FocusMgr()->IsFocused(this)) state |= states::FOCUSED;
    232 
    233  // Expose stale state until the document is ready (DOM is loaded and tree is
    234  // constructed).
    235  if (!HasLoadState(eReady)) state |= states::STALE;
    236 
    237  // Expose state busy until the document and all its subdocuments is completely
    238  // loaded.
    239  if (!HasLoadState(eCompletelyLoaded)) state |= states::BUSY;
    240 
    241  nsIFrame* frame = GetFrame();
    242  if (!frame || !frame->IsVisibleConsideringAncestors(
    243                    nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
    244    state |= states::INVISIBLE | states::OFFSCREEN;
    245  }
    246 
    247  RefPtr<EditorBase> editorBase = GetEditor();
    248  state |= editorBase ? states::EDITABLE : states::READONLY;
    249 
    250  // GetFrame() returns the root frame, which is normally what we want. However,
    251  // user-select: none might be set on the body, in which case this won't be
    252  // exposed on the root frame. Therefore, we explicitly use the body frame
    253  // here (if any).
    254  nsIFrame* bodyFrame = mContent ? mContent->GetPrimaryFrame() : nullptr;
    255  if ((state & states::EDITABLE) || (bodyFrame && bodyFrame->IsSelectable())) {
    256    // If the accessible is editable the layout selectable state only disables
    257    // mouse selection, but keyboard (shift+arrow) selection is still possible.
    258    state |= states::SELECTABLE_TEXT;
    259  }
    260 
    261  return state;
    262 }
    263 
    264 uint64_t DocAccessible::NativeInteractiveState() const {
    265  // Document is always focusable.
    266  return states::FOCUSABLE;
    267 }
    268 
    269 bool DocAccessible::NativelyUnavailable() const { return false; }
    270 
    271 // LocalAccessible public method
    272 void DocAccessible::ApplyARIAState(uint64_t* aState) const {
    273  // Grab states from content element.
    274  if (mContent) LocalAccessible::ApplyARIAState(aState);
    275 
    276  // Allow iframe/frame etc. to have final state override via ARIA.
    277  if (mParent) mParent->ApplyARIAState(aState);
    278 }
    279 
    280 Accessible* DocAccessible::FocusedChild() {
    281  // Return an accessible for the current global focus, which does not have to
    282  // be contained within the current document.
    283  return FocusMgr()->FocusedAccessible();
    284 }
    285 
    286 void DocAccessible::TakeFocus() const {
    287  // Focus the document.
    288  nsFocusManager* fm = nsFocusManager::GetFocusManager();
    289  RefPtr<dom::Element> newFocus;
    290  dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
    291  fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
    292                nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
    293 }
    294 
    295 // HyperTextAccessible method
    296 already_AddRefed<EditorBase> DocAccessible::GetEditor() const {
    297  // Check if document is editable (designMode="on" case). Otherwise check if
    298  // the html:body (for HTML document case) or document element is editable.
    299  if (!mDocumentNode->IsInDesignMode() &&
    300      (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE))) {
    301    return nullptr;
    302  }
    303 
    304  nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
    305  if (!docShell) {
    306    return nullptr;
    307  }
    308 
    309  nsCOMPtr<nsIEditingSession> editingSession;
    310  docShell->GetEditingSession(getter_AddRefs(editingSession));
    311  if (!editingSession) return nullptr;  // No editing session interface
    312 
    313  RefPtr<HTMLEditor> htmlEditor =
    314      editingSession->GetHTMLEditorForWindow(mDocumentNode->GetWindow());
    315  if (!htmlEditor) {
    316    return nullptr;
    317  }
    318 
    319  bool isEditable = false;
    320  htmlEditor->GetIsDocumentEditable(&isEditable);
    321  if (isEditable) {
    322    return htmlEditor.forget();
    323  }
    324 
    325  return nullptr;
    326 }
    327 
    328 // DocAccessible public method
    329 
    330 void DocAccessible::URL(nsAString& aURL) const {
    331  aURL.Truncate();
    332  nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
    333  nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
    334  if (MOZ_UNLIKELY(!webNav)) {
    335    return;
    336  }
    337 
    338  nsCOMPtr<nsIURI> uri;
    339  webNav->GetCurrentURI(getter_AddRefs(uri));
    340  if (MOZ_UNLIKELY(!uri)) {
    341    return;
    342  }
    343  // Let's avoid treating too long URI in the main process for avoiding
    344  // memory fragmentation as far as possible.
    345  if (uri->SchemeIs("data") || uri->SchemeIs("blob")) {
    346    return;
    347  }
    348 
    349  nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service();
    350  if (NS_WARN_IF(!io)) {
    351    return;
    352  }
    353  nsCOMPtr<nsIURI> exposableURI;
    354  if (NS_FAILED(io->CreateExposableURI(uri, getter_AddRefs(exposableURI))) ||
    355      MOZ_UNLIKELY(!exposableURI)) {
    356    return;
    357  }
    358  nsAutoCString theURL;
    359  if (NS_SUCCEEDED(exposableURI->GetSpec(theURL))) {
    360    CopyUTF8toUTF16(theURL, aURL);
    361  }
    362 }
    363 
    364 void DocAccessible::Title(nsString& aTitle) const {
    365  mDocumentNode->GetTitle(aTitle);
    366 }
    367 
    368 void DocAccessible::MimeType(nsAString& aType) const {
    369  mDocumentNode->GetContentType(aType);
    370 }
    371 
    372 void DocAccessible::DocType(nsAString& aType) const {
    373  dom::DocumentType* docType = mDocumentNode->GetDoctype();
    374  if (docType) docType->GetPublicId(aType);
    375 }
    376 
    377 // Certain cache domain updates might require updating other cache domains.
    378 // This function takes the given cache domains and returns those cache domains
    379 // plus any other required associated cache domains. Made for use with
    380 // QueueCacheUpdate.
    381 static uint64_t GetCacheDomainsQueueUpdateSuperset(uint64_t aCacheDomains) {
    382  // Text domain updates imply updates to the TextOffsetAttributes and
    383  // TextBounds domains.
    384  if (aCacheDomains & CacheDomain::Text) {
    385    aCacheDomains |= CacheDomain::TextOffsetAttributes;
    386    aCacheDomains |= CacheDomain::TextBounds;
    387  }
    388  // Bounds domain updates imply updates to the TextBounds domain.
    389  if (aCacheDomains & CacheDomain::Bounds) {
    390    aCacheDomains |= CacheDomain::TextBounds;
    391  }
    392  return aCacheDomains;
    393 }
    394 
    395 void DocAccessible::QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain,
    396                                     bool aBypassActiveDomains) {
    397  if (!mIPCDoc) {
    398    return;
    399  }
    400  // These strong references aren't necessary because WithEntryHandle is
    401  // guaranteed to run synchronously. However, static analysis complains without
    402  // them.
    403  RefPtr<DocAccessible> self = this;
    404  RefPtr<LocalAccessible> acc = aAcc;
    405  size_t arrayIndex =
    406      mQueuedCacheUpdatesHash.WithEntryHandle(aAcc, [self, acc](auto&& entry) {
    407        if (entry.HasEntry()) {
    408          // This LocalAccessible has already been queued. Return its index in
    409          // the queue array so we can update its queued domains.
    410          return entry.Data();
    411        }
    412        // Add this LocalAccessible to the queue array.
    413        size_t index = self->mQueuedCacheUpdatesArray.Length();
    414        self->mQueuedCacheUpdatesArray.EmplaceBack(std::make_pair(acc, 0));
    415        // Also add it to the hash map so we can avoid processing the same
    416        // LocalAccessible twice.
    417        return entry.Insert(index);
    418      });
    419 
    420  // We may need to bypass the active domain restriction when populating domains
    421  // for the first time. In that case, queue cache updates regardless of domain.
    422  if (aBypassActiveDomains) {
    423    auto& [arrayAcc, domain] = mQueuedCacheUpdatesArray[arrayIndex];
    424    MOZ_ASSERT(arrayAcc == aAcc);
    425    domain |= aNewDomain;
    426    Controller()->ScheduleProcessing();
    427    return;
    428  }
    429 
    430  // Potentially queue updates for required related domains.
    431  const uint64_t newDomains = GetCacheDomainsQueueUpdateSuperset(aNewDomain);
    432 
    433  // Only queue cache updates for domains that are active.
    434  const uint64_t domainsToUpdate =
    435      nsAccessibilityService::GetActiveCacheDomains() & newDomains;
    436 
    437  // Avoid queueing cache updates if we have no domains to update.
    438  if (domainsToUpdate == CacheDomain::None) {
    439    return;
    440  }
    441 
    442  auto& [arrayAcc, domain] = mQueuedCacheUpdatesArray[arrayIndex];
    443  MOZ_ASSERT(arrayAcc == aAcc);
    444  domain |= domainsToUpdate;
    445  Controller()->ScheduleProcessing();
    446 }
    447 
    448 void DocAccessible::QueueCacheUpdateForDependentRelations(
    449    LocalAccessible* aAcc, const nsAttrValue* aOldId) {
    450  if (!mIPCDoc || !aAcc || !aAcc->IsInDocument() || aAcc->IsDefunct()) {
    451    return;
    452  }
    453  dom::Element* el = aAcc->Elm();
    454  if (!el) {
    455    return;
    456  }
    457 
    458  // We call this function when we've noticed an ID change, or when an acc
    459  // is getting bound to its document. We need to ensure any existing accs
    460  // that depend on this acc's ID or Element have their relation cache entries
    461  // updated.
    462  RelatedAccIterator iter(this, el, nullptr);
    463  while (LocalAccessible* relatedAcc = iter.Next()) {
    464    if (relatedAcc->IsDefunct() || !relatedAcc->IsInDocument() ||
    465        mInsertedAccessibles.Contains(relatedAcc)) {
    466      continue;
    467    }
    468    QueueCacheUpdate(relatedAcc, CacheDomain::Relations);
    469  }
    470 
    471  if (aOldId) {
    472    // If we have an old ID, we need to update any accessibles that depended on
    473    // that ID as well.
    474    nsAutoString id;
    475    aOldId->ToString(id);
    476    if (!id.IsEmpty()) {
    477      auto* providers = GetRelProviders(el, id);
    478      if (providers) {
    479        for (auto& provider : *providers) {
    480          if (LocalAccessible* oldRelatedAcc =
    481                  GetAccessible(provider->mContent)) {
    482            QueueCacheUpdate(oldRelatedAcc, CacheDomain::Relations);
    483          }
    484        }
    485      }
    486    }
    487  }
    488 
    489  if (const nsIFrame* anchorFrame = nsCoreUtils::GetAnchorForPositionedFrame(
    490          mPresShell, aAcc->GetFrame())) {
    491    // If this accessible is anchored, retrieve the anchor and update its
    492    // relations.
    493    if (LocalAccessible* anchorAcc = GetAccessible(anchorFrame->GetContent())) {
    494      if (!mInsertedAccessibles.Contains(anchorAcc)) {
    495        QueueCacheUpdate(anchorAcc, CacheDomain::Relations);
    496      }
    497    }
    498  }
    499 
    500  if (nsIFrame* positionedFrame = nsCoreUtils::GetPositionedFrameForAnchor(
    501          mPresShell, aAcc->GetFrame())) {
    502    // If this accessible is an anchor, retrieve the positioned frame and
    503    // refresh the cache on all its anchors.
    504    if (LocalAccessible* targetAcc =
    505            GetAccessible(positionedFrame->GetContent())) {
    506      RefreshAnchorRelationCacheForTarget(targetAcc);
    507    }
    508  }
    509 }
    510 
    511 ////////////////////////////////////////////////////////////////////////////////
    512 // LocalAccessible
    513 
    514 void DocAccessible::Init() {
    515 #ifdef A11Y_LOG
    516  if (logging::IsEnabled(logging::eDocCreate)) {
    517    logging::DocCreate("document initialize", mDocumentNode, this);
    518  }
    519 #endif
    520 
    521  // Initialize notification controller.
    522  mNotificationController = new NotificationController(this, mPresShell);
    523 
    524  // Mark the DocAccessible as loaded if its DOM document is already loaded at
    525  // this point. This can happen for one of three reasons:
    526  // 1. A11y was started late.
    527  // 2. DOM loading for a document (probably an in-process iframe) completed
    528  // before its Accessible container was created.
    529  // 3. The PresShell for the document was created after DOM loading completed.
    530  // In that case, we tried to create the DocAccessible when DOM loading
    531  // completed, but we can't create a DocAccessible without a PresShell, so
    532  // this failed. The DocAccessible was subsequently created due to a layout
    533  // notification.
    534  if (mDocumentNode->GetReadyStateEnum() ==
    535          dom::Document::READYSTATE_COMPLETE &&
    536      !mDocumentNode->IsUncommittedInitialDocument()) {
    537    mLoadState |= eDOMLoaded;
    538    // If this happened due to reasons 1 or 2, it isn't *necessary* to fire a
    539    // doc load complete event. If it happened due to reason 3, we need to fire
    540    // doc load complete because clients (especially tests) might be waiting
    541    // for the document to load using this event. We can't distinguish why this
    542    // happened at this point, so just fire it regardless. It won't do any
    543    // harm even if it isn't necessary. We set mLoadEventType here and it will
    544    // be fired in ProcessLoad as usual.
    545    mLoadEventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
    546  } else if (mDocumentNode->IsUncommittedInitialDocument()) {
    547    // The initial about:blank always has its readyState as "complete"
    548    // even if it didn't fire a load event yet. We cannot know whether
    549    // it will load, so mark it loaded to avoid waiting for it.
    550    mLoadState |= eDOMLoaded;
    551  }
    552 
    553  AddEventListeners();
    554 }
    555 
    556 void DocAccessible::Shutdown() {
    557  if (!mPresShell) {  // already shutdown
    558    return;
    559  }
    560 
    561 #ifdef A11Y_LOG
    562  if (logging::IsEnabled(logging::eDocDestroy)) {
    563    logging::DocDestroy("document shutdown", mDocumentNode, this);
    564  }
    565 #endif
    566 
    567  // Mark the document as shutdown before AT is notified about the document
    568  // removal from its container (valid for root documents on ATK and due to
    569  // some reason for MSAA, refer to bug 757392 for details).
    570  MOZ_DIAGNOSTIC_ASSERT(!IsDefunct(),
    571                        "Already marked defunct. Reentrant shutdown!");
    572  mStateFlags |= eIsDefunct;
    573 
    574  if (mNotificationController) {
    575    mNotificationController->Shutdown();
    576    mNotificationController = nullptr;
    577  }
    578 
    579  RemoveEventListeners();
    580 
    581  if (mParent) {
    582    DocAccessible* parentDocument = mParent->Document();
    583    if (parentDocument) parentDocument->RemoveChildDocument(this);
    584 
    585    mParent->RemoveChild(this);
    586    MOZ_ASSERT(!mParent, "Parent has to be null!");
    587  }
    588 
    589  mPresShell->SetDocAccessible(nullptr);
    590  mPresShell = nullptr;  // Avoid reentrancy
    591 
    592  // Walk the array backwards because child documents remove themselves from the
    593  // array as they are shutdown.
    594  int32_t childDocCount = mChildDocuments.Length();
    595  for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
    596    mChildDocuments[idx]->Shutdown();
    597  }
    598 
    599  mChildDocuments.Clear();
    600  // mQueuedCacheUpdates* can contain a reference to this document (ex. if the
    601  // doc is scrollable and we're sending a scroll position update). Clear the
    602  // map here to avoid creating ref cycles.
    603  mQueuedCacheUpdatesArray.Clear();
    604  mQueuedCacheUpdatesHash.Clear();
    605 
    606  // XXX thinking about ordering?
    607  if (mIPCDoc) {
    608    MOZ_ASSERT(IPCAccessibilityActive());
    609    mIPCDoc->Shutdown();
    610    MOZ_ASSERT(!mIPCDoc);
    611  }
    612 
    613  mDependentIDsHashes.Clear();
    614  mDependentElementsMap.Clear();
    615  mNodeToAccessibleMap.Clear();
    616 
    617  mAnchorJumpElm = nullptr;
    618  mInvalidationList.Clear();
    619  mPendingUpdates.Clear();
    620 
    621  for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
    622    LocalAccessible* accessible = iter.Data();
    623    MOZ_ASSERT(accessible);
    624    if (accessible) {
    625      // This might have been focused with FocusManager::ActiveItemChanged. In
    626      // that case, we must notify FocusManager so that it clears the active
    627      // item. Otherwise, it will hold on to a defunct Accessible. Normally,
    628      // this happens in UnbindFromDocument, but we don't call that when the
    629      // whole document shuts down.
    630      if (FocusMgr()->WasLastFocused(accessible)) {
    631        FocusMgr()->ActiveItemChanged(nullptr);
    632 #ifdef A11Y_LOG
    633        if (logging::IsEnabled(logging::eFocus)) {
    634          logging::ActiveItemChangeCausedBy("doc shutdown", accessible);
    635        }
    636 #endif
    637      }
    638      if (!accessible->IsDefunct()) {
    639        // Unlink parent to avoid its cleaning overhead in shutdown.
    640        accessible->mParent = nullptr;
    641        accessible->Shutdown();
    642      }
    643    }
    644    iter.Remove();
    645  }
    646 
    647  HyperTextAccessible::Shutdown();
    648 
    649  MOZ_ASSERT(GetAccService());
    650  GetAccService()->NotifyOfDocumentShutdown(this, mDocumentNode);
    651  mDocumentNode = nullptr;
    652 }
    653 
    654 nsIFrame* DocAccessible::GetFrame() const {
    655  nsIFrame* root = nullptr;
    656  if (mPresShell) {
    657    root = mPresShell->GetRootFrame();
    658  }
    659 
    660  return root;
    661 }
    662 
    663 nsINode* DocAccessible::GetNode() const { return mDocumentNode; }
    664 
    665 // DocAccessible protected member
    666 nsRect DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const {
    667  *aRelativeFrame = GetFrame();
    668 
    669  dom::Document* document = mDocumentNode;
    670  dom::Document* parentDoc = nullptr;
    671 
    672  nsRect bounds;
    673  while (document) {
    674    PresShell* presShell = document->GetPresShell();
    675    if (!presShell) {
    676      return nsRect();
    677    }
    678 
    679    nsRect scrollPort;
    680    ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame();
    681    if (sf) {
    682      scrollPort = sf->GetScrollPortRect();
    683    } else {
    684      nsIFrame* rootFrame = presShell->GetRootFrame();
    685      if (!rootFrame) return nsRect();
    686 
    687      scrollPort = rootFrame->GetRect();
    688    }
    689 
    690    if (parentDoc) {  // After first time thru loop
    691      // XXXroc bogus code! scrollPort is relative to the viewport of
    692      // this document, but we're intersecting rectangles derived from
    693      // multiple documents and assuming they're all in the same coordinate
    694      // system. See bug 514117.
    695      bounds.IntersectRect(scrollPort, bounds);
    696    } else {  // First time through loop
    697      bounds = scrollPort;
    698    }
    699 
    700    document = parentDoc = document->GetInProcessParentDocument();
    701  }
    702 
    703  return bounds;
    704 }
    705 
    706 // DocAccessible protected member
    707 nsresult DocAccessible::AddEventListeners() {
    708  SelectionMgr()->AddDocSelectionListener(mPresShell);
    709 
    710  // Add document observer.
    711  mDocumentNode->AddObserver(this);
    712  return NS_OK;
    713 }
    714 
    715 // DocAccessible protected member
    716 nsresult DocAccessible::RemoveEventListeners() {
    717  // Remove listeners associated with content documents
    718  NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
    719 
    720  if (mDocumentNode) {
    721    mDocumentNode->RemoveObserver(this);
    722  }
    723 
    724  if (mScrollWatchTimer) {
    725    mScrollWatchTimer->Cancel();
    726    mScrollWatchTimer = nullptr;
    727    NS_RELEASE_THIS();  // Kung fu death grip
    728  }
    729 
    730  SelectionMgr()->RemoveDocSelectionListener(mPresShell);
    731  return NS_OK;
    732 }
    733 
    734 void DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure) {
    735  DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
    736 
    737  if (docAcc) {
    738    // Dispatch a scroll-end for all entries in table. They have not
    739    // been scrolled in at least `kScrollEventInterval`.
    740    for (auto iter = docAcc->mLastScrollingDispatch.Iter(); !iter.Done();
    741         iter.Next()) {
    742      docAcc->DispatchScrollingEvent(iter.Key(),
    743                                     nsIAccessibleEvent::EVENT_SCROLLING_END);
    744      iter.Remove();
    745    }
    746 
    747    if (docAcc->mScrollWatchTimer) {
    748      docAcc->mScrollWatchTimer = nullptr;
    749      NS_RELEASE(docAcc);  // Release kung fu death grip
    750    }
    751  }
    752 }
    753 
    754 void DocAccessible::HandleScroll(nsINode* aTarget) {
    755  nsINode* target = aTarget;
    756  LocalAccessible* targetAcc = GetAccessible(target);
    757  if (!targetAcc && target->IsInNativeAnonymousSubtree()) {
    758    // The scroll event for textareas comes from a native anonymous div. We need
    759    // the closest non-anonymous ancestor to get the right Accessible.
    760    target = target->GetClosestNativeAnonymousSubtreeRootParentOrHost();
    761    targetAcc = GetAccessible(target);
    762  }
    763  // Regardless of our scroll timer, we need to send a cache update
    764  // to ensure the next Bounds() query accurately reflects our position
    765  // after scrolling.
    766  if (targetAcc) {
    767    QueueCacheUpdate(targetAcc, CacheDomain::ScrollPosition);
    768  }
    769 
    770  const uint32_t kScrollEventInterval = 100;
    771  // If we haven't dispatched a scrolling event for a target in at least
    772  // kScrollEventInterval milliseconds, dispatch one now.
    773  mLastScrollingDispatch.WithEntryHandle(target, [&](auto&& lastDispatch) {
    774    const TimeStamp now = TimeStamp::Now();
    775 
    776    if (!lastDispatch ||
    777        (now - lastDispatch.Data()).ToMilliseconds() >= kScrollEventInterval) {
    778      // We can't fire events on a document whose tree isn't constructed yet.
    779      if (HasLoadState(eTreeConstructed)) {
    780        DispatchScrollingEvent(target, nsIAccessibleEvent::EVENT_SCROLLING);
    781      }
    782      lastDispatch.InsertOrUpdate(now);
    783    }
    784  });
    785 
    786  // If timer callback is still pending, push it 100ms into the future.
    787  // When scrolling ends and we don't fire HandleScroll anymore, the
    788  // timer callback will fire and dispatch an EVENT_SCROLLING_END.
    789  if (!mScrollWatchTimer) {
    790    // Can only fail on OOM and in that case we'd crash.
    791    mScrollWatchTimer = NS_NewTimer();
    792    NS_ADDREF_THIS();  // Kung fu death grip
    793  }
    794  mScrollWatchTimer->InitWithNamedFuncCallback(
    795      ScrollTimerCallback, this, kScrollEventInterval, nsITimer::TYPE_ONE_SHOT,
    796      "a11y::DocAccessible::ScrollPositionDidChange"_ns);
    797 }
    798 
    799 std::pair<nsPoint, nsRect> DocAccessible::ComputeScrollData(
    800    const LocalAccessible* aAcc, bool aShouldScaleByResolution) {
    801  nsPoint scrollPoint;
    802  nsRect scrollRange;
    803 
    804  if (nsIFrame* frame = aAcc->GetFrame()) {
    805    ScrollContainerFrame* sf = aAcc == this
    806                                   ? mPresShell->GetRootScrollContainerFrame()
    807                                   : frame->GetScrollTargetFrame();
    808 
    809    // If there is no scrollable frame, it's likely a scroll in a popup, like
    810    // <select>. Return a scroll offset and range of 0. The scroll info
    811    // is currently only used on Android, and popups are rendered natively
    812    // there.
    813    if (sf) {
    814      scrollPoint = sf->GetScrollPosition();
    815      scrollRange = sf->GetScrollRange();
    816 
    817      if (aShouldScaleByResolution) {
    818        scrollPoint = scrollPoint * mPresShell->GetResolution();
    819        scrollRange.ScaleRoundOut(mPresShell->GetResolution());
    820      }
    821    }
    822  }
    823 
    824  return {scrollPoint, scrollRange};
    825 }
    826 
    827 ////////////////////////////////////////////////////////////////////////////////
    828 // nsIDocumentObserver
    829 
    830 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
    831 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
    832 
    833 // When a reflected element IDL attribute changes, we might get the following
    834 // synchronous calls:
    835 // 1. AttributeWillChange for the element.
    836 // 2. AttributeWillChange for the content attribute.
    837 // 3. AttributeChanged for the content attribute.
    838 // 4. AttributeChanged for the element.
    839 // Since the content attribute value is "" for any element, we won't always get
    840 // 2 or 3. Even if we do, they might occur after the element has already
    841 // changed, which means we can't detect any relevant state changes there; e.g.
    842 // mPrevStateBits. Thus, we need 1 and 4, and we must ignore 2 and 3. To
    843 // facilitate this, sIsAttrElementChanging will be set to true for 2 and 3.
    844 static bool sIsAttrElementChanging = false;
    845 
    846 void DocAccessible::AttributeWillChange(dom::Element* aElement,
    847                                        int32_t aNameSpaceID,
    848                                        nsAtom* aAttribute,
    849                                        AttrModType aModType) {
    850  if (sIsAttrElementChanging) {
    851    // See the comment above the definition of sIsAttrElementChanging.
    852    return;
    853  }
    854  LocalAccessible* accessible = GetAccessible(aElement);
    855  if (!accessible) {
    856    if (aElement != mContent) return;
    857 
    858    accessible = this;
    859  }
    860 
    861  // Update dependent IDs cache. Take care of elements that are accessible
    862  // because dependent IDs cache doesn't contain IDs from non accessible
    863  // elements. We do this for attribute additions as well because there might
    864  // be an ElementInternals default value.
    865  RemoveDependentIDsFor(accessible, aAttribute);
    866  RemoveDependentElementsFor(accessible, aAttribute);
    867 
    868  if (aAttribute == nsGkAtoms::id) {
    869    if (accessible->IsActiveDescendant()) {
    870      RefPtr<AccEvent> event =
    871          new AccStateChangeEvent(accessible, states::ACTIVE, false);
    872      FireDelayedEvent(event);
    873    }
    874 
    875    RelocateARIAOwnedIfNeeded(aElement);
    876  }
    877 
    878  if (aAttribute == nsGkAtoms::aria_activedescendant) {
    879    if (LocalAccessible* activeDescendant = accessible->CurrentItem()) {
    880      RefPtr<AccEvent> event =
    881          new AccStateChangeEvent(activeDescendant, states::ACTIVE, false);
    882      FireDelayedEvent(event);
    883    }
    884  }
    885 
    886  if ((aModType == AttrModType::Modification ||
    887       aModType == AttrModType::Removal)) {
    888    // If this is a modification or removal of aria-actions, and the
    889    // accessible's name is calculated by the subtree, there may be a change to
    890    // the name of the accessible.
    891    // If this is a modification or removal of an id, an aria-actions relation
    892    // might be severed, and thus change the name of any ancestors.
    893    // XXX: We don't track the actual changes, so the name change event might
    894    // be fired for not actual name change, but better to fire an event than to
    895    // not.
    896    MaybeHandleChangeToAriaActions(accessible, aAttribute);
    897  }
    898 
    899  // If attribute affects accessible's state, store the old state so we can
    900  // later compare it against the state of the accessible after the attribute
    901  // change.
    902  if (accessible->AttributeChangesState(aAttribute)) {
    903    mPrevStateBits = accessible->State();
    904  } else {
    905    mPrevStateBits = 0;
    906  }
    907 }
    908 
    909 void DocAccessible::AttributeChanged(dom::Element* aElement,
    910                                     int32_t aNameSpaceID, nsAtom* aAttribute,
    911                                     AttrModType aModType,
    912                                     const nsAttrValue* aOldValue) {
    913  if (sIsAttrElementChanging) {
    914    // See the comment above the definition of sIsAttrElementChanging.
    915    return;
    916  }
    917  NS_ASSERTION(!IsDefunct(),
    918               "Attribute changed called on defunct document accessible!");
    919 
    920  // Proceed even if the element is not accessible because element may become
    921  // accessible if it gets certain attribute.
    922  if (UpdateAccessibleOnAttrChange(aElement, aAttribute)) return;
    923 
    924  // Update the accessible tree on aria-hidden change. Make sure to not create
    925  // a tree under aria-hidden='true'.
    926  if (aAttribute == nsGkAtoms::aria_hidden) {
    927    if (aria::IsValidARIAHidden(aElement)) {
    928      ContentRemoved(aElement);
    929    } else {
    930      ContentInserted(aElement, aElement->GetNextSibling());
    931    }
    932    return;
    933  }
    934 
    935  if (aAttribute == nsGkAtoms::slot &&
    936      !aElement->GetFlattenedTreeParentNode() && aElement != mContent) {
    937    // Element is inside a shadow host but is no longer slotted.
    938    mDoc->ContentRemoved(aElement);
    939    return;
    940  }
    941 
    942  LocalAccessible* accessible = GetAccessible(aElement);
    943  if (!accessible) {
    944    if (mContent == aElement) {
    945      // The attribute change occurred on the root content of this
    946      // DocAccessible, so handle it as an attribute change on this.
    947      accessible = this;
    948    } else {
    949      if (aModType == AttrModType::Addition &&
    950          aria::AttrCharacteristicsFor(aAttribute) & ATTR_GLOBAL) {
    951        // The element doesn't have an Accessible, but a global ARIA attribute
    952        // was just added, which means we should probably create an Accessible.
    953        ContentInserted(aElement, aElement->GetNextSibling());
    954        return;
    955      }
    956      // The element doesn't have an Accessible, so ignore the attribute
    957      // change.
    958      return;
    959    }
    960  }
    961 
    962  MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
    963             "DOM attribute change on an accessible detached from the tree");
    964 
    965  if (aAttribute == nsGkAtoms::id) {
    966    dom::Element* elm = accessible->Elm();
    967    RelocateARIAOwnedIfNeeded(elm);
    968    ARIAActiveDescendantIDMaybeMoved(accessible);
    969    QueueCacheUpdate(accessible, CacheDomain::DOMNodeIDAndClass);
    970    QueueCacheUpdateForDependentRelations(accessible, aOldValue);
    971  }
    972 
    973  // The activedescendant universal property redirects accessible focus events
    974  // to the element with the id that activedescendant points to. Make sure
    975  // the tree up to date before processing. In other words, when a node has just
    976  // been inserted, the tree won't be up to date yet, so we must always schedule
    977  // an async notification so that a newly inserted node will be present in
    978  // the tree.
    979  if (aAttribute == nsGkAtoms::aria_activedescendant) {
    980    mNotificationController
    981        ->ScheduleNotification<DocAccessible, LocalAccessible>(
    982            this, &DocAccessible::ARIAActiveDescendantChanged, accessible);
    983  }
    984 
    985  // Defer to accessible any needed actions like changing states or emiting
    986  // events.
    987  accessible->DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, aOldValue,
    988                                  mPrevStateBits);
    989 
    990  // Update dependent IDs cache. We handle elements with accessibles.
    991  // If the accessible or element with the ID doesn't exist yet the cache will
    992  // be updated when they are added.
    993  if (IsAdditionOrModification(aModType)) {
    994    AddDependentIDsFor(accessible, aAttribute);
    995    AddDependentElementsFor(accessible, aAttribute);
    996 
    997    // If this is a modification or addition of aria-actions, and the
    998    // accessible's name is calculated by the subtree, there may be a change to
    999    // the name of the accessible.
   1000    // If this is a modification or addition of an id, an aria-actions relation
   1001    // might be restored, and thus change the name of any ancestors.
   1002    // XXX: We don't track the actual changes, so the name change event might
   1003    // be fired for not actual name change, but better to fire an event than to
   1004    // not.
   1005    // In the case of a modification we may have already queued a name
   1006    // change event in the `AttributeWillChange` stage, but we rely on
   1007    // EventQueue to quash any duplicates.
   1008    MaybeHandleChangeToAriaActions(accessible, aAttribute);
   1009  }
   1010 }
   1011 
   1012 void DocAccessible::ARIAAttributeDefaultWillChange(dom::Element* aElement,
   1013                                                   nsAtom* aAttribute,
   1014                                                   AttrModType aModType) {
   1015  NS_ASSERTION(!IsDefunct(),
   1016               "Attribute changed called on defunct document accessible!");
   1017 
   1018  if (aElement->HasAttr(aAttribute)) {
   1019    return;
   1020  }
   1021 
   1022  AttributeWillChange(aElement, kNameSpaceID_None, aAttribute, aModType);
   1023 }
   1024 
   1025 void DocAccessible::ARIAAttributeDefaultChanged(dom::Element* aElement,
   1026                                                nsAtom* aAttribute,
   1027                                                AttrModType aModType) {
   1028  NS_ASSERTION(!IsDefunct(),
   1029               "Attribute changed called on defunct document accessible!");
   1030 
   1031  if (aElement->HasAttr(aAttribute)) {
   1032    return;
   1033  }
   1034 
   1035  AttributeChanged(aElement, kNameSpaceID_None, aAttribute, aModType, nullptr);
   1036 }
   1037 
   1038 void DocAccessible::ARIAActiveDescendantChanged(LocalAccessible* aAccessible) {
   1039  if (dom::Element* elm = aAccessible->Elm()) {
   1040    nsAutoString id;
   1041    if (dom::Element* activeDescendantElm =
   1042            nsCoreUtils::GetAriaActiveDescendantElement(elm)) {
   1043      LocalAccessible* activeDescendant = GetAccessible(activeDescendantElm);
   1044      if (activeDescendant) {
   1045        RefPtr<AccEvent> event =
   1046            new AccStateChangeEvent(activeDescendant, states::ACTIVE, true);
   1047        FireDelayedEvent(event);
   1048        if (aAccessible->IsActiveWidget()) {
   1049          FocusMgr()->ActiveItemChanged(activeDescendant, false);
   1050 #ifdef A11Y_LOG
   1051          if (logging::IsEnabled(logging::eFocus)) {
   1052            logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
   1053                                              activeDescendant);
   1054          }
   1055 #endif
   1056        }
   1057        return;
   1058      }
   1059    }
   1060 
   1061    // aria-activedescendant was cleared or changed to a non-existent node.
   1062    // Move focus back to the element itself if it has DOM focus.
   1063    if (aAccessible->IsActiveWidget()) {
   1064      FocusMgr()->ActiveItemChanged(aAccessible, false);
   1065 #ifdef A11Y_LOG
   1066      if (logging::IsEnabled(logging::eFocus)) {
   1067        logging::ActiveItemChangeCausedBy("ARIA activedescedant cleared",
   1068                                          aAccessible);
   1069      }
   1070 #endif
   1071    }
   1072  }
   1073 }
   1074 
   1075 void DocAccessible::ContentAppended(nsIContent* aFirstNewContent,
   1076                                    const ContentAppendInfo&) {
   1077  MaybeHandleChangeToHiddenNameOrDescription(aFirstNewContent);
   1078 }
   1079 
   1080 void DocAccessible::ElementStateChanged(dom::Document* aDocument,
   1081                                        dom::Element* aElement,
   1082                                        dom::ElementState aStateMask) {
   1083  LocalAccessible* accessible =
   1084      aElement == mContent ? this : GetAccessible(aElement);
   1085 
   1086  if (!accessible) {
   1087    return;
   1088  }
   1089 
   1090  if (aStateMask.HasState(dom::ElementState::READWRITE) &&
   1091      !accessible->IsTextField()) {
   1092    const bool isEditable =
   1093        aElement->State().HasState(dom::ElementState::READWRITE);
   1094    RefPtr<AccEvent> event =
   1095        new AccStateChangeEvent(accessible, states::EDITABLE, isEditable);
   1096    FireDelayedEvent(event);
   1097    if (accessible == this || aElement->IsHTMLElement(nsGkAtoms::article)) {
   1098      // We want <article> to behave like a document in terms of readonly state.
   1099      event =
   1100          new AccStateChangeEvent(accessible, states::READONLY, !isEditable);
   1101      FireDelayedEvent(event);
   1102    }
   1103 
   1104    if (aElement->HasAttr(nsGkAtoms::aria_owns)) {
   1105      // If this has aria-owns, update children that are relocated into here.
   1106      // If we are becoming editable, put them back into their original
   1107      // containers, if we are becoming readonly, acquire them.
   1108      mNotificationController->ScheduleRelocation(accessible);
   1109    }
   1110 
   1111    // If this is a node inside of a newly editable subtree, it needs to be
   1112    // un-aria-owned. And inversely, if the node becomes uneditable, allow the
   1113    // node to be aria-owned.
   1114    RelocateARIAOwnedIfNeeded(aElement);
   1115  }
   1116 
   1117  if (aStateMask.HasState(dom::ElementState::CHECKED)) {
   1118    LocalAccessible* widget = accessible->ContainerWidget();
   1119    if (widget && widget->IsSelect()) {
   1120      // Changing selection here changes what we cache for
   1121      // the viewport.
   1122      SetViewportCacheDirty(true);
   1123      AccSelChangeEvent::SelChangeType selChangeType =
   1124          aElement->State().HasState(dom::ElementState::CHECKED)
   1125              ? AccSelChangeEvent::eSelectionAdd
   1126              : AccSelChangeEvent::eSelectionRemove;
   1127      RefPtr<AccEvent> event =
   1128          new AccSelChangeEvent(widget, accessible, selChangeType);
   1129      FireDelayedEvent(event);
   1130      return;
   1131    }
   1132 
   1133    RefPtr<AccEvent> event = new AccStateChangeEvent(
   1134        accessible, states::CHECKED,
   1135        aElement->State().HasState(dom::ElementState::CHECKED));
   1136    FireDelayedEvent(event);
   1137  }
   1138 
   1139  if (aStateMask.HasState(dom::ElementState::INVALID)) {
   1140    RefPtr<AccEvent> event =
   1141        new AccStateChangeEvent(accessible, states::INVALID);
   1142    FireDelayedEvent(event);
   1143  }
   1144 
   1145  if (aStateMask.HasState(dom::ElementState::REQUIRED)) {
   1146    RefPtr<AccEvent> event =
   1147        new AccStateChangeEvent(accessible, states::REQUIRED);
   1148    FireDelayedEvent(event);
   1149  }
   1150 
   1151  if (aStateMask.HasState(dom::ElementState::VISITED)) {
   1152    RefPtr<AccEvent> event =
   1153        new AccStateChangeEvent(accessible, states::TRAVERSED, true);
   1154    FireDelayedEvent(event);
   1155  }
   1156 
   1157  // We only expose dom::ElementState::DEFAULT on buttons, but we can get
   1158  // notifications for other controls like checkboxes.
   1159  if (aStateMask.HasState(dom::ElementState::DEFAULT) &&
   1160      accessible->IsButton()) {
   1161    RefPtr<AccEvent> event =
   1162        new AccStateChangeEvent(accessible, states::DEFAULT);
   1163    FireDelayedEvent(event);
   1164  }
   1165 
   1166  if (aStateMask.HasState(dom::ElementState::INDETERMINATE)) {
   1167    RefPtr<AccEvent> event = new AccStateChangeEvent(accessible, states::MIXED);
   1168    FireDelayedEvent(event);
   1169  }
   1170 
   1171  if (aStateMask.HasState(dom::ElementState::DISABLED) &&
   1172      !nsAccUtils::ARIAAttrValueIs(aElement, nsGkAtoms::aria_disabled,
   1173                                   nsGkAtoms::_true, eCaseMatters)) {
   1174    // The DOM disabled state has changed and there is no aria-disabled="true"
   1175    // taking precedence.
   1176    RefPtr<AccEvent> event =
   1177        new AccStateChangeEvent(accessible, states::UNAVAILABLE);
   1178    FireDelayedEvent(event);
   1179    event = new AccStateChangeEvent(accessible, states::ENABLED);
   1180    FireDelayedEvent(event);
   1181    // This likely changes focusability as well.
   1182    event = new AccStateChangeEvent(accessible, states::FOCUSABLE);
   1183    FireDelayedEvent(event);
   1184  }
   1185 }
   1186 
   1187 void DocAccessible::CharacterDataWillChange(nsIContent* aContent,
   1188                                            const CharacterDataChangeInfo&) {}
   1189 
   1190 void DocAccessible::CharacterDataChanged(nsIContent* aContent,
   1191                                         const CharacterDataChangeInfo&) {
   1192  MaybeHandleChangeToHiddenNameOrDescription(aContent);
   1193 }
   1194 
   1195 void DocAccessible::ContentInserted(nsIContent* aChild,
   1196                                    const ContentInsertInfo&) {
   1197  MaybeHandleChangeToHiddenNameOrDescription(aChild);
   1198 }
   1199 
   1200 void DocAccessible::ContentWillBeRemoved(nsIContent* aChildNode,
   1201                                         const ContentRemoveInfo&) {
   1202 #ifdef A11Y_LOG
   1203  if (logging::IsEnabled(logging::eTree)) {
   1204    logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
   1205    logging::Node("container node", aChildNode->GetParent());
   1206    logging::Node("content node", aChildNode);
   1207    logging::MsgEnd();
   1208  }
   1209 #endif
   1210  ContentRemoved(aChildNode);
   1211 }
   1212 
   1213 void DocAccessible::ParentChainChanged(nsIContent* aContent) {}
   1214 
   1215 ////////////////////////////////////////////////////////////////////////////////
   1216 // LocalAccessible
   1217 
   1218 #ifdef A11Y_LOG
   1219 nsresult DocAccessible::HandleAccEvent(AccEvent* aEvent) {
   1220  if (logging::IsEnabled(logging::eDocLoad)) {
   1221    logging::DocLoadEventHandled(aEvent);
   1222  }
   1223 
   1224  return HyperTextAccessible::HandleAccEvent(aEvent);
   1225 }
   1226 #endif
   1227 
   1228 ////////////////////////////////////////////////////////////////////////////////
   1229 // Public members
   1230 
   1231 nsPresContext* DocAccessible::PresContext() const {
   1232  return mPresShell->GetPresContext();
   1233 }
   1234 
   1235 void* DocAccessible::GetNativeWindow() const {
   1236  if (!mPresShell) {
   1237    return nullptr;
   1238  }
   1239  if (nsIWidget* widget = mPresShell->GetRootWidget()) {
   1240    return widget->GetNativeData(NS_NATIVE_WINDOW);
   1241  }
   1242  return nullptr;
   1243 }
   1244 
   1245 LocalAccessible* DocAccessible::GetAccessibleByUniqueIDInSubtree(
   1246    void* aUniqueID) {
   1247  LocalAccessible* child = GetAccessibleByUniqueID(aUniqueID);
   1248  if (child) return child;
   1249 
   1250  uint32_t childDocCount = mChildDocuments.Length();
   1251  for (uint32_t childDocIdx = 0; childDocIdx < childDocCount; childDocIdx++) {
   1252    DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
   1253    child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
   1254    if (child) return child;
   1255  }
   1256 
   1257  return nullptr;
   1258 }
   1259 
   1260 LocalAccessible* DocAccessible::GetAccessibleOrContainer(
   1261    nsINode* aNode, bool aNoContainerIfPruned) const {
   1262  if (!aNode || !aNode->GetComposedDoc()) {
   1263    return nullptr;
   1264  }
   1265 
   1266  nsINode* start = aNode;
   1267  if (auto* shadowRoot = dom::ShadowRoot::FromNode(aNode)) {
   1268    // This can happen, for example, when called within
   1269    // SelectionManager::ProcessSelectionChanged due to focusing a direct
   1270    // child of a shadow root.
   1271    // GetFlattenedTreeParent works on children of a shadow root, but not the
   1272    // shadow root itself.
   1273    start = shadowRoot->GetHost();
   1274    if (!start) {
   1275      return nullptr;
   1276    }
   1277  }
   1278 
   1279  for (nsINode* currNode : dom::InclusiveFlatTreeAncestors(*start)) {
   1280    // No container if is inside of aria-hidden subtree.
   1281    if (aNoContainerIfPruned && currNode->IsElement() &&
   1282        aria::IsValidARIAHidden(currNode->AsElement())) {
   1283      return nullptr;
   1284    }
   1285 
   1286    // Check if node is in zero-sized map
   1287    if (aNoContainerIfPruned && currNode->IsHTMLElement(nsGkAtoms::map)) {
   1288      if (nsIFrame* frame = currNode->AsContent()->GetPrimaryFrame()) {
   1289        if (nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame->GetParent())
   1290                .IsEmpty()) {
   1291          return nullptr;
   1292        }
   1293      }
   1294    }
   1295 
   1296    if (LocalAccessible* accessible = GetAccessible(currNode)) {
   1297      return accessible;
   1298    }
   1299  }
   1300 
   1301  return nullptr;
   1302 }
   1303 
   1304 LocalAccessible* DocAccessible::GetContainerAccessible(nsINode* aNode) const {
   1305  return aNode ? GetAccessibleOrContainer(aNode->GetFlattenedTreeParentNode())
   1306               : nullptr;
   1307 }
   1308 
   1309 LocalAccessible* DocAccessible::GetAccessibleOrDescendant(
   1310    nsINode* aNode) const {
   1311  LocalAccessible* acc = GetAccessible(aNode);
   1312  if (acc) return acc;
   1313 
   1314  if (aNode == mContent || aNode == mDocumentNode->GetRootElement()) {
   1315    // If the node is the doc's body or root element, return the doc accessible.
   1316    return const_cast<DocAccessible*>(this);
   1317  }
   1318 
   1319  acc = GetContainerAccessible(aNode);
   1320  if (acc) {
   1321    TreeWalker walker(acc, aNode->AsContent(),
   1322                      TreeWalker::eWalkCache | TreeWalker::eScoped);
   1323    return walker.Next();
   1324  }
   1325 
   1326  return nullptr;
   1327 }
   1328 
   1329 void DocAccessible::BindToDocument(LocalAccessible* aAccessible,
   1330                                   const nsRoleMapEntry* aRoleMapEntry) {
   1331  // Put into DOM node cache.
   1332  if (aAccessible->IsNodeMapEntry()) {
   1333    mNodeToAccessibleMap.InsertOrUpdate(aAccessible->GetNode(), aAccessible);
   1334  }
   1335 
   1336  // Put into unique ID cache.
   1337  mAccessibleCache.InsertOrUpdate(aAccessible->UniqueID(), RefPtr{aAccessible});
   1338 
   1339  aAccessible->SetRoleMapEntry(aRoleMapEntry);
   1340 
   1341  if (aAccessible->HasOwnContent()) {
   1342    AddDependentIDsFor(aAccessible);
   1343    AddDependentElementsFor(aAccessible);
   1344 
   1345    nsIContent* content = aAccessible->GetContent();
   1346    if (content->IsElement() &&
   1347        nsAccUtils::HasARIAAttr(content->AsElement(), nsGkAtoms::aria_owns)) {
   1348      mNotificationController->ScheduleRelocation(aAccessible);
   1349    }
   1350  }
   1351 
   1352  if (mIPCDoc && HasLoadState(eTreeConstructed)) {
   1353    // Child process and not in initial tree construction phase.
   1354    // We need to track inserted accessibles so we don't mark them as moved
   1355    // before their initial show event.
   1356    // If this is the initial tree construction, we will push the tree to the
   1357    // parent process before we process moves, so we will always need to mark a
   1358    // relocated accessible as moved.
   1359    mInsertedAccessibles.EnsureInserted(aAccessible);
   1360  }
   1361 
   1362  QueueCacheUpdateForDependentRelations(aAccessible);
   1363 }
   1364 
   1365 void DocAccessible::UnbindFromDocument(LocalAccessible* aAccessible) {
   1366  NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
   1367               "Unbinding the unbound accessible!");
   1368 
   1369  // Fire focus event on accessible having DOM focus if last focus was removed
   1370  // from the tree.
   1371  if (FocusMgr()->WasLastFocused(aAccessible)) {
   1372    FocusMgr()->ActiveItemChanged(nullptr);
   1373 #ifdef A11Y_LOG
   1374    if (logging::IsEnabled(logging::eFocus)) {
   1375      logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
   1376    }
   1377 #endif
   1378  }
   1379 
   1380  // Remove an accessible from node-to-accessible map if it exists there.
   1381  if (aAccessible->IsNodeMapEntry() &&
   1382      mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible) {
   1383    mNodeToAccessibleMap.Remove(aAccessible->GetNode());
   1384  }
   1385 
   1386  aAccessible->mStateFlags |= eIsNotInDocument;
   1387 
   1388  // Update XPCOM part.
   1389  xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
   1390  if (xpcDoc) xpcDoc->NotifyOfShutdown(aAccessible);
   1391 
   1392  void* uniqueID = aAccessible->UniqueID();
   1393 
   1394  NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
   1395  aAccessible->Shutdown();
   1396 
   1397  mAccessibleCache.Remove(uniqueID);
   1398 }
   1399 
   1400 void DocAccessible::ContentInserted(nsIContent* aStartChildNode,
   1401                                    nsIContent* aEndChildNode) {
   1402  // Ignore content insertions until we constructed accessible tree. Otherwise
   1403  // schedule tree update on content insertion after layout.
   1404  if (!mNotificationController || !HasLoadState(eTreeConstructed)) {
   1405    return;
   1406  }
   1407 
   1408  // The frame constructor guarantees that only ranges with the same parent
   1409  // arrive here in presence of dynamic changes to the page, see
   1410  // nsCSSFrameConstructor::IssueSingleInsertNotifications' callers.
   1411  nsINode* parent = aStartChildNode->GetFlattenedTreeParentNode();
   1412  if (!parent) {
   1413    return;
   1414  }
   1415 
   1416  LocalAccessible* container = AccessibleOrTrueContainer(parent);
   1417  if (!container) {
   1418    return;
   1419  }
   1420 
   1421  AutoTArray<nsCOMPtr<nsIContent>, 10> list;
   1422  for (nsIContent* node = aStartChildNode; node != aEndChildNode;
   1423       node = node->GetNextSibling()) {
   1424    MOZ_ASSERT(parent == node->GetFlattenedTreeParentNode());
   1425    if (PruneOrInsertSubtree(node)) {
   1426      list.AppendElement(node);
   1427    }
   1428  }
   1429 
   1430  mNotificationController->ScheduleContentInsertion(container, list);
   1431 }
   1432 
   1433 void DocAccessible::ScheduleTreeUpdate(nsIContent* aContent) {
   1434  if (mPendingUpdates.Contains(aContent)) {
   1435    return;
   1436  }
   1437  mPendingUpdates.AppendElement(aContent);
   1438  mNotificationController->ScheduleProcessing();
   1439 }
   1440 
   1441 void DocAccessible::ProcessPendingUpdates() {
   1442  auto updates = std::move(mPendingUpdates);
   1443  for (auto update : updates) {
   1444    if (update->GetComposedDoc() != mDocumentNode) {
   1445      continue;
   1446    }
   1447    // The pruning logic will take care of avoiding unnecessary notifications.
   1448    ContentInserted(update, update->GetNextSibling());
   1449  }
   1450 }
   1451 
   1452 bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
   1453  AUTO_PROFILER_MARKER_TEXT("DocAccessible::PruneOrInsertSubtree", A11Y, {},
   1454                            ""_ns);
   1455  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_PruneOrInsertSubtree>
   1456      autoRecording;
   1457  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
   1458 
   1459  bool insert = false;
   1460 
   1461  // In the case that we are, or are in, a shadow host, we need to assure
   1462  // some accessibles are removed if they are not rendered anymore.
   1463  nsIContent* shadowHost =
   1464      aRoot->GetShadowRoot() ? aRoot : aRoot->GetContainingShadowHost();
   1465  if (shadowHost) {
   1466    // Check all explicit children in the host, if they are not slotted
   1467    // then remove their accessibles and subtrees.
   1468    for (nsIContent* childNode = shadowHost->GetFirstChild(); childNode;
   1469         childNode = childNode->GetNextSibling()) {
   1470      if (!childNode->GetPrimaryFrame() &&
   1471          !nsCoreUtils::CanCreateAccessibleWithoutFrame(childNode)) {
   1472        ContentRemoved(childNode);
   1473      }
   1474    }
   1475 
   1476    // If this is a slot, check to see if its fallback content is rendered,
   1477    // if not - remove it.
   1478    if (aRoot->IsHTMLElement(nsGkAtoms::slot)) {
   1479      for (nsIContent* childNode = aRoot->GetFirstChild(); childNode;
   1480           childNode = childNode->GetNextSibling()) {
   1481        if (!childNode->GetPrimaryFrame() &&
   1482            !nsCoreUtils::CanCreateAccessibleWithoutFrame(childNode)) {
   1483          ContentRemoved(childNode);
   1484        }
   1485      }
   1486    }
   1487  }
   1488 
   1489  // If we already have an accessible, check if we need to remove it, recreate
   1490  // it, or keep it in place.
   1491  LocalAccessible* acc = GetAccessible(aRoot);
   1492  if (acc) {
   1493    MOZ_ASSERT(aRoot == acc->GetContent(),
   1494               "LocalAccessible has differing content!");
   1495 #ifdef A11Y_LOG
   1496    if (logging::IsEnabled(logging::eTree)) {
   1497      logging::MsgBegin(
   1498          "TREE", "inserted content already has accessible; doc: %p", this);
   1499      logging::Node("content node", aRoot);
   1500      logging::AccessibleInfo("accessible node", acc);
   1501      logging::MsgEnd();
   1502    }
   1503 #endif
   1504 
   1505    nsIFrame* frame = acc->GetFrame();
   1506    if (frame) {
   1507      acc->MaybeQueueCacheUpdateForStyleChanges();
   1508    }
   1509 
   1510    // LocalAccessible has no frame and it's not display:contents. Remove it.
   1511    // As well as removing the a11y subtree, we must also remove Accessibles
   1512    // for DOM descendants, since some of these might be relocated Accessibles
   1513    // and their DOM nodes are now hidden as well.
   1514    if (!frame && !nsCoreUtils::CanCreateAccessibleWithoutFrame(aRoot)) {
   1515      ContentRemoved(aRoot);
   1516      return false;
   1517    }
   1518 
   1519    if (!frame && aRoot->IsElement() &&
   1520        aRoot->AsElement()->IsDisplayContents() && acc->mOldComputedStyle) {
   1521      // This element has probably just become display: contents. We won't be
   1522      // notified of a computed style change in this case. Also, the bounds we
   1523      // cached previously are now invalid, but our normal bounds change
   1524      // notifications won't fire either. Therefore, queue cache updates for
   1525      // both.
   1526      QueueCacheUpdate(acc, CacheDomain::Style | CacheDomain::Bounds);
   1527    }
   1528 
   1529    // If the frame is hidden because its ancestor is specified with
   1530    // `content-visibility: hidden`, remove its Accessible.
   1531    if (frame && frame->IsHiddenByContentVisibilityOnAnyAncestor(
   1532                     nsIFrame::IncludeContentVisibility::Hidden)) {
   1533      ContentRemoved(aRoot);
   1534      return false;
   1535    }
   1536 
   1537    // If it's a XULLabel it was probably reframed because a `value` attribute
   1538    // was added. The accessible creates its text leaf upon construction, so we
   1539    // need to recreate. Remove it, and schedule for reconstruction.
   1540    if (acc->IsXULLabel()) {
   1541      ContentRemoved(acc);
   1542      return true;
   1543    }
   1544 
   1545    if (frame && frame->IsReplaced() && frame->AccessibleType() == eImageType &&
   1546        !aRoot->IsHTMLElement(nsGkAtoms::img)) {
   1547      // This is an image specified using the CSS content property which
   1548      // replaces the content of the node. Its frame might be reconstructed,
   1549      // which means its alt text might have changed. We expose the alt text
   1550      // as the name, so fire a name change event.
   1551      // We will schedule this for reinsertion below, and prune any children if
   1552      // they exist.
   1553      FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, acc);
   1554    } else if (frame &&
   1555               (acc->IsImage() != (frame->AccessibleType() == eImageType))) {
   1556      // It is a broken image that is being reframed because it either got
   1557      // or lost an `alt` tag that would rerender this node as text.
   1558      ContentRemoved(aRoot);
   1559      return true;
   1560    }
   1561 
   1562    // If the frame is an OuterDoc frame but this isn't an OuterDocAccessible,
   1563    // we need to recreate the LocalAccessible. This can happen for embed or
   1564    // object elements if their embedded content changes to be web content.
   1565    if (frame && !acc->IsOuterDoc() &&
   1566        frame->AccessibleType() == eOuterDocType) {
   1567      ContentRemoved(aRoot);
   1568      return true;
   1569    }
   1570 
   1571    // If the content is focused, and is being re-framed, reset the selection
   1572    // listener for the node because the previous selection listener is on the
   1573    // old frame.
   1574    if (aRoot->IsElement() && FocusMgr()->HasDOMFocus(aRoot)) {
   1575      SelectionMgr()->SetControlSelectionListener(aRoot->AsElement());
   1576    }
   1577 
   1578    // If the accessible is a table, or table part, its layout table
   1579    // status may have changed. We need to invalidate the associated
   1580    // mac table cache, which listens for the following event. We don't
   1581    // use this cache when the core cache is enabled, so to minimise event
   1582    // traffic only fire this event when that cache is off.
   1583    if (acc->IsTable() || acc->IsTableRow() || acc->IsTableCell()) {
   1584      LocalAccessible* table = nsAccUtils::TableFor(acc);
   1585      if (table && table->IsTable()) {
   1586        QueueCacheUpdate(table, CacheDomain::Table);
   1587      }
   1588    }
   1589 
   1590    // The accessible can be reparented or reordered in its parent.
   1591    // We schedule it for reinsertion. For example, a slotted element
   1592    // can change its slot attribute to a different slot.
   1593    insert = true;
   1594 
   1595    // If the frame is invisible, remove it.
   1596    // Normally, layout sends explicit a11y notifications for visibility
   1597    // changes (see SendA11yNotifications in RestyleManager). However, if a
   1598    // visibility change also reconstructs the frame, we must handle it here.
   1599    if (frame && !frame->StyleVisibility()->IsVisible()) {
   1600      ContentRemoved(aRoot);
   1601      // There might be visible descendants, so we want to walk the subtree.
   1602      // However, we know we don't want to reinsert this node, so we set insert
   1603      // to false.
   1604      insert = false;
   1605    }
   1606  } else {
   1607    // If there is no current accessible, and the node has a frame, or is
   1608    // display:contents, schedule it for insertion.
   1609    if (aRoot->GetPrimaryFrame() ||
   1610        nsCoreUtils::CanCreateAccessibleWithoutFrame(aRoot)) {
   1611      // This may be a new subtree, the insertion process will recurse through
   1612      // its descendants.
   1613      if (!GetAccessibleOrDescendant(aRoot)) {
   1614        return true;
   1615      }
   1616 
   1617      // Content is not an accessible, but has accessible descendants.
   1618      // We schedule this container for insertion strictly for the case where it
   1619      // itself now needs an accessible. We will still need to recurse into the
   1620      // descendant content to prune accessibles, and in all likelyness to
   1621      // insert accessibles since accessible insertions will likeley get missed
   1622      // in an existing subtree.
   1623      insert = true;
   1624    }
   1625  }
   1626 
   1627  if (LocalAccessible* container = AccessibleOrTrueContainer(aRoot)) {
   1628    AutoTArray<nsCOMPtr<nsIContent>, 10> list;
   1629    dom::AllChildrenIterator iter =
   1630        dom::AllChildrenIterator(aRoot, nsIContent::eAllChildren, true);
   1631    while (nsIContent* childNode = iter.GetNextChild()) {
   1632      if (PruneOrInsertSubtree(childNode)) {
   1633        list.AppendElement(childNode);
   1634      }
   1635    }
   1636 
   1637    if (!list.IsEmpty()) {
   1638      mNotificationController->ScheduleContentInsertion(container, list);
   1639    }
   1640  }
   1641 
   1642  return insert;
   1643 }
   1644 
   1645 void DocAccessible::RecreateAccessible(nsIContent* aContent) {
   1646 #ifdef A11Y_LOG
   1647  if (logging::IsEnabled(logging::eTree)) {
   1648    logging::MsgBegin("TREE", "accessible recreated");
   1649    logging::Node("content", aContent);
   1650    logging::MsgEnd();
   1651  }
   1652 #endif
   1653 
   1654  // XXX: we shouldn't recreate whole accessible subtree, instead we should
   1655  // subclass hide and show events to handle them separately and implement their
   1656  // coalescence with normal hide and show events. Note, in this case they
   1657  // should be coalesced with normal show/hide events.
   1658  ContentRemoved(aContent);
   1659  ContentInserted(aContent, aContent->GetNextSibling());
   1660 }
   1661 
   1662 void DocAccessible::ProcessInvalidationList() {
   1663  // Invalidate children of container accessible for each element in
   1664  // invalidation list. Allow invalidation list insertions while container
   1665  // children are recached.
   1666  for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
   1667    nsIContent* content = mInvalidationList[idx];
   1668    if (!HasAccessible(content) && content->HasID()) {
   1669      LocalAccessible* container = GetContainerAccessible(content);
   1670      if (container) {
   1671        // Check if the node is a target of aria-owns, and if so, don't process
   1672        // it here and let DoARIAOwnsRelocation process it.
   1673        AttrRelProviders* list = GetRelProviders(
   1674            content->AsElement(), nsDependentAtomString(content->GetID()));
   1675        bool shouldProcess = !!list;
   1676        if (shouldProcess) {
   1677          for (uint32_t idx = 0; idx < list->Length(); idx++) {
   1678            if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
   1679              shouldProcess = false;
   1680              break;
   1681            }
   1682          }
   1683 
   1684          if (shouldProcess) {
   1685            ProcessContentInserted(container, content);
   1686          }
   1687        }
   1688      }
   1689    }
   1690  }
   1691 
   1692  mInvalidationList.Clear();
   1693 }
   1694 
   1695 void DocAccessible::ProcessQueuedCacheUpdates(uint64_t aInitialDomains) {
   1696  AUTO_PROFILER_MARKER_UNTYPED("DocAccessible::ProcessQueuedCacheUpdates", A11Y,
   1697                               {});
   1698  PerfStats::AutoMetricRecording<
   1699      PerfStats::Metric::A11Y_ProcessQueuedCacheUpdate>
   1700      autoRecording;
   1701  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
   1702 
   1703  nsTArray<CacheData> data;
   1704  for (auto [acc, domain] : mQueuedCacheUpdatesArray) {
   1705    if (acc && acc->IsInDocument() && !acc->IsDefunct()) {
   1706      RefPtr<AccAttributes> fields = acc->BundleFieldsForCache(
   1707          domain, CacheUpdateType::Update, aInitialDomains);
   1708 
   1709      if (fields->Count()) {
   1710        data.AppendElement(CacheData(
   1711            acc->IsDoc() ? 0 : reinterpret_cast<uint64_t>(acc->UniqueID()),
   1712            fields));
   1713      }
   1714    }
   1715  }
   1716 
   1717  mQueuedCacheUpdatesArray.Clear();
   1718  mQueuedCacheUpdatesHash.Clear();
   1719 
   1720  if (mViewportCacheDirty) {
   1721    RefPtr<AccAttributes> fields =
   1722        BundleFieldsForCache(CacheDomain::Viewport, CacheUpdateType::Update);
   1723    if (fields->Count()) {
   1724      data.AppendElement(CacheData(0, fields));
   1725    }
   1726    mViewportCacheDirty = false;
   1727  }
   1728 
   1729  if (data.Length()) {
   1730    IPCDoc()->SendCache(CacheUpdateType::Update, data);
   1731  }
   1732 }
   1733 
   1734 void DocAccessible::SendAccessiblesWillMove() {
   1735  if (!mIPCDoc) {
   1736    return;
   1737  }
   1738  nsTArray<uint64_t> ids;
   1739  for (LocalAccessible* acc : mMovedAccessibles) {
   1740    // If acc is defunct or not in a document, it was removed after it was
   1741    // moved.
   1742    if (!acc->IsDefunct() && acc->IsInDocument()) {
   1743      ids.AppendElement(reinterpret_cast<uintptr_t>(acc->UniqueID()));
   1744      // acc might have been re-parented. Since we cache bounds relative to the
   1745      // parent, we need to update the cache.
   1746      QueueCacheUpdate(acc, CacheDomain::Bounds);
   1747    }
   1748  }
   1749  if (!ids.IsEmpty()) {
   1750    mIPCDoc->SendAccessiblesWillMove(ids);
   1751  }
   1752 }
   1753 
   1754 LocalAccessible* DocAccessible::GetAccessibleEvenIfNotInMap(
   1755    nsINode* aNode) const {
   1756  if (!aNode->IsContent() ||
   1757      !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area)) {
   1758    return GetAccessible(aNode);
   1759  }
   1760 
   1761  // XXX Bug 135040, incorrect when multiple images use the same map.
   1762  nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
   1763  nsImageFrame* imageFrame = do_QueryFrame(frame);
   1764  if (imageFrame) {
   1765    LocalAccessible* parent = GetAccessible(imageFrame->GetContent());
   1766    if (parent) {
   1767      if (HTMLImageMapAccessible* imageMap = parent->AsImageMap()) {
   1768        return imageMap->GetChildAccessibleFor(aNode);
   1769      }
   1770      return nullptr;
   1771    }
   1772  }
   1773 
   1774  return GetAccessible(aNode);
   1775 }
   1776 
   1777 ////////////////////////////////////////////////////////////////////////////////
   1778 // Protected members
   1779 
   1780 void DocAccessible::NotifyOfLoading(bool aIsReloading) {
   1781  // Mark the document accessible as loading, if it stays alive then we'll mark
   1782  // it as loaded when we receive proper notification.
   1783  mLoadState &= ~eDOMLoaded;
   1784 
   1785  if (!IsLoadEventTarget()) return;
   1786 
   1787  if (aIsReloading && !mLoadEventType &&
   1788      // We can't fire events on a document whose tree isn't constructed yet.
   1789      HasLoadState(eTreeConstructed)) {
   1790    // Fire reload and state busy events on existing document accessible while
   1791    // event from user input flag can be calculated properly and accessible
   1792    // is alive. When new document gets loaded then this one is destroyed.
   1793    RefPtr<AccEvent> reloadEvent =
   1794        new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
   1795    nsEventShell::FireEvent(reloadEvent);
   1796  }
   1797 
   1798  // Fire state busy change event. Use delayed event since we don't care
   1799  // actually if event isn't delivered when the document goes away like a shot.
   1800  RefPtr<AccEvent> stateEvent =
   1801      new AccStateChangeEvent(this, states::BUSY, true);
   1802  FireDelayedEvent(stateEvent);
   1803 }
   1804 
   1805 void DocAccessible::DoInitialUpdate() {
   1806  AUTO_PROFILER_MARKER_UNTYPED("DocAccessible::DoInitialUpdate", A11Y, {});
   1807  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_DoInitialUpdate>
   1808      autoRecording;
   1809  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
   1810 
   1811  if (nsCoreUtils::IsTopLevelContentDocInProcess(mDocumentNode)) {
   1812    mDocFlags |= eTopLevelContentDocInProcess;
   1813    if (IPCAccessibilityActive()) {
   1814      nsIDocShell* docShell = mDocumentNode->GetDocShell();
   1815      if (RefPtr<dom::BrowserChild> browserChild =
   1816              dom::BrowserChild::GetFrom(docShell)) {
   1817        // In content processes, top level content documents are always
   1818        // RootAccessibles.
   1819        MOZ_ASSERT(IsRoot());
   1820        DocAccessibleChild* ipcDoc = IPCDoc();
   1821        if (!ipcDoc) {
   1822          ipcDoc = new DocAccessibleChild(this, browserChild);
   1823          MOZ_RELEASE_ASSERT(browserChild->SendPDocAccessibleConstructor(
   1824              ipcDoc, nullptr, 0, mDocumentNode->GetBrowsingContext()));
   1825          // trying to recover from this failing is problematic
   1826          SetIPCDoc(ipcDoc);
   1827        }
   1828      }
   1829    }
   1830  }
   1831 
   1832  // Set up a root element and ARIA role mapping.
   1833  UpdateRootElIfNeeded();
   1834 
   1835  // Build initial tree.
   1836  CacheChildrenInSubtree(this);
   1837 
   1838  mLoadState |= eTreeConstructed;
   1839 
   1840 #ifdef A11Y_LOG
   1841  if (logging::IsEnabled(logging::eVerbose)) {
   1842    logging::Tree("TREE", "Initial subtree", this);
   1843  }
   1844  if (logging::IsEnabled(logging::eTreeSize)) {
   1845    logging::TreeSize("TREE SIZE", "Initial subtree", this);
   1846  }
   1847 #endif
   1848 
   1849  // Fire reorder event after the document tree is constructed. Note, since
   1850  // this reorder event is processed by parent document then events targeted to
   1851  // this document may be fired prior to this reorder event. If this is
   1852  // a problem then consider to keep event processing per tab document.
   1853  if (!IsRoot()) {
   1854    RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(LocalParent());
   1855    ParentDocument()->FireDelayedEvent(reorderEvent);
   1856  }
   1857 
   1858  if (AppShutdown::IsShutdownImpending()) {
   1859    return;
   1860  }
   1861  if (IPCAccessibilityActive()) {
   1862    DocAccessibleChild* ipcDoc = IPCDoc();
   1863    MOZ_ASSERT(ipcDoc);
   1864    if (ipcDoc) {
   1865      // Send an initial update for this document and its attributes. Each acc
   1866      // contained in this doc will have its initial update sent in
   1867      // `InsertIntoIpcTree`.
   1868      SendCache(nsAccessibilityService::GetActiveCacheDomains(),
   1869                CacheUpdateType::Initial);
   1870 
   1871      for (auto idx = 0U; idx < mChildren.Length(); idx++) {
   1872        ipcDoc->InsertIntoIpcTree(mChildren.ElementAt(idx), true);
   1873      }
   1874      ipcDoc->SendQueuedMutationEvents();
   1875    }
   1876  }
   1877 }
   1878 
   1879 void DocAccessible::ProcessLoad() {
   1880  mLoadState |= eCompletelyLoaded;
   1881 
   1882 #ifdef A11Y_LOG
   1883  if (logging::IsEnabled(logging::eDocLoad)) {
   1884    logging::DocCompleteLoad(this, IsLoadEventTarget());
   1885  }
   1886 #endif
   1887 
   1888  // Do not fire document complete/stop events for root chrome document
   1889  // accessibles because screen readers start working on focus event in the case
   1890  // of root chrome documents.
   1891  if (!IsLoadEventTarget()) return;
   1892 
   1893  // Fire complete/load stopped if the load event type is given.
   1894  if (mLoadEventType) {
   1895    RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
   1896    FireDelayedEvent(loadEvent);
   1897 
   1898    mLoadEventType = 0;
   1899  }
   1900 
   1901  // Fire busy state change event.
   1902  RefPtr<AccEvent> stateEvent =
   1903      new AccStateChangeEvent(this, states::BUSY, false);
   1904  FireDelayedEvent(stateEvent);
   1905 }
   1906 
   1907 void DocAccessible::AddDependentIDsFor(LocalAccessible* aRelProvider,
   1908                                       nsAtom* aRelAttr) {
   1909  dom::Element* relProviderEl = aRelProvider->Elm();
   1910  if (!relProviderEl) return;
   1911 
   1912  for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
   1913    nsStaticAtom* relAttr = kRelationAttrs[idx];
   1914    if (aRelAttr && aRelAttr != relAttr) continue;
   1915 
   1916    if (relAttr == nsGkAtoms::_for) {
   1917      if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
   1918                                              nsGkAtoms::output)) {
   1919        continue;
   1920      }
   1921 
   1922    } else if (relAttr == nsGkAtoms::control) {
   1923      if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
   1924                                             nsGkAtoms::description)) {
   1925        continue;
   1926      }
   1927    }
   1928 
   1929    AssociatedElementsIterator iter(this, relProviderEl, relAttr);
   1930    while (true) {
   1931      const nsDependentSubstring id = iter.NextID();
   1932      if (id.IsEmpty()) break;
   1933 
   1934      AttrRelProviders* providers = GetOrCreateRelProviders(relProviderEl, id);
   1935      if (providers) {
   1936        AttrRelProvider* provider = new AttrRelProvider(relAttr, relProviderEl);
   1937        if (provider) {
   1938          providers->AppendElement(provider);
   1939 
   1940          // We've got here during the children caching. If the referenced
   1941          // content is not accessible then store it to pend its container
   1942          // children invalidation (this happens immediately after the caching
   1943          // is finished).
   1944          nsIContent* dependentContent = iter.GetElem(id);
   1945          if (dependentContent) {
   1946            if (!HasAccessible(dependentContent)) {
   1947              mInvalidationList.AppendElement(dependentContent);
   1948            }
   1949          }
   1950        }
   1951      }
   1952    }
   1953 
   1954    // If the relation attribute is given then we don't have anything else to
   1955    // check.
   1956    if (aRelAttr) break;
   1957  }
   1958 
   1959  // Make sure to schedule the tree update if needed.
   1960  mNotificationController->ScheduleProcessing();
   1961 }
   1962 
   1963 void DocAccessible::RemoveDependentIDsFor(LocalAccessible* aRelProvider,
   1964                                          nsAtom* aRelAttr) {
   1965  dom::Element* relProviderElm = aRelProvider->Elm();
   1966  if (!relProviderElm) return;
   1967 
   1968  for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
   1969    nsStaticAtom* relAttr = kRelationAttrs[idx];
   1970    if (aRelAttr && aRelAttr != kRelationAttrs[idx]) continue;
   1971 
   1972    AssociatedElementsIterator iter(this, relProviderElm, relAttr);
   1973    while (true) {
   1974      const nsDependentSubstring id = iter.NextID();
   1975      if (id.IsEmpty()) break;
   1976 
   1977      AttrRelProviders* providers = GetRelProviders(relProviderElm, id);
   1978      if (providers) {
   1979        providers->RemoveElementsBy(
   1980            [relAttr, relProviderElm](const auto& provider) {
   1981              return provider->mRelAttr == relAttr &&
   1982                     provider->mContent == relProviderElm;
   1983            });
   1984 
   1985        RemoveRelProvidersIfEmpty(relProviderElm, id);
   1986      }
   1987    }
   1988 
   1989    // If the relation attribute is given then we don't have anything else to
   1990    // check.
   1991    if (aRelAttr) break;
   1992  }
   1993 }
   1994 
   1995 void DocAccessible::AddDependentElementsFor(LocalAccessible* aRelProvider,
   1996                                            nsAtom* aRelAttr) {
   1997  dom::Element* providerEl = aRelProvider->Elm();
   1998  if (!providerEl) {
   1999    return;
   2000  }
   2001  for (nsStaticAtom* attr : kSingleElementRelationIdlAttrs) {
   2002    if (aRelAttr && aRelAttr != attr) {
   2003      continue;
   2004    }
   2005    if (dom::Element* targetEl =
   2006            providerEl->GetExplicitlySetAttrElement(attr)) {
   2007      AttrRelProviders& providers =
   2008          mDependentElementsMap.LookupOrInsert(targetEl);
   2009      AttrRelProvider* provider = new AttrRelProvider(attr, providerEl);
   2010      providers.AppendElement(provider);
   2011    }
   2012    // If the relation attribute was given, we've already handled it. We don't
   2013    // have anything else to check.
   2014    if (aRelAttr) {
   2015      break;
   2016    }
   2017  }
   2018 
   2019  aria::AttrWithCharacteristicsIterator multipleElementsRelationIter(
   2020      ATTR_REFLECT_ELEMENTS);
   2021  while (multipleElementsRelationIter.Next()) {
   2022    nsStaticAtom* attr = multipleElementsRelationIter.AttrName();
   2023    if (aRelAttr && aRelAttr != attr) {
   2024      continue;
   2025    }
   2026    nsTArray<dom::Element*> elements;
   2027    nsAccUtils::GetARIAElementsAttr(providerEl, attr, elements);
   2028    for (dom::Element* targetEl : elements) {
   2029      AttrRelProviders& providers =
   2030          mDependentElementsMap.LookupOrInsert(targetEl);
   2031      AttrRelProvider* provider = new AttrRelProvider(attr, providerEl);
   2032      providers.AppendElement(provider);
   2033    }
   2034    // If the relation attribute was given, we've already handled it. We don't
   2035    // have anything else to check.
   2036    if (aRelAttr) {
   2037      break;
   2038    }
   2039  }
   2040 }
   2041 
   2042 void DocAccessible::RemoveDependentElementsFor(LocalAccessible* aRelProvider,
   2043                                               nsAtom* aRelAttr) {
   2044  dom::Element* providerEl = aRelProvider->Elm();
   2045  if (!providerEl) {
   2046    return;
   2047  }
   2048  for (nsStaticAtom* attr : kSingleElementRelationIdlAttrs) {
   2049    if (aRelAttr && aRelAttr != attr) {
   2050      continue;
   2051    }
   2052    if (dom::Element* targetEl =
   2053            providerEl->GetExplicitlySetAttrElement(attr)) {
   2054      if (auto providers = mDependentElementsMap.Lookup(targetEl)) {
   2055        providers.Data().RemoveElementsBy([attr,
   2056                                           providerEl](const auto& provider) {
   2057          return provider->mRelAttr == attr && provider->mContent == providerEl;
   2058        });
   2059        if (providers.Data().IsEmpty()) {
   2060          providers.Remove();
   2061        }
   2062      }
   2063    }
   2064    // If the relation attribute was given, we've already handled it. We don't
   2065    // have anything else to check.
   2066    if (aRelAttr) {
   2067      break;
   2068    }
   2069  }
   2070 
   2071  aria::AttrWithCharacteristicsIterator multipleElementsRelationIter(
   2072      ATTR_REFLECT_ELEMENTS);
   2073  while (multipleElementsRelationIter.Next()) {
   2074    nsStaticAtom* attr = multipleElementsRelationIter.AttrName();
   2075    if (aRelAttr && aRelAttr != attr) {
   2076      continue;
   2077    }
   2078    nsTArray<dom::Element*> elements;
   2079    nsAccUtils::GetARIAElementsAttr(providerEl, attr, elements);
   2080    for (dom::Element* targetEl : elements) {
   2081      if (auto providers = mDependentElementsMap.Lookup(targetEl)) {
   2082        providers.Data().RemoveElementsBy([attr,
   2083                                           providerEl](const auto& provider) {
   2084          return provider->mRelAttr == attr && provider->mContent == providerEl;
   2085        });
   2086        if (providers.Data().IsEmpty()) {
   2087          providers.Remove();
   2088        }
   2089      }
   2090    }
   2091 
   2092    // If the relation attribute was given, we've already handled it. We don't
   2093    // have anything else to check.
   2094    if (aRelAttr) {
   2095      break;
   2096    }
   2097  }
   2098 }
   2099 
   2100 bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
   2101                                                 nsAtom* aAttribute) {
   2102  if (aAttribute == nsGkAtoms::role) {
   2103    // It is common for js libraries to set the role on the body element after
   2104    // the document has loaded. In this case we just update the role map entry.
   2105    if (mContent == aElement) {
   2106      SetRoleMapEntryForDoc(aElement);
   2107      if (mIPCDoc) {
   2108        mIPCDoc->SendRoleChangedEvent(Role(), mRoleMapEntryIndex);
   2109      }
   2110 
   2111      return true;
   2112    }
   2113 
   2114    // Recreate the accessible when role is changed because we might require a
   2115    // different accessible class for the new role or the accessible may expose
   2116    // a different sets of interfaces (COM restriction).
   2117    RecreateAccessible(aElement);
   2118 
   2119    return true;
   2120  }
   2121 
   2122  if (aAttribute == nsGkAtoms::multiple) {
   2123    if (dom::HTMLSelectElement* select =
   2124            dom::HTMLSelectElement::FromNode(aElement)) {
   2125      if (select->Size() <= 1) {
   2126        // Adding the 'multiple' attribute to a select that has a size of 1
   2127        // creates a listbox as opposed to a combobox with a popup combobox
   2128        // list. Removing the attribute does the opposite.
   2129        RecreateAccessible(aElement);
   2130        return true;
   2131      }
   2132    }
   2133  }
   2134 
   2135  if (aAttribute == nsGkAtoms::size &&
   2136      aElement->IsHTMLElement(nsGkAtoms::select)) {
   2137    // Changing the size of a select element can potentially change it from a
   2138    // combobox button to a listbox with different underlying implementations.
   2139    RecreateAccessible(aElement);
   2140    return true;
   2141  }
   2142 
   2143  if (aAttribute == nsGkAtoms::type) {
   2144    // If the input[type] changes, we should recreate the accessible.
   2145    RecreateAccessible(aElement);
   2146    return true;
   2147  }
   2148 
   2149  if (aAttribute == nsGkAtoms::href &&
   2150      !nsCoreUtils::HasClickListener(aElement)) {
   2151    // If the href is added or removed for a or area elements without click
   2152    // listeners, we need to recreate the accessible since the role might have
   2153    // changed. Without an href or click listener, the accessible must be a
   2154    // generic.
   2155    if (aElement->IsHTMLElement(nsGkAtoms::a)) {
   2156      LocalAccessible* acc = GetAccessible(aElement);
   2157      if (!acc) {
   2158        return false;
   2159      }
   2160      if (acc->IsHTMLLink() != aElement->HasAttr(nsGkAtoms::href)) {
   2161        RecreateAccessible(aElement);
   2162        return true;
   2163      }
   2164    } else if (aElement->IsHTMLElement(nsGkAtoms::area)) {
   2165      // For area accessibles, we have to recreate the entire image map, since
   2166      // the image map accessible manages the tree itself.
   2167      LocalAccessible* areaAcc = GetAccessibleEvenIfNotInMap(aElement);
   2168      if (!areaAcc || !areaAcc->LocalParent()) {
   2169        return false;
   2170      }
   2171      RecreateAccessible(areaAcc->LocalParent()->GetContent());
   2172      return true;
   2173    }
   2174  }
   2175 
   2176  if (aElement->IsHTMLElement(nsGkAtoms::img) && aAttribute == nsGkAtoms::alt) {
   2177    // If alt text changes on an img element, we may want to create or remove an
   2178    // accessible for that img.
   2179    if (nsAccessibilityService::ShouldCreateImgAccessible(aElement, this)) {
   2180      if (GetAccessible(aElement)) {
   2181        // If the accessible already exists, there's no need to create one.
   2182        return false;
   2183      }
   2184      ContentInserted(aElement, aElement->GetNextSibling());
   2185    } else {
   2186      ContentRemoved(aElement);
   2187    }
   2188    return true;
   2189  }
   2190 
   2191  if (aAttribute == nsGkAtoms::popover && aElement->IsHTMLElement()) {
   2192    // Changing the popover attribute might change the role.
   2193    RecreateAccessible(aElement);
   2194    return true;
   2195  }
   2196 
   2197  return false;
   2198 }
   2199 
   2200 void DocAccessible::UpdateRootElIfNeeded() {
   2201  dom::Element* rootEl = mDocumentNode->GetBodyElement();
   2202  if (!rootEl) {
   2203    rootEl = mDocumentNode->GetRootElement();
   2204  }
   2205  if (rootEl != mContent) {
   2206    mContent = rootEl;
   2207    SetRoleMapEntryForDoc(rootEl);
   2208    if (mIPCDoc) {
   2209      mIPCDoc->SendRoleChangedEvent(Role(), mRoleMapEntryIndex);
   2210    }
   2211  }
   2212 }
   2213 
   2214 /**
   2215 * Content insertion helper.
   2216 */
   2217 class InsertIterator final {
   2218 public:
   2219  InsertIterator(LocalAccessible* aContext,
   2220                 const nsTArray<nsCOMPtr<nsIContent>>* aNodes)
   2221      : mChild(nullptr),
   2222        mChildBefore(nullptr),
   2223        mWalker(aContext),
   2224        mNodes(aNodes),
   2225        mNodesIdx(0) {
   2226    MOZ_ASSERT(aContext, "No context");
   2227    MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
   2228    MOZ_COUNT_CTOR(InsertIterator);
   2229  }
   2230  MOZ_COUNTED_DTOR(InsertIterator)
   2231 
   2232  LocalAccessible* Context() const { return mWalker.Context(); }
   2233  LocalAccessible* Child() const { return mChild; }
   2234  LocalAccessible* ChildBefore() const { return mChildBefore; }
   2235  DocAccessible* Document() const { return mWalker.Document(); }
   2236 
   2237  /**
   2238   * Iterates to a next accessible within the inserted content.
   2239   */
   2240  bool Next();
   2241 
   2242  void Rejected() {
   2243    mChild = nullptr;
   2244    mChildBefore = nullptr;
   2245  }
   2246 
   2247 private:
   2248  LocalAccessible* mChild;
   2249  LocalAccessible* mChildBefore;
   2250  TreeWalker mWalker;
   2251 
   2252  const nsTArray<nsCOMPtr<nsIContent>>* mNodes;
   2253  nsTHashSet<nsPtrHashKey<const nsIContent>> mProcessedNodes;
   2254  uint32_t mNodesIdx;
   2255 };
   2256 
   2257 bool InsertIterator::Next() {
   2258  if (mNodesIdx > 0) {
   2259    // If we already processed the first node in the mNodes list,
   2260    // check if we can just use the walker to get its next sibling.
   2261    LocalAccessible* nextChild = mWalker.Next();
   2262    if (nextChild) {
   2263      mChildBefore = mChild;
   2264      mChild = nextChild;
   2265      return true;
   2266    }
   2267  }
   2268 
   2269  while (mNodesIdx < mNodes->Length()) {
   2270    nsIContent* node = mNodes->ElementAt(mNodesIdx++);
   2271    // Check to see if we already processed this node with this iterator.
   2272    // this can happen if we get two redundant insertions in the case of a
   2273    // text and frame insertion.
   2274    if (!mProcessedNodes.EnsureInserted(node)) {
   2275      continue;
   2276    }
   2277 
   2278    LocalAccessible* container = Document()->AccessibleOrTrueContainer(
   2279        node->GetFlattenedTreeParentNode(), true);
   2280    // Ignore nodes that are not contained by the container anymore.
   2281    // The container might be changed, for example, because of the subsequent
   2282    // overlapping content insertion (i.e. other content was inserted between
   2283    // this inserted content and its container or the content was reinserted
   2284    // into different container of unrelated part of tree). To avoid a double
   2285    // processing of the content insertion ignore this insertion notification.
   2286    // Note, the inserted content might be not in tree at all at this point
   2287    // what means there's no container. Ignore the insertion too.
   2288    if (container != Context()) {
   2289      continue;
   2290    }
   2291 
   2292    // HTML comboboxes have no-content list accessible as an intermediate
   2293    // containing all options.
   2294    if (container->IsHTMLCombobox()) {
   2295      container = container->LocalFirstChild();
   2296    }
   2297 
   2298    if (!container->IsAcceptableChild(node)) {
   2299      continue;
   2300    }
   2301 
   2302 #ifdef A11Y_LOG
   2303    logging::TreeInfo("traversing an inserted node", logging::eVerbose,
   2304                      "container", container, "node", node);
   2305 #endif
   2306 
   2307    nsIContent* prevNode = mChild ? mChild->GetContent() : nullptr;
   2308    if (prevNode && prevNode->GetNextSibling() == node) {
   2309      // If inserted nodes are siblings then just move the walker next.
   2310      LocalAccessible* nextChild = mWalker.Scope(node);
   2311      if (nextChild) {
   2312        mChildBefore = mChild;
   2313        mChild = nextChild;
   2314        return true;
   2315      }
   2316    } else {
   2317      // Otherwise use a new walker to find this node in the container's
   2318      // subtree, and retrieve its preceding sibling.
   2319      TreeWalker finder(container);
   2320      if (finder.Seek(node)) {
   2321        mChild = mWalker.Scope(node);
   2322        if (mChild) {
   2323          MOZ_ASSERT(!mChild->IsRelocated(), "child cannot be aria owned");
   2324          mChildBefore = finder.Prev();
   2325          return true;
   2326        }
   2327      }
   2328    }
   2329  }
   2330 
   2331  return false;
   2332 }
   2333 
   2334 void DocAccessible::MaybeFireEventsForChangedPopover(LocalAccessible* aAcc) {
   2335  dom::Element* el = aAcc->Elm();
   2336  if (!el || !el->IsHTMLElement() || !el->HasAttr(nsGkAtoms::popover)) {
   2337    return;  // Not a popover.
   2338  }
   2339  // A popover has just been inserted into or removed from the a11y tree, which
   2340  // means it just appeared or disappeared. Fire expanded state changes on its
   2341  // popovertarget invokers.
   2342  Relation invokers(new RelatedAccIterator(mDoc, el, nsGkAtoms::popovertarget));
   2343  // Additionally iterate over any commandfor invokers.
   2344  invokers.AppendIter(new RelatedAccIterator(mDoc, el, nsGkAtoms::commandfor));
   2345  while (Accessible* invoker = invokers.LocalNext()) {
   2346    RefPtr<AccEvent> expandedChangeEvent =
   2347        new AccStateChangeEvent(invoker->AsLocal(), states::EXPANDED);
   2348    FireDelayedEvent(expandedChangeEvent);
   2349  }
   2350 }
   2351 
   2352 void DocAccessible::ProcessContentInserted(
   2353    LocalAccessible* aContainer, const nsTArray<nsCOMPtr<nsIContent>>* aNodes) {
   2354  // Process insertions if the container accessible is still in tree.
   2355  if (!aContainer->IsInDocument()) {
   2356    return;
   2357  }
   2358 
   2359  // If new root content has been inserted then update it.
   2360  if (aContainer == this) {
   2361    UpdateRootElIfNeeded();
   2362  }
   2363 
   2364  InsertIterator iter(aContainer, aNodes);
   2365  if (!iter.Next()) {
   2366    return;
   2367  }
   2368 
   2369 #ifdef A11Y_LOG
   2370  logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
   2371 #endif
   2372 
   2373  TreeMutation mt(aContainer);
   2374  bool inserted = false;
   2375  do {
   2376    LocalAccessible* parent = iter.Child()->LocalParent();
   2377    if (parent) {
   2378      LocalAccessible* previousSibling = iter.ChildBefore();
   2379      if (parent != aContainer ||
   2380          iter.Child()->LocalPrevSibling() != previousSibling) {
   2381        if (previousSibling && previousSibling->LocalParent() != aContainer) {
   2382          // previousSibling hasn't been moved into aContainer yet.
   2383          // previousSibling should be later in the insertion list, so the tree
   2384          // will get adjusted when we process it later.
   2385          MOZ_DIAGNOSTIC_ASSERT(parent == aContainer,
   2386                                "Child moving to new parent, but previous "
   2387                                "sibling in wrong parent");
   2388          continue;
   2389        }
   2390 #ifdef A11Y_LOG
   2391        logging::TreeInfo("relocating accessible", 0, "old parent", parent,
   2392                          "new parent", aContainer, "child", iter.Child(),
   2393                          nullptr);
   2394 #endif
   2395        MoveChild(iter.Child(), aContainer,
   2396                  previousSibling ? previousSibling->IndexInParent() + 1 : 0);
   2397        inserted = true;
   2398      }
   2399      continue;
   2400    }
   2401 
   2402    if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
   2403 #ifdef A11Y_LOG
   2404      logging::TreeInfo("accessible was inserted", 0, "container", aContainer,
   2405                        "child", iter.Child(), nullptr);
   2406 #endif
   2407 
   2408      CreateSubtree(iter.Child());
   2409      mt.AfterInsertion(iter.Child());
   2410      inserted = true;
   2411      MaybeFireEventsForChangedPopover(iter.Child());
   2412      continue;
   2413    }
   2414 
   2415    MOZ_ASSERT_UNREACHABLE("accessible was rejected");
   2416    iter.Rejected();
   2417  } while (iter.Next());
   2418 
   2419  mt.Done();
   2420 
   2421 #ifdef A11Y_LOG
   2422  logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
   2423 #endif
   2424 
   2425  // We might not have actually inserted anything if layout frame reconstruction
   2426  // occurred.
   2427  if (inserted) {
   2428    FireEventsOnInsertion(aContainer);
   2429  }
   2430 }
   2431 
   2432 void DocAccessible::ProcessContentInserted(LocalAccessible* aContainer,
   2433                                           nsIContent* aNode) {
   2434  if (!aContainer->IsInDocument()) {
   2435    return;
   2436  }
   2437 
   2438 #ifdef A11Y_LOG
   2439  logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
   2440 #endif
   2441 
   2442 #ifdef A11Y_LOG
   2443  logging::TreeInfo("traversing an inserted node", logging::eVerbose,
   2444                    "container", aContainer, "node", aNode);
   2445 #endif
   2446 
   2447  TreeWalker walker(aContainer);
   2448  if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
   2449    LocalAccessible* child = GetAccessible(aNode);
   2450    if (!child) {
   2451      child = GetAccService()->CreateAccessible(aNode, aContainer);
   2452    }
   2453 
   2454    if (child) {
   2455      TreeMutation mt(aContainer);
   2456      if (!aContainer->InsertAfter(child, walker.Prev())) {
   2457        return;
   2458      }
   2459      CreateSubtree(child);
   2460      mt.AfterInsertion(child);
   2461      mt.Done();
   2462 
   2463      FireEventsOnInsertion(aContainer);
   2464    }
   2465  }
   2466 
   2467 #ifdef A11Y_LOG
   2468  logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
   2469 #endif
   2470 }
   2471 
   2472 void DocAccessible::FireEventsOnInsertion(LocalAccessible* aContainer) {
   2473  // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
   2474  // if it did.
   2475  if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
   2476    LocalAccessible* ancestor = aContainer;
   2477    do {
   2478      if (ancestor->IsAlert()) {
   2479        FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
   2480        break;
   2481      }
   2482    } while ((ancestor = ancestor->LocalParent()));
   2483  }
   2484 }
   2485 
   2486 void DocAccessible::ContentRemoved(LocalAccessible* aChild) {
   2487  AUTO_PROFILER_MARKER_TEXT("DocAccessible::ContentRemovedAcc", A11Y, {},
   2488                            ""_ns);
   2489  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ContentRemovedAcc>
   2490      autoRecording;
   2491  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
   2492 
   2493  MOZ_DIAGNOSTIC_ASSERT(aChild != this, "Should never be called for the doc");
   2494  LocalAccessible* parent = aChild->LocalParent();
   2495  MOZ_DIAGNOSTIC_ASSERT(parent, "Unattached accessible from tree");
   2496 
   2497 #ifdef A11Y_LOG
   2498  logging::TreeInfo("process content removal", 0, "container", parent, "child",
   2499                    aChild, nullptr);
   2500 #endif
   2501 
   2502  // XXX: event coalescence may kill us
   2503  RefPtr<LocalAccessible> kungFuDeathGripChild(aChild);
   2504 
   2505  TreeMutation mt(parent);
   2506  mt.BeforeRemoval(aChild);
   2507 
   2508  if (aChild->IsDefunct()) {
   2509    MOZ_ASSERT_UNREACHABLE("Event coalescence killed the accessible");
   2510    mt.Done();
   2511    return;
   2512  }
   2513 
   2514  MOZ_DIAGNOSTIC_ASSERT(aChild->LocalParent(), "Alive but unparented #1");
   2515 
   2516  if (aChild->IsRelocated()) {
   2517    nsTArray<RefPtr<LocalAccessible>>* owned = mARIAOwnsHash.Get(parent);
   2518    MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
   2519    owned->RemoveElement(aChild);
   2520    if (owned->Length() == 0) {
   2521      mARIAOwnsHash.Remove(parent);
   2522    }
   2523  }
   2524  MOZ_DIAGNOSTIC_ASSERT(aChild->LocalParent(), "Unparented #2");
   2525  UncacheChildrenInSubtree(aChild);
   2526  parent->RemoveChild(aChild);
   2527 
   2528  mt.Done();
   2529 }
   2530 
   2531 void DocAccessible::ContentRemoved(nsIContent* aContentNode) {
   2532  AUTO_PROFILER_MARKER_TEXT("DocAccessible::ContentRemovedNode", A11Y, {},
   2533                            ""_ns);
   2534  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ContentRemovedNode>
   2535      autoRecording;
   2536  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
   2537 
   2538  if (!mRemovedNodes.EnsureInserted(aContentNode)) {
   2539    return;
   2540  }
   2541 
   2542  // If child node is not accessible then look for its accessible children.
   2543  LocalAccessible* acc = GetAccessible(aContentNode);
   2544  if (acc) {
   2545    ContentRemoved(acc);
   2546  }
   2547 
   2548  dom::AllChildrenIterator iter =
   2549      dom::AllChildrenIterator(aContentNode, nsIContent::eAllChildren, true);
   2550  while (nsIContent* childNode = iter.GetNextChild()) {
   2551    ContentRemoved(childNode);
   2552  }
   2553 
   2554  // If this node has a shadow root, remove its explicit children too.
   2555  // The host node may be removed after the shadow root was attached, and
   2556  // before we asynchronously prune the light DOM and construct the shadow DOM.
   2557  // If this is a case where the node does not have its own accessible, we will
   2558  // not recurse into its current children, so we need to use an
   2559  // ExplicitChildIterator in order to get its accessible children in the light
   2560  // DOM, since they are not accessible anymore via AllChildrenIterator.
   2561  if (aContentNode->GetShadowRoot()) {
   2562    for (nsIContent* childNode = aContentNode->GetFirstChild(); childNode;
   2563         childNode = childNode->GetNextSibling()) {
   2564      ContentRemoved(childNode);
   2565    }
   2566  }
   2567 }
   2568 
   2569 bool DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement) {
   2570  RelatedAccIterator owners(mDoc, aElement, nsGkAtoms::aria_owns);
   2571  if (Accessible* owner = owners.Next()) {
   2572    mNotificationController->ScheduleRelocation(owner->AsLocal());
   2573    return true;
   2574  }
   2575 
   2576  return false;
   2577 }
   2578 
   2579 void DocAccessible::DoARIAOwnsRelocation(LocalAccessible* aOwner) {
   2580  MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
   2581  MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
   2582 
   2583 #ifdef A11Y_LOG
   2584  logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
   2585 #endif
   2586 
   2587  const nsRoleMapEntry* roleMap = aOwner->ARIARoleMap();
   2588  if (roleMap && roleMap->role == roles::EDITCOMBOBOX) {
   2589    // The READWRITE state of a combobox may sever aria-owns relations
   2590    // we fallback to "controls" relations.
   2591    QueueCacheUpdate(aOwner, CacheDomain::Relations);
   2592  }
   2593 
   2594  nsTArray<RefPtr<LocalAccessible>>* owned =
   2595      mARIAOwnsHash.GetOrInsertNew(aOwner);
   2596 
   2597  if (aOwner->Elm()->State().HasState(dom::ElementState::READWRITE)) {
   2598    // The container is editable.
   2599    PutChildrenBack(owned, 0);
   2600    return;
   2601  }
   2602 
   2603  AssociatedElementsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
   2604  uint32_t idx = 0;
   2605  while (dom::Element* childEl = iter.NextElem()) {
   2606    if (childEl->State().HasState(dom::ElementState::READWRITE)) {
   2607      dom::Element* parentEl = childEl->GetFlattenedTreeParentElement();
   2608      if (parentEl &&
   2609          parentEl->State().HasState(dom::ElementState::READWRITE)) {
   2610        // The child is inside of an editable subtree, don't relocate it.
   2611        continue;
   2612      }
   2613    }
   2614 
   2615    LocalAccessible* child = GetAccessible(childEl);
   2616    auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
   2617 
   2618    // Make an attempt to create an accessible if it wasn't created yet.
   2619    if (!child) {
   2620      // An owned child cannot be an ancestor of the owner.
   2621      bool ok = true;
   2622      bool check = true;
   2623      for (LocalAccessible* parent = aOwner; parent && !parent->IsDoc();
   2624           parent = parent->LocalParent()) {
   2625        if (check) {
   2626          if (parent->Elm()->IsInclusiveDescendantOf(childEl)) {
   2627            ok = false;
   2628            break;
   2629          }
   2630        }
   2631        // We need to do the DOM descendant check again whenever the DOM
   2632        // lineage changes. If parent is relocated, that means the next
   2633        // ancestor will have a different DOM lineage.
   2634        check = parent->IsRelocated();
   2635      }
   2636      if (!ok) {
   2637        continue;
   2638      }
   2639 
   2640      if (aOwner->IsAcceptableChild(childEl)) {
   2641        child = GetAccService()->CreateAccessible(childEl, aOwner);
   2642        if (child) {
   2643          TreeMutation imut(aOwner);
   2644          aOwner->InsertChildAt(insertIdx, child);
   2645          imut.AfterInsertion(child);
   2646          imut.Done();
   2647 
   2648          child->SetRelocated(true);
   2649          owned->InsertElementAt(idx, child);
   2650          idx++;
   2651 
   2652          // Create subtree before adjusting the insertion index, since subtree
   2653          // creation may alter children in the container.
   2654          CreateSubtree(child);
   2655          FireEventsOnInsertion(aOwner);
   2656        }
   2657      }
   2658      continue;
   2659    }
   2660 
   2661 #ifdef A11Y_LOG
   2662    logging::TreeInfo("aria owns traversal", logging::eVerbose, "candidate",
   2663                      child, nullptr);
   2664 #endif
   2665 
   2666    if (owned->IndexOf(child) < idx) {
   2667      continue;  // ignore second entry of same ID
   2668    }
   2669 
   2670    // Same child on same position, no change.
   2671    if (child->LocalParent() == aOwner) {
   2672      int32_t indexInParent = child->IndexInParent();
   2673 
   2674      // The child is being placed in its current index,
   2675      // eg. aria-owns='id1 id2 id3' is changed to aria-owns='id3 id2 id1'.
   2676      if (indexInParent == static_cast<int32_t>(insertIdx)) {
   2677        MOZ_ASSERT(child->IsRelocated(),
   2678                   "A child, having an index in parent from aria ownded "
   2679                   "indices range, has to be aria owned");
   2680        MOZ_ASSERT(owned->ElementAt(idx) == child,
   2681                   "Unexpected child in ARIA owned array");
   2682        idx++;
   2683        continue;
   2684      }
   2685 
   2686      // The child is being inserted directly after its current index,
   2687      // resulting in a no-move case. This will happen when a parent aria-owns
   2688      // its last ordinal child:
   2689      // <ul aria-owns='id2'><li id='id1'></li><li id='id2'></li></ul>
   2690      if (indexInParent == static_cast<int32_t>(insertIdx) - 1) {
   2691        MOZ_ASSERT(!child->IsRelocated(),
   2692                   "Child should be in its ordinal position");
   2693        child->SetRelocated(true);
   2694        owned->InsertElementAt(idx, child);
   2695        idx++;
   2696        continue;
   2697      }
   2698    }
   2699 
   2700    MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
   2701 
   2702    // A new child is found, check for loops.
   2703    if (child->LocalParent() != aOwner) {
   2704      // Child is aria-owned by another container, skip.
   2705      if (child->IsRelocated()) {
   2706        continue;
   2707      }
   2708 
   2709      LocalAccessible* parent = aOwner;
   2710      while (parent && parent != child && !parent->IsDoc()) {
   2711        parent = parent->LocalParent();
   2712      }
   2713      // A referred child cannot be a parent of the owner.
   2714      if (parent == child) {
   2715        continue;
   2716      }
   2717    }
   2718 
   2719    if (MoveChild(child, aOwner, insertIdx)) {
   2720      child->SetRelocated(true);
   2721      MOZ_ASSERT(owned == mARIAOwnsHash.Get(aOwner));
   2722      owned = mARIAOwnsHash.GetOrInsertNew(aOwner);
   2723      owned->InsertElementAt(idx, child);
   2724      idx++;
   2725    }
   2726  }
   2727 
   2728  // Put back children that are not seized anymore.
   2729  PutChildrenBack(owned, idx);
   2730  if (owned->Length() == 0) {
   2731    mARIAOwnsHash.Remove(aOwner);
   2732  }
   2733 }
   2734 
   2735 void DocAccessible::PutChildrenBack(
   2736    nsTArray<RefPtr<LocalAccessible>>* aChildren, uint32_t aStartIdx) {
   2737  MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
   2738 
   2739  for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
   2740    LocalAccessible* child = aChildren->ElementAt(idx);
   2741    if (!child->IsInDocument()) {
   2742      continue;
   2743    }
   2744 
   2745    // Remove the child from the owner
   2746    LocalAccessible* owner = child->LocalParent();
   2747    if (!owner) {
   2748      NS_ERROR("Cannot put the child back. No parent, a broken tree.");
   2749      continue;
   2750    }
   2751 
   2752 #ifdef A11Y_LOG
   2753    logging::TreeInfo("aria owns put child back", 0, "old parent", owner,
   2754                      "child", child, nullptr);
   2755 #endif
   2756 
   2757    // Unset relocated flag to find an insertion point for the child.
   2758    child->SetRelocated(false);
   2759 
   2760    nsIContent* content = child->GetContent();
   2761    int32_t idxInParent = -1;
   2762    LocalAccessible* origContainer =
   2763        AccessibleOrTrueContainer(content->GetFlattenedTreeParentNode());
   2764    // This node has probably been detached or removed from the DOM, so we have
   2765    // nowhere to move it.
   2766    if (!origContainer) {
   2767      continue;
   2768    }
   2769 
   2770    // If the target container or any of its ancestors aren't in the document,
   2771    // there's no need to determine where the child should go for relocation
   2772    // since the target tree is going away.
   2773    bool origContainerHasOutOfDocAncestor = false;
   2774    LocalAccessible* ancestor = origContainer;
   2775    while (ancestor) {
   2776      if (ancestor->IsDoc()) {
   2777        break;
   2778      }
   2779      if (!ancestor->IsInDocument()) {
   2780        origContainerHasOutOfDocAncestor = true;
   2781        break;
   2782      }
   2783      ancestor = ancestor->LocalParent();
   2784    }
   2785    if (origContainerHasOutOfDocAncestor) {
   2786      continue;
   2787    }
   2788 
   2789    TreeWalker walker(origContainer);
   2790    if (!walker.Seek(content)) {
   2791      continue;
   2792    }
   2793    LocalAccessible* prevChild = walker.Prev();
   2794    if (prevChild) {
   2795      idxInParent = prevChild->IndexInParent() + 1;
   2796      MOZ_DIAGNOSTIC_ASSERT(origContainer == prevChild->LocalParent(),
   2797                            "Broken tree");
   2798      origContainer = prevChild->LocalParent();
   2799    } else {
   2800      idxInParent = 0;
   2801    }
   2802 
   2803    // The child may have already be in its ordinal place for 2 reasons:
   2804    // 1. It was the last ordinal child, and the first aria-owned child.
   2805    //    given:      <ul id="list" aria-owns="b"><li id="a"></li><li
   2806    //    id="b"></li></ul> after load: $("list").setAttribute("aria-owns", "");
   2807    // 2. The preceding adopted children were just reclaimed, eg:
   2808    //    given:      <ul id="list"><li id="b"></li></ul>
   2809    //    after load: $("list").setAttribute("aria-owns", "a b");
   2810    //    later:      $("list").setAttribute("aria-owns", "");
   2811    if (origContainer != owner || child->IndexInParent() != idxInParent) {
   2812      // Only attempt to move the child if the target container would accept it.
   2813      // Otherwise, just allow it to be removed from the tree, since it would
   2814      // not be allowed in normal tree creation.
   2815      if (origContainer->IsAcceptableChild(child->GetContent())) {
   2816        DebugOnly<bool> moved = MoveChild(child, origContainer, idxInParent);
   2817        MOZ_ASSERT(moved, "Failed to put child back.");
   2818      }
   2819    } else {
   2820      MOZ_ASSERT(!child->LocalPrevSibling() ||
   2821                     !child->LocalPrevSibling()->IsRelocated(),
   2822                 "No relocated child should appear before this one");
   2823      MOZ_ASSERT(!child->LocalNextSibling() ||
   2824                     child->LocalNextSibling()->IsRelocated(),
   2825                 "No ordinal child should appear after this one");
   2826    }
   2827  }
   2828 
   2829  aChildren->RemoveLastElements(aChildren->Length() - aStartIdx);
   2830 }
   2831 
   2832 void DocAccessible::TrackMovedAccessible(LocalAccessible* aAcc) {
   2833  MOZ_ASSERT(aAcc->mDoc == this);
   2834  // If an Accessible is inserted and moved during the same tick, don't track
   2835  // it as a move because it hasn't been shown yet.
   2836  if (!mInsertedAccessibles.Contains(aAcc)) {
   2837    mMovedAccessibles.EnsureInserted(aAcc);
   2838  }
   2839  // When we move an Accessible, we're also moving its descendants.
   2840  if (aAcc->IsOuterDoc()) {
   2841    // Don't descend into other documents.
   2842    return;
   2843  }
   2844  for (uint32_t c = 0, count = aAcc->ContentChildCount(); c < count; ++c) {
   2845    TrackMovedAccessible(aAcc->ContentChildAt(c));
   2846  }
   2847 }
   2848 
   2849 bool DocAccessible::MoveChild(LocalAccessible* aChild,
   2850                              LocalAccessible* aNewParent,
   2851                              int32_t aIdxInParent) {
   2852  MOZ_ASSERT(aChild, "No child");
   2853  MOZ_ASSERT(aChild->LocalParent(), "No parent");
   2854  // We can't guarantee MoveChild works correctly for accessibilities storing
   2855  // children outside mChildren.
   2856  MOZ_ASSERT(
   2857      aIdxInParent <= static_cast<int32_t>(aNewParent->mChildren.Length()),
   2858      "Wrong insertion point for a moving child");
   2859 
   2860  LocalAccessible* curParent = aChild->LocalParent();
   2861 
   2862  if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
   2863    return false;
   2864  }
   2865 
   2866 #ifdef A11Y_LOG
   2867  logging::TreeInfo("move child", 0, "old parent", curParent, "new parent",
   2868                    aNewParent, "child", aChild, nullptr);
   2869 #endif
   2870 
   2871  // Forget aria-owns info in case of ARIA owned element. The caller is expected
   2872  // to update it if needed.
   2873  if (aChild->IsRelocated()) {
   2874    aChild->SetRelocated(false);
   2875    nsTArray<RefPtr<LocalAccessible>>* owned = mARIAOwnsHash.Get(curParent);
   2876    MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
   2877    owned->RemoveElement(aChild);
   2878    if (owned->Length() == 0) {
   2879      mARIAOwnsHash.Remove(curParent);
   2880    }
   2881  }
   2882 
   2883  if (curParent == aNewParent) {
   2884    MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
   2885    curParent->RelocateChild(aIdxInParent, aChild);
   2886    if (mIPCDoc) {
   2887      TrackMovedAccessible(aChild);
   2888    }
   2889 
   2890 #ifdef A11Y_LOG
   2891    logging::TreeInfo("move child: parent tree after", logging::eVerbose,
   2892                      curParent);
   2893 #endif
   2894    return true;
   2895  }
   2896 
   2897  // If the child cannot be re-inserted into the tree, then make sure to remove
   2898  // it from its present parent and then shutdown it.
   2899  bool hasInsertionPoint =
   2900      (aIdxInParent >= 0) &&
   2901      (aIdxInParent <= static_cast<int32_t>(aNewParent->mChildren.Length()));
   2902 
   2903  TreeMutation rmut(curParent);
   2904  rmut.BeforeRemoval(aChild, hasInsertionPoint && TreeMutation::kNoShutdown);
   2905  curParent->RemoveChild(aChild);
   2906  rmut.Done();
   2907 
   2908  // No insertion point for the child.
   2909  if (!hasInsertionPoint) {
   2910    return true;
   2911  }
   2912 
   2913  TreeMutation imut(aNewParent);
   2914  aNewParent->InsertChildAt(aIdxInParent, aChild);
   2915  if (mIPCDoc) {
   2916    TrackMovedAccessible(aChild);
   2917  }
   2918  imut.AfterInsertion(aChild);
   2919  imut.Done();
   2920 
   2921 #ifdef A11Y_LOG
   2922  logging::TreeInfo("move child: old parent tree after", logging::eVerbose,
   2923                    curParent);
   2924  logging::TreeInfo("move child: new parent tree after", logging::eVerbose,
   2925                    aNewParent);
   2926 #endif
   2927 
   2928  return true;
   2929 }
   2930 
   2931 void DocAccessible::CacheChildrenInSubtree(LocalAccessible* aRoot,
   2932                                           LocalAccessible** aFocusedAcc) {
   2933  // If the accessible is focused then report a focus event after all related
   2934  // mutation events.
   2935  if (aFocusedAcc && !*aFocusedAcc &&
   2936      FocusMgr()->HasDOMFocus(aRoot->GetContent())) {
   2937    *aFocusedAcc = aRoot;
   2938  }
   2939 
   2940  LocalAccessible* root =
   2941      aRoot->IsHTMLCombobox() ? aRoot->LocalFirstChild() : aRoot;
   2942  if (root->KidsFromDOM()) {
   2943    TreeMutation mt(root, TreeMutation::kNoEvents);
   2944    TreeWalker walker(root);
   2945    while (LocalAccessible* child = walker.Next()) {
   2946      if (child->IsBoundToParent()) {
   2947        MoveChild(child, root, root->mChildren.Length());
   2948        continue;
   2949      }
   2950 
   2951      root->AppendChild(child);
   2952      mt.AfterInsertion(child);
   2953 
   2954      CacheChildrenInSubtree(child, aFocusedAcc);
   2955    }
   2956    mt.Done();
   2957  }
   2958 
   2959  // Fire events for ARIA elements.
   2960  if (!aRoot->HasARIARole()) {
   2961    return;
   2962  }
   2963 
   2964  roles::Role role = aRoot->ARIARole();
   2965  if (!aRoot->IsDoc()) {
   2966    if (role == roles::DIALOG || role == roles::NON_NATIVE_DOCUMENT) {
   2967      // XXX: we should delay document load complete event if the ARIA document
   2968      // has aria-busy.
   2969      FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
   2970    } else if (role == roles::MENUPOPUP && HasLoadState(eCompletelyLoaded)) {
   2971      FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aRoot);
   2972    } else if (role == roles::ALERT && HasLoadState(eCompletelyLoaded)) {
   2973      FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, aRoot);
   2974    }
   2975  }
   2976 }
   2977 
   2978 void DocAccessible::UncacheChildrenInSubtree(LocalAccessible* aRoot) {
   2979  MaybeFireEventsForChangedPopover(aRoot);
   2980  if (aRoot->ARIARole() == roles::MENUPOPUP) {
   2981    nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aRoot);
   2982  }
   2983 
   2984  aRoot->mStateFlags |= eIsNotInDocument;
   2985  RemoveDependentIDsFor(aRoot);
   2986  RemoveDependentElementsFor(aRoot);
   2987 
   2988  // The parent of the removed subtree is about to be cleared, so we must do
   2989  // this here rather than in LocalAccessible::UnbindFromParent because we need
   2990  // the ancestry for this to work.
   2991  if (aRoot->IsTable() || aRoot->IsTableCell()) {
   2992    CachedTableAccessible::Invalidate(aRoot);
   2993  }
   2994 
   2995  // Put relocated children back in their original places instead of removing
   2996  // them from the tree.
   2997  nsTArray<RefPtr<LocalAccessible>>* owned = mARIAOwnsHash.Get(aRoot);
   2998  if (owned) {
   2999    PutChildrenBack(owned, 0);
   3000    MOZ_ASSERT(owned->IsEmpty(),
   3001               "Owned Accessibles should be cleared after PutChildrenBack.");
   3002    mARIAOwnsHash.Remove(aRoot);
   3003    owned = nullptr;
   3004  }
   3005 
   3006  const uint32_t count = aRoot->ContentChildCount();
   3007  for (uint32_t idx = 0; idx < count; ++idx) {
   3008    LocalAccessible* child = aRoot->ContentChildAt(idx);
   3009 
   3010    MOZ_ASSERT(!child->IsRelocated(),
   3011               "No children should be relocated here. They should all have "
   3012               "been relocated by PutChildrenBack.");
   3013 
   3014    // Removing this accessible from the document doesn't mean anything about
   3015    // accessibles for subdocuments, so skip removing those from the tree.
   3016    if (!child->IsDoc()) {
   3017      UncacheChildrenInSubtree(child);
   3018    }
   3019  }
   3020 
   3021  if (aRoot->IsNodeMapEntry() &&
   3022      mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot) {
   3023    mNodeToAccessibleMap.Remove(aRoot->GetNode());
   3024  }
   3025 }
   3026 
   3027 void DocAccessible::ShutdownChildrenInSubtree(LocalAccessible* aAccessible) {
   3028  AUTO_PROFILER_MARKER_TEXT("DocAccessible::ShutdownChildrenInSubtree", A11Y,
   3029                            {}, ""_ns);
   3030  PerfStats::AutoMetricRecording<
   3031      PerfStats::Metric::A11Y_ShutdownChildrenInSubtree>
   3032      autoRecording;
   3033  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
   3034 
   3035  MOZ_ASSERT(!nsAccessibilityService::IsShutdown());
   3036  // Traverse through children and shutdown them before this accessible. When
   3037  // child gets shutdown then it removes itself from children array of its
   3038  // parent. Use jdx index to process the cases if child is not attached to the
   3039  // parent and as result doesn't remove itself from its children.
   3040  uint32_t count = aAccessible->ContentChildCount();
   3041  for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
   3042    LocalAccessible* child = aAccessible->ContentChildAt(jdx);
   3043    if (!child->IsBoundToParent()) {
   3044      NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
   3045      jdx++;
   3046    }
   3047 
   3048    // Don't cross document boundaries. The outerdoc shutdown takes care about
   3049    // its subdocument.
   3050    if (!child->IsDoc()) {
   3051      ShutdownChildrenInSubtree(child);
   3052      if (nsAccessibilityService::IsShutdown()) {
   3053        // If XPCOM is the only consumer (devtools & mochitests), shutting down
   3054        // the child's subtree can cause a11y to shut down because the last
   3055        // xpcom accessibles will be removed. In that case, return early, our
   3056        // work is done.
   3057        return;
   3058      }
   3059    }
   3060  }
   3061 
   3062  UnbindFromDocument(aAccessible);
   3063 }
   3064 
   3065 bool DocAccessible::IsLoadEventTarget() const {
   3066  return mDocumentNode->GetBrowsingContext()->IsContent();
   3067 }
   3068 
   3069 void DocAccessible::SetIPCDoc(DocAccessibleChild* aIPCDoc) {
   3070  MOZ_ASSERT(!mIPCDoc || !aIPCDoc, "Clobbering an attached IPCDoc!");
   3071  mIPCDoc = aIPCDoc;
   3072 }
   3073 
   3074 void DocAccessible::DispatchScrollingEvent(nsINode* aTarget,
   3075                                           uint32_t aEventType) {
   3076  LocalAccessible* acc = GetAccessible(aTarget);
   3077  if (!acc) {
   3078    return;
   3079  }
   3080 
   3081  nsIFrame* frame = acc->GetFrame();
   3082  if (!frame) {
   3083    // Although the accessible had a frame at scroll time, it may now be gone
   3084    // because of display: contents.
   3085    return;
   3086  }
   3087 
   3088  auto [scrollPoint, scrollRange] = ComputeScrollData(acc);
   3089 
   3090  int32_t appUnitsPerDevPixel =
   3091      mPresShell->GetPresContext()->AppUnitsPerDevPixel();
   3092 
   3093  LayoutDeviceIntPoint scrollPointDP = LayoutDevicePoint::FromAppUnitsToNearest(
   3094      scrollPoint, appUnitsPerDevPixel);
   3095  LayoutDeviceIntRect scrollRangeDP =
   3096      LayoutDeviceRect::FromAppUnitsToNearest(scrollRange, appUnitsPerDevPixel);
   3097 
   3098  RefPtr<AccEvent> event =
   3099      new AccScrollingEvent(aEventType, acc, scrollPointDP.x, scrollPointDP.y,
   3100                            scrollRangeDP.width, scrollRangeDP.height);
   3101  nsEventShell::FireEvent(event);
   3102 }
   3103 
   3104 void DocAccessible::ARIAActiveDescendantIDMaybeMoved(
   3105    LocalAccessible* aAccessible) {
   3106  LocalAccessible* widget = nullptr;
   3107  if (aAccessible->IsActiveDescendant(&widget) && widget) {
   3108    // The active descendant might have just been inserted and may not be in the
   3109    // tree yet. Therefore, schedule this async to ensure the tree is up to
   3110    // date.
   3111    mNotificationController
   3112        ->ScheduleNotification<DocAccessible, LocalAccessible>(
   3113            this, &DocAccessible::ARIAActiveDescendantChanged, widget);
   3114  }
   3115 }
   3116 
   3117 void DocAccessible::SetRoleMapEntryForDoc(dom::Element* aElement) {
   3118  const nsRoleMapEntry* entry = aria::GetRoleMap(aElement);
   3119  if (!entry || entry->role == roles::APPLICATION ||
   3120      entry->role == roles::DIALOG ||
   3121      // Role alert isn't valid on the body element according to the ARIA spec,
   3122      // but it's useful for our UI; e.g. the WebRTC sharing indicator.
   3123      (entry->role == roles::ALERT && !mDocumentNode->IsContentDocument())) {
   3124    SetRoleMapEntry(entry);
   3125    return;
   3126  }
   3127  // No other ARIA roles are valid on body elements.
   3128  SetRoleMapEntry(nullptr);
   3129 }
   3130 
   3131 LocalAccessible* DocAccessible::GetAccessible(nsINode* aNode) const {
   3132  return aNode == mDocumentNode ? const_cast<DocAccessible*>(this)
   3133                                : mNodeToAccessibleMap.Get(aNode);
   3134 }
   3135 
   3136 bool DocAccessible::HasPrimaryAction() const {
   3137  if (HyperTextAccessible::HasPrimaryAction()) {
   3138    return true;
   3139  }
   3140  // mContent is normally the body, but there might be a click listener on the
   3141  // root.
   3142  dom::Element* root = mDocumentNode->GetRootElement();
   3143  if (mContent != root) {
   3144    return nsCoreUtils::HasClickListener(root);
   3145  }
   3146  return false;
   3147 }
   3148 
   3149 void DocAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
   3150  aName.Truncate();
   3151  if (aIndex != 0) {
   3152    return;
   3153  }
   3154  if (HasPrimaryAction()) {
   3155    aName.AssignLiteral("click");
   3156  }
   3157 }
   3158 
   3159 void DocAccessible::MaybeHandleChangeToHiddenNameOrDescription(
   3160    nsIContent* aChild) {
   3161  if (!HasLoadState(eTreeConstructed)) {
   3162    return;
   3163  }
   3164  for (nsIContent* content = aChild; content; content = content->GetParent()) {
   3165    if (HasAccessible(content)) {
   3166      // This node isn't hidden. Events for name/description dependents will be
   3167      // fired elsewhere.
   3168      // ... but we do need to handle firing an event for text value changes on
   3169      // meters, since inner meter text is never rendered by layout.
   3170      if (content->IsHTMLElement(nsGkAtoms::meter)) {
   3171        FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE,
   3172                         GetAccessible(content));
   3173      }
   3174      break;
   3175    }
   3176    nsAtom* id = content->GetID();
   3177    if (!id) {
   3178      continue;
   3179    }
   3180    auto* providers =
   3181        GetRelProviders(content->AsElement(), nsDependentAtomString(id));
   3182    if (!providers) {
   3183      continue;
   3184    }
   3185    for (auto& provider : *providers) {
   3186      if (provider->mRelAttr != nsGkAtoms::aria_labelledby &&
   3187          provider->mRelAttr != nsGkAtoms::aria_describedby) {
   3188        continue;
   3189      }
   3190      LocalAccessible* dependentAcc = GetAccessible(provider->mContent);
   3191      if (!dependentAcc) {
   3192        continue;
   3193      }
   3194      FireDelayedEvent(provider->mRelAttr == nsGkAtoms::aria_labelledby
   3195                           ? nsIAccessibleEvent::EVENT_NAME_CHANGE
   3196                           : nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
   3197                       dependentAcc);
   3198    }
   3199  }
   3200 }
   3201 
   3202 void DocAccessible::MaybeHandleChangeToAriaActions(LocalAccessible* aAcc,
   3203                                                   const nsAtom* aAttribute) {
   3204  if (aAttribute == nsGkAtoms::aria_actions &&
   3205      nsTextEquivUtils::HasNameRule(aAcc, eNameFromSubtreeIfReqRule)) {
   3206    // Search for action targets in subtree, and fire a name change event
   3207    // on aAcc if any are found.
   3208    AssociatedElementsIterator iter(mDoc, aAcc->Elm(), nsGkAtoms::aria_actions);
   3209    while (LocalAccessible* target = iter.Next()) {
   3210      if (aAcc->IsAncestorOf(target)) {
   3211        mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAcc);
   3212        break;
   3213      }
   3214    }
   3215  }
   3216 
   3217  if (aAttribute == nsGkAtoms::id) {
   3218    RelatedAccIterator iter(mDoc, aAcc->Elm(), nsGkAtoms::aria_actions);
   3219    while (LocalAccessible* host = iter.Next()) {
   3220      // Search for any ancestor action hosts and fire a name change
   3221      // if any are found that calculate their name from the subtree.
   3222      if (host->IsAncestorOf(aAcc) &&
   3223          nsTextEquivUtils::HasNameRule(host, eNameFromSubtreeIfReqRule)) {
   3224        mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, host);
   3225      }
   3226    }
   3227  }
   3228 }
   3229 
   3230 void DocAccessible::AttrElementWillChange(dom::Element* aElement,
   3231                                          nsAtom* aAttr) {
   3232  MOZ_ASSERT(!sIsAttrElementChanging);
   3233  AttributeWillChange(aElement, kNameSpaceID_None, aAttr,
   3234                      AttrModType::Modification);
   3235  // We might get notified about a related content attribute change. Ignore
   3236  // it.
   3237  sIsAttrElementChanging = true;
   3238 }
   3239 
   3240 void DocAccessible::AttrElementChanged(dom::Element* aElement, nsAtom* aAttr) {
   3241  MOZ_ASSERT(sIsAttrElementChanging);
   3242  // The element has changed and the content attribute change notifications
   3243  // (if any) have been sent.
   3244  sIsAttrElementChanging = false;
   3245  AttributeChanged(aElement, kNameSpaceID_None, aAttr,
   3246                   AttrModType::Modification, nullptr);
   3247 }
   3248 
   3249 bool DocAccessible::ProcessAnchorJump() {
   3250  if (!mAnchorJumpElm) {
   3251    return true;
   3252  }
   3253  LocalAccessible* target = GetAccessibleOrContainer(mAnchorJumpElm);
   3254  if (!target) {
   3255    // This node isn't in the tree.
   3256    mAnchorJumpElm = nullptr;
   3257    return true;
   3258  }
   3259  const Accessible* focusedAcc = FocusMgr()->FocusedAccessible();
   3260  if (!focusedAcc || (focusedAcc != this && !focusedAcc->IsNonInteractive())) {
   3261    // Focus is nowhere or on an interactive element. Ignore the anchor jump for
   3262    // now.
   3263    return false;
   3264  }
   3265  nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START, target);
   3266  // We've processed this anchor jump now. Clear it so it isn't processed again.
   3267  mAnchorJumpElm = nullptr;
   3268  return true;
   3269 }
   3270 
   3271 void DocAccessible::RefreshAnchorRelationCacheForTarget(
   3272    LocalAccessible* aTarget) {
   3273  nsIFrame* frame = aTarget->GetFrame();
   3274  if (!frame || !frame->HasProperty(nsIFrame::AnchorPosReferences())) {
   3275    return;
   3276  }
   3277 
   3278  AnchorPosReferenceData* referencedAnchors =
   3279      frame->GetProperty(nsIFrame::AnchorPosReferences());
   3280  for (auto& entry : *referencedAnchors) {
   3281    const auto& anchorName = entry.GetKey();
   3282    if (const nsIFrame* anchorFrame =
   3283            mPresShell->GetAnchorPosAnchor(anchorName, frame)) {
   3284      if (LocalAccessible* anchorAcc =
   3285              GetAccessible(anchorFrame->GetContent())) {
   3286        if (!mInsertedAccessibles.Contains(anchorAcc)) {
   3287          QueueCacheUpdate(anchorAcc, CacheDomain::Relations);
   3288        }
   3289      }
   3290    }
   3291  }
   3292 }