tor-browser

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

NotificationController.cpp (44389B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "NotificationController.h"
      7 
      8 #include "CssAltContent.h"
      9 #include "DocAccessible-inl.h"
     10 #include "DocAccessibleChild.h"
     11 #include "LocalAccessible-inl.h"
     12 #include "nsEventShell.h"
     13 #include "TextLeafAccessible.h"
     14 #include "TextUpdater.h"
     15 
     16 #include "nsIContentInlines.h"
     17 
     18 #include "mozilla/AppShutdown.h"
     19 #include "mozilla/dom/BrowserChild.h"
     20 #include "mozilla/dom/Element.h"
     21 #include "mozilla/PerfStats.h"
     22 #include "mozilla/PresShell.h"
     23 #include "mozilla/ProfilerMarkers.h"
     24 #include "nsAccessibilityService.h"
     25 #include "mozilla/glean/AccessibleMetrics.h"
     26 
     27 using namespace mozilla;
     28 using namespace mozilla::a11y;
     29 using namespace mozilla::dom;
     30 
     31 ////////////////////////////////////////////////////////////////////////////////
     32 // NotificationCollector
     33 ////////////////////////////////////////////////////////////////////////////////
     34 
     35 NotificationController::NotificationController(DocAccessible* aDocument,
     36                                               PresShell* aPresShell)
     37    : EventQueue(aDocument),
     38      mObservingState(eNotObservingRefresh),
     39      mPresShell(aPresShell),
     40      mEventGeneration(0) {
     41  // Schedule initial accessible tree construction.
     42  ScheduleProcessing();
     43 }
     44 
     45 NotificationController::~NotificationController() {
     46  NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
     47  if (mDocument) {
     48    Shutdown();
     49  }
     50  MOZ_RELEASE_ASSERT(mObservingState == eNotObservingRefresh,
     51                     "Must unregister before being destroyed");
     52 }
     53 
     54 ////////////////////////////////////////////////////////////////////////////////
     55 // NotificationCollector: AddRef/Release and cycle collection
     56 
     57 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
     58 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
     59 
     60 NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
     61 
     62 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
     63  if (tmp->mDocument) {
     64    tmp->Shutdown();
     65  }
     66 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     67 
     68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
     69  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
     70  for (const auto& entry : tmp->mContentInsertions) {
     71    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key");
     72    cb.NoteXPCOMChild(entry.GetKey());
     73    nsTArray<nsCOMPtr<nsIContent>>* list = entry.GetData().get();
     74    for (uint32_t i = 0; i < list->Length(); i++) {
     75      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions value item");
     76      cb.NoteXPCOMChild(list->ElementAt(i));
     77    }
     78  }
     79  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusEvent)
     80  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
     81  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
     82 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     83 
     84 ////////////////////////////////////////////////////////////////////////////////
     85 // NotificationCollector: public
     86 
     87 void NotificationController::Shutdown() {
     88  if (mObservingState != eNotObservingRefresh &&
     89      mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
     90    // Note, this was our last chance to unregister, since we're about to
     91    // clear mPresShell further down in this function.
     92    mObservingState = eNotObservingRefresh;
     93  }
     94  MOZ_RELEASE_ASSERT(mObservingState == eNotObservingRefresh,
     95                     "Must unregister before being destroyed (and we just "
     96                     "passed our last change to unregister)");
     97  // Immediately null out mPresShell, to prevent us from being registered as a
     98  // refresh observer again.
     99  mPresShell = nullptr;
    100 
    101  // Shutdown handling child documents.
    102  int32_t childDocCount = mHangingChildDocuments.Length();
    103  for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
    104    if (!mHangingChildDocuments[idx]->IsDefunct()) {
    105      mHangingChildDocuments[idx]->Shutdown();
    106    }
    107  }
    108 
    109  mHangingChildDocuments.Clear();
    110 
    111  mDocument = nullptr;
    112 
    113  mTextArray.Clear();
    114  mContentInsertions.Clear();
    115  mNotifications.Clear();
    116  mFocusEvent = nullptr;
    117  mEvents.Clear();
    118  mRelocations.Clear();
    119 }
    120 
    121 void NotificationController::CoalesceHideEvent(AccHideEvent* aHideEvent) {
    122  LocalAccessible* parent = aHideEvent->LocalParent();
    123  while (parent) {
    124    if (parent->IsDoc()) {
    125      break;
    126    }
    127 
    128    if (parent->HideEventTarget()) {
    129      DropMutationEvent(aHideEvent);
    130      break;
    131    }
    132 
    133    if (parent->ShowEventTarget()) {
    134      AccShowEvent* showEvent =
    135          downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ShowEvent));
    136      if (showEvent->EventGeneration() < aHideEvent->EventGeneration()) {
    137        DropMutationEvent(aHideEvent);
    138        break;
    139      }
    140    }
    141 
    142    parent = parent->LocalParent();
    143  }
    144 }
    145 
    146 bool NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent) {
    147  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
    148    // We have to allow there to be a hide and then a show event for a target
    149    // because of targets getting moved.  However we need to coalesce a show and
    150    // then a hide for a target which means we need to check for that here.
    151    if (aEvent->GetAccessible()->ShowEventTarget()) {
    152      AccTreeMutationEvent* showEvent =
    153          mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
    154      DropMutationEvent(showEvent);
    155      return false;
    156    }
    157 
    158    // Don't queue a hide event on an accessible that's already being moved. It
    159    // or an ancestor should already have a hide event queued.
    160    if (mDocument &&
    161        mDocument->IsAccessibleBeingMoved(aEvent->GetAccessible())) {
    162      return false;
    163    }
    164 
    165    // If this is an additional hide event, the accessible may be hidden, or
    166    // moved again after a move. Preserve the original hide event since
    167    // its properties are consistent with the tree that existed before
    168    // the next batch of mutation events is processed.
    169    if (aEvent->GetAccessible()->HideEventTarget()) {
    170      return false;
    171    }
    172  }
    173 
    174  AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
    175  mEventGeneration++;
    176  mutEvent->SetEventGeneration(mEventGeneration);
    177 
    178  if (!mFirstMutationEvent) {
    179    mFirstMutationEvent = aEvent;
    180    ScheduleProcessing();
    181  }
    182 
    183  if (mLastMutationEvent) {
    184    NS_ASSERTION(!mLastMutationEvent->NextEvent(),
    185                 "why isn't the last event the end?");
    186    mLastMutationEvent->SetNextEvent(aEvent);
    187  }
    188 
    189  aEvent->SetPrevEvent(mLastMutationEvent);
    190  mLastMutationEvent = aEvent;
    191  mMutationMap.PutEvent(aEvent);
    192 
    193  // Because we could be hiding the target of a show event we need to get rid
    194  // of any such events.
    195  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
    196    CoalesceHideEvent(downcast_accEvent(aEvent));
    197 
    198    // mLastMutationEvent will point to something other than aEvent if and only
    199    // if aEvent was just coalesced away.  In that case a parent accessible
    200    // must already have the required reorder and text change events so we are
    201    // done here.
    202    if (mLastMutationEvent != aEvent) {
    203      return false;
    204    }
    205  }
    206 
    207  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE ||
    208      aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
    209    LocalAccessible* target = aEvent->GetAccessible();
    210    // We need to do this here while the relation is still intact. During the
    211    // tick, where we we call PushNameOrDescriptionChange, it will be too late
    212    // since we will already have unparented the label and severed the relation.
    213    if (PushNameOrDescriptionChangeToRelations(target,
    214                                               RelationType::LABEL_FOR) ||
    215        PushNameOrDescriptionChangeToRelations(target,
    216                                               RelationType::DESCRIPTION_FOR)) {
    217      ScheduleProcessing();
    218    }
    219  }
    220 
    221  // We need to fire a reorder event after all of the events targeted at shown
    222  // or hidden children of a container.  So either queue a new one, or move an
    223  // existing one to the end of the queue if the container already has a
    224  // reorder event.
    225  LocalAccessible* container = aEvent->GetAccessible()->LocalParent();
    226  RefPtr<AccReorderEvent> reorder;
    227  if (!container->ReorderEventTarget()) {
    228    reorder = new AccReorderEvent(container);
    229    container->SetReorderEventTarget(true);
    230    mMutationMap.PutEvent(reorder);
    231  } else {
    232    AccReorderEvent* event = downcast_accEvent(
    233        mMutationMap.GetEvent(container, EventMap::ReorderEvent));
    234    reorder = event;
    235    if (mFirstMutationEvent == event) {
    236      mFirstMutationEvent = event->NextEvent();
    237    } else {
    238      event->PrevEvent()->SetNextEvent(event->NextEvent());
    239    }
    240 
    241    event->NextEvent()->SetPrevEvent(event->PrevEvent());
    242    event->SetNextEvent(nullptr);
    243  }
    244 
    245  reorder->SetEventGeneration(mEventGeneration);
    246  reorder->SetPrevEvent(mLastMutationEvent);
    247  mLastMutationEvent->SetNextEvent(reorder);
    248  mLastMutationEvent = reorder;
    249 
    250  // It is not possible to have a text change event for something other than a
    251  // hyper text accessible.
    252  if (!container->IsHyperText()) {
    253    return true;
    254  }
    255 
    256  MOZ_ASSERT(mutEvent);
    257 
    258  nsString text;
    259  aEvent->GetAccessible()->AppendTextTo(text);
    260  if (text.IsEmpty()) {
    261    return true;
    262  }
    263 
    264  LocalAccessible* target = aEvent->GetAccessible();
    265  int32_t offset = container->AsHyperText()->GetChildOffset(target);
    266  AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
    267  while (prevEvent &&
    268         prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
    269    prevEvent = prevEvent->PrevEvent();
    270  }
    271 
    272  if (prevEvent &&
    273      prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
    274      mutEvent->IsHide()) {
    275    AccHideEvent* prevHide = downcast_accEvent(prevEvent);
    276    AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent;
    277    if (prevTextChange && prevHide->LocalParent() == mutEvent->LocalParent()) {
    278      if (prevHide->mNextSibling == target) {
    279        target->AppendTextTo(prevTextChange->mModifiedText);
    280        prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
    281      } else if (prevHide->mPrevSibling == target) {
    282        nsString temp;
    283        target->AppendTextTo(temp);
    284 
    285        uint32_t extraLen = temp.Length();
    286        temp += prevTextChange->mModifiedText;
    287        ;
    288        prevTextChange->mModifiedText = temp;
    289        prevTextChange->mStart -= extraLen;
    290        prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
    291      }
    292    }
    293  } else if (prevEvent && mutEvent->IsShow() &&
    294             prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
    295    AccShowEvent* prevShow = downcast_accEvent(prevEvent);
    296    AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent;
    297    if (prevTextChange && prevShow->LocalParent() == target->LocalParent()) {
    298      int32_t index = target->IndexInParent();
    299      int32_t prevIndex = prevShow->GetAccessible()->IndexInParent();
    300      if (prevIndex + 1 == index) {
    301        target->AppendTextTo(prevTextChange->mModifiedText);
    302        prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
    303      } else if (index + 1 == prevIndex) {
    304        nsString temp;
    305        target->AppendTextTo(temp);
    306        prevTextChange->mStart -= temp.Length();
    307        temp += prevTextChange->mModifiedText;
    308        prevTextChange->mModifiedText = temp;
    309        prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
    310      }
    311    }
    312  }
    313 
    314  if (!mutEvent->mTextChangeEvent) {
    315    mutEvent->mTextChangeEvent = new AccTextChangeEvent(
    316        container, offset, text, mutEvent->IsShow(),
    317        aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
    318  }
    319 
    320  return true;
    321 }
    322 
    323 void NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent) {
    324  const uint32_t eventType = aEvent->GetEventType();
    325  MOZ_ASSERT(eventType != nsIAccessibleEvent::EVENT_INNER_REORDER,
    326             "Inner reorder has already been dropped, cannot drop again");
    327  if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
    328    // We don't fully drop reorder events, we just change them to inner reorder
    329    // events.
    330    AccReorderEvent* reorderEvent = downcast_accEvent(aEvent);
    331 
    332    MOZ_ASSERT(reorderEvent);
    333    reorderEvent->SetInner();
    334    return;
    335  }
    336  if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
    337    // unset the event bits since the event isn't being fired any more.
    338    aEvent->GetAccessible()->SetShowEventTarget(false);
    339  } else if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
    340    // unset the event bits since the event isn't being fired any more.
    341    aEvent->GetAccessible()->SetHideEventTarget(false);
    342 
    343    AccHideEvent* hideEvent = downcast_accEvent(aEvent);
    344    MOZ_ASSERT(hideEvent);
    345 
    346    if (hideEvent->NeedsShutdown()) {
    347      mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
    348    }
    349  } else {
    350    MOZ_ASSERT_UNREACHABLE("Mutation event has non-mutation event type");
    351  }
    352 
    353  // Do the work to splice the event out of the list.
    354  if (mFirstMutationEvent == aEvent) {
    355    mFirstMutationEvent = aEvent->NextEvent();
    356  } else {
    357    aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
    358  }
    359 
    360  if (mLastMutationEvent == aEvent) {
    361    mLastMutationEvent = aEvent->PrevEvent();
    362  } else {
    363    aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent());
    364  }
    365 
    366  aEvent->SetPrevEvent(nullptr);
    367  aEvent->SetNextEvent(nullptr);
    368  mMutationMap.RemoveEvent(aEvent);
    369 }
    370 
    371 void NotificationController::CoalesceMutationEvents() {
    372  AUTO_PROFILER_MARKER_TEXT("NotificationController::CoalesceMutationEvents",
    373                            A11Y, {}, ""_ns);
    374  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_CoalesceMutationEvents>
    375      autoRecording;
    376  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
    377 
    378  AccTreeMutationEvent* event = mFirstMutationEvent;
    379  while (event) {
    380    AccTreeMutationEvent* nextEvent = event->NextEvent();
    381    uint32_t eventType = event->GetEventType();
    382    if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
    383      LocalAccessible* acc = event->GetAccessible();
    384      while (acc) {
    385        if (acc->IsDoc()) {
    386          break;
    387        }
    388 
    389        // if a parent of the reorder event's target is being hidden that
    390        // hide event's target must have a parent that is also a reorder event
    391        // target.  That means we don't need this reorder event.
    392        if (acc->HideEventTarget()) {
    393          DropMutationEvent(event);
    394          break;
    395        }
    396 
    397        LocalAccessible* parent = acc->LocalParent();
    398        if (parent && parent->ReorderEventTarget()) {
    399          AccReorderEvent* reorder = downcast_accEvent(
    400              mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
    401 
    402          // We want to make sure that a reorder event comes after any show or
    403          // hide events targeted at the children of its target.  We keep the
    404          // invariant that event generation goes up as you are farther in the
    405          // queue, so we want to use the spot of the event with the higher
    406          // generation number, and keep that generation number.
    407          if (reorder &&
    408              reorder->EventGeneration() < event->EventGeneration()) {
    409            reorder->SetEventGeneration(event->EventGeneration());
    410 
    411            // It may be true that reorder was before event, and we coalesced
    412            // away all the show / hide events between them.  In that case
    413            // event is already immediately after reorder in the queue and we
    414            // do not need to rearrange the list of events.
    415            if (event != reorder->NextEvent()) {
    416              // There really should be a show or hide event before the first
    417              // reorder event.
    418              if (reorder->PrevEvent()) {
    419                reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
    420              } else {
    421                mFirstMutationEvent = reorder->NextEvent();
    422              }
    423 
    424              reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
    425              event->PrevEvent()->SetNextEvent(reorder);
    426              reorder->SetPrevEvent(event->PrevEvent());
    427              event->SetPrevEvent(reorder);
    428              reorder->SetNextEvent(event);
    429            }
    430          }
    431          DropMutationEvent(event);
    432          break;
    433        }
    434 
    435        acc = parent;
    436      }
    437    } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
    438      LocalAccessible* parent = event->GetAccessible()->LocalParent();
    439      while (parent) {
    440        if (parent->IsDoc()) {
    441          break;
    442        }
    443 
    444        // if the parent of a show event is being either shown or hidden then
    445        // we don't need to fire a show event for a subtree of that change.
    446        if (parent->ShowEventTarget() || parent->HideEventTarget()) {
    447          DropMutationEvent(event);
    448          break;
    449        }
    450 
    451        parent = parent->LocalParent();
    452      }
    453    } else if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
    454      MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE,
    455                 "mutation event list has an invalid event");
    456 
    457      AccHideEvent* hideEvent = downcast_accEvent(event);
    458      CoalesceHideEvent(hideEvent);
    459    }
    460 
    461    event = nextEvent;
    462  }
    463 }
    464 
    465 void NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument) {
    466  // Schedule child document binding to the tree.
    467  mHangingChildDocuments.AppendElement(aDocument);
    468  ScheduleProcessing();
    469 }
    470 
    471 void NotificationController::ScheduleContentInsertion(
    472    LocalAccessible* aContainer, nsTArray<nsCOMPtr<nsIContent>>& aInsertions) {
    473  if (!aInsertions.IsEmpty()) {
    474    mContentInsertions.GetOrInsertNew(aContainer)->AppendElements(aInsertions);
    475    ScheduleProcessing();
    476  }
    477 }
    478 
    479 void NotificationController::ScheduleProcessing() {
    480  // If notification flush isn't planned yet, start notification flush
    481  // asynchronously (after style and layout).
    482  // Note: the mPresShell null-check might be unnecessary; it's just to prevent
    483  // a null-deref here, if we somehow get called after we've been shut down.
    484  if (mObservingState == eNotObservingRefresh && mPresShell) {
    485    if (mPresShell->AddRefreshObserver(this, FlushType::Display,
    486                                       "Accessibility notifications")) {
    487      mObservingState = eRefreshObserving;
    488    }
    489  }
    490 }
    491 
    492 ////////////////////////////////////////////////////////////////////////////////
    493 // NotificationCollector: protected
    494 
    495 bool NotificationController::IsUpdatePending() const {
    496  return mPresShell->NeedStyleFlush() || mPresShell->NeedLayoutFlush() ||
    497         mObservingState == eRefreshProcessingForUpdate || WaitingForParent() ||
    498         mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
    499         !mTextArray.IsEmpty() ||
    500         !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
    501 }
    502 
    503 bool NotificationController::WaitingForParent() const {
    504  DocAccessible* parentdoc = mDocument->ParentDocument();
    505  if (!parentdoc) {
    506    return false;
    507  }
    508 
    509  NotificationController* parent = parentdoc->mNotificationController;
    510  if (!parent || parent == this) {
    511    // Do not wait for nothing or ourselves
    512    return false;
    513  }
    514 
    515  // Wait for parent's notifications processing
    516  return parent->mContentInsertions.Count() != 0 ||
    517         parent->mNotifications.Length() != 0;
    518 }
    519 
    520 void NotificationController::ProcessMutationEvents() {
    521  // Firing an event can indirectly run script; e.g. an XPCOM event observer
    522  // or querying a XUL interface. Further mutations might be queued as a result.
    523  // It's important that the mutation queue and state bits from one tick don't
    524  // interfere with the next tick. Otherwise, we can end up dropping events.
    525  // Therefore:
    526  // 1. Clear the state bits, which we only need for coalescence.
    527  for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
    528       event = event->NextEvent()) {
    529    LocalAccessible* acc = event->GetAccessible();
    530    acc->SetShowEventTarget(false);
    531    acc->SetHideEventTarget(false);
    532    acc->SetReorderEventTarget(false);
    533  }
    534  // 2. Keep the current queue locally, but clear the queue on the instance.
    535  RefPtr<AccTreeMutationEvent> firstEvent = mFirstMutationEvent;
    536  mFirstMutationEvent = mLastMutationEvent = nullptr;
    537  mMutationMap.Clear();
    538  mEventGeneration = 0;
    539 
    540  // Group the show events by the parent of their target.
    541  nsTHashMap<nsPtrHashKey<LocalAccessible>, nsTArray<AccTreeMutationEvent*>>
    542      showEvents;
    543  for (AccTreeMutationEvent* event = firstEvent; event;
    544       event = event->NextEvent()) {
    545    if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
    546      continue;
    547    }
    548 
    549    LocalAccessible* parent = event->GetAccessible()->LocalParent();
    550    showEvents.LookupOrInsert(parent).AppendElement(event);
    551  }
    552 
    553  // We need to fire show events for the children of an accessible in the order
    554  // of their indices at this point.  So sort each set of events for the same
    555  // container by the index of their target. We do this before firing any events
    556  // because firing an event might indirectly run script which might alter the
    557  // tree, breaking our sort. However, we don't actually fire the events yet.
    558  for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
    559    struct AccIdxComparator {
    560      bool LessThan(const AccTreeMutationEvent* a,
    561                    const AccTreeMutationEvent* b) const {
    562        int32_t aIdx = a->GetAccessible()->IndexInParent();
    563        int32_t bIdx = b->GetAccessible()->IndexInParent();
    564        MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && (a == b || aIdx != bIdx));
    565        return aIdx < bIdx;
    566      }
    567      bool Equals(const AccTreeMutationEvent* a,
    568                  const AccTreeMutationEvent* b) const {
    569        DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent();
    570        DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent();
    571        MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && (a == b || aIdx != bIdx));
    572        return a == b;
    573      }
    574    };
    575 
    576    nsTArray<AccTreeMutationEvent*>& events = iter.Data();
    577    events.Sort(AccIdxComparator());
    578  }
    579 
    580  // there is no reason to fire a hide event for a child of a show event
    581  // target.  That can happen if something is inserted into the tree and
    582  // removed before the next refresh driver tick, but it should not be
    583  // observable outside gecko so it should be safe to coalesce away any such
    584  // events.  This means that it should be fine to fire all of the hide events
    585  // first, and then deal with any shown subtrees.
    586  for (AccTreeMutationEvent* event = firstEvent; event;
    587       event = event->NextEvent()) {
    588    if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) {
    589      continue;
    590    }
    591 
    592    nsEventShell::FireEvent(event);
    593    if (!mDocument) {
    594      return;
    595    }
    596 
    597    AccMutationEvent* mutEvent = downcast_accEvent(event);
    598    if (mutEvent->mTextChangeEvent) {
    599      nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
    600      if (!mDocument) {
    601        return;
    602      }
    603    }
    604 
    605    AccHideEvent* hideEvent = downcast_accEvent(event);
    606    if (hideEvent->NeedsShutdown()) {
    607      mDocument->ShutdownChildrenInSubtree(event->mAccessible);
    608    }
    609  }
    610 
    611  // Fire the show events we sorted earlier.
    612  for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
    613    nsTArray<AccTreeMutationEvent*>& events = iter.Data();
    614    for (AccTreeMutationEvent* event : events) {
    615      nsEventShell::FireEvent(event);
    616      if (!mDocument) {
    617        return;
    618      }
    619 
    620      AccMutationEvent* mutEvent = downcast_accEvent(event);
    621      if (mutEvent->mTextChangeEvent) {
    622        nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
    623        if (!mDocument) {
    624          return;
    625        }
    626      }
    627    }
    628  }
    629 
    630  // Now we can fire the reorder events after all the show and hide events.
    631  for (const uint32_t reorderType : {nsIAccessibleEvent::EVENT_INNER_REORDER,
    632                                     nsIAccessibleEvent::EVENT_REORDER}) {
    633    for (AccTreeMutationEvent* event = firstEvent; event;
    634         event = event->NextEvent()) {
    635      if (event->GetEventType() != reorderType) {
    636        continue;
    637      }
    638 
    639      if (event->GetAccessible()->IsDefunct()) {
    640        // An inner reorder target may have been hidden itself and no
    641        // longer bound to the document.
    642        MOZ_ASSERT(reorderType == nsIAccessibleEvent::EVENT_INNER_REORDER,
    643                   "An 'outer' reorder target should not be defunct");
    644        continue;
    645      }
    646 
    647      nsEventShell::FireEvent(event);
    648      if (!mDocument) {
    649        return;
    650      }
    651 
    652      // The mutation in the container can change its name, or an ancestor's
    653      // name. A labelled/described by relation would also need to be notified
    654      // if this is the case.
    655      if (PushNameOrDescriptionChange(event)) {
    656        ScheduleProcessing();
    657      }
    658 
    659      LocalAccessible* target = event->GetAccessible();
    660      target->Document()->MaybeNotifyOfValueChange(target);
    661      if (!mDocument) {
    662        return;
    663      }
    664    }
    665  }
    666 
    667  // Our events are in a doubly linked list. Clear the pointers to reduce
    668  // pressure on the cycle collector. Even though clearing the previous pointers
    669  // removes cycles, this isn't enough. The cycle collector still gets bogged
    670  // down when there are lots of mutation events if the next pointers aren't
    671  // cleared. Even without the cycle collector, not clearing the next pointers
    672  // potentially results in deep recursion because releasing each event releases
    673  // its next event.
    674  RefPtr<AccTreeMutationEvent> event = firstEvent;
    675  while (event) {
    676    RefPtr<AccTreeMutationEvent> next = event->NextEvent();
    677    event->SetNextEvent(nullptr);
    678    event->SetPrevEvent(nullptr);
    679    event = next;
    680  }
    681 }
    682 
    683 ////////////////////////////////////////////////////////////////////////////////
    684 // NotificationCollector: private
    685 
    686 void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
    687  AUTO_PROFILER_MARKER_UNTYPED("NotificationController::WillRefresh", A11Y, {});
    688 
    689  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_WillRefresh>
    690      autoRecording;
    691  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
    692  auto timer = glean::a11y::tree_update_timing.Measure();
    693  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
    694 
    695  AUTO_PROFILER_LABEL("NotificationController::WillRefresh", A11Y);
    696 
    697  // If mDocument is null, the document accessible that this notification
    698  // controller was created for is now shut down. This means we've lost our
    699  // ability to unregister ourselves, which is bad. (However, it also shouldn't
    700  // be logically possible for us to get here with a null mDocument; the only
    701  // thing that clears that pointer is our Shutdown() method, which first
    702  // unregisters and fatally asserts if that fails).
    703  MOZ_RELEASE_ASSERT(
    704      mDocument,
    705      "The document was shut down while refresh observer is attached!");
    706 
    707  if (AppShutdown::IsShutdownImpending()) {
    708    return;
    709  }
    710 
    711  // Wait until an update, we have started, or an interruptible reflow is
    712  // finished. We also check the existance of our pres context and root pres
    713  // context, since if we can't reach either of these the frame tree is being
    714  // destroyed.
    715  nsPresContext* pc = mPresShell->GetPresContext();
    716  if (mObservingState == eRefreshProcessing ||
    717      mObservingState == eRefreshProcessingForUpdate ||
    718      mPresShell->IsReflowInterrupted() || !pc || !pc->GetRootPresContext()) {
    719    return;
    720  }
    721 
    722  if (mDocument->IPCDoc() && mDocument->IPCDoc()->HasUnackedMutationEvents()) {
    723    // We've sent mutation events to the parent process, but we haven't
    724    // received its ACK yet. We defer accessibility updates until we do.
    725    // Otherwise, we might flood the IPDL queue with many later mutation events
    726    // while the parent process is still trying to process earlier ones, getting
    727    // further and further behind and causing the browser to hang for extended
    728    // periods. If the same nodes are repeatedly changing or being recreated,
    729    // deferring updates can significantly reduce the overall number of events
    730    // because we avoid generating events for the intermediate changes that
    731    // occur while the parent process is busy. This also avoids the associated
    732    // work to update the tree in the content process. We must defer all
    733    // work here, not just mutation events, because otherwise, the tree and
    734    // other events might get out of sync with the mutation events we've
    735    // processed. Queued content insertions, events, etc. will be processed in
    736    // a subsequent tick after we receive the ACK, though some of them may be
    737    // irrelevant (and thus dropped) by the time that happens if a DOM node or
    738    // Accessible was removed in the interim.
    739    return;
    740  }
    741 
    742  // Process parent's notifications before ours, to get proper ordering between
    743  // e.g. tab event and content event.
    744  if (WaitingForParent()) {
    745    mDocument->ParentDocument()->mNotificationController->WillRefresh(aTime);
    746    if (!mDocument || AppShutdown::IsShutdownImpending()) {
    747      return;
    748    }
    749  }
    750 
    751  // Any generic notifications should be queued if we're processing content
    752  // insertions or generic notifications.
    753  mObservingState = eRefreshProcessingForUpdate;
    754 
    755  // Initial accessible tree construction.
    756  if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
    757    // (1) If document is not bound to parent at this point, or
    758    // (2) the PresShell is not initialized (and it isn't about:blank),
    759    // then the document is not ready yet (process notifications later).
    760    if (!mDocument->IsBoundToParent() ||
    761        (!mPresShell->DidInitialize() &&
    762         !mDocument->DocumentNode()->IsInitialDocument())) {
    763      mObservingState = eRefreshObserving;
    764      return;
    765    }
    766 
    767 #ifdef A11Y_LOG
    768    if (logging::IsEnabled(logging::eTree)) {
    769      logging::MsgBegin("TREE", "initial tree created");
    770      logging::Address("document", mDocument);
    771      logging::MsgEnd();
    772    }
    773 #endif
    774 
    775    mDocument->DoInitialUpdate();
    776    if (AppShutdown::IsShutdownImpending()) {
    777      return;
    778    }
    779 
    780    NS_ASSERTION(mContentInsertions.Count() == 0,
    781                 "Pending content insertions while initial accessible tree "
    782                 "isn't created!");
    783  }
    784 
    785  mDocument->ProcessPendingUpdates();
    786 
    787  // Process rendered text change notifications. Even though we want to process
    788  // them in the order in which they were queued, we still want to avoid
    789  // duplicates.
    790  nsTHashSet<nsIContent*> textHash;
    791  for (nsIContent* textNode : mTextArray) {
    792    if (!textHash.EnsureInserted(textNode)) {
    793      continue;  // Already processed.
    794    }
    795    LocalAccessible* textAcc = mDocument->GetAccessible(textNode);
    796 
    797    // If the text node is not in tree or doesn't have a frame, or placed in
    798    // another document, then this case should have been handled already by
    799    // content removal notifications.
    800    nsINode* containerNode = textNode->GetFlattenedTreeParentNode();
    801    if (!containerNode || textNode->OwnerDoc() != mDocument->DocumentNode()) {
    802      MOZ_ASSERT(!textAcc,
    803                 "Text node was removed but accessible is kept alive!");
    804      continue;
    805    }
    806 
    807    nsIFrame* textFrame = textNode->GetPrimaryFrame();
    808    if (!textFrame) {
    809      MOZ_ASSERT(!textAcc,
    810                 "Text node isn't rendered but accessible is kept alive!");
    811      continue;
    812    }
    813 
    814 #ifdef A11Y_LOG
    815    nsIContent* containerElm =
    816        containerNode->IsElement() ? containerNode->AsElement() : nullptr;
    817 #endif
    818 
    819    nsIFrame::RenderedText text = textFrame->GetRenderedText(
    820        0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
    821        nsIFrame::TrailingWhitespace::DontTrim);
    822 
    823    if (textAcc) {
    824      // Remove the TextLeafAccessible if:
    825      // 1. The rendered text is empty; or
    826      // 2. The text is invisible, semantically irrelevant whitespace before a
    827      // hard line break.
    828      if (text.mString.IsEmpty() ||
    829          nsCoreUtils::IsTrimmedWhitespaceBeforeHardLineBreak(textFrame)) {
    830 #ifdef A11Y_LOG
    831        if (logging::IsEnabled(logging::eTree | logging::eText)) {
    832          logging::MsgBegin("TREE", "text node lost its content; doc: %p",
    833                            mDocument);
    834          logging::Node("container", containerElm);
    835          logging::Node("content", textNode);
    836          logging::MsgEnd();
    837        }
    838 #endif
    839 
    840        mDocument->ContentRemoved(textAcc);
    841        continue;
    842      }
    843 
    844      // Update text of the accessible and fire text change events.
    845 #ifdef A11Y_LOG
    846      if (logging::IsEnabled(logging::eText)) {
    847        logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
    848        logging::Node("container", containerElm);
    849        logging::Node("content", textNode);
    850        logging::MsgEntry(
    851            "old text '%s'",
    852            NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
    853        logging::MsgEntry("new text: '%s'",
    854                          NS_ConvertUTF16toUTF8(text.mString).get());
    855        logging::MsgEnd();
    856      }
    857 #endif
    858 
    859      if (CssAltContent(textNode)) {
    860        // A11y doesn't care about the text rendered by layout if there is CSS
    861        // content alt text. We skip this here rather than when the update is
    862        // queued because the TextLeafAccessible might not exist yet and we
    863        // might need to create it below.
    864        continue;
    865      }
    866 
    867      TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
    868      continue;
    869    }
    870 
    871    // Append an accessible if rendered text is not empty.
    872    if (!text.mString.IsEmpty()) {
    873 #ifdef A11Y_LOG
    874      if (logging::IsEnabled(logging::eTree | logging::eText)) {
    875        logging::MsgBegin("TREE", "text node gains new content; doc: %p",
    876                          mDocument);
    877        logging::Node("container", containerElm);
    878        logging::Node("content", textNode);
    879        logging::MsgEnd();
    880      }
    881 #endif
    882 
    883      MOZ_ASSERT(mDocument->AccessibleOrTrueContainer(containerNode),
    884                 "Text node having rendered text hasn't accessible document!");
    885 
    886      LocalAccessible* container =
    887          mDocument->AccessibleOrTrueContainer(containerNode, true);
    888      if (container) {
    889        nsTArray<nsCOMPtr<nsIContent>>* list =
    890            mContentInsertions.GetOrInsertNew(container);
    891        list->AppendElement(textNode);
    892      }
    893    }
    894  }
    895  textHash.Clear();
    896  mTextArray.Clear();
    897 
    898  // Process content inserted notifications to update the tree.
    899  // Processing an insertion can indirectly run script (e.g. querying a XUL
    900  // interface), which might result in another insertion being queued.
    901  // We don't want to lose any queued insertions if this happens. Therefore, we
    902  // move the current insertions into a temporary data structure and process
    903  // them from there. Any insertions queued during processing will get handled
    904  // in subsequent refresh driver ticks.
    905  const auto contentInsertions = std::move(mContentInsertions);
    906  for (const auto& entry : contentInsertions) {
    907    mDocument->ProcessContentInserted(entry.GetKey(), entry.GetData().get());
    908    if (!mDocument) {
    909      return;
    910    }
    911  }
    912 
    913  // Bind hanging child documents unless we are using IPC and the
    914  // document has no IPC actor.  If we fail to bind the child doc then
    915  // shut it down.
    916  uint32_t hangingDocCnt = mHangingChildDocuments.Length();
    917  nsTArray<RefPtr<DocAccessible>> newChildDocs;
    918  for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
    919    DocAccessible* childDoc = mHangingChildDocuments[idx];
    920    if (childDoc->IsDefunct()) {
    921      continue;
    922    }
    923 
    924    if (IPCAccessibilityActive() && !mDocument->IPCDoc()) {
    925      childDoc->Shutdown();
    926      continue;
    927    }
    928 
    929    nsIContent* ownerContent = childDoc->DocumentNode()->GetEmbedderElement();
    930    if (ownerContent) {
    931      LocalAccessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
    932      if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
    933        if (mDocument->AppendChildDocument(childDoc)) {
    934          newChildDocs.AppendElement(std::move(mHangingChildDocuments[idx]));
    935          continue;
    936        }
    937 
    938        outerDocAcc->RemoveChild(childDoc);
    939      }
    940 
    941      // Failed to bind the child document, destroy it.
    942      childDoc->Shutdown();
    943    }
    944  }
    945 
    946  // Clear the hanging documents list, even if we didn't bind them.
    947  mHangingChildDocuments.Clear();
    948  MOZ_ASSERT(mDocument, "Illicit document shutdown");
    949  if (!mDocument) {
    950    return;
    951  }
    952 
    953  // If the document is ready and all its subdocuments are completely loaded
    954  // then process the document load.
    955  if (mDocument->HasLoadState(DocAccessible::eReady) &&
    956      !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
    957      hangingDocCnt == 0) {
    958    uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
    959    for (; childDocIdx < childDocCnt; childDocIdx++) {
    960      DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
    961      if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) {
    962        break;
    963      }
    964    }
    965 
    966    if (childDocIdx == childDocCnt) {
    967      mDocument->ProcessLoad();
    968      if (!mDocument) {
    969        return;
    970      }
    971    }
    972  }
    973 
    974  // Process invalidation list of the document after all accessible tree
    975  // mutation is done.
    976  mDocument->ProcessInvalidationList();
    977 
    978  // Process relocation list.
    979  for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
    980    // owner should be in a document and have na associated DOM node (docs
    981    // sometimes don't)
    982    if (mRelocations[idx]->IsInDocument() &&
    983        mRelocations[idx]->HasOwnContent()) {
    984      mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
    985    }
    986  }
    987  mRelocations.Clear();
    988 
    989  // Process only currently queued generic notifications.
    990  // These are used for processing aria-activedescendant, DOMMenuItemActive,
    991  // etc. Therefore, they must be processed after relocations, since relocated
    992  // subtrees might not have been created before relocation processing and the
    993  // target might be inside a relocated subtree.
    994  const nsTArray<RefPtr<Notification>> notifications =
    995      std::move(mNotifications);
    996 
    997  uint32_t notificationCount = notifications.Length();
    998  for (uint32_t idx = 0; idx < notificationCount; idx++) {
    999    notifications[idx]->Process();
   1000    if (!mDocument) {
   1001      return;
   1002    }
   1003  }
   1004 
   1005  if (AppShutdown::IsShutdownImpending()) {
   1006    return;
   1007  }
   1008 
   1009  // If a generic notification occurs after this point then we may be allowed to
   1010  // process it synchronously.  However we do not want to reenter if fireing
   1011  // events causes script to run.
   1012  mObservingState = eRefreshProcessing;
   1013 
   1014  mDocument->SendAccessiblesWillMove();
   1015 
   1016  // Send any queued cache updates before we fire any mutation events so the
   1017  // cache is up to date when mutation events are fired. We do this after
   1018  // insertions (but not their events) so that cache updates dependent on the
   1019  // tree work correctly; e.g. line start calculation.
   1020  if (IPCAccessibilityActive() && mDocument) {
   1021    mDocument->ProcessQueuedCacheUpdates();
   1022  }
   1023 
   1024  CoalesceMutationEvents();
   1025  ProcessMutationEvents();
   1026 
   1027  // ProcessMutationEvents for content process documents merely queues mutation
   1028  // events. Send those events in a batch now if applicable.
   1029  if (mDocument && mDocument->IPCDoc()) {
   1030    mDocument->IPCDoc()->SendQueuedMutationEvents();
   1031  }
   1032 
   1033  // When firing mutation events, mObservingState is set to
   1034  // eRefreshProcessing. Any calls to ScheduleProcessing() that
   1035  // occur before mObservingState is reset will be dropped because we only
   1036  // schedule a tick if mObservingState == eNotObservingRefresh.
   1037  // This sometimes results in our viewport cache being out-of-date after
   1038  // processing mutation events. Call ProcessQueuedCacheUpdates again to
   1039  // ensure it is updated.
   1040  if (IPCAccessibilityActive() && mDocument) {
   1041    mDocument->ProcessQueuedCacheUpdates();
   1042  }
   1043 
   1044  if (mDocument) {
   1045    mDocument->ClearMutationData();
   1046  }
   1047 
   1048  if (AppShutdown::IsShutdownImpending()) {
   1049    return;
   1050  }
   1051 
   1052  ProcessEventQueue();
   1053  if (mDocument) {
   1054    // Process an anchor jump (if any) now that the tree and focus are up to
   1055    // date.
   1056    mDocument->ProcessAnchorJump();
   1057  }
   1058 
   1059  if (mDocument && mDocument->IPCDoc()) {
   1060    // There should not be any more mutation events in the mutation event queue.
   1061    // ProcessEventQueue should have sent all of them.
   1062    MOZ_ASSERT(mDocument->IPCDoc()->MutationEventQueueLength() == 0,
   1063               "Mutation event queue is non-empty.");
   1064    if (mDocument->IPCDoc()->HasUnackedMutationEvents()) {
   1065      // Now that all mutation events have been sent, request an ACK from the
   1066      // parent process. This request will be after the mutation events in the
   1067      // IPDL queue, so the parent process will respond once it has finished
   1068      // handling all the mutation events.
   1069      (void)mDocument->IPCDoc()->SendRequestAckMutationEvents();
   1070    }
   1071  }
   1072 
   1073  if (IPCAccessibilityActive()) {
   1074    size_t newDocCount = newChildDocs.Length();
   1075    for (size_t i = 0; i < newDocCount; i++) {
   1076      DocAccessible* childDoc = newChildDocs[i];
   1077      if (childDoc->IsDefunct()) {
   1078        continue;
   1079      }
   1080 
   1081      LocalAccessible* parent = childDoc->LocalParent();
   1082      DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
   1083      MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc);
   1084      uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
   1085      MOZ_DIAGNOSTIC_ASSERT(id);
   1086      DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
   1087      if (ipcDoc) {
   1088        parentIPCDoc->SendBindChildDoc(WrapNotNull(ipcDoc), id);
   1089        continue;
   1090      }
   1091 
   1092      ipcDoc = new DocAccessibleChild(childDoc, parentIPCDoc->Manager());
   1093      childDoc->SetIPCDoc(ipcDoc);
   1094 
   1095      nsCOMPtr<nsIBrowserChild> browserChild =
   1096          do_GetInterface(mDocument->DocumentNode()->GetDocShell());
   1097      if (browserChild) {
   1098        static_cast<BrowserChild*>(browserChild.get())
   1099            ->SendPDocAccessibleConstructor(
   1100                ipcDoc, parentIPCDoc, id,
   1101                childDoc->DocumentNode()->GetBrowsingContext());
   1102      }
   1103    }
   1104  }
   1105 
   1106  if (!mDocument) {
   1107    // A null mDocument means we've gotten a Shutdown() call (presumably via
   1108    // some script that we triggered above), and that means we're done here.
   1109    // Note: in this case, it's important that don't modify mObservingState;
   1110    // Shutdown() will have *unregistered* us as a refresh observer, and we
   1111    // don't want to mistakenly overwrite mObservingState and fool ourselves
   1112    // into thinking we've re-registered when we really haven't!
   1113    MOZ_ASSERT(mObservingState == eNotObservingRefresh,
   1114               "We've been shutdown, which means we should've been "
   1115               "unregistered as a refresh observer");
   1116    return;
   1117  }
   1118  mObservingState = eRefreshObserving;
   1119 
   1120  // Stop further processing if there are no new notifications of any kind or
   1121  // events and document load is processed.
   1122  if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() &&
   1123      !mFocusEvent && mEvents.IsEmpty() && mTextArray.IsEmpty() &&
   1124      mHangingChildDocuments.IsEmpty() &&
   1125      mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
   1126      mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
   1127    mObservingState = eNotObservingRefresh;
   1128  }
   1129 }
   1130 
   1131 void NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent) {
   1132  EventType type = GetEventType(aEvent);
   1133  uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
   1134  MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
   1135  addr |= type;
   1136  mTable.InsertOrUpdate(addr, RefPtr{aEvent});
   1137 }
   1138 
   1139 AccTreeMutationEvent* NotificationController::EventMap::GetEvent(
   1140    LocalAccessible* aTarget, EventType aType) {
   1141  uint64_t addr = reinterpret_cast<uintptr_t>(aTarget);
   1142  MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned");
   1143 
   1144  addr |= aType;
   1145  return mTable.GetWeak(addr);
   1146 }
   1147 
   1148 void NotificationController::EventMap::RemoveEvent(
   1149    AccTreeMutationEvent* aEvent) {
   1150  EventType type = GetEventType(aEvent);
   1151  uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
   1152  MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
   1153  addr |= type;
   1154 
   1155  MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
   1156  mTable.Remove(addr);
   1157 }
   1158 
   1159 NotificationController::EventMap::EventType
   1160 NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent) {
   1161  switch (aEvent->GetEventType()) {
   1162    case nsIAccessibleEvent::EVENT_SHOW:
   1163      return ShowEvent;
   1164    case nsIAccessibleEvent::EVENT_HIDE:
   1165      return HideEvent;
   1166    case nsIAccessibleEvent::EVENT_REORDER:
   1167    case nsIAccessibleEvent::EVENT_INNER_REORDER:
   1168      return ReorderEvent;
   1169    default:
   1170      MOZ_ASSERT_UNREACHABLE("event has invalid type");
   1171      return ShowEvent;
   1172  }
   1173 }