1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/base/NotificationController.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,428 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "NotificationController.h" 1.10 + 1.11 +#include "DocAccessible-inl.h" 1.12 +#include "TextLeafAccessible.h" 1.13 +#include "TextUpdater.h" 1.14 + 1.15 +#include "mozilla/dom/Element.h" 1.16 +#include "mozilla/Telemetry.h" 1.17 + 1.18 +using namespace mozilla; 1.19 +using namespace mozilla::a11y; 1.20 + 1.21 +//////////////////////////////////////////////////////////////////////////////// 1.22 +// NotificationCollector 1.23 +//////////////////////////////////////////////////////////////////////////////// 1.24 + 1.25 +NotificationController::NotificationController(DocAccessible* aDocument, 1.26 + nsIPresShell* aPresShell) : 1.27 + EventQueue(aDocument), mObservingState(eNotObservingRefresh), 1.28 + mPresShell(aPresShell) 1.29 +{ 1.30 + // Schedule initial accessible tree construction. 1.31 + ScheduleProcessing(); 1.32 +} 1.33 + 1.34 +NotificationController::~NotificationController() 1.35 +{ 1.36 + NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!"); 1.37 + if (mDocument) 1.38 + Shutdown(); 1.39 +} 1.40 + 1.41 +//////////////////////////////////////////////////////////////////////////////// 1.42 +// NotificationCollector: AddRef/Release and cycle collection 1.43 + 1.44 +NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController) 1.45 +NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController) 1.46 + 1.47 +NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController) 1.48 + 1.49 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController) 1.50 + if (tmp->mDocument) 1.51 + tmp->Shutdown(); 1.52 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.53 + 1.54 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController) 1.55 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments) 1.56 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentInsertions) 1.57 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents) 1.58 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.59 + 1.60 +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef) 1.61 +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release) 1.62 + 1.63 +//////////////////////////////////////////////////////////////////////////////// 1.64 +// NotificationCollector: public 1.65 + 1.66 +void 1.67 +NotificationController::Shutdown() 1.68 +{ 1.69 + if (mObservingState != eNotObservingRefresh && 1.70 + mPresShell->RemoveRefreshObserver(this, Flush_Display)) { 1.71 + mObservingState = eNotObservingRefresh; 1.72 + } 1.73 + 1.74 + // Shutdown handling child documents. 1.75 + int32_t childDocCount = mHangingChildDocuments.Length(); 1.76 + for (int32_t idx = childDocCount - 1; idx >= 0; idx--) { 1.77 + if (!mHangingChildDocuments[idx]->IsDefunct()) 1.78 + mHangingChildDocuments[idx]->Shutdown(); 1.79 + } 1.80 + 1.81 + mHangingChildDocuments.Clear(); 1.82 + 1.83 + mDocument = nullptr; 1.84 + mPresShell = nullptr; 1.85 + 1.86 + mTextHash.Clear(); 1.87 + mContentInsertions.Clear(); 1.88 + mNotifications.Clear(); 1.89 + mEvents.Clear(); 1.90 +} 1.91 + 1.92 +void 1.93 +NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument) 1.94 +{ 1.95 + // Schedule child document binding to the tree. 1.96 + mHangingChildDocuments.AppendElement(aDocument); 1.97 + ScheduleProcessing(); 1.98 +} 1.99 + 1.100 +void 1.101 +NotificationController::ScheduleContentInsertion(Accessible* aContainer, 1.102 + nsIContent* aStartChildNode, 1.103 + nsIContent* aEndChildNode) 1.104 +{ 1.105 + nsRefPtr<ContentInsertion> insertion = new ContentInsertion(mDocument, 1.106 + aContainer); 1.107 + if (insertion && insertion->InitChildList(aStartChildNode, aEndChildNode) && 1.108 + mContentInsertions.AppendElement(insertion)) { 1.109 + ScheduleProcessing(); 1.110 + } 1.111 +} 1.112 + 1.113 +//////////////////////////////////////////////////////////////////////////////// 1.114 +// NotificationCollector: protected 1.115 + 1.116 +void 1.117 +NotificationController::ScheduleProcessing() 1.118 +{ 1.119 + // If notification flush isn't planed yet start notification flush 1.120 + // asynchronously (after style and layout). 1.121 + if (mObservingState == eNotObservingRefresh) { 1.122 + if (mPresShell->AddRefreshObserver(this, Flush_Display)) 1.123 + mObservingState = eRefreshObserving; 1.124 + } 1.125 +} 1.126 + 1.127 +bool 1.128 +NotificationController::IsUpdatePending() 1.129 +{ 1.130 + return mPresShell->IsLayoutFlushObserver() || 1.131 + mObservingState == eRefreshProcessingForUpdate || 1.132 + mContentInsertions.Length() != 0 || mNotifications.Length() != 0 || 1.133 + mTextHash.Count() != 0 || 1.134 + !mDocument->HasLoadState(DocAccessible::eTreeConstructed); 1.135 +} 1.136 + 1.137 +//////////////////////////////////////////////////////////////////////////////// 1.138 +// NotificationCollector: private 1.139 + 1.140 +void 1.141 +NotificationController::WillRefresh(mozilla::TimeStamp aTime) 1.142 +{ 1.143 + Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer; 1.144 + 1.145 + // If the document accessible that notification collector was created for is 1.146 + // now shut down, don't process notifications anymore. 1.147 + NS_ASSERTION(mDocument, 1.148 + "The document was shut down while refresh observer is attached!"); 1.149 + if (!mDocument) 1.150 + return; 1.151 + 1.152 + if (mObservingState == eRefreshProcessing || 1.153 + mObservingState == eRefreshProcessingForUpdate) 1.154 + return; 1.155 + 1.156 + // Any generic notifications should be queued if we're processing content 1.157 + // insertions or generic notifications. 1.158 + mObservingState = eRefreshProcessingForUpdate; 1.159 + 1.160 + // Initial accessible tree construction. 1.161 + if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) { 1.162 + // If document is not bound to parent at this point then the document is not 1.163 + // ready yet (process notifications later). 1.164 + if (!mDocument->IsBoundToParent()) { 1.165 + mObservingState = eRefreshObserving; 1.166 + return; 1.167 + } 1.168 + 1.169 +#ifdef A11Y_LOG 1.170 + if (logging::IsEnabled(logging::eTree)) { 1.171 + logging::MsgBegin("TREE", "initial tree created"); 1.172 + logging::Address("document", mDocument); 1.173 + logging::MsgEnd(); 1.174 + } 1.175 +#endif 1.176 + 1.177 + mDocument->DoInitialUpdate(); 1.178 + 1.179 + NS_ASSERTION(mContentInsertions.Length() == 0, 1.180 + "Pending content insertions while initial accessible tree isn't created!"); 1.181 + } 1.182 + 1.183 + // Initialize scroll support if needed. 1.184 + if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized)) 1.185 + mDocument->AddScrollListener(); 1.186 + 1.187 + // Process content inserted notifications to update the tree. Process other 1.188 + // notifications like DOM events and then flush event queue. If any new 1.189 + // notifications are queued during this processing then they will be processed 1.190 + // on next refresh. If notification processing queues up new events then they 1.191 + // are processed in this refresh. If events processing queues up new events 1.192 + // then new events are processed on next refresh. 1.193 + // Note: notification processing or event handling may shut down the owning 1.194 + // document accessible. 1.195 + 1.196 + // Process only currently queued content inserted notifications. 1.197 + nsTArray<nsRefPtr<ContentInsertion> > contentInsertions; 1.198 + contentInsertions.SwapElements(mContentInsertions); 1.199 + 1.200 + uint32_t insertionCount = contentInsertions.Length(); 1.201 + for (uint32_t idx = 0; idx < insertionCount; idx++) { 1.202 + contentInsertions[idx]->Process(); 1.203 + if (!mDocument) 1.204 + return; 1.205 + } 1.206 + 1.207 + // Process rendered text change notifications. 1.208 + mTextHash.EnumerateEntries(TextEnumerator, mDocument); 1.209 + mTextHash.Clear(); 1.210 + 1.211 + // Bind hanging child documents. 1.212 + uint32_t hangingDocCnt = mHangingChildDocuments.Length(); 1.213 + for (uint32_t idx = 0; idx < hangingDocCnt; idx++) { 1.214 + DocAccessible* childDoc = mHangingChildDocuments[idx]; 1.215 + if (childDoc->IsDefunct()) 1.216 + continue; 1.217 + 1.218 + nsIContent* ownerContent = mDocument->DocumentNode()-> 1.219 + FindContentForSubDocument(childDoc->DocumentNode()); 1.220 + if (ownerContent) { 1.221 + Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent); 1.222 + if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) { 1.223 + if (mDocument->AppendChildDocument(childDoc)) 1.224 + continue; 1.225 + 1.226 + outerDocAcc->RemoveChild(childDoc); 1.227 + } 1.228 + 1.229 + // Failed to bind the child document, destroy it. 1.230 + childDoc->Shutdown(); 1.231 + } 1.232 + } 1.233 + mHangingChildDocuments.Clear(); 1.234 + 1.235 + // If the document is ready and all its subdocuments are completely loaded 1.236 + // then process the document load. 1.237 + if (mDocument->HasLoadState(DocAccessible::eReady) && 1.238 + !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && 1.239 + hangingDocCnt == 0) { 1.240 + uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0; 1.241 + for (; childDocIdx < childDocCnt; childDocIdx++) { 1.242 + DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx); 1.243 + if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) 1.244 + break; 1.245 + } 1.246 + 1.247 + if (childDocIdx == childDocCnt) { 1.248 + mDocument->ProcessLoad(); 1.249 + if (!mDocument) 1.250 + return; 1.251 + } 1.252 + } 1.253 + 1.254 + // Process only currently queued generic notifications. 1.255 + nsTArray < nsRefPtr<Notification> > notifications; 1.256 + notifications.SwapElements(mNotifications); 1.257 + 1.258 + uint32_t notificationCount = notifications.Length(); 1.259 + for (uint32_t idx = 0; idx < notificationCount; idx++) { 1.260 + notifications[idx]->Process(); 1.261 + if (!mDocument) 1.262 + return; 1.263 + } 1.264 + 1.265 + // Process invalidation list of the document after all accessible tree 1.266 + // modification are done. 1.267 + mDocument->ProcessInvalidationList(); 1.268 + 1.269 + // If a generic notification occurs after this point then we may be allowed to 1.270 + // process it synchronously. However we do not want to reenter if fireing 1.271 + // events causes script to run. 1.272 + mObservingState = eRefreshProcessing; 1.273 + 1.274 + ProcessEventQueue(); 1.275 + mObservingState = eRefreshObserving; 1.276 + if (!mDocument) 1.277 + return; 1.278 + 1.279 + // Stop further processing if there are no new notifications of any kind or 1.280 + // events and document load is processed. 1.281 + if (mContentInsertions.IsEmpty() && mNotifications.IsEmpty() && 1.282 + mEvents.IsEmpty() && mTextHash.Count() == 0 && 1.283 + mHangingChildDocuments.IsEmpty() && 1.284 + mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && 1.285 + mPresShell->RemoveRefreshObserver(this, Flush_Display)) { 1.286 + mObservingState = eNotObservingRefresh; 1.287 + } 1.288 +} 1.289 + 1.290 +//////////////////////////////////////////////////////////////////////////////// 1.291 +// Notification controller: text leaf accessible text update 1.292 + 1.293 +PLDHashOperator 1.294 +NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry, 1.295 + void* aUserArg) 1.296 +{ 1.297 + DocAccessible* document = static_cast<DocAccessible*>(aUserArg); 1.298 + nsIContent* textNode = aEntry->GetKey(); 1.299 + Accessible* textAcc = document->GetAccessible(textNode); 1.300 + 1.301 + // If the text node is not in tree or doesn't have frame then this case should 1.302 + // have been handled already by content removal notifications. 1.303 + nsINode* containerNode = textNode->GetParentNode(); 1.304 + if (!containerNode) { 1.305 + NS_ASSERTION(!textAcc, 1.306 + "Text node was removed but accessible is kept alive!"); 1.307 + return PL_DHASH_NEXT; 1.308 + } 1.309 + 1.310 + nsIFrame* textFrame = textNode->GetPrimaryFrame(); 1.311 + if (!textFrame) { 1.312 + NS_ASSERTION(!textAcc, 1.313 + "Text node isn't rendered but accessible is kept alive!"); 1.314 + return PL_DHASH_NEXT; 1.315 + } 1.316 + 1.317 + nsIContent* containerElm = containerNode->IsElement() ? 1.318 + containerNode->AsElement() : nullptr; 1.319 + 1.320 + nsAutoString text; 1.321 + textFrame->GetRenderedText(&text); 1.322 + 1.323 + // Remove text accessible if rendered text is empty. 1.324 + if (textAcc) { 1.325 + if (text.IsEmpty()) { 1.326 +#ifdef A11Y_LOG 1.327 + if (logging::IsEnabled(logging::eTree | logging::eText)) { 1.328 + logging::MsgBegin("TREE", "text node lost its content"); 1.329 + logging::Node("container", containerElm); 1.330 + logging::Node("content", textNode); 1.331 + logging::MsgEnd(); 1.332 + } 1.333 +#endif 1.334 + 1.335 + document->ContentRemoved(containerElm, textNode); 1.336 + return PL_DHASH_NEXT; 1.337 + } 1.338 + 1.339 + // Update text of the accessible and fire text change events. 1.340 +#ifdef A11Y_LOG 1.341 + if (logging::IsEnabled(logging::eText)) { 1.342 + logging::MsgBegin("TEXT", "text may be changed"); 1.343 + logging::Node("container", containerElm); 1.344 + logging::Node("content", textNode); 1.345 + logging::MsgEntry("old text '%s'", 1.346 + NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get()); 1.347 + logging::MsgEntry("new text: '%s'", 1.348 + NS_ConvertUTF16toUTF8(text).get()); 1.349 + logging::MsgEnd(); 1.350 + } 1.351 +#endif 1.352 + 1.353 + TextUpdater::Run(document, textAcc->AsTextLeaf(), text); 1.354 + return PL_DHASH_NEXT; 1.355 + } 1.356 + 1.357 + // Append an accessible if rendered text is not empty. 1.358 + if (!text.IsEmpty()) { 1.359 +#ifdef A11Y_LOG 1.360 + if (logging::IsEnabled(logging::eTree | logging::eText)) { 1.361 + logging::MsgBegin("TREE", "text node gains new content"); 1.362 + logging::Node("container", containerElm); 1.363 + logging::Node("content", textNode); 1.364 + logging::MsgEnd(); 1.365 + } 1.366 +#endif 1.367 + 1.368 + // Make sure the text node is in accessible document still. 1.369 + Accessible* container = document->GetAccessibleOrContainer(containerNode); 1.370 + NS_ASSERTION(container, 1.371 + "Text node having rendered text hasn't accessible document!"); 1.372 + if (container) { 1.373 + nsTArray<nsCOMPtr<nsIContent> > insertedContents; 1.374 + insertedContents.AppendElement(textNode); 1.375 + document->ProcessContentInserted(container, &insertedContents); 1.376 + } 1.377 + } 1.378 + 1.379 + return PL_DHASH_NEXT; 1.380 +} 1.381 + 1.382 + 1.383 +//////////////////////////////////////////////////////////////////////////////// 1.384 +// NotificationController: content inserted notification 1.385 + 1.386 +NotificationController::ContentInsertion:: 1.387 + ContentInsertion(DocAccessible* aDocument, Accessible* aContainer) : 1.388 + mDocument(aDocument), mContainer(aContainer) 1.389 +{ 1.390 +} 1.391 + 1.392 +bool 1.393 +NotificationController::ContentInsertion:: 1.394 + InitChildList(nsIContent* aStartChildNode, nsIContent* aEndChildNode) 1.395 +{ 1.396 + bool haveToUpdate = false; 1.397 + 1.398 + nsIContent* node = aStartChildNode; 1.399 + while (node != aEndChildNode) { 1.400 + // Notification triggers for content insertion even if no content was 1.401 + // actually inserted, check if the given content has a frame to discard 1.402 + // this case early. 1.403 + if (node->GetPrimaryFrame()) { 1.404 + if (mInsertedContent.AppendElement(node)) 1.405 + haveToUpdate = true; 1.406 + } 1.407 + 1.408 + node = node->GetNextSibling(); 1.409 + } 1.410 + 1.411 + return haveToUpdate; 1.412 +} 1.413 + 1.414 +NS_IMPL_CYCLE_COLLECTION(NotificationController::ContentInsertion, 1.415 + mContainer) 1.416 + 1.417 +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController::ContentInsertion, 1.418 + AddRef) 1.419 +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController::ContentInsertion, 1.420 + Release) 1.421 + 1.422 +void 1.423 +NotificationController::ContentInsertion::Process() 1.424 +{ 1.425 + mDocument->ProcessContentInserted(mContainer, &mInsertedContent); 1.426 + 1.427 + mDocument = nullptr; 1.428 + mContainer = nullptr; 1.429 + mInsertedContent.Clear(); 1.430 +} 1.431 +