Line data Source code
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/. */
5 :
6 : #include "NotificationController.h"
7 :
8 : #include "DocAccessible-inl.h"
9 : #include "DocAccessibleChild.h"
10 : #include "TextLeafAccessible.h"
11 : #include "TextUpdater.h"
12 :
13 : #include "mozilla/dom/TabChild.h"
14 : #include "mozilla/dom/Element.h"
15 : #include "mozilla/Telemetry.h"
16 :
17 : using namespace mozilla;
18 : using namespace mozilla::a11y;
19 :
20 : ////////////////////////////////////////////////////////////////////////////////
21 : // NotificationCollector
22 : ////////////////////////////////////////////////////////////////////////////////
23 :
24 0 : NotificationController::NotificationController(DocAccessible* aDocument,
25 0 : nsIPresShell* aPresShell) :
26 : EventQueue(aDocument), mObservingState(eNotObservingRefresh),
27 0 : mPresShell(aPresShell), mEventGeneration(0)
28 : {
29 : #ifdef DEBUG
30 0 : mMoveGuardOnStack = false;
31 : #endif
32 :
33 : // Schedule initial accessible tree construction.
34 0 : ScheduleProcessing();
35 0 : }
36 :
37 0 : NotificationController::~NotificationController()
38 : {
39 0 : NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
40 0 : if (mDocument)
41 0 : Shutdown();
42 0 : }
43 :
44 : ////////////////////////////////////////////////////////////////////////////////
45 : // NotificationCollector: AddRef/Release and cycle collection
46 :
47 0 : NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
48 0 : NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
49 :
50 : NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
51 :
52 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
53 0 : if (tmp->mDocument)
54 0 : tmp->Shutdown();
55 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
56 :
57 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
58 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
59 0 : for (auto it = tmp->mContentInsertions.ConstIter(); !it.Done(); it.Next()) {
60 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key");
61 0 : cb.NoteXPCOMChild(it.Key());
62 0 : nsTArray<nsCOMPtr<nsIContent>>* list = it.UserData();
63 0 : for (uint32_t i = 0; i < list->Length(); i++) {
64 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
65 0 : "mContentInsertions value item");
66 0 : cb.NoteXPCOMChild(list->ElementAt(i));
67 : }
68 : }
69 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
70 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
71 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
72 :
73 0 : NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
74 0 : NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
75 :
76 : ////////////////////////////////////////////////////////////////////////////////
77 : // NotificationCollector: public
78 :
79 : void
80 0 : NotificationController::Shutdown()
81 : {
82 0 : if (mObservingState != eNotObservingRefresh &&
83 0 : mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
84 0 : mObservingState = eNotObservingRefresh;
85 : }
86 :
87 : // Shutdown handling child documents.
88 0 : int32_t childDocCount = mHangingChildDocuments.Length();
89 0 : for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
90 0 : if (!mHangingChildDocuments[idx]->IsDefunct())
91 0 : mHangingChildDocuments[idx]->Shutdown();
92 : }
93 :
94 0 : mHangingChildDocuments.Clear();
95 :
96 0 : mDocument = nullptr;
97 0 : mPresShell = nullptr;
98 :
99 0 : mTextHash.Clear();
100 0 : mContentInsertions.Clear();
101 0 : mNotifications.Clear();
102 0 : mEvents.Clear();
103 0 : mRelocations.Clear();
104 0 : mEventTree.Clear();
105 0 : }
106 :
107 : EventTree*
108 0 : NotificationController::QueueMutation(Accessible* aContainer)
109 : {
110 0 : EventTree* tree = mEventTree.FindOrInsert(aContainer);
111 0 : if (tree) {
112 0 : ScheduleProcessing();
113 : }
114 0 : return tree;
115 : }
116 :
117 : bool
118 0 : NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent)
119 : {
120 : // We have to allow there to be a hide and then a show event for a target
121 : // because of targets getting moved. However we need to coalesce a show and
122 : // then a hide for a target which means we need to check for that here.
123 0 : if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
124 0 : aEvent->GetAccessible()->ShowEventTarget()) {
125 0 : AccTreeMutationEvent* showEvent = mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
126 0 : DropMutationEvent(showEvent);
127 0 : return false;
128 : }
129 :
130 0 : AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
131 0 : mEventGeneration++;
132 0 : mutEvent->SetEventGeneration(mEventGeneration);
133 :
134 0 : if (!mFirstMutationEvent) {
135 0 : mFirstMutationEvent = aEvent;
136 0 : ScheduleProcessing();
137 : }
138 :
139 0 : if (mLastMutationEvent) {
140 0 : NS_ASSERTION(!mLastMutationEvent->NextEvent(), "why isn't the last event the end?");
141 0 : mLastMutationEvent->SetNextEvent(aEvent);
142 : }
143 :
144 0 : aEvent->SetPrevEvent(mLastMutationEvent);
145 0 : mLastMutationEvent = aEvent;
146 0 : mMutationMap.PutEvent(aEvent);
147 :
148 : // Because we could be hiding the target of a show event we need to get rid
149 : // of any such events. It may be possible to do less than coallesce all
150 : // events, however that is easiest.
151 0 : if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
152 0 : CoalesceMutationEvents();
153 :
154 : // mLastMutationEvent will point to something other than aEvent if and only
155 : // if aEvent was just coalesced away. In that case a parent accessible
156 : // must already have the required reorder and text change events so we are
157 : // done here.
158 0 : if (mLastMutationEvent != aEvent) {
159 0 : return false;
160 : }
161 : }
162 :
163 : // We need to fire a reorder event after all of the events targeted at shown or
164 : // hidden children of a container. So either queue a new one, or move an
165 : // existing one to the end of the queue if the container already has a
166 : // reorder event.
167 0 : Accessible* target = aEvent->GetAccessible();
168 0 : Accessible* container = aEvent->GetAccessible()->Parent();
169 0 : RefPtr<AccReorderEvent> reorder;
170 0 : if (!container->ReorderEventTarget()) {
171 0 : reorder = new AccReorderEvent(container);
172 0 : container->SetReorderEventTarget(true);
173 0 : mMutationMap.PutEvent(reorder);
174 :
175 : // Since this is the first child of container that is changing, the name of
176 : // container may be changing.
177 0 : QueueNameChange(target);
178 : } else {
179 0 : AccReorderEvent* event = downcast_accEvent(mMutationMap.GetEvent(container, EventMap::ReorderEvent));
180 0 : reorder = event;
181 0 : if (mFirstMutationEvent == event) {
182 0 : mFirstMutationEvent = event->NextEvent();
183 : } else {
184 0 : event->PrevEvent()->SetNextEvent(event->NextEvent());
185 : }
186 :
187 0 : event->NextEvent()->SetPrevEvent(event->PrevEvent());
188 0 : event->SetNextEvent(nullptr);
189 : }
190 :
191 0 : reorder->SetEventGeneration(mEventGeneration);
192 0 : reorder->SetPrevEvent(mLastMutationEvent);
193 0 : mLastMutationEvent->SetNextEvent(reorder);
194 0 : mLastMutationEvent = reorder;
195 :
196 : // It is not possible to have a text change event for something other than a
197 : // hyper text accessible.
198 0 : if (!container->IsHyperText()) {
199 0 : return true;
200 : }
201 :
202 0 : MOZ_ASSERT(mutEvent);
203 :
204 0 : nsString text;
205 0 : aEvent->GetAccessible()->AppendTextTo(text);
206 0 : if (text.IsEmpty()) {
207 0 : return true;
208 : }
209 :
210 0 : int32_t offset = container->AsHyperText()->GetChildOffset(target);
211 0 : AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
212 0 : while (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
213 0 : prevEvent = prevEvent->PrevEvent();
214 : }
215 :
216 0 : if (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
217 0 : mutEvent->IsHide()) {
218 0 : AccHideEvent* prevHide = downcast_accEvent(prevEvent);
219 0 : AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent;
220 0 : if (prevTextChange && prevHide->Parent() == mutEvent->Parent()) {
221 0 : if (prevHide->mNextSibling == target) {
222 0 : target->AppendTextTo(prevTextChange->mModifiedText);
223 0 : prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
224 0 : } else if (prevHide->mPrevSibling == target) {
225 0 : nsString temp;
226 0 : target->AppendTextTo(temp);
227 :
228 0 : uint32_t extraLen = temp.Length();
229 0 : temp += prevTextChange->mModifiedText;;
230 0 : prevTextChange->mModifiedText = temp;
231 0 : prevTextChange->mStart -= extraLen;
232 0 : prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
233 : }
234 : }
235 0 : } else if (prevEvent && mutEvent->IsShow() &&
236 0 : prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
237 0 : AccShowEvent* prevShow = downcast_accEvent(prevEvent);
238 0 : AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent;
239 0 : if (prevTextChange && prevShow->Parent() == target->Parent()) {
240 0 : int32_t index = target->IndexInParent();
241 0 : int32_t prevIndex = prevShow->GetAccessible()->IndexInParent();
242 0 : if (prevIndex + 1 == index) {
243 0 : target->AppendTextTo(prevTextChange->mModifiedText);
244 0 : prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
245 0 : } else if (index + 1 == prevIndex) {
246 0 : nsString temp;
247 0 : target->AppendTextTo(temp);
248 0 : prevTextChange->mStart -= temp.Length();
249 0 : temp += prevTextChange->mModifiedText;
250 0 : prevTextChange->mModifiedText = temp;
251 0 : prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
252 : }
253 : }
254 : }
255 :
256 0 : if (!mutEvent->mTextChangeEvent) {
257 : mutEvent->mTextChangeEvent =
258 0 : new AccTextChangeEvent(container, offset, text, mutEvent->IsShow(),
259 0 : aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
260 : }
261 :
262 0 : return true;
263 : }
264 :
265 : void
266 0 : NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent)
267 : {
268 : // unset the event bits since the event isn't being fired any more.
269 0 : if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
270 0 : aEvent->GetAccessible()->SetReorderEventTarget(false);
271 0 : } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
272 0 : aEvent->GetAccessible()->SetShowEventTarget(false);
273 : } else {
274 0 : AccHideEvent* hideEvent = downcast_accEvent(aEvent);
275 0 : MOZ_ASSERT(hideEvent);
276 :
277 0 : if (hideEvent->NeedsShutdown()) {
278 0 : mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
279 : }
280 : }
281 :
282 : // Do the work to splice the event out of the list.
283 0 : if (mFirstMutationEvent == aEvent) {
284 0 : mFirstMutationEvent = aEvent->NextEvent();
285 : } else {
286 0 : aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
287 : }
288 :
289 0 : if (mLastMutationEvent == aEvent) {
290 0 : mLastMutationEvent = aEvent->PrevEvent();
291 : } else {
292 0 : aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent());
293 : }
294 :
295 0 : aEvent->SetPrevEvent(nullptr);
296 0 : aEvent->SetNextEvent(nullptr);
297 0 : mMutationMap.RemoveEvent(aEvent);
298 0 : }
299 :
300 : void
301 0 : NotificationController::CoalesceMutationEvents()
302 : {
303 0 : AccTreeMutationEvent* event = mFirstMutationEvent;
304 0 : while (event) {
305 0 : AccTreeMutationEvent* nextEvent = event->NextEvent();
306 0 : uint32_t eventType = event->GetEventType();
307 0 : if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
308 0 : Accessible* acc = event->GetAccessible();
309 0 : while (acc) {
310 0 : if (acc->IsDoc()) {
311 0 : break;
312 : }
313 :
314 : // if a parent of the reorder event's target is being hidden that
315 : // hide event's target must have a parent that is also a reorder event
316 : // target. That means we don't need this reorder event.
317 0 : if (acc->HideEventTarget()) {
318 0 : DropMutationEvent(event);
319 0 : break;
320 : }
321 :
322 0 : Accessible* parent = acc->Parent();
323 0 : if (parent->ReorderEventTarget()) {
324 0 : AccReorderEvent* reorder = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
325 :
326 : // We want to make sure that a reorder event comes after any show or
327 : // hide events targeted at the children of its target. We keep the
328 : // invariant that event generation goes up as you are farther in the
329 : // queue, so we want to use the spot of the event with the higher
330 : // generation number, and keep that generation number.
331 0 : if (reorder && reorder->EventGeneration() < event->EventGeneration()) {
332 0 : reorder->SetEventGeneration(event->EventGeneration());
333 :
334 : // It may be true that reorder was before event, and we coalesced
335 : // away all the show / hide events between them. In that case
336 : // event is already immediately after reorder in the queue and we
337 : // do not need to rearrange the list of events.
338 0 : if (event != reorder->NextEvent()) {
339 : // There really should be a show or hide event before the first
340 : // reorder event.
341 0 : if (reorder->PrevEvent()) {
342 0 : reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
343 : } else {
344 0 : mFirstMutationEvent = reorder->NextEvent();
345 : }
346 :
347 0 : reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
348 0 : event->PrevEvent()->SetNextEvent(reorder);
349 0 : reorder->SetPrevEvent(event->PrevEvent());
350 0 : event->SetPrevEvent(reorder);
351 0 : reorder->SetNextEvent(event);
352 : }
353 : }
354 0 : DropMutationEvent(event);
355 0 : break;
356 : }
357 :
358 0 : acc = parent;
359 : }
360 0 : } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
361 0 : Accessible* parent = event->GetAccessible()->Parent();
362 0 : while (parent) {
363 0 : if (parent->IsDoc()) {
364 0 : break;
365 : }
366 :
367 : // if the parent of a show event is being either shown or hidden then
368 : // we don't need to fire a show event for a subtree of that change.
369 0 : if (parent->ShowEventTarget() || parent->HideEventTarget()) {
370 0 : DropMutationEvent(event);
371 0 : break;
372 : }
373 :
374 0 : parent = parent->Parent();
375 : }
376 : } else {
377 0 : MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE, "mutation event list has an invalid event");
378 :
379 0 : AccHideEvent* hideEvent = downcast_accEvent(event);
380 0 : Accessible* parent = hideEvent->Parent();
381 0 : while (parent) {
382 0 : if (parent->IsDoc()) {
383 0 : break;
384 : }
385 :
386 0 : if (parent->HideEventTarget()) {
387 0 : DropMutationEvent(event);
388 0 : break;
389 : }
390 :
391 0 : if (parent->ShowEventTarget()) {
392 0 : AccShowEvent* showEvent = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ShowEvent));
393 0 : if (showEvent->EventGeneration() < hideEvent->EventGeneration()) {
394 0 : DropMutationEvent(hideEvent);
395 0 : break;
396 : }
397 : }
398 :
399 0 : parent = parent->Parent();
400 : }
401 : }
402 :
403 0 : event = nextEvent;
404 : }
405 0 : }
406 :
407 : void
408 0 : NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
409 : {
410 : // Schedule child document binding to the tree.
411 0 : mHangingChildDocuments.AppendElement(aDocument);
412 0 : ScheduleProcessing();
413 0 : }
414 :
415 : void
416 0 : NotificationController::ScheduleContentInsertion(Accessible* aContainer,
417 : nsIContent* aStartChildNode,
418 : nsIContent* aEndChildNode)
419 : {
420 : nsTArray<nsCOMPtr<nsIContent>>* list =
421 0 : mContentInsertions.LookupOrAdd(aContainer);
422 :
423 0 : bool needsProcessing = false;
424 0 : nsIContent* node = aStartChildNode;
425 0 : while (node != aEndChildNode) {
426 : // Notification triggers for content insertion even if no content was
427 : // actually inserted, check if the given content has a frame to discard
428 : // this case early.
429 0 : if (node->GetPrimaryFrame()) {
430 0 : if (list->AppendElement(node))
431 0 : needsProcessing = true;
432 : }
433 0 : node = node->GetNextSibling();
434 : }
435 :
436 0 : if (needsProcessing) {
437 0 : ScheduleProcessing();
438 : }
439 0 : }
440 :
441 : void
442 0 : NotificationController::ScheduleProcessing()
443 : {
444 : // If notification flush isn't planed yet start notification flush
445 : // asynchronously (after style and layout).
446 0 : if (mObservingState == eNotObservingRefresh) {
447 0 : if (mPresShell->AddRefreshObserver(this, FlushType::Display))
448 0 : mObservingState = eRefreshObserving;
449 : }
450 0 : }
451 :
452 : ////////////////////////////////////////////////////////////////////////////////
453 : // NotificationCollector: protected
454 :
455 : bool
456 0 : NotificationController::IsUpdatePending()
457 : {
458 0 : return mPresShell->IsLayoutFlushObserver() ||
459 0 : mObservingState == eRefreshProcessingForUpdate ||
460 0 : mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
461 0 : mTextHash.Count() != 0 ||
462 0 : !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
463 : }
464 :
465 : void
466 0 : NotificationController::ProcessMutationEvents()
467 : {
468 : // there is no reason to fire a hide event for a child of a show event
469 : // target. That can happen if something is inserted into the tree and
470 : // removed before the next refresh driver tick, but it should not be
471 : // observable outside gecko so it should be safe to coalesce away any such
472 : // events. This means that it should be fine to fire all of the hide events
473 : // first, and then deal with any shown subtrees.
474 0 : for (AccTreeMutationEvent* event = mFirstMutationEvent;
475 0 : event; event = event->NextEvent()) {
476 0 : if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) {
477 0 : continue;
478 : }
479 :
480 0 : nsEventShell::FireEvent(event);
481 0 : if (!mDocument) {
482 0 : return;
483 : }
484 :
485 0 : AccMutationEvent* mutEvent = downcast_accEvent(event);
486 0 : if (mutEvent->mTextChangeEvent) {
487 0 : nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
488 0 : if (!mDocument) {
489 0 : return;
490 : }
491 : }
492 :
493 : // Fire menupopup end event before a hide event if a menu goes away.
494 :
495 : // XXX: We don't look into children of hidden subtree to find hiding
496 : // menupopup (as we did prior bug 570275) because we don't do that when
497 : // menu is showing (and that's impossible until bug 606924 is fixed).
498 : // Nevertheless we should do this at least because layout coalesces
499 : // the changes before our processing and we may miss some menupopup
500 : // events. Now we just want to be consistent in content insertion/removal
501 : // handling.
502 0 : if (event->mAccessible->ARIARole() == roles::MENUPOPUP) {
503 0 : nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
504 0 : event->mAccessible);
505 0 : if (!mDocument) {
506 0 : return;
507 : }
508 : }
509 :
510 0 : AccHideEvent* hideEvent = downcast_accEvent(event);
511 0 : if (hideEvent->NeedsShutdown()) {
512 0 : mDocument->ShutdownChildrenInSubtree(event->mAccessible);
513 : }
514 : }
515 :
516 : // Group the show events by the parent of their target.
517 0 : nsDataHashtable<nsPtrHashKey<Accessible>, nsTArray<AccTreeMutationEvent*>> showEvents;
518 0 : for (AccTreeMutationEvent* event = mFirstMutationEvent;
519 0 : event; event = event->NextEvent()) {
520 0 : if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
521 0 : continue;
522 : }
523 :
524 0 : Accessible* parent = event->GetAccessible()->Parent();
525 0 : showEvents.GetOrInsert(parent).AppendElement(event);
526 : }
527 :
528 : // We need to fire show events for the children of an accessible in the order
529 : // of their indices at this point. So sort each set of events for the same
530 : // container by the index of their target.
531 0 : for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
532 : struct AccIdxComparator {
533 0 : bool LessThan(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
534 : {
535 0 : int32_t aIdx = a->GetAccessible()->IndexInParent();
536 0 : int32_t bIdx = b->GetAccessible()->IndexInParent();
537 0 : MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
538 0 : return aIdx < bIdx;
539 : }
540 0 : bool Equals(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
541 : {
542 0 : DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent();
543 0 : DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent();
544 0 : MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
545 0 : return false;
546 : }
547 : };
548 :
549 0 : nsTArray<AccTreeMutationEvent*>& events = iter.Data();
550 0 : events.Sort(AccIdxComparator());
551 0 : for (AccTreeMutationEvent* event: events) {
552 0 : nsEventShell::FireEvent(event);
553 0 : if (!mDocument) {
554 0 : return;
555 : }
556 :
557 0 : AccMutationEvent* mutEvent = downcast_accEvent(event);
558 0 : if (mutEvent->mTextChangeEvent) {
559 0 : nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
560 0 : if (!mDocument) {
561 0 : return;
562 : }
563 : }
564 : }
565 : }
566 :
567 : // Now we can fire the reorder events after all the show and hide events.
568 0 : for (AccTreeMutationEvent* event = mFirstMutationEvent;
569 0 : event; event = event->NextEvent()) {
570 0 : if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) {
571 0 : continue;
572 : }
573 :
574 0 : nsEventShell::FireEvent(event);
575 0 : if (!mDocument) {
576 0 : return;
577 : }
578 :
579 0 : Accessible* target = event->GetAccessible();
580 0 : target->Document()->MaybeNotifyOfValueChange(target);
581 0 : if (!mDocument) {
582 0 : return;
583 : }
584 : }
585 : }
586 :
587 : ////////////////////////////////////////////////////////////////////////////////
588 : // NotificationCollector: private
589 :
590 : void
591 0 : NotificationController::WillRefresh(mozilla::TimeStamp aTime)
592 : {
593 0 : AUTO_PROFILER_LABEL("NotificationController::WillRefresh", OTHER);
594 :
595 : // If the document accessible that notification collector was created for is
596 : // now shut down, don't process notifications anymore.
597 0 : NS_ASSERTION(mDocument,
598 : "The document was shut down while refresh observer is attached!");
599 0 : if (!mDocument)
600 0 : return;
601 :
602 0 : if (mObservingState == eRefreshProcessing ||
603 0 : mObservingState == eRefreshProcessingForUpdate)
604 0 : return;
605 :
606 : // Any generic notifications should be queued if we're processing content
607 : // insertions or generic notifications.
608 0 : mObservingState = eRefreshProcessingForUpdate;
609 :
610 : // Initial accessible tree construction.
611 0 : if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
612 : // If document is not bound to parent at this point then the document is not
613 : // ready yet (process notifications later).
614 0 : if (!mDocument->IsBoundToParent()) {
615 0 : mObservingState = eRefreshObserving;
616 0 : return;
617 : }
618 :
619 : #ifdef A11Y_LOG
620 0 : if (logging::IsEnabled(logging::eTree)) {
621 0 : logging::MsgBegin("TREE", "initial tree created");
622 0 : logging::Address("document", mDocument);
623 0 : logging::MsgEnd();
624 : }
625 : #endif
626 :
627 0 : mDocument->DoInitialUpdate();
628 :
629 0 : NS_ASSERTION(mContentInsertions.Count() == 0,
630 : "Pending content insertions while initial accessible tree isn't created!");
631 : }
632 :
633 : // Initialize scroll support if needed.
634 0 : if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
635 0 : mDocument->AddScrollListener();
636 :
637 : // Process rendered text change notifications.
638 0 : for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
639 0 : nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
640 0 : nsIContent* textNode = entry->GetKey();
641 0 : Accessible* textAcc = mDocument->GetAccessible(textNode);
642 :
643 : // If the text node is not in tree or doesn't have frame then this case should
644 : // have been handled already by content removal notifications.
645 0 : nsINode* containerNode = textNode->GetParentNode();
646 0 : if (!containerNode) {
647 0 : NS_ASSERTION(!textAcc,
648 : "Text node was removed but accessible is kept alive!");
649 0 : continue;
650 : }
651 :
652 0 : nsIFrame* textFrame = textNode->GetPrimaryFrame();
653 0 : if (!textFrame) {
654 0 : NS_ASSERTION(!textAcc,
655 : "Text node isn't rendered but accessible is kept alive!");
656 0 : continue;
657 : }
658 :
659 : #ifdef A11Y_LOG
660 0 : nsIContent* containerElm = containerNode->IsElement() ?
661 0 : containerNode->AsElement() : nullptr;
662 : #endif
663 :
664 : nsIFrame::RenderedText text = textFrame->GetRenderedText(0,
665 : UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
666 0 : nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
667 :
668 : // Remove text accessible if rendered text is empty.
669 0 : if (textAcc) {
670 0 : if (text.mString.IsEmpty()) {
671 : #ifdef A11Y_LOG
672 0 : if (logging::IsEnabled(logging::eTree | logging::eText)) {
673 0 : logging::MsgBegin("TREE", "text node lost its content; doc: %p", mDocument);
674 0 : logging::Node("container", containerElm);
675 0 : logging::Node("content", textNode);
676 0 : logging::MsgEnd();
677 : }
678 : #endif
679 :
680 0 : mDocument->ContentRemoved(textAcc);
681 0 : continue;
682 : }
683 :
684 : // Update text of the accessible and fire text change events.
685 : #ifdef A11Y_LOG
686 0 : if (logging::IsEnabled(logging::eText)) {
687 0 : logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
688 0 : logging::Node("container", containerElm);
689 0 : logging::Node("content", textNode);
690 0 : logging::MsgEntry("old text '%s'",
691 0 : NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
692 0 : logging::MsgEntry("new text: '%s'",
693 0 : NS_ConvertUTF16toUTF8(text.mString).get());
694 0 : logging::MsgEnd();
695 : }
696 : #endif
697 :
698 0 : TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
699 0 : continue;
700 : }
701 :
702 : // Append an accessible if rendered text is not empty.
703 0 : if (!text.mString.IsEmpty()) {
704 : #ifdef A11Y_LOG
705 0 : if (logging::IsEnabled(logging::eTree | logging::eText)) {
706 0 : logging::MsgBegin("TREE", "text node gains new content; doc: %p", mDocument);
707 0 : logging::Node("container", containerElm);
708 0 : logging::Node("content", textNode);
709 0 : logging::MsgEnd();
710 : }
711 : #endif
712 :
713 0 : Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode);
714 0 : MOZ_ASSERT(container,
715 : "Text node having rendered text hasn't accessible document!");
716 0 : if (container) {
717 : nsTArray<nsCOMPtr<nsIContent>>* list =
718 0 : mContentInsertions.LookupOrAdd(container);
719 0 : list->AppendElement(textNode);
720 : }
721 : }
722 : }
723 0 : mTextHash.Clear();
724 :
725 : // Process content inserted notifications to update the tree.
726 0 : for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
727 0 : mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
728 0 : if (!mDocument) {
729 0 : return;
730 : }
731 : }
732 0 : mContentInsertions.Clear();
733 :
734 : // Bind hanging child documents unless we are using IPC and the
735 : // document has no IPC actor. If we fail to bind the child doc then
736 : // shut it down.
737 0 : uint32_t hangingDocCnt = mHangingChildDocuments.Length();
738 0 : nsTArray<RefPtr<DocAccessible>> newChildDocs;
739 0 : for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
740 0 : DocAccessible* childDoc = mHangingChildDocuments[idx];
741 0 : if (childDoc->IsDefunct())
742 0 : continue;
743 :
744 0 : if (IPCAccessibilityActive() && !mDocument->IPCDoc()) {
745 0 : childDoc->Shutdown();
746 0 : continue;
747 : }
748 :
749 0 : nsIContent* ownerContent = mDocument->DocumentNode()->
750 0 : FindContentForSubDocument(childDoc->DocumentNode());
751 0 : if (ownerContent) {
752 0 : Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
753 0 : if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
754 0 : if (mDocument->AppendChildDocument(childDoc)) {
755 0 : newChildDocs.AppendElement(Move(mHangingChildDocuments[idx]));
756 0 : continue;
757 : }
758 :
759 0 : outerDocAcc->RemoveChild(childDoc);
760 : }
761 :
762 : // Failed to bind the child document, destroy it.
763 0 : childDoc->Shutdown();
764 : }
765 : }
766 :
767 : // Clear the hanging documents list, even if we didn't bind them.
768 0 : mHangingChildDocuments.Clear();
769 0 : MOZ_ASSERT(mDocument, "Illicit document shutdown");
770 0 : if (!mDocument) {
771 0 : return;
772 : }
773 :
774 : // If the document is ready and all its subdocuments are completely loaded
775 : // then process the document load.
776 0 : if (mDocument->HasLoadState(DocAccessible::eReady) &&
777 0 : !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
778 : hangingDocCnt == 0) {
779 0 : uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
780 0 : for (; childDocIdx < childDocCnt; childDocIdx++) {
781 0 : DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
782 0 : if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded))
783 0 : break;
784 : }
785 :
786 0 : if (childDocIdx == childDocCnt) {
787 0 : mDocument->ProcessLoad();
788 0 : if (!mDocument)
789 0 : return;
790 : }
791 : }
792 :
793 : // Process only currently queued generic notifications.
794 0 : nsTArray < RefPtr<Notification> > notifications;
795 0 : notifications.SwapElements(mNotifications);
796 :
797 0 : uint32_t notificationCount = notifications.Length();
798 0 : for (uint32_t idx = 0; idx < notificationCount; idx++) {
799 0 : notifications[idx]->Process();
800 0 : if (!mDocument)
801 0 : return;
802 : }
803 :
804 : // Process invalidation list of the document after all accessible tree
805 : // modification are done.
806 0 : mDocument->ProcessInvalidationList();
807 :
808 : // Process relocation list.
809 0 : for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
810 0 : if (mRelocations[idx]->IsInDocument()) {
811 0 : mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
812 : }
813 : }
814 0 : mRelocations.Clear();
815 :
816 : // If a generic notification occurs after this point then we may be allowed to
817 : // process it synchronously. However we do not want to reenter if fireing
818 : // events causes script to run.
819 0 : mObservingState = eRefreshProcessing;
820 :
821 0 : CoalesceMutationEvents();
822 0 : ProcessMutationEvents();
823 0 : mEventGeneration = 0;
824 :
825 : // Now that we are done with them get rid of the events we fired.
826 0 : RefPtr<AccTreeMutationEvent> mutEvent = Move(mFirstMutationEvent);
827 0 : mLastMutationEvent = nullptr;
828 0 : mFirstMutationEvent = nullptr;
829 0 : while (mutEvent) {
830 0 : RefPtr<AccTreeMutationEvent> nextEvent = mutEvent->NextEvent();
831 0 : Accessible* target = mutEvent->GetAccessible();
832 :
833 : // We need to be careful here, while it may seem that we can simply 0 all
834 : // the pending event bits that is not true. Because accessibles may be
835 : // reparented they may be the target of both a hide event and a show event
836 : // at the same time.
837 0 : if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
838 0 : target->SetShowEventTarget(false);
839 : }
840 :
841 0 : if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
842 0 : target->SetHideEventTarget(false);
843 : }
844 :
845 : // However it is not possible for a reorder event target to also be the
846 : // target of a show or hide, so we can just zero that.
847 0 : target->SetReorderEventTarget(false);
848 :
849 0 : mutEvent->SetPrevEvent(nullptr);
850 0 : mutEvent->SetNextEvent(nullptr);
851 0 : mMutationMap.RemoveEvent(mutEvent);
852 0 : mutEvent = nextEvent;
853 : }
854 :
855 0 : ProcessEventQueue();
856 :
857 0 : if (IPCAccessibilityActive()) {
858 0 : size_t newDocCount = newChildDocs.Length();
859 0 : for (size_t i = 0; i < newDocCount; i++) {
860 0 : DocAccessible* childDoc = newChildDocs[i];
861 0 : if (childDoc->IsDefunct()) {
862 0 : continue;
863 : }
864 :
865 0 : Accessible* parent = childDoc->Parent();
866 0 : DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
867 0 : MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc);
868 0 : uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
869 0 : MOZ_DIAGNOSTIC_ASSERT(id);
870 0 : DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
871 0 : if (ipcDoc) {
872 0 : parentIPCDoc->SendBindChildDoc(ipcDoc, id);
873 0 : continue;
874 : }
875 :
876 0 : ipcDoc = new DocAccessibleChild(childDoc, parentIPCDoc->Manager());
877 0 : childDoc->SetIPCDoc(ipcDoc);
878 :
879 : #if defined(XP_WIN)
880 : parentIPCDoc->ConstructChildDocInParentProcess(ipcDoc, id,
881 : AccessibleWrap::GetChildIDFor(childDoc));
882 : #else
883 : nsCOMPtr<nsITabChild> tabChild =
884 0 : do_GetInterface(mDocument->DocumentNode()->GetDocShell());
885 0 : if (tabChild) {
886 0 : static_cast<TabChild*>(tabChild.get())->
887 0 : SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
888 : }
889 : #endif
890 : }
891 : }
892 :
893 0 : mObservingState = eRefreshObserving;
894 0 : if (!mDocument)
895 0 : return;
896 :
897 : // Stop further processing if there are no new notifications of any kind or
898 : // events and document load is processed.
899 0 : if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() &&
900 0 : mEvents.IsEmpty() && mTextHash.Count() == 0 &&
901 0 : mHangingChildDocuments.IsEmpty() &&
902 0 : mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
903 0 : mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
904 0 : mObservingState = eNotObservingRefresh;
905 : }
906 : }
907 :
908 : void
909 0 : NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent)
910 : {
911 0 : EventType type = GetEventType(aEvent);
912 0 : uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
913 0 : MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
914 0 : addr |= type;
915 0 : mTable.Put(addr, aEvent);
916 0 : }
917 :
918 : AccTreeMutationEvent*
919 0 : NotificationController::EventMap::GetEvent(Accessible* aTarget, EventType aType)
920 : {
921 0 : uint64_t addr = reinterpret_cast<uintptr_t>(aTarget);
922 0 : MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned");
923 :
924 0 : addr |= aType;
925 0 : return mTable.GetWeak(addr);
926 : }
927 :
928 : void
929 0 : NotificationController::EventMap::RemoveEvent(AccTreeMutationEvent* aEvent)
930 : {
931 0 : EventType type = GetEventType(aEvent);
932 0 : uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
933 0 : MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
934 0 : addr |= type;
935 :
936 0 : MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
937 0 : mTable.Remove(addr);
938 0 : }
939 :
940 : NotificationController::EventMap::EventType
941 0 : NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent)
942 : {
943 0 : switch(aEvent->GetEventType())
944 : {
945 : case nsIAccessibleEvent::EVENT_SHOW:
946 0 : return ShowEvent;
947 : case nsIAccessibleEvent::EVENT_HIDE:
948 0 : return HideEvent;
949 : case nsIAccessibleEvent::EVENT_REORDER:
950 0 : return ReorderEvent;
951 : default:
952 0 : MOZ_ASSERT_UNREACHABLE("event has invalid type");
953 : return ShowEvent;
954 : }
955 : }
|