accessible/src/base/NotificationController.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "NotificationController.h"
michael@0 7
michael@0 8 #include "DocAccessible-inl.h"
michael@0 9 #include "TextLeafAccessible.h"
michael@0 10 #include "TextUpdater.h"
michael@0 11
michael@0 12 #include "mozilla/dom/Element.h"
michael@0 13 #include "mozilla/Telemetry.h"
michael@0 14
michael@0 15 using namespace mozilla;
michael@0 16 using namespace mozilla::a11y;
michael@0 17
michael@0 18 ////////////////////////////////////////////////////////////////////////////////
michael@0 19 // NotificationCollector
michael@0 20 ////////////////////////////////////////////////////////////////////////////////
michael@0 21
michael@0 22 NotificationController::NotificationController(DocAccessible* aDocument,
michael@0 23 nsIPresShell* aPresShell) :
michael@0 24 EventQueue(aDocument), mObservingState(eNotObservingRefresh),
michael@0 25 mPresShell(aPresShell)
michael@0 26 {
michael@0 27 // Schedule initial accessible tree construction.
michael@0 28 ScheduleProcessing();
michael@0 29 }
michael@0 30
michael@0 31 NotificationController::~NotificationController()
michael@0 32 {
michael@0 33 NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
michael@0 34 if (mDocument)
michael@0 35 Shutdown();
michael@0 36 }
michael@0 37
michael@0 38 ////////////////////////////////////////////////////////////////////////////////
michael@0 39 // NotificationCollector: AddRef/Release and cycle collection
michael@0 40
michael@0 41 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
michael@0 42 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
michael@0 43
michael@0 44 NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
michael@0 45
michael@0 46 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
michael@0 47 if (tmp->mDocument)
michael@0 48 tmp->Shutdown();
michael@0 49 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
michael@0 50
michael@0 51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
michael@0 52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
michael@0 53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentInsertions)
michael@0 54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
michael@0 55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
michael@0 56
michael@0 57 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
michael@0 58 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
michael@0 59
michael@0 60 ////////////////////////////////////////////////////////////////////////////////
michael@0 61 // NotificationCollector: public
michael@0 62
michael@0 63 void
michael@0 64 NotificationController::Shutdown()
michael@0 65 {
michael@0 66 if (mObservingState != eNotObservingRefresh &&
michael@0 67 mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
michael@0 68 mObservingState = eNotObservingRefresh;
michael@0 69 }
michael@0 70
michael@0 71 // Shutdown handling child documents.
michael@0 72 int32_t childDocCount = mHangingChildDocuments.Length();
michael@0 73 for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
michael@0 74 if (!mHangingChildDocuments[idx]->IsDefunct())
michael@0 75 mHangingChildDocuments[idx]->Shutdown();
michael@0 76 }
michael@0 77
michael@0 78 mHangingChildDocuments.Clear();
michael@0 79
michael@0 80 mDocument = nullptr;
michael@0 81 mPresShell = nullptr;
michael@0 82
michael@0 83 mTextHash.Clear();
michael@0 84 mContentInsertions.Clear();
michael@0 85 mNotifications.Clear();
michael@0 86 mEvents.Clear();
michael@0 87 }
michael@0 88
michael@0 89 void
michael@0 90 NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
michael@0 91 {
michael@0 92 // Schedule child document binding to the tree.
michael@0 93 mHangingChildDocuments.AppendElement(aDocument);
michael@0 94 ScheduleProcessing();
michael@0 95 }
michael@0 96
michael@0 97 void
michael@0 98 NotificationController::ScheduleContentInsertion(Accessible* aContainer,
michael@0 99 nsIContent* aStartChildNode,
michael@0 100 nsIContent* aEndChildNode)
michael@0 101 {
michael@0 102 nsRefPtr<ContentInsertion> insertion = new ContentInsertion(mDocument,
michael@0 103 aContainer);
michael@0 104 if (insertion && insertion->InitChildList(aStartChildNode, aEndChildNode) &&
michael@0 105 mContentInsertions.AppendElement(insertion)) {
michael@0 106 ScheduleProcessing();
michael@0 107 }
michael@0 108 }
michael@0 109
michael@0 110 ////////////////////////////////////////////////////////////////////////////////
michael@0 111 // NotificationCollector: protected
michael@0 112
michael@0 113 void
michael@0 114 NotificationController::ScheduleProcessing()
michael@0 115 {
michael@0 116 // If notification flush isn't planed yet start notification flush
michael@0 117 // asynchronously (after style and layout).
michael@0 118 if (mObservingState == eNotObservingRefresh) {
michael@0 119 if (mPresShell->AddRefreshObserver(this, Flush_Display))
michael@0 120 mObservingState = eRefreshObserving;
michael@0 121 }
michael@0 122 }
michael@0 123
michael@0 124 bool
michael@0 125 NotificationController::IsUpdatePending()
michael@0 126 {
michael@0 127 return mPresShell->IsLayoutFlushObserver() ||
michael@0 128 mObservingState == eRefreshProcessingForUpdate ||
michael@0 129 mContentInsertions.Length() != 0 || mNotifications.Length() != 0 ||
michael@0 130 mTextHash.Count() != 0 ||
michael@0 131 !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
michael@0 132 }
michael@0 133
michael@0 134 ////////////////////////////////////////////////////////////////////////////////
michael@0 135 // NotificationCollector: private
michael@0 136
michael@0 137 void
michael@0 138 NotificationController::WillRefresh(mozilla::TimeStamp aTime)
michael@0 139 {
michael@0 140 Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer;
michael@0 141
michael@0 142 // If the document accessible that notification collector was created for is
michael@0 143 // now shut down, don't process notifications anymore.
michael@0 144 NS_ASSERTION(mDocument,
michael@0 145 "The document was shut down while refresh observer is attached!");
michael@0 146 if (!mDocument)
michael@0 147 return;
michael@0 148
michael@0 149 if (mObservingState == eRefreshProcessing ||
michael@0 150 mObservingState == eRefreshProcessingForUpdate)
michael@0 151 return;
michael@0 152
michael@0 153 // Any generic notifications should be queued if we're processing content
michael@0 154 // insertions or generic notifications.
michael@0 155 mObservingState = eRefreshProcessingForUpdate;
michael@0 156
michael@0 157 // Initial accessible tree construction.
michael@0 158 if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
michael@0 159 // If document is not bound to parent at this point then the document is not
michael@0 160 // ready yet (process notifications later).
michael@0 161 if (!mDocument->IsBoundToParent()) {
michael@0 162 mObservingState = eRefreshObserving;
michael@0 163 return;
michael@0 164 }
michael@0 165
michael@0 166 #ifdef A11Y_LOG
michael@0 167 if (logging::IsEnabled(logging::eTree)) {
michael@0 168 logging::MsgBegin("TREE", "initial tree created");
michael@0 169 logging::Address("document", mDocument);
michael@0 170 logging::MsgEnd();
michael@0 171 }
michael@0 172 #endif
michael@0 173
michael@0 174 mDocument->DoInitialUpdate();
michael@0 175
michael@0 176 NS_ASSERTION(mContentInsertions.Length() == 0,
michael@0 177 "Pending content insertions while initial accessible tree isn't created!");
michael@0 178 }
michael@0 179
michael@0 180 // Initialize scroll support if needed.
michael@0 181 if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
michael@0 182 mDocument->AddScrollListener();
michael@0 183
michael@0 184 // Process content inserted notifications to update the tree. Process other
michael@0 185 // notifications like DOM events and then flush event queue. If any new
michael@0 186 // notifications are queued during this processing then they will be processed
michael@0 187 // on next refresh. If notification processing queues up new events then they
michael@0 188 // are processed in this refresh. If events processing queues up new events
michael@0 189 // then new events are processed on next refresh.
michael@0 190 // Note: notification processing or event handling may shut down the owning
michael@0 191 // document accessible.
michael@0 192
michael@0 193 // Process only currently queued content inserted notifications.
michael@0 194 nsTArray<nsRefPtr<ContentInsertion> > contentInsertions;
michael@0 195 contentInsertions.SwapElements(mContentInsertions);
michael@0 196
michael@0 197 uint32_t insertionCount = contentInsertions.Length();
michael@0 198 for (uint32_t idx = 0; idx < insertionCount; idx++) {
michael@0 199 contentInsertions[idx]->Process();
michael@0 200 if (!mDocument)
michael@0 201 return;
michael@0 202 }
michael@0 203
michael@0 204 // Process rendered text change notifications.
michael@0 205 mTextHash.EnumerateEntries(TextEnumerator, mDocument);
michael@0 206 mTextHash.Clear();
michael@0 207
michael@0 208 // Bind hanging child documents.
michael@0 209 uint32_t hangingDocCnt = mHangingChildDocuments.Length();
michael@0 210 for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
michael@0 211 DocAccessible* childDoc = mHangingChildDocuments[idx];
michael@0 212 if (childDoc->IsDefunct())
michael@0 213 continue;
michael@0 214
michael@0 215 nsIContent* ownerContent = mDocument->DocumentNode()->
michael@0 216 FindContentForSubDocument(childDoc->DocumentNode());
michael@0 217 if (ownerContent) {
michael@0 218 Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
michael@0 219 if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
michael@0 220 if (mDocument->AppendChildDocument(childDoc))
michael@0 221 continue;
michael@0 222
michael@0 223 outerDocAcc->RemoveChild(childDoc);
michael@0 224 }
michael@0 225
michael@0 226 // Failed to bind the child document, destroy it.
michael@0 227 childDoc->Shutdown();
michael@0 228 }
michael@0 229 }
michael@0 230 mHangingChildDocuments.Clear();
michael@0 231
michael@0 232 // If the document is ready and all its subdocuments are completely loaded
michael@0 233 // then process the document load.
michael@0 234 if (mDocument->HasLoadState(DocAccessible::eReady) &&
michael@0 235 !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
michael@0 236 hangingDocCnt == 0) {
michael@0 237 uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
michael@0 238 for (; childDocIdx < childDocCnt; childDocIdx++) {
michael@0 239 DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
michael@0 240 if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded))
michael@0 241 break;
michael@0 242 }
michael@0 243
michael@0 244 if (childDocIdx == childDocCnt) {
michael@0 245 mDocument->ProcessLoad();
michael@0 246 if (!mDocument)
michael@0 247 return;
michael@0 248 }
michael@0 249 }
michael@0 250
michael@0 251 // Process only currently queued generic notifications.
michael@0 252 nsTArray < nsRefPtr<Notification> > notifications;
michael@0 253 notifications.SwapElements(mNotifications);
michael@0 254
michael@0 255 uint32_t notificationCount = notifications.Length();
michael@0 256 for (uint32_t idx = 0; idx < notificationCount; idx++) {
michael@0 257 notifications[idx]->Process();
michael@0 258 if (!mDocument)
michael@0 259 return;
michael@0 260 }
michael@0 261
michael@0 262 // Process invalidation list of the document after all accessible tree
michael@0 263 // modification are done.
michael@0 264 mDocument->ProcessInvalidationList();
michael@0 265
michael@0 266 // If a generic notification occurs after this point then we may be allowed to
michael@0 267 // process it synchronously. However we do not want to reenter if fireing
michael@0 268 // events causes script to run.
michael@0 269 mObservingState = eRefreshProcessing;
michael@0 270
michael@0 271 ProcessEventQueue();
michael@0 272 mObservingState = eRefreshObserving;
michael@0 273 if (!mDocument)
michael@0 274 return;
michael@0 275
michael@0 276 // Stop further processing if there are no new notifications of any kind or
michael@0 277 // events and document load is processed.
michael@0 278 if (mContentInsertions.IsEmpty() && mNotifications.IsEmpty() &&
michael@0 279 mEvents.IsEmpty() && mTextHash.Count() == 0 &&
michael@0 280 mHangingChildDocuments.IsEmpty() &&
michael@0 281 mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
michael@0 282 mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
michael@0 283 mObservingState = eNotObservingRefresh;
michael@0 284 }
michael@0 285 }
michael@0 286
michael@0 287 ////////////////////////////////////////////////////////////////////////////////
michael@0 288 // Notification controller: text leaf accessible text update
michael@0 289
michael@0 290 PLDHashOperator
michael@0 291 NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
michael@0 292 void* aUserArg)
michael@0 293 {
michael@0 294 DocAccessible* document = static_cast<DocAccessible*>(aUserArg);
michael@0 295 nsIContent* textNode = aEntry->GetKey();
michael@0 296 Accessible* textAcc = document->GetAccessible(textNode);
michael@0 297
michael@0 298 // If the text node is not in tree or doesn't have frame then this case should
michael@0 299 // have been handled already by content removal notifications.
michael@0 300 nsINode* containerNode = textNode->GetParentNode();
michael@0 301 if (!containerNode) {
michael@0 302 NS_ASSERTION(!textAcc,
michael@0 303 "Text node was removed but accessible is kept alive!");
michael@0 304 return PL_DHASH_NEXT;
michael@0 305 }
michael@0 306
michael@0 307 nsIFrame* textFrame = textNode->GetPrimaryFrame();
michael@0 308 if (!textFrame) {
michael@0 309 NS_ASSERTION(!textAcc,
michael@0 310 "Text node isn't rendered but accessible is kept alive!");
michael@0 311 return PL_DHASH_NEXT;
michael@0 312 }
michael@0 313
michael@0 314 nsIContent* containerElm = containerNode->IsElement() ?
michael@0 315 containerNode->AsElement() : nullptr;
michael@0 316
michael@0 317 nsAutoString text;
michael@0 318 textFrame->GetRenderedText(&text);
michael@0 319
michael@0 320 // Remove text accessible if rendered text is empty.
michael@0 321 if (textAcc) {
michael@0 322 if (text.IsEmpty()) {
michael@0 323 #ifdef A11Y_LOG
michael@0 324 if (logging::IsEnabled(logging::eTree | logging::eText)) {
michael@0 325 logging::MsgBegin("TREE", "text node lost its content");
michael@0 326 logging::Node("container", containerElm);
michael@0 327 logging::Node("content", textNode);
michael@0 328 logging::MsgEnd();
michael@0 329 }
michael@0 330 #endif
michael@0 331
michael@0 332 document->ContentRemoved(containerElm, textNode);
michael@0 333 return PL_DHASH_NEXT;
michael@0 334 }
michael@0 335
michael@0 336 // Update text of the accessible and fire text change events.
michael@0 337 #ifdef A11Y_LOG
michael@0 338 if (logging::IsEnabled(logging::eText)) {
michael@0 339 logging::MsgBegin("TEXT", "text may be changed");
michael@0 340 logging::Node("container", containerElm);
michael@0 341 logging::Node("content", textNode);
michael@0 342 logging::MsgEntry("old text '%s'",
michael@0 343 NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
michael@0 344 logging::MsgEntry("new text: '%s'",
michael@0 345 NS_ConvertUTF16toUTF8(text).get());
michael@0 346 logging::MsgEnd();
michael@0 347 }
michael@0 348 #endif
michael@0 349
michael@0 350 TextUpdater::Run(document, textAcc->AsTextLeaf(), text);
michael@0 351 return PL_DHASH_NEXT;
michael@0 352 }
michael@0 353
michael@0 354 // Append an accessible if rendered text is not empty.
michael@0 355 if (!text.IsEmpty()) {
michael@0 356 #ifdef A11Y_LOG
michael@0 357 if (logging::IsEnabled(logging::eTree | logging::eText)) {
michael@0 358 logging::MsgBegin("TREE", "text node gains new content");
michael@0 359 logging::Node("container", containerElm);
michael@0 360 logging::Node("content", textNode);
michael@0 361 logging::MsgEnd();
michael@0 362 }
michael@0 363 #endif
michael@0 364
michael@0 365 // Make sure the text node is in accessible document still.
michael@0 366 Accessible* container = document->GetAccessibleOrContainer(containerNode);
michael@0 367 NS_ASSERTION(container,
michael@0 368 "Text node having rendered text hasn't accessible document!");
michael@0 369 if (container) {
michael@0 370 nsTArray<nsCOMPtr<nsIContent> > insertedContents;
michael@0 371 insertedContents.AppendElement(textNode);
michael@0 372 document->ProcessContentInserted(container, &insertedContents);
michael@0 373 }
michael@0 374 }
michael@0 375
michael@0 376 return PL_DHASH_NEXT;
michael@0 377 }
michael@0 378
michael@0 379
michael@0 380 ////////////////////////////////////////////////////////////////////////////////
michael@0 381 // NotificationController: content inserted notification
michael@0 382
michael@0 383 NotificationController::ContentInsertion::
michael@0 384 ContentInsertion(DocAccessible* aDocument, Accessible* aContainer) :
michael@0 385 mDocument(aDocument), mContainer(aContainer)
michael@0 386 {
michael@0 387 }
michael@0 388
michael@0 389 bool
michael@0 390 NotificationController::ContentInsertion::
michael@0 391 InitChildList(nsIContent* aStartChildNode, nsIContent* aEndChildNode)
michael@0 392 {
michael@0 393 bool haveToUpdate = false;
michael@0 394
michael@0 395 nsIContent* node = aStartChildNode;
michael@0 396 while (node != aEndChildNode) {
michael@0 397 // Notification triggers for content insertion even if no content was
michael@0 398 // actually inserted, check if the given content has a frame to discard
michael@0 399 // this case early.
michael@0 400 if (node->GetPrimaryFrame()) {
michael@0 401 if (mInsertedContent.AppendElement(node))
michael@0 402 haveToUpdate = true;
michael@0 403 }
michael@0 404
michael@0 405 node = node->GetNextSibling();
michael@0 406 }
michael@0 407
michael@0 408 return haveToUpdate;
michael@0 409 }
michael@0 410
michael@0 411 NS_IMPL_CYCLE_COLLECTION(NotificationController::ContentInsertion,
michael@0 412 mContainer)
michael@0 413
michael@0 414 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController::ContentInsertion,
michael@0 415 AddRef)
michael@0 416 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController::ContentInsertion,
michael@0 417 Release)
michael@0 418
michael@0 419 void
michael@0 420 NotificationController::ContentInsertion::Process()
michael@0 421 {
michael@0 422 mDocument->ProcessContentInserted(mContainer, &mInsertedContent);
michael@0 423
michael@0 424 mDocument = nullptr;
michael@0 425 mContainer = nullptr;
michael@0 426 mInsertedContent.Clear();
michael@0 427 }
michael@0 428

mercurial