PLDHashOperator NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry, void* aUserArg) { DocAccessible* document = static_cast<DocAccessible*>(aUserArg); nsIContent* textNode = aEntry->GetKey(); Accessible* textAcc = document->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!"); return PL_DHASH_NEXT; } nsIFrame* textFrame = textNode->GetPrimaryFrame(); if (!textFrame) { NS_ASSERTION(!textAcc, "Text node isn't rendered but accessible is kept alive!"); return PL_DHASH_NEXT; } nsIContent* containerElm = containerNode->IsElement() ? containerNode->AsElement() : nullptr; nsAutoString text; textFrame->GetRenderedText(&text); // Remove text accessible if rendered text is empty. if (textAcc) { if (text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node lost its content"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif document->ContentRemoved(containerElm, textNode); return PL_DHASH_NEXT; } // 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"); 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).get()); logging::MsgEnd(); } #endif TextUpdater::Run(document, textAcc->AsTextLeaf(), text); return PL_DHASH_NEXT; } // Append an accessible if rendered text is not empty. if (!text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node gains new content"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif // Make sure the text node is in accessible document still. Accessible* container = document->GetAccessibleOrContainer(containerNode); NS_ASSERTION(container, "Text node having rendered text hasn't accessible document!"); if (container) { nsTArray<nsCOMPtr<nsIContent> > insertedContents; insertedContents.AppendElement(textNode); document->ProcessContentInserted(container, &insertedContents); } } return PL_DHASH_NEXT; }
void NotificationController::WillRefresh(mozilla::TimeStamp aTime) { 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.Length() == 0, "Pending content insertions while initial accessible tree isn't created!"); } // Initialize scroll support if needed. if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized)) mDocument->AddScrollListener(); // Process content inserted notifications to update the tree. Process other // notifications like DOM events and then flush event queue. If any new // notifications are queued during this processing then they will be processed // on next refresh. If notification processing queues up new events then they // are processed in this refresh. If events processing queues up new events // then new events are processed on next refresh. // Note: notification processing or event handling may shut down the owning // document accessible. // Process only currently queued content inserted notifications. nsTArray<nsRefPtr<ContentInsertion> > contentInsertions; contentInsertions.SwapElements(mContentInsertions); uint32_t insertionCount = contentInsertions.Length(); for (uint32_t idx = 0; idx < insertionCount; idx++) { contentInsertions[idx]->Process(); if (!mDocument) return; } // 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; nsAutoString text; textFrame->GetRenderedText(&text); // Remove text accessible if rendered text is empty. if (textAcc) { if (text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node lost its content"); 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"); 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).get()); logging::MsgEnd(); } #endif TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text); continue; } // Append an accessible if rendered text is not empty. if (!text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node gains new content"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif // Make sure the text node is in accessible document still. Accessible* container = mDocument->GetAccessibleOrContainer(containerNode); NS_ASSERTION(container, "Text node having rendered text hasn't accessible document!"); if (container) { nsTArray<nsCOMPtr<nsIContent> > insertedContents; insertedContents.AppendElement(textNode); mDocument->ProcessContentInserted(container, &insertedContents); } } } mTextHash.Clear(); // Bind hanging child documents. uint32_t hangingDocCnt = mHangingChildDocuments.Length(); nsTArray<nsRefPtr<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 < nsRefPtr<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(); // 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; ProcessEventQueue(); if (IPCAccessibilityActive()) { size_t newDocCount = newChildDocs.Length(); for (size_t i = 0; i < newDocCount; i++) { DocAccessible* childDoc = newChildDocs[i]; 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); nsCOMPtr<nsITabChild> tabChild = do_GetInterface(mDocument->DocumentNode()->GetDocShell()); static_cast<TabChild*>(tabChild.get())-> SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id); } } 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.IsEmpty() && mNotifications.IsEmpty() && mEvents.IsEmpty() && mTextHash.Count() == 0 && mHangingChildDocuments.IsEmpty() && mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && mPresShell->RemoveRefreshObserver(this, Flush_Display)) { mObservingState = eNotObservingRefresh; } }
PLDHashOperator NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry, void* aUserArg) { DocAccessible* document = static_cast<DocAccessible*>(aUserArg); nsIContent* textNode = aEntry->GetKey(); Accessible* textAcc = document->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->GetNodeParent(); if (!containerNode) { NS_ASSERTION(!textAcc, "Text node was removed but accessible is kept alive!"); return PL_DHASH_NEXT; } nsIFrame* textFrame = textNode->GetPrimaryFrame(); if (!textFrame) { NS_ASSERTION(!textAcc, "Text node isn't rendered but accessible is kept alive!"); return PL_DHASH_NEXT; } nsIContent* containerElm = containerNode->IsElement() ? containerNode->AsElement() : nsnull; nsAutoString text; textFrame->GetRenderedText(&text); // Remove text accessible if rendered text is empty. if (textAcc) { if (text.IsEmpty()) { #ifdef DEBUG_NOTIFICATIONS PRUint32 index = containerNode->IndexOf(textNode); nsCAutoString tag; nsCAutoString id; if (containerElm) { containerElm->Tag()->ToUTF8String(tag); nsIAtom* atomid = containerElm->GetID(); if (atomid) atomid->ToUTF8String(id); } printf("\npending text node removal: container: %s@id='%s', index in container: %d\n\n", tag.get(), id.get(), index); #endif document->ContentRemoved(containerElm, textNode); return PL_DHASH_NEXT; } // Update text of the accessible and fire text change events. #ifdef DEBUG_TEXTCHANGE PRUint32 index = containerNode->IndexOf(textNode); nsCAutoString tag; nsCAutoString id; if (containerElm) { containerElm->Tag()->ToUTF8String(tag); nsIAtom* atomid = containerElm->GetID(); if (atomid) atomid->ToUTF8String(id); } printf("\ntext may be changed: container: %s@id='%s', index in container: %d, old text '%s', new text: '%s'\n\n", tag.get(), id.get(), index, NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get(), NS_ConvertUTF16toUTF8(text).get()); #endif TextUpdater::Run(document, textAcc->AsTextLeaf(), text); return PL_DHASH_NEXT; } // Append an accessible if rendered text is not empty. if (!text.IsEmpty()) { #ifdef DEBUG_NOTIFICATIONS PRUint32 index = containerNode->IndexOf(textNode); nsCAutoString tag; nsCAutoString id; if (containerElm) { containerElm->Tag()->ToUTF8String(tag); nsIAtom* atomid = containerElm->GetID(); if (atomid) atomid->ToUTF8String(id); } printf("\npending text node insertion: container: %s@id='%s', index in container: %d\n\n", tag.get(), id.get(), index); #endif // Make sure the text node is in accessible document still. Accessible* container = document->GetAccessibleOrContainer(containerNode); NS_ASSERTION(container, "Text node having rendered text hasn't accessible document!"); if (container) { nsTArray<nsCOMPtr<nsIContent> > insertedContents; insertedContents.AppendElement(textNode); document->ProcessContentInserted(container, &insertedContents); } } return PL_DHASH_NEXT; }