summaryrefslogtreecommitdiffstats
path: root/accessible/base/NotificationController.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/base/NotificationController.cpp')
-rw-r--r--accessible/base/NotificationController.cpp947
1 files changed, 947 insertions, 0 deletions
diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp
new file mode 100644
index 000000000..73d364641
--- /dev/null
+++ b/accessible/base/NotificationController.cpp
@@ -0,0 +1,947 @@
+/* -*- 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 "TextLeafAccessible.h"
+#include "TextUpdater.h"
+
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector
+////////////////////////////////////////////////////////////////////////////////
+
+NotificationController::NotificationController(DocAccessible* aDocument,
+ nsIPresShell* 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, Flush_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 {
+ 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,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode)
+{
+ nsTArray<nsCOMPtr<nsIContent>>* list =
+ mContentInsertions.LookupOrAdd(aContainer);
+
+ bool needsProcessing = false;
+ nsIContent* node = aStartChildNode;
+ while (node != aEndChildNode) {
+ // Notification triggers for content insertion even if no content was
+ // actually inserted, check if the given content has a frame to discard
+ // this case early.
+ if (node->GetPrimaryFrame()) {
+ if (list->AppendElement(node))
+ needsProcessing = true;
+ }
+ node = node->GetNextSibling();
+ }
+
+ if (needsProcessing) {
+ 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, Flush_Display))
+ mObservingState = eRefreshObserving;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: protected
+
+bool
+NotificationController::IsUpdatePending()
+{
+ return mPresShell->IsLayoutFlushObserver() ||
+ mObservingState == eRefreshProcessingForUpdate ||
+ mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
+ mTextHash.Count() != 0 ||
+ !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
+}
+
+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)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+ Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer;
+
+ // 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;
+
+ if (mObservingState == eRefreshProcessing ||
+ mObservingState == eRefreshProcessingForUpdate)
+ 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 frame then this case should
+ // have been handled already by content removal notifications.
+ nsINode* containerNode = textNode->GetParentNode();
+ if (!containerNode) {
+ NS_ASSERTION(!textAcc,
+ "Text node was removed but accessible is kept alive!");
+ continue;
+ }
+
+ nsIFrame* textFrame = textNode->GetPrimaryFrame();
+ if (!textFrame) {
+ NS_ASSERTION(!textAcc,
+ "Text node isn't rendered but accessible is kept alive!");
+ continue;
+ }
+
+ nsIContent* containerElm = containerNode->IsElement() ?
+ containerNode->AsElement() : nullptr;
+
+ nsIFrame::RenderedText text = textFrame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+
+ // 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(containerElm, textNode);
+ 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
+
+ Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode);
+ MOZ_ASSERT(container,
+ "Text node having rendered text hasn't accessible document!");
+ 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.
+ 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;
+
+ 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(Move(mHangingChildDocuments[idx]));
+ continue;
+ }
+
+ outerDocAcc->RemoveChild(childDoc);
+ }
+
+ // Failed to bind the child document, destroy it.
+ childDoc->Shutdown();
+ }
+ }
+
+ mHangingChildDocuments.Clear();
+
+ // 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 only currently queued generic notifications.
+ 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;
+ }
+
+ // Process invalidation list of the document after all accessible tree
+ // modification are done.
+ mDocument->ProcessInvalidationList();
+
+ // We cannot rely on DOM tree to keep aria-owns relations updated. Make
+ // a validation to remove dead links.
+ mDocument->ValidateARIAOwned();
+
+ // Process relocation list.
+ for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
+ if (mRelocations[idx]->IsInDocument()) {
+ mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
+ }
+ }
+ mRelocations.Clear();
+
+ // 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 = 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();
+ uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
+ MOZ_ASSERT(id);
+ DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
+ if (ipcDoc) {
+ parentIPCDoc->SendBindChildDoc(ipcDoc, id);
+ continue;
+ }
+
+ ipcDoc = new DocAccessibleChild(childDoc);
+ childDoc->SetIPCDoc(ipcDoc);
+
+#if defined(XP_WIN)
+ MOZ_ASSERT(parentIPCDoc);
+ parentIPCDoc->ConstructChildDocInParentProcess(ipcDoc, id,
+ AccessibleWrap::GetChildIDFor(childDoc));
+#else
+ nsCOMPtr<nsITabChild> tabChild =
+ do_GetInterface(mDocument->DocumentNode()->GetDocShell());
+ if (tabChild) {
+ MOZ_ASSERT(parentIPCDoc);
+ static_cast<TabChild*>(tabChild.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, Flush_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;
+ }
+}