/* -*- 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>* 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 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 the accessible doesn't have a frame, we are probably mid frame // destruction bail early. if (!container->IsHyperText() || !container->GetFrame()) { 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>& 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, nsTArray> 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 aIdx = a->GetAccessible()->IndexInParent(); DebugOnly bIdx = b->GetAccessible()->IndexInParent(); MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx); return false; } }; nsTArray& 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 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* 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>* 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> 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> 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 mutEvent = std::move(mFirstMutationEvent); mLastMutationEvent = nullptr; mFirstMutationEvent = nullptr; while (mutEvent) { RefPtr 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(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 browserChild = do_GetInterface(mDocument->DocumentNode()->GetDocShell()); if (browserChild) { static_cast(browserChild.get()) ->SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0); ipcDoc->SendPDocAccessiblePlatformExtConstructor(); } #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(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(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(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; } }