forked from mirrors/gecko-dev
		
	Previously, if a hidden, aria-owned subtree was shown and aria-activedescendant was simultaneously targeted inside it, aria-activedescendant would fail. This occurred because when we processed insertions, the presence of aria-owns meant we didn't create the subtree. This meant that when we processed aria-activedescendant (which occurred before relocations), the active descendant didn't exist yet. To fix this, we now process generic notifications (including aria-activedescendant) *after* relocations. Differential Revision: https://phabricator.services.mozilla.com/D40579 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			966 lines
		
	
	
	
		
			33 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			966 lines
		
	
	
	
		
			33 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "NotificationController.h"
 | 
						|
 | 
						|
#include "DocAccessible-inl.h"
 | 
						|
#include "DocAccessibleChild.h"
 | 
						|
#include "nsEventShell.h"
 | 
						|
#include "TextLeafAccessible.h"
 | 
						|
#include "TextUpdater.h"
 | 
						|
 | 
						|
#include "mozilla/dom/BrowserChild.h"
 | 
						|
#include "mozilla/dom/Element.h"
 | 
						|
#include "mozilla/PresShell.h"
 | 
						|
#include "mozilla/Telemetry.h"
 | 
						|
 | 
						|
using namespace mozilla;
 | 
						|
using namespace mozilla::a11y;
 | 
						|
using namespace mozilla::dom;
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
// NotificationCollector
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
NotificationController::NotificationController(DocAccessible* aDocument,
 | 
						|
                                               PresShell* aPresShell)
 | 
						|
    : EventQueue(aDocument),
 | 
						|
      mObservingState(eNotObservingRefresh),
 | 
						|
      mPresShell(aPresShell),
 | 
						|
      mEventGeneration(0) {
 | 
						|
#ifdef DEBUG
 | 
						|
  mMoveGuardOnStack = false;
 | 
						|
#endif
 | 
						|
 | 
						|
  // Schedule initial accessible tree construction.
 | 
						|
  ScheduleProcessing();
 | 
						|
}
 | 
						|
 | 
						|
NotificationController::~NotificationController() {
 | 
						|
  NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
 | 
						|
  if (mDocument) Shutdown();
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
// NotificationCollector: AddRef/Release and cycle collection
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
 | 
						|
NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
 | 
						|
  if (tmp->mDocument) tmp->Shutdown();
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
 | 
						|
  for (auto it = tmp->mContentInsertions.ConstIter(); !it.Done(); it.Next()) {
 | 
						|
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key");
 | 
						|
    cb.NoteXPCOMChild(it.Key());
 | 
						|
    nsTArray<nsCOMPtr<nsIContent>>* list = it.UserData();
 | 
						|
    for (uint32_t i = 0; i < list->Length(); i++) {
 | 
						|
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions value item");
 | 
						|
      cb.NoteXPCOMChild(list->ElementAt(i));
 | 
						|
    }
 | 
						|
  }
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
// NotificationCollector: public
 | 
						|
 | 
						|
void NotificationController::Shutdown() {
 | 
						|
  if (mObservingState != eNotObservingRefresh &&
 | 
						|
      mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
 | 
						|
    mObservingState = eNotObservingRefresh;
 | 
						|
  }
 | 
						|
 | 
						|
  // Shutdown handling child documents.
 | 
						|
  int32_t childDocCount = mHangingChildDocuments.Length();
 | 
						|
  for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
 | 
						|
    if (!mHangingChildDocuments[idx]->IsDefunct())
 | 
						|
      mHangingChildDocuments[idx]->Shutdown();
 | 
						|
  }
 | 
						|
 | 
						|
  mHangingChildDocuments.Clear();
 | 
						|
 | 
						|
  mDocument = nullptr;
 | 
						|
  mPresShell = nullptr;
 | 
						|
 | 
						|
  mTextHash.Clear();
 | 
						|
  mContentInsertions.Clear();
 | 
						|
  mNotifications.Clear();
 | 
						|
  mEvents.Clear();
 | 
						|
  mRelocations.Clear();
 | 
						|
  mEventTree.Clear();
 | 
						|
}
 | 
						|
 | 
						|
EventTree* NotificationController::QueueMutation(Accessible* aContainer) {
 | 
						|
  EventTree* tree = mEventTree.FindOrInsert(aContainer);
 | 
						|
  if (tree) {
 | 
						|
    ScheduleProcessing();
 | 
						|
  }
 | 
						|
  return tree;
 | 
						|
}
 | 
						|
 | 
						|
bool NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent) {
 | 
						|
  // We have to allow there to be a hide and then a show event for a target
 | 
						|
  // because of targets getting moved.  However we need to coalesce a show and
 | 
						|
  // then a hide for a target which means we need to check for that here.
 | 
						|
  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
 | 
						|
      aEvent->GetAccessible()->ShowEventTarget()) {
 | 
						|
    AccTreeMutationEvent* showEvent =
 | 
						|
        mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
 | 
						|
    DropMutationEvent(showEvent);
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
 | 
						|
  mEventGeneration++;
 | 
						|
  mutEvent->SetEventGeneration(mEventGeneration);
 | 
						|
 | 
						|
  if (!mFirstMutationEvent) {
 | 
						|
    mFirstMutationEvent = aEvent;
 | 
						|
    ScheduleProcessing();
 | 
						|
  }
 | 
						|
 | 
						|
  if (mLastMutationEvent) {
 | 
						|
    NS_ASSERTION(!mLastMutationEvent->NextEvent(),
 | 
						|
                 "why isn't the last event the end?");
 | 
						|
    mLastMutationEvent->SetNextEvent(aEvent);
 | 
						|
  }
 | 
						|
 | 
						|
  aEvent->SetPrevEvent(mLastMutationEvent);
 | 
						|
  mLastMutationEvent = aEvent;
 | 
						|
  mMutationMap.PutEvent(aEvent);
 | 
						|
 | 
						|
  // Because we could be hiding the target of a show event we need to get rid
 | 
						|
  // of any such events.  It may be possible to do less than coallesce all
 | 
						|
  // events, however that is easiest.
 | 
						|
  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
 | 
						|
    CoalesceMutationEvents();
 | 
						|
 | 
						|
    // mLastMutationEvent will point to something other than aEvent if and only
 | 
						|
    // if aEvent was just coalesced away.  In that case a parent accessible
 | 
						|
    // must already have the required reorder and text change events so we are
 | 
						|
    // done here.
 | 
						|
    if (mLastMutationEvent != aEvent) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // We need to fire a reorder event after all of the events targeted at shown
 | 
						|
  // or hidden children of a container.  So either queue a new one, or move an
 | 
						|
  // existing one to the end of the queue if the container already has a
 | 
						|
  // reorder event.
 | 
						|
  Accessible* target = aEvent->GetAccessible();
 | 
						|
  Accessible* container = aEvent->GetAccessible()->Parent();
 | 
						|
  RefPtr<AccReorderEvent> reorder;
 | 
						|
  if (!container->ReorderEventTarget()) {
 | 
						|
    reorder = new AccReorderEvent(container);
 | 
						|
    container->SetReorderEventTarget(true);
 | 
						|
    mMutationMap.PutEvent(reorder);
 | 
						|
 | 
						|
    // Since this is the first child of container that is changing, the name of
 | 
						|
    // container may be changing.
 | 
						|
    QueueNameChange(target);
 | 
						|
  } else {
 | 
						|
    AccReorderEvent* event = downcast_accEvent(
 | 
						|
        mMutationMap.GetEvent(container, EventMap::ReorderEvent));
 | 
						|
    reorder = event;
 | 
						|
    if (mFirstMutationEvent == event) {
 | 
						|
      mFirstMutationEvent = event->NextEvent();
 | 
						|
    } else {
 | 
						|
      event->PrevEvent()->SetNextEvent(event->NextEvent());
 | 
						|
    }
 | 
						|
 | 
						|
    event->NextEvent()->SetPrevEvent(event->PrevEvent());
 | 
						|
    event->SetNextEvent(nullptr);
 | 
						|
  }
 | 
						|
 | 
						|
  reorder->SetEventGeneration(mEventGeneration);
 | 
						|
  reorder->SetPrevEvent(mLastMutationEvent);
 | 
						|
  mLastMutationEvent->SetNextEvent(reorder);
 | 
						|
  mLastMutationEvent = reorder;
 | 
						|
 | 
						|
  // It is not possible to have a text change event for something other than a
 | 
						|
  // hyper text accessible.
 | 
						|
  if (!container->IsHyperText()) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(mutEvent);
 | 
						|
 | 
						|
  nsString text;
 | 
						|
  aEvent->GetAccessible()->AppendTextTo(text);
 | 
						|
  if (text.IsEmpty()) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  int32_t offset = container->AsHyperText()->GetChildOffset(target);
 | 
						|
  AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
 | 
						|
  while (prevEvent &&
 | 
						|
         prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
 | 
						|
    prevEvent = prevEvent->PrevEvent();
 | 
						|
  }
 | 
						|
 | 
						|
  if (prevEvent &&
 | 
						|
      prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
 | 
						|
      mutEvent->IsHide()) {
 | 
						|
    AccHideEvent* prevHide = downcast_accEvent(prevEvent);
 | 
						|
    AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent;
 | 
						|
    if (prevTextChange && prevHide->Parent() == mutEvent->Parent()) {
 | 
						|
      if (prevHide->mNextSibling == target) {
 | 
						|
        target->AppendTextTo(prevTextChange->mModifiedText);
 | 
						|
        prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
 | 
						|
      } else if (prevHide->mPrevSibling == target) {
 | 
						|
        nsString temp;
 | 
						|
        target->AppendTextTo(temp);
 | 
						|
 | 
						|
        uint32_t extraLen = temp.Length();
 | 
						|
        temp += prevTextChange->mModifiedText;
 | 
						|
        ;
 | 
						|
        prevTextChange->mModifiedText = temp;
 | 
						|
        prevTextChange->mStart -= extraLen;
 | 
						|
        prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  } else if (prevEvent && mutEvent->IsShow() &&
 | 
						|
             prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
 | 
						|
    AccShowEvent* prevShow = downcast_accEvent(prevEvent);
 | 
						|
    AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent;
 | 
						|
    if (prevTextChange && prevShow->Parent() == target->Parent()) {
 | 
						|
      int32_t index = target->IndexInParent();
 | 
						|
      int32_t prevIndex = prevShow->GetAccessible()->IndexInParent();
 | 
						|
      if (prevIndex + 1 == index) {
 | 
						|
        target->AppendTextTo(prevTextChange->mModifiedText);
 | 
						|
        prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
 | 
						|
      } else if (index + 1 == prevIndex) {
 | 
						|
        nsString temp;
 | 
						|
        target->AppendTextTo(temp);
 | 
						|
        prevTextChange->mStart -= temp.Length();
 | 
						|
        temp += prevTextChange->mModifiedText;
 | 
						|
        prevTextChange->mModifiedText = temp;
 | 
						|
        prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mutEvent->mTextChangeEvent) {
 | 
						|
    mutEvent->mTextChangeEvent = new AccTextChangeEvent(
 | 
						|
        container, offset, text, mutEvent->IsShow(),
 | 
						|
        aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent) {
 | 
						|
  // unset the event bits since the event isn't being fired any more.
 | 
						|
  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
 | 
						|
    aEvent->GetAccessible()->SetReorderEventTarget(false);
 | 
						|
  } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
 | 
						|
    aEvent->GetAccessible()->SetShowEventTarget(false);
 | 
						|
  } else {
 | 
						|
    aEvent->GetAccessible()->SetHideEventTarget(false);
 | 
						|
 | 
						|
    AccHideEvent* hideEvent = downcast_accEvent(aEvent);
 | 
						|
    MOZ_ASSERT(hideEvent);
 | 
						|
 | 
						|
    if (hideEvent->NeedsShutdown()) {
 | 
						|
      mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Do the work to splice the event out of the list.
 | 
						|
  if (mFirstMutationEvent == aEvent) {
 | 
						|
    mFirstMutationEvent = aEvent->NextEvent();
 | 
						|
  } else {
 | 
						|
    aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
 | 
						|
  }
 | 
						|
 | 
						|
  if (mLastMutationEvent == aEvent) {
 | 
						|
    mLastMutationEvent = aEvent->PrevEvent();
 | 
						|
  } else {
 | 
						|
    aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent());
 | 
						|
  }
 | 
						|
 | 
						|
  aEvent->SetPrevEvent(nullptr);
 | 
						|
  aEvent->SetNextEvent(nullptr);
 | 
						|
  mMutationMap.RemoveEvent(aEvent);
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::CoalesceMutationEvents() {
 | 
						|
  AccTreeMutationEvent* event = mFirstMutationEvent;
 | 
						|
  while (event) {
 | 
						|
    AccTreeMutationEvent* nextEvent = event->NextEvent();
 | 
						|
    uint32_t eventType = event->GetEventType();
 | 
						|
    if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
 | 
						|
      Accessible* acc = event->GetAccessible();
 | 
						|
      while (acc) {
 | 
						|
        if (acc->IsDoc()) {
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        // if a parent of the reorder event's target is being hidden that
 | 
						|
        // hide event's target must have a parent that is also a reorder event
 | 
						|
        // target.  That means we don't need this reorder event.
 | 
						|
        if (acc->HideEventTarget()) {
 | 
						|
          DropMutationEvent(event);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        Accessible* parent = acc->Parent();
 | 
						|
        if (parent->ReorderEventTarget()) {
 | 
						|
          AccReorderEvent* reorder = downcast_accEvent(
 | 
						|
              mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
 | 
						|
 | 
						|
          // We want to make sure that a reorder event comes after any show or
 | 
						|
          // hide events targeted at the children of its target.  We keep the
 | 
						|
          // invariant that event generation goes up as you are farther in the
 | 
						|
          // queue, so we want to use the spot of the event with the higher
 | 
						|
          // generation number, and keep that generation number.
 | 
						|
          if (reorder &&
 | 
						|
              reorder->EventGeneration() < event->EventGeneration()) {
 | 
						|
            reorder->SetEventGeneration(event->EventGeneration());
 | 
						|
 | 
						|
            // It may be true that reorder was before event, and we coalesced
 | 
						|
            // away all the show / hide events between them.  In that case
 | 
						|
            // event is already immediately after reorder in the queue and we
 | 
						|
            // do not need to rearrange the list of events.
 | 
						|
            if (event != reorder->NextEvent()) {
 | 
						|
              // There really should be a show or hide event before the first
 | 
						|
              // reorder event.
 | 
						|
              if (reorder->PrevEvent()) {
 | 
						|
                reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
 | 
						|
              } else {
 | 
						|
                mFirstMutationEvent = reorder->NextEvent();
 | 
						|
              }
 | 
						|
 | 
						|
              reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
 | 
						|
              event->PrevEvent()->SetNextEvent(reorder);
 | 
						|
              reorder->SetPrevEvent(event->PrevEvent());
 | 
						|
              event->SetPrevEvent(reorder);
 | 
						|
              reorder->SetNextEvent(event);
 | 
						|
            }
 | 
						|
          }
 | 
						|
          DropMutationEvent(event);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        acc = parent;
 | 
						|
      }
 | 
						|
    } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
 | 
						|
      Accessible* parent = event->GetAccessible()->Parent();
 | 
						|
      while (parent) {
 | 
						|
        if (parent->IsDoc()) {
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        // if the parent of a show event is being either shown or hidden then
 | 
						|
        // we don't need to fire a show event for a subtree of that change.
 | 
						|
        if (parent->ShowEventTarget() || parent->HideEventTarget()) {
 | 
						|
          DropMutationEvent(event);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        parent = parent->Parent();
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE,
 | 
						|
                 "mutation event list has an invalid event");
 | 
						|
 | 
						|
      AccHideEvent* hideEvent = downcast_accEvent(event);
 | 
						|
      Accessible* parent = hideEvent->Parent();
 | 
						|
      while (parent) {
 | 
						|
        if (parent->IsDoc()) {
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        if (parent->HideEventTarget()) {
 | 
						|
          DropMutationEvent(event);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        if (parent->ShowEventTarget()) {
 | 
						|
          AccShowEvent* showEvent = downcast_accEvent(
 | 
						|
              mMutationMap.GetEvent(parent, EventMap::ShowEvent));
 | 
						|
          if (showEvent->EventGeneration() < hideEvent->EventGeneration()) {
 | 
						|
            DropMutationEvent(hideEvent);
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        parent = parent->Parent();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    event = nextEvent;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument) {
 | 
						|
  // Schedule child document binding to the tree.
 | 
						|
  mHangingChildDocuments.AppendElement(aDocument);
 | 
						|
  ScheduleProcessing();
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::ScheduleContentInsertion(
 | 
						|
    Accessible* aContainer, nsTArray<nsCOMPtr<nsIContent>>& aInsertions) {
 | 
						|
  if (!aInsertions.IsEmpty()) {
 | 
						|
    mContentInsertions.LookupOrAdd(aContainer)->AppendElements(aInsertions);
 | 
						|
    ScheduleProcessing();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::ScheduleProcessing() {
 | 
						|
  // If notification flush isn't planed yet start notification flush
 | 
						|
  // asynchronously (after style and layout).
 | 
						|
  if (mObservingState == eNotObservingRefresh) {
 | 
						|
    if (mPresShell->AddRefreshObserver(this, FlushType::Display))
 | 
						|
      mObservingState = eRefreshObserving;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
// NotificationCollector: protected
 | 
						|
 | 
						|
bool NotificationController::IsUpdatePending() {
 | 
						|
  return mPresShell->IsLayoutFlushObserver() ||
 | 
						|
         mObservingState == eRefreshProcessingForUpdate || WaitingForParent() ||
 | 
						|
         mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
 | 
						|
         mTextHash.Count() != 0 ||
 | 
						|
         !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
 | 
						|
}
 | 
						|
 | 
						|
bool NotificationController::WaitingForParent() {
 | 
						|
  DocAccessible* parentdoc = mDocument->ParentDocument();
 | 
						|
  if (!parentdoc) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  NotificationController* parent = parentdoc->mNotificationController;
 | 
						|
  if (!parent || parent == this) {
 | 
						|
    // Do not wait for nothing or ourselves
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Wait for parent's notifications processing
 | 
						|
  return parent->mContentInsertions.Count() != 0 ||
 | 
						|
         parent->mNotifications.Length() != 0;
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::ProcessMutationEvents() {
 | 
						|
  // there is no reason to fire a hide event for a child of a show event
 | 
						|
  // target.  That can happen if something is inserted into the tree and
 | 
						|
  // removed before the next refresh driver tick, but it should not be
 | 
						|
  // observable outside gecko so it should be safe to coalesce away any such
 | 
						|
  // events.  This means that it should be fine to fire all of the hide events
 | 
						|
  // first, and then deal with any shown subtrees.
 | 
						|
  for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
 | 
						|
       event = event->NextEvent()) {
 | 
						|
    if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    nsEventShell::FireEvent(event);
 | 
						|
    if (!mDocument) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    AccMutationEvent* mutEvent = downcast_accEvent(event);
 | 
						|
    if (mutEvent->mTextChangeEvent) {
 | 
						|
      nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
 | 
						|
      if (!mDocument) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Fire menupopup end event before a hide event if a menu goes away.
 | 
						|
 | 
						|
    // XXX: We don't look into children of hidden subtree to find hiding
 | 
						|
    // menupopup (as we did prior bug 570275) because we don't do that when
 | 
						|
    // menu is showing (and that's impossible until bug 606924 is fixed).
 | 
						|
    // Nevertheless we should do this at least because layout coalesces
 | 
						|
    // the changes before our processing and we may miss some menupopup
 | 
						|
    // events. Now we just want to be consistent in content insertion/removal
 | 
						|
    // handling.
 | 
						|
    if (event->mAccessible->ARIARole() == roles::MENUPOPUP) {
 | 
						|
      nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
 | 
						|
                              event->mAccessible);
 | 
						|
      if (!mDocument) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    AccHideEvent* hideEvent = downcast_accEvent(event);
 | 
						|
    if (hideEvent->NeedsShutdown()) {
 | 
						|
      mDocument->ShutdownChildrenInSubtree(event->mAccessible);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Group the show events by the parent of their target.
 | 
						|
  nsDataHashtable<nsPtrHashKey<Accessible>, nsTArray<AccTreeMutationEvent*>>
 | 
						|
      showEvents;
 | 
						|
  for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
 | 
						|
       event = event->NextEvent()) {
 | 
						|
    if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    Accessible* parent = event->GetAccessible()->Parent();
 | 
						|
    showEvents.GetOrInsert(parent).AppendElement(event);
 | 
						|
  }
 | 
						|
 | 
						|
  // We need to fire show events for the children of an accessible in the order
 | 
						|
  // of their indices at this point.  So sort each set of events for the same
 | 
						|
  // container by the index of their target.
 | 
						|
  for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
 | 
						|
    struct AccIdxComparator {
 | 
						|
      bool LessThan(const AccTreeMutationEvent* a,
 | 
						|
                    const AccTreeMutationEvent* b) const {
 | 
						|
        int32_t aIdx = a->GetAccessible()->IndexInParent();
 | 
						|
        int32_t bIdx = b->GetAccessible()->IndexInParent();
 | 
						|
        MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
 | 
						|
        return aIdx < bIdx;
 | 
						|
      }
 | 
						|
      bool Equals(const AccTreeMutationEvent* a,
 | 
						|
                  const AccTreeMutationEvent* b) const {
 | 
						|
        DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent();
 | 
						|
        DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent();
 | 
						|
        MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    nsTArray<AccTreeMutationEvent*>& events = iter.Data();
 | 
						|
    events.Sort(AccIdxComparator());
 | 
						|
    for (AccTreeMutationEvent* event : events) {
 | 
						|
      nsEventShell::FireEvent(event);
 | 
						|
      if (!mDocument) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      AccMutationEvent* mutEvent = downcast_accEvent(event);
 | 
						|
      if (mutEvent->mTextChangeEvent) {
 | 
						|
        nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
 | 
						|
        if (!mDocument) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Now we can fire the reorder events after all the show and hide events.
 | 
						|
  for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
 | 
						|
       event = event->NextEvent()) {
 | 
						|
    if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    nsEventShell::FireEvent(event);
 | 
						|
    if (!mDocument) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    Accessible* target = event->GetAccessible();
 | 
						|
    target->Document()->MaybeNotifyOfValueChange(target);
 | 
						|
    if (!mDocument) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////////////////////////
 | 
						|
// NotificationCollector: private
 | 
						|
 | 
						|
void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
 | 
						|
  Telemetry::AutoTimer<Telemetry::A11Y_TREE_UPDATE_TIMING_MS> timer;
 | 
						|
 | 
						|
  AUTO_PROFILER_LABEL("NotificationController::WillRefresh", OTHER);
 | 
						|
 | 
						|
  // If the document accessible that notification collector was created for is
 | 
						|
  // now shut down, don't process notifications anymore.
 | 
						|
  NS_ASSERTION(
 | 
						|
      mDocument,
 | 
						|
      "The document was shut down while refresh observer is attached!");
 | 
						|
  if (!mDocument) return;
 | 
						|
 | 
						|
  // Wait until an update, we have started, or an interruptible reflow is
 | 
						|
  // finished.
 | 
						|
  if (mObservingState == eRefreshProcessing ||
 | 
						|
      mObservingState == eRefreshProcessingForUpdate ||
 | 
						|
      mPresShell->IsReflowInterrupted()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Process parent's notifications before ours, to get proper ordering between
 | 
						|
  // e.g. tab event and content event.
 | 
						|
  if (WaitingForParent()) {
 | 
						|
    mDocument->ParentDocument()->mNotificationController->WillRefresh(aTime);
 | 
						|
    if (!mDocument) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Any generic notifications should be queued if we're processing content
 | 
						|
  // insertions or generic notifications.
 | 
						|
  mObservingState = eRefreshProcessingForUpdate;
 | 
						|
 | 
						|
  // Initial accessible tree construction.
 | 
						|
  if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
 | 
						|
    // If document is not bound to parent at this point then the document is not
 | 
						|
    // ready yet (process notifications later).
 | 
						|
    if (!mDocument->IsBoundToParent()) {
 | 
						|
      mObservingState = eRefreshObserving;
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
#ifdef A11Y_LOG
 | 
						|
    if (logging::IsEnabled(logging::eTree)) {
 | 
						|
      logging::MsgBegin("TREE", "initial tree created");
 | 
						|
      logging::Address("document", mDocument);
 | 
						|
      logging::MsgEnd();
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    mDocument->DoInitialUpdate();
 | 
						|
 | 
						|
    NS_ASSERTION(mContentInsertions.Count() == 0,
 | 
						|
                 "Pending content insertions while initial accessible tree "
 | 
						|
                 "isn't created!");
 | 
						|
  }
 | 
						|
 | 
						|
  // Initialize scroll support if needed.
 | 
						|
  if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
 | 
						|
    mDocument->AddScrollListener();
 | 
						|
 | 
						|
  // Process rendered text change notifications.
 | 
						|
  for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
 | 
						|
    nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
 | 
						|
    nsIContent* textNode = entry->GetKey();
 | 
						|
    Accessible* textAcc = mDocument->GetAccessible(textNode);
 | 
						|
 | 
						|
    // If the text node is not in tree or doesn't have a frame, or placed in
 | 
						|
    // another document, then this case should have been handled already by
 | 
						|
    // content removal notifications.
 | 
						|
    nsINode* containerNode = textNode->GetFlattenedTreeParentNode();
 | 
						|
    if (!containerNode || textNode->OwnerDoc() != mDocument->DocumentNode()) {
 | 
						|
      MOZ_ASSERT(!textAcc,
 | 
						|
                 "Text node was removed but accessible is kept alive!");
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    nsIFrame* textFrame = textNode->GetPrimaryFrame();
 | 
						|
    if (!textFrame) {
 | 
						|
      MOZ_ASSERT(!textAcc,
 | 
						|
                 "Text node isn't rendered but accessible is kept alive!");
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
#ifdef A11Y_LOG
 | 
						|
    nsIContent* containerElm =
 | 
						|
        containerNode->IsElement() ? containerNode->AsElement() : nullptr;
 | 
						|
#endif
 | 
						|
 | 
						|
    nsIFrame::RenderedText text = textFrame->GetRenderedText(
 | 
						|
        0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
 | 
						|
        nsIFrame::TrailingWhitespace::DontTrim);
 | 
						|
 | 
						|
    // Remove text accessible if rendered text is empty.
 | 
						|
    if (textAcc) {
 | 
						|
      if (text.mString.IsEmpty()) {
 | 
						|
#ifdef A11Y_LOG
 | 
						|
        if (logging::IsEnabled(logging::eTree | logging::eText)) {
 | 
						|
          logging::MsgBegin("TREE", "text node lost its content; doc: %p",
 | 
						|
                            mDocument);
 | 
						|
          logging::Node("container", containerElm);
 | 
						|
          logging::Node("content", textNode);
 | 
						|
          logging::MsgEnd();
 | 
						|
        }
 | 
						|
#endif
 | 
						|
 | 
						|
        mDocument->ContentRemoved(textAcc);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      // Update text of the accessible and fire text change events.
 | 
						|
#ifdef A11Y_LOG
 | 
						|
      if (logging::IsEnabled(logging::eText)) {
 | 
						|
        logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
 | 
						|
        logging::Node("container", containerElm);
 | 
						|
        logging::Node("content", textNode);
 | 
						|
        logging::MsgEntry(
 | 
						|
            "old text '%s'",
 | 
						|
            NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
 | 
						|
        logging::MsgEntry("new text: '%s'",
 | 
						|
                          NS_ConvertUTF16toUTF8(text.mString).get());
 | 
						|
        logging::MsgEnd();
 | 
						|
      }
 | 
						|
#endif
 | 
						|
 | 
						|
      TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Append an accessible if rendered text is not empty.
 | 
						|
    if (!text.mString.IsEmpty()) {
 | 
						|
#ifdef A11Y_LOG
 | 
						|
      if (logging::IsEnabled(logging::eTree | logging::eText)) {
 | 
						|
        logging::MsgBegin("TREE", "text node gains new content; doc: %p",
 | 
						|
                          mDocument);
 | 
						|
        logging::Node("container", containerElm);
 | 
						|
        logging::Node("content", textNode);
 | 
						|
        logging::MsgEnd();
 | 
						|
      }
 | 
						|
#endif
 | 
						|
 | 
						|
      MOZ_ASSERT(mDocument->AccessibleOrTrueContainer(containerNode),
 | 
						|
                 "Text node having rendered text hasn't accessible document!");
 | 
						|
 | 
						|
      Accessible* container =
 | 
						|
          mDocument->AccessibleOrTrueContainer(containerNode, true);
 | 
						|
      if (container) {
 | 
						|
        nsTArray<nsCOMPtr<nsIContent>>* list =
 | 
						|
            mContentInsertions.LookupOrAdd(container);
 | 
						|
        list->AppendElement(textNode);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  mTextHash.Clear();
 | 
						|
 | 
						|
  // Process content inserted notifications to update the tree.
 | 
						|
  for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
 | 
						|
    mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
 | 
						|
    if (!mDocument) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  mContentInsertions.Clear();
 | 
						|
 | 
						|
  // Bind hanging child documents unless we are using IPC and the
 | 
						|
  // document has no IPC actor.  If we fail to bind the child doc then
 | 
						|
  // shut it down.
 | 
						|
  uint32_t hangingDocCnt = mHangingChildDocuments.Length();
 | 
						|
  nsTArray<RefPtr<DocAccessible>> newChildDocs;
 | 
						|
  for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
 | 
						|
    DocAccessible* childDoc = mHangingChildDocuments[idx];
 | 
						|
    if (childDoc->IsDefunct()) continue;
 | 
						|
 | 
						|
    if (IPCAccessibilityActive() && !mDocument->IPCDoc()) {
 | 
						|
      childDoc->Shutdown();
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    nsIContent* ownerContent =
 | 
						|
        mDocument->DocumentNode()->FindContentForSubDocument(
 | 
						|
            childDoc->DocumentNode());
 | 
						|
    if (ownerContent) {
 | 
						|
      Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
 | 
						|
      if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
 | 
						|
        if (mDocument->AppendChildDocument(childDoc)) {
 | 
						|
          newChildDocs.AppendElement(std::move(mHangingChildDocuments[idx]));
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        outerDocAcc->RemoveChild(childDoc);
 | 
						|
      }
 | 
						|
 | 
						|
      // Failed to bind the child document, destroy it.
 | 
						|
      childDoc->Shutdown();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Clear the hanging documents list, even if we didn't bind them.
 | 
						|
  mHangingChildDocuments.Clear();
 | 
						|
  MOZ_ASSERT(mDocument, "Illicit document shutdown");
 | 
						|
  if (!mDocument) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // If the document is ready and all its subdocuments are completely loaded
 | 
						|
  // then process the document load.
 | 
						|
  if (mDocument->HasLoadState(DocAccessible::eReady) &&
 | 
						|
      !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
 | 
						|
      hangingDocCnt == 0) {
 | 
						|
    uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
 | 
						|
    for (; childDocIdx < childDocCnt; childDocIdx++) {
 | 
						|
      DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
 | 
						|
      if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) break;
 | 
						|
    }
 | 
						|
 | 
						|
    if (childDocIdx == childDocCnt) {
 | 
						|
      mDocument->ProcessLoad();
 | 
						|
      if (!mDocument) return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Process invalidation list of the document after all accessible tree
 | 
						|
  // mutation is done.
 | 
						|
  mDocument->ProcessInvalidationList();
 | 
						|
 | 
						|
  // Process relocation list.
 | 
						|
  for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
 | 
						|
    // owner should be in a document and have na associated DOM node (docs
 | 
						|
    // sometimes don't)
 | 
						|
    if (mRelocations[idx]->IsInDocument() &&
 | 
						|
        mRelocations[idx]->HasOwnContent()) {
 | 
						|
      mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  mRelocations.Clear();
 | 
						|
 | 
						|
  // Process only currently queued generic notifications.
 | 
						|
  // These are used for processing aria-activedescendant, DOMMenuItemActive,
 | 
						|
  // etc. Therefore, they must be processed after relocations, since relocated
 | 
						|
  // subtrees might not have been created before relocation processing and the
 | 
						|
  // target might be inside a relocated subtree.
 | 
						|
  nsTArray<RefPtr<Notification>> notifications;
 | 
						|
  notifications.SwapElements(mNotifications);
 | 
						|
 | 
						|
  uint32_t notificationCount = notifications.Length();
 | 
						|
  for (uint32_t idx = 0; idx < notificationCount; idx++) {
 | 
						|
    notifications[idx]->Process();
 | 
						|
    if (!mDocument) return;
 | 
						|
  }
 | 
						|
 | 
						|
  // If a generic notification occurs after this point then we may be allowed to
 | 
						|
  // process it synchronously.  However we do not want to reenter if fireing
 | 
						|
  // events causes script to run.
 | 
						|
  mObservingState = eRefreshProcessing;
 | 
						|
 | 
						|
  CoalesceMutationEvents();
 | 
						|
  ProcessMutationEvents();
 | 
						|
  mEventGeneration = 0;
 | 
						|
 | 
						|
  // Now that we are done with them get rid of the events we fired.
 | 
						|
  RefPtr<AccTreeMutationEvent> mutEvent = std::move(mFirstMutationEvent);
 | 
						|
  mLastMutationEvent = nullptr;
 | 
						|
  mFirstMutationEvent = nullptr;
 | 
						|
  while (mutEvent) {
 | 
						|
    RefPtr<AccTreeMutationEvent> nextEvent = mutEvent->NextEvent();
 | 
						|
    Accessible* target = mutEvent->GetAccessible();
 | 
						|
 | 
						|
    // We need to be careful here, while it may seem that we can simply 0 all
 | 
						|
    // the pending event bits that is not true.  Because accessibles may be
 | 
						|
    // reparented they may be the target of both a hide event and a show event
 | 
						|
    // at the same time.
 | 
						|
    if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
 | 
						|
      target->SetShowEventTarget(false);
 | 
						|
    }
 | 
						|
 | 
						|
    if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
 | 
						|
      target->SetHideEventTarget(false);
 | 
						|
    }
 | 
						|
 | 
						|
    // However it is not possible for a reorder event target to also be the
 | 
						|
    // target of a show or hide, so we can just zero that.
 | 
						|
    target->SetReorderEventTarget(false);
 | 
						|
 | 
						|
    mutEvent->SetPrevEvent(nullptr);
 | 
						|
    mutEvent->SetNextEvent(nullptr);
 | 
						|
    mMutationMap.RemoveEvent(mutEvent);
 | 
						|
    mutEvent = nextEvent;
 | 
						|
  }
 | 
						|
 | 
						|
  ProcessEventQueue();
 | 
						|
 | 
						|
  if (IPCAccessibilityActive()) {
 | 
						|
    size_t newDocCount = newChildDocs.Length();
 | 
						|
    for (size_t i = 0; i < newDocCount; i++) {
 | 
						|
      DocAccessible* childDoc = newChildDocs[i];
 | 
						|
      if (childDoc->IsDefunct()) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      Accessible* parent = childDoc->Parent();
 | 
						|
      DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
 | 
						|
      MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc);
 | 
						|
      uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
 | 
						|
      MOZ_DIAGNOSTIC_ASSERT(id);
 | 
						|
      DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
 | 
						|
      if (ipcDoc) {
 | 
						|
        parentIPCDoc->SendBindChildDoc(ipcDoc, id);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      ipcDoc = new DocAccessibleChild(childDoc, parentIPCDoc->Manager());
 | 
						|
      childDoc->SetIPCDoc(ipcDoc);
 | 
						|
 | 
						|
#if defined(XP_WIN)
 | 
						|
      parentIPCDoc->ConstructChildDocInParentProcess(
 | 
						|
          ipcDoc, id, AccessibleWrap::GetChildIDFor(childDoc));
 | 
						|
#else
 | 
						|
      nsCOMPtr<nsIBrowserChild> browserChild =
 | 
						|
          do_GetInterface(mDocument->DocumentNode()->GetDocShell());
 | 
						|
      if (browserChild) {
 | 
						|
        static_cast<BrowserChild*>(browserChild.get())
 | 
						|
            ->SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
 | 
						|
      }
 | 
						|
#endif
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  mObservingState = eRefreshObserving;
 | 
						|
  if (!mDocument) return;
 | 
						|
 | 
						|
  // Stop further processing if there are no new notifications of any kind or
 | 
						|
  // events and document load is processed.
 | 
						|
  if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() &&
 | 
						|
      mEvents.IsEmpty() && mTextHash.Count() == 0 &&
 | 
						|
      mHangingChildDocuments.IsEmpty() &&
 | 
						|
      mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
 | 
						|
      mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
 | 
						|
    mObservingState = eNotObservingRefresh;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent) {
 | 
						|
  EventType type = GetEventType(aEvent);
 | 
						|
  uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
 | 
						|
  MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
 | 
						|
  addr |= type;
 | 
						|
  mTable.Put(addr, aEvent);
 | 
						|
}
 | 
						|
 | 
						|
AccTreeMutationEvent* NotificationController::EventMap::GetEvent(
 | 
						|
    Accessible* aTarget, EventType aType) {
 | 
						|
  uint64_t addr = reinterpret_cast<uintptr_t>(aTarget);
 | 
						|
  MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned");
 | 
						|
 | 
						|
  addr |= aType;
 | 
						|
  return mTable.GetWeak(addr);
 | 
						|
}
 | 
						|
 | 
						|
void NotificationController::EventMap::RemoveEvent(
 | 
						|
    AccTreeMutationEvent* aEvent) {
 | 
						|
  EventType type = GetEventType(aEvent);
 | 
						|
  uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
 | 
						|
  MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
 | 
						|
  addr |= type;
 | 
						|
 | 
						|
  MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
 | 
						|
  mTable.Remove(addr);
 | 
						|
}
 | 
						|
 | 
						|
NotificationController::EventMap::EventType
 | 
						|
NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent) {
 | 
						|
  switch (aEvent->GetEventType()) {
 | 
						|
    case nsIAccessibleEvent::EVENT_SHOW:
 | 
						|
      return ShowEvent;
 | 
						|
    case nsIAccessibleEvent::EVENT_HIDE:
 | 
						|
      return HideEvent;
 | 
						|
    case nsIAccessibleEvent::EVENT_REORDER:
 | 
						|
      return ReorderEvent;
 | 
						|
    default:
 | 
						|
      MOZ_ASSERT_UNREACHABLE("event has invalid type");
 | 
						|
      return ShowEvent;
 | 
						|
  }
 | 
						|
}
 |