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