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 "DocAccessibleChild.h"
10 #include "LocalAccessible-inl.h"
11 #include "nsEventShell.h"
12 #include "TextLeafAccessible.h"
13 #include "TextUpdater.h"
15 #include "nsIContentInlines.h"
17 #include "mozilla/dom/BrowserChild.h"
18 #include "mozilla/dom/Element.h"
19 #include "mozilla/ipc/ProcessChild.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/ProfilerMarkers.h"
22 #include "nsAccessibilityService.h"
23 #include "mozilla/Telemetry.h"
25 using namespace mozilla
;
26 using namespace mozilla::a11y
;
27 using namespace mozilla::dom
;
29 ////////////////////////////////////////////////////////////////////////////////
30 // NotificationCollector
31 ////////////////////////////////////////////////////////////////////////////////
33 NotificationController::NotificationController(DocAccessible
* aDocument
,
34 PresShell
* aPresShell
)
35 : EventQueue(aDocument
),
36 mObservingState(eNotObservingRefresh
),
37 mPresShell(aPresShell
),
39 // Schedule initial accessible tree construction.
43 NotificationController::~NotificationController() {
44 NS_ASSERTION(!mDocument
, "Controller wasn't shutdown properly!");
48 MOZ_RELEASE_ASSERT(mObservingState
== eNotObservingRefresh
,
49 "Must unregister before being destroyed");
52 ////////////////////////////////////////////////////////////////////////////////
53 // NotificationCollector: AddRef/Release and cycle collection
55 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController
)
56 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController
)
58 NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController
)
60 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController
)
64 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
66 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController
)
67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments
)
68 for (const auto& entry
: tmp
->mContentInsertions
) {
69 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mContentInsertions key");
70 cb
.NoteXPCOMChild(entry
.GetKey());
71 nsTArray
<nsCOMPtr
<nsIContent
>>* list
= entry
.GetData().get();
72 for (uint32_t i
= 0; i
< list
->Length(); i
++) {
73 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mContentInsertions value item");
74 cb
.NoteXPCOMChild(list
->ElementAt(i
));
77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusEvent
)
78 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents
)
79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations
)
80 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
82 ////////////////////////////////////////////////////////////////////////////////
83 // NotificationCollector: public
85 void NotificationController::Shutdown() {
86 if (mObservingState
!= eNotObservingRefresh
&&
87 mPresShell
->RemoveRefreshObserver(this, FlushType::Display
)) {
88 // Note, this was our last chance to unregister, since we're about to
89 // clear mPresShell further down in this function.
90 mObservingState
= eNotObservingRefresh
;
92 MOZ_RELEASE_ASSERT(mObservingState
== eNotObservingRefresh
,
93 "Must unregister before being destroyed (and we just "
94 "passed our last change to unregister)");
95 // Immediately null out mPresShell, to prevent us from being registered as a
96 // refresh observer again.
99 // Shutdown handling child documents.
100 int32_t childDocCount
= mHangingChildDocuments
.Length();
101 for (int32_t idx
= childDocCount
- 1; idx
>= 0; idx
--) {
102 if (!mHangingChildDocuments
[idx
]->IsDefunct()) {
103 mHangingChildDocuments
[idx
]->Shutdown();
107 mHangingChildDocuments
.Clear();
112 mContentInsertions
.Clear();
113 mNotifications
.Clear();
114 mFocusEvent
= nullptr;
116 mRelocations
.Clear();
119 void NotificationController::CoalesceHideEvent(AccHideEvent
* aHideEvent
) {
120 LocalAccessible
* parent
= aHideEvent
->LocalParent();
122 if (parent
->IsDoc()) {
126 if (parent
->HideEventTarget()) {
127 DropMutationEvent(aHideEvent
);
131 if (parent
->ShowEventTarget()) {
132 AccShowEvent
* showEvent
=
133 downcast_accEvent(mMutationMap
.GetEvent(parent
, EventMap::ShowEvent
));
134 if (showEvent
->EventGeneration() < aHideEvent
->EventGeneration()) {
135 DropMutationEvent(aHideEvent
);
140 parent
= parent
->LocalParent();
144 bool NotificationController::QueueMutationEvent(AccTreeMutationEvent
* aEvent
) {
145 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
146 // We have to allow there to be a hide and then a show event for a target
147 // because of targets getting moved. However we need to coalesce a show and
148 // then a hide for a target which means we need to check for that here.
149 if (aEvent
->GetAccessible()->ShowEventTarget()) {
150 AccTreeMutationEvent
* showEvent
=
151 mMutationMap
.GetEvent(aEvent
->GetAccessible(), EventMap::ShowEvent
);
152 DropMutationEvent(showEvent
);
156 // Don't queue a hide event on an accessible that's already being moved. It
157 // or an ancestor should already have a hide event queued.
159 mDocument
->IsAccessibleBeingMoved(aEvent
->GetAccessible())) {
163 // If this is an additional hide event, the accessible may be hidden, or
164 // moved again after a move. Preserve the original hide event since
165 // its properties are consistent with the tree that existed before
166 // the next batch of mutation events is processed.
167 if (aEvent
->GetAccessible()->HideEventTarget()) {
172 AccMutationEvent
* mutEvent
= downcast_accEvent(aEvent
);
174 mutEvent
->SetEventGeneration(mEventGeneration
);
176 if (!mFirstMutationEvent
) {
177 mFirstMutationEvent
= aEvent
;
178 ScheduleProcessing();
181 if (mLastMutationEvent
) {
182 NS_ASSERTION(!mLastMutationEvent
->NextEvent(),
183 "why isn't the last event the end?");
184 mLastMutationEvent
->SetNextEvent(aEvent
);
187 aEvent
->SetPrevEvent(mLastMutationEvent
);
188 mLastMutationEvent
= aEvent
;
189 mMutationMap
.PutEvent(aEvent
);
191 // Because we could be hiding the target of a show event we need to get rid
192 // of any such events.
193 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
194 CoalesceHideEvent(downcast_accEvent(aEvent
));
196 // mLastMutationEvent will point to something other than aEvent if and only
197 // if aEvent was just coalesced away. In that case a parent accessible
198 // must already have the required reorder and text change events so we are
200 if (mLastMutationEvent
!= aEvent
) {
205 // We need to fire a reorder event after all of the events targeted at shown
206 // or hidden children of a container. So either queue a new one, or move an
207 // existing one to the end of the queue if the container already has a
209 LocalAccessible
* container
= aEvent
->GetAccessible()->LocalParent();
210 RefPtr
<AccReorderEvent
> reorder
;
211 if (!container
->ReorderEventTarget()) {
212 reorder
= new AccReorderEvent(container
);
213 container
->SetReorderEventTarget(true);
214 mMutationMap
.PutEvent(reorder
);
216 // Since this is the first child of container that is changing, the name
217 // and/or description of dependent Accessibles may be changing.
218 if (PushNameOrDescriptionChange(aEvent
)) {
219 ScheduleProcessing();
222 AccReorderEvent
* event
= downcast_accEvent(
223 mMutationMap
.GetEvent(container
, EventMap::ReorderEvent
));
225 if (mFirstMutationEvent
== event
) {
226 mFirstMutationEvent
= event
->NextEvent();
228 event
->PrevEvent()->SetNextEvent(event
->NextEvent());
231 event
->NextEvent()->SetPrevEvent(event
->PrevEvent());
232 event
->SetNextEvent(nullptr);
235 reorder
->SetEventGeneration(mEventGeneration
);
236 reorder
->SetPrevEvent(mLastMutationEvent
);
237 mLastMutationEvent
->SetNextEvent(reorder
);
238 mLastMutationEvent
= reorder
;
240 // It is not possible to have a text change event for something other than a
241 // hyper text accessible.
242 if (!container
->IsHyperText()) {
246 MOZ_ASSERT(mutEvent
);
249 aEvent
->GetAccessible()->AppendTextTo(text
);
250 if (text
.IsEmpty()) {
254 LocalAccessible
* target
= aEvent
->GetAccessible();
255 int32_t offset
= container
->AsHyperText()->GetChildOffset(target
);
256 AccTreeMutationEvent
* prevEvent
= aEvent
->PrevEvent();
258 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
259 prevEvent
= prevEvent
->PrevEvent();
263 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
&&
264 mutEvent
->IsHide()) {
265 AccHideEvent
* prevHide
= downcast_accEvent(prevEvent
);
266 AccTextChangeEvent
* prevTextChange
= prevHide
->mTextChangeEvent
;
267 if (prevTextChange
&& prevHide
->LocalParent() == mutEvent
->LocalParent()) {
268 if (prevHide
->mNextSibling
== target
) {
269 target
->AppendTextTo(prevTextChange
->mModifiedText
);
270 prevHide
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
271 } else if (prevHide
->mPrevSibling
== target
) {
273 target
->AppendTextTo(temp
);
275 uint32_t extraLen
= temp
.Length();
276 temp
+= prevTextChange
->mModifiedText
;
278 prevTextChange
->mModifiedText
= temp
;
279 prevTextChange
->mStart
-= extraLen
;
280 prevHide
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
283 } else if (prevEvent
&& mutEvent
->IsShow() &&
284 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_SHOW
) {
285 AccShowEvent
* prevShow
= downcast_accEvent(prevEvent
);
286 AccTextChangeEvent
* prevTextChange
= prevShow
->mTextChangeEvent
;
287 if (prevTextChange
&& prevShow
->LocalParent() == target
->LocalParent()) {
288 int32_t index
= target
->IndexInParent();
289 int32_t prevIndex
= prevShow
->GetAccessible()->IndexInParent();
290 if (prevIndex
+ 1 == index
) {
291 target
->AppendTextTo(prevTextChange
->mModifiedText
);
292 prevShow
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
293 } else if (index
+ 1 == prevIndex
) {
295 target
->AppendTextTo(temp
);
296 prevTextChange
->mStart
-= temp
.Length();
297 temp
+= prevTextChange
->mModifiedText
;
298 prevTextChange
->mModifiedText
= temp
;
299 prevShow
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
304 if (!mutEvent
->mTextChangeEvent
) {
305 mutEvent
->mTextChangeEvent
= new AccTextChangeEvent(
306 container
, offset
, text
, mutEvent
->IsShow(),
307 aEvent
->mIsFromUserInput
? eFromUserInput
: eNoUserInput
);
313 void NotificationController::DropMutationEvent(AccTreeMutationEvent
* aEvent
) {
314 const uint32_t eventType
= aEvent
->GetEventType();
315 MOZ_ASSERT(eventType
!= nsIAccessibleEvent::EVENT_INNER_REORDER
,
316 "Inner reorder has already been dropped, cannot drop again");
317 if (eventType
== nsIAccessibleEvent::EVENT_REORDER
) {
318 // We don't fully drop reorder events, we just change them to inner reorder
320 AccReorderEvent
* reorderEvent
= downcast_accEvent(aEvent
);
322 MOZ_ASSERT(reorderEvent
);
323 reorderEvent
->SetInner();
326 if (eventType
== nsIAccessibleEvent::EVENT_SHOW
) {
327 // unset the event bits since the event isn't being fired any more.
328 aEvent
->GetAccessible()->SetShowEventTarget(false);
329 } else if (eventType
== nsIAccessibleEvent::EVENT_HIDE
) {
330 // unset the event bits since the event isn't being fired any more.
331 aEvent
->GetAccessible()->SetHideEventTarget(false);
333 AccHideEvent
* hideEvent
= downcast_accEvent(aEvent
);
334 MOZ_ASSERT(hideEvent
);
336 if (hideEvent
->NeedsShutdown()) {
337 mDocument
->ShutdownChildrenInSubtree(aEvent
->GetAccessible());
340 MOZ_ASSERT_UNREACHABLE("Mutation event has non-mutation event type");
343 // Do the work to splice the event out of the list.
344 if (mFirstMutationEvent
== aEvent
) {
345 mFirstMutationEvent
= aEvent
->NextEvent();
347 aEvent
->PrevEvent()->SetNextEvent(aEvent
->NextEvent());
350 if (mLastMutationEvent
== aEvent
) {
351 mLastMutationEvent
= aEvent
->PrevEvent();
353 aEvent
->NextEvent()->SetPrevEvent(aEvent
->PrevEvent());
356 aEvent
->SetPrevEvent(nullptr);
357 aEvent
->SetNextEvent(nullptr);
358 mMutationMap
.RemoveEvent(aEvent
);
361 void NotificationController::CoalesceMutationEvents() {
362 AccTreeMutationEvent
* event
= mFirstMutationEvent
;
364 AccTreeMutationEvent
* nextEvent
= event
->NextEvent();
365 uint32_t eventType
= event
->GetEventType();
366 if (event
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
367 LocalAccessible
* acc
= event
->GetAccessible();
373 // if a parent of the reorder event's target is being hidden that
374 // hide event's target must have a parent that is also a reorder event
375 // target. That means we don't need this reorder event.
376 if (acc
->HideEventTarget()) {
377 DropMutationEvent(event
);
381 LocalAccessible
* parent
= acc
->LocalParent();
382 if (parent
&& parent
->ReorderEventTarget()) {
383 AccReorderEvent
* reorder
= downcast_accEvent(
384 mMutationMap
.GetEvent(parent
, EventMap::ReorderEvent
));
386 // We want to make sure that a reorder event comes after any show or
387 // hide events targeted at the children of its target. We keep the
388 // invariant that event generation goes up as you are farther in the
389 // queue, so we want to use the spot of the event with the higher
390 // generation number, and keep that generation number.
392 reorder
->EventGeneration() < event
->EventGeneration()) {
393 reorder
->SetEventGeneration(event
->EventGeneration());
395 // It may be true that reorder was before event, and we coalesced
396 // away all the show / hide events between them. In that case
397 // event is already immediately after reorder in the queue and we
398 // do not need to rearrange the list of events.
399 if (event
!= reorder
->NextEvent()) {
400 // There really should be a show or hide event before the first
402 if (reorder
->PrevEvent()) {
403 reorder
->PrevEvent()->SetNextEvent(reorder
->NextEvent());
405 mFirstMutationEvent
= reorder
->NextEvent();
408 reorder
->NextEvent()->SetPrevEvent(reorder
->PrevEvent());
409 event
->PrevEvent()->SetNextEvent(reorder
);
410 reorder
->SetPrevEvent(event
->PrevEvent());
411 event
->SetPrevEvent(reorder
);
412 reorder
->SetNextEvent(event
);
415 DropMutationEvent(event
);
421 } else if (eventType
== nsIAccessibleEvent::EVENT_SHOW
) {
422 LocalAccessible
* parent
= event
->GetAccessible()->LocalParent();
424 if (parent
->IsDoc()) {
428 // if the parent of a show event is being either shown or hidden then
429 // we don't need to fire a show event for a subtree of that change.
430 if (parent
->ShowEventTarget() || parent
->HideEventTarget()) {
431 DropMutationEvent(event
);
435 parent
= parent
->LocalParent();
437 } else if (eventType
== nsIAccessibleEvent::EVENT_HIDE
) {
438 MOZ_ASSERT(eventType
== nsIAccessibleEvent::EVENT_HIDE
,
439 "mutation event list has an invalid event");
441 AccHideEvent
* hideEvent
= downcast_accEvent(event
);
442 CoalesceHideEvent(hideEvent
);
449 void NotificationController::ScheduleChildDocBinding(DocAccessible
* aDocument
) {
450 // Schedule child document binding to the tree.
451 mHangingChildDocuments
.AppendElement(aDocument
);
452 ScheduleProcessing();
455 void NotificationController::ScheduleContentInsertion(
456 LocalAccessible
* aContainer
, nsTArray
<nsCOMPtr
<nsIContent
>>& aInsertions
) {
457 if (!aInsertions
.IsEmpty()) {
458 mContentInsertions
.GetOrInsertNew(aContainer
)->AppendElements(aInsertions
);
459 ScheduleProcessing();
463 void NotificationController::ScheduleProcessing() {
464 // If notification flush isn't planned yet, start notification flush
465 // asynchronously (after style and layout).
466 // Note: the mPresShell null-check might be unnecessary; it's just to prevent
467 // a null-deref here, if we somehow get called after we've been shut down.
468 if (mObservingState
== eNotObservingRefresh
&& mPresShell
) {
469 if (mPresShell
->AddRefreshObserver(this, FlushType::Display
,
470 "Accessibility notifications")) {
471 mObservingState
= eRefreshObserving
;
476 ////////////////////////////////////////////////////////////////////////////////
477 // NotificationCollector: protected
479 bool NotificationController::IsUpdatePending() {
480 return mPresShell
->IsLayoutFlushObserver() ||
481 mObservingState
== eRefreshProcessingForUpdate
|| WaitingForParent() ||
482 mContentInsertions
.Count() != 0 || mNotifications
.Length() != 0 ||
483 !mTextArray
.IsEmpty() ||
484 !mDocument
->HasLoadState(DocAccessible::eTreeConstructed
);
487 bool NotificationController::WaitingForParent() {
488 DocAccessible
* parentdoc
= mDocument
->ParentDocument();
493 NotificationController
* parent
= parentdoc
->mNotificationController
;
494 if (!parent
|| parent
== this) {
495 // Do not wait for nothing or ourselves
499 // Wait for parent's notifications processing
500 return parent
->mContentInsertions
.Count() != 0 ||
501 parent
->mNotifications
.Length() != 0;
504 void NotificationController::ProcessMutationEvents() {
505 // Firing an event can indirectly run script; e.g. an XPCOM event observer
506 // or querying a XUL interface. Further mutations might be queued as a result.
507 // It's important that the mutation queue and state bits from one tick don't
508 // interfere with the next tick. Otherwise, we can end up dropping events.
510 // 1. Clear the state bits, which we only need for coalescence.
511 for (AccTreeMutationEvent
* event
= mFirstMutationEvent
; event
;
512 event
= event
->NextEvent()) {
513 LocalAccessible
* acc
= event
->GetAccessible();
514 acc
->SetShowEventTarget(false);
515 acc
->SetHideEventTarget(false);
516 acc
->SetReorderEventTarget(false);
518 // 2. Keep the current queue locally, but clear the queue on the instance.
519 RefPtr
<AccTreeMutationEvent
> firstEvent
= mFirstMutationEvent
;
520 mFirstMutationEvent
= mLastMutationEvent
= nullptr;
521 mMutationMap
.Clear();
522 mEventGeneration
= 0;
524 // Group the show events by the parent of their target.
525 nsTHashMap
<nsPtrHashKey
<LocalAccessible
>, nsTArray
<AccTreeMutationEvent
*>>
527 for (AccTreeMutationEvent
* event
= firstEvent
; event
;
528 event
= event
->NextEvent()) {
529 if (event
->GetEventType() != nsIAccessibleEvent::EVENT_SHOW
) {
533 LocalAccessible
* parent
= event
->GetAccessible()->LocalParent();
534 showEvents
.LookupOrInsert(parent
).AppendElement(event
);
537 // We need to fire show events for the children of an accessible in the order
538 // of their indices at this point. So sort each set of events for the same
539 // container by the index of their target. We do this before firing any events
540 // because firing an event might indirectly run script which might alter the
541 // tree, breaking our sort. However, we don't actually fire the events yet.
542 for (auto iter
= showEvents
.Iter(); !iter
.Done(); iter
.Next()) {
543 struct AccIdxComparator
{
544 bool LessThan(const AccTreeMutationEvent
* a
,
545 const AccTreeMutationEvent
* b
) const {
546 int32_t aIdx
= a
->GetAccessible()->IndexInParent();
547 int32_t bIdx
= b
->GetAccessible()->IndexInParent();
548 MOZ_ASSERT(aIdx
>= 0 && bIdx
>= 0 && (a
== b
|| aIdx
!= bIdx
));
551 bool Equals(const AccTreeMutationEvent
* a
,
552 const AccTreeMutationEvent
* b
) const {
553 DebugOnly
<int32_t> aIdx
= a
->GetAccessible()->IndexInParent();
554 DebugOnly
<int32_t> bIdx
= b
->GetAccessible()->IndexInParent();
555 MOZ_ASSERT(aIdx
>= 0 && bIdx
>= 0 && (a
== b
|| aIdx
!= bIdx
));
560 nsTArray
<AccTreeMutationEvent
*>& events
= iter
.Data();
561 events
.Sort(AccIdxComparator());
564 // there is no reason to fire a hide event for a child of a show event
565 // target. That can happen if something is inserted into the tree and
566 // removed before the next refresh driver tick, but it should not be
567 // observable outside gecko so it should be safe to coalesce away any such
568 // events. This means that it should be fine to fire all of the hide events
569 // first, and then deal with any shown subtrees.
570 for (AccTreeMutationEvent
* event
= firstEvent
; event
;
571 event
= event
->NextEvent()) {
572 if (event
->GetEventType() != nsIAccessibleEvent::EVENT_HIDE
) {
576 nsEventShell::FireEvent(event
);
581 AccMutationEvent
* mutEvent
= downcast_accEvent(event
);
582 if (mutEvent
->mTextChangeEvent
) {
583 nsEventShell::FireEvent(mutEvent
->mTextChangeEvent
);
589 // Fire menupopup end event before a hide event if a menu goes away.
591 // XXX: We don't look into children of hidden subtree to find hiding
592 // menupopup (as we did prior bug 570275) because we don't do that when
593 // menu is showing (and that's impossible until bug 606924 is fixed).
594 // Nevertheless we should do this at least because layout coalesces
595 // the changes before our processing and we may miss some menupopup
596 // events. Now we just want to be consistent in content insertion/removal
598 if (event
->mAccessible
->ARIARole() == roles::MENUPOPUP
) {
599 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END
,
606 AccHideEvent
* hideEvent
= downcast_accEvent(event
);
607 if (hideEvent
->NeedsShutdown()) {
608 mDocument
->ShutdownChildrenInSubtree(event
->mAccessible
);
612 // Fire the show events we sorted earlier.
613 for (auto iter
= showEvents
.Iter(); !iter
.Done(); iter
.Next()) {
614 nsTArray
<AccTreeMutationEvent
*>& events
= iter
.Data();
615 for (AccTreeMutationEvent
* event
: events
) {
616 nsEventShell::FireEvent(event
);
621 AccMutationEvent
* mutEvent
= downcast_accEvent(event
);
622 if (mutEvent
->mTextChangeEvent
) {
623 nsEventShell::FireEvent(mutEvent
->mTextChangeEvent
);
631 // Now we can fire the reorder events after all the show and hide events.
632 for (const uint32_t reorderType
: {nsIAccessibleEvent::EVENT_INNER_REORDER
,
633 nsIAccessibleEvent::EVENT_REORDER
}) {
634 for (AccTreeMutationEvent
* event
= firstEvent
; event
;
635 event
= event
->NextEvent()) {
636 if (event
->GetEventType() != reorderType
) {
640 if (event
->GetAccessible()->IsDefunct()) {
641 // An inner reorder target may have been hidden itself and no
642 // longer bound to the document.
643 MOZ_ASSERT(reorderType
== nsIAccessibleEvent::EVENT_INNER_REORDER
,
644 "An 'outer' reorder target should not be defunct");
648 nsEventShell::FireEvent(event
);
653 LocalAccessible
* target
= event
->GetAccessible();
654 target
->Document()->MaybeNotifyOfValueChange(target
);
661 // Our events are in a doubly linked list. Clear the pointers to reduce
662 // pressure on the cycle collector. Even though clearing the previous pointers
663 // removes cycles, this isn't enough. The cycle collector still gets bogged
664 // down when there are lots of mutation events if the next pointers aren't
665 // cleared. Even without the cycle collector, not clearing the next pointers
666 // potentially results in deep recursion because releasing each event releases
668 RefPtr
<AccTreeMutationEvent
> event
= firstEvent
;
670 RefPtr
<AccTreeMutationEvent
> next
= event
->NextEvent();
671 event
->SetNextEvent(nullptr);
672 event
->SetPrevEvent(nullptr);
677 ////////////////////////////////////////////////////////////////////////////////
678 // NotificationCollector: private
680 void NotificationController::WillRefresh(mozilla::TimeStamp aTime
) {
681 AUTO_PROFILER_MARKER_TEXT("NotificationController::WillRefresh", A11Y
, {},
683 Telemetry::AutoTimer
<Telemetry::A11Y_TREE_UPDATE_TIMING_MS
> timer
;
684 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
686 AUTO_PROFILER_LABEL("NotificationController::WillRefresh", A11Y
);
688 // If mDocument is null, the document accessible that this notification
689 // controller was created for is now shut down. This means we've lost our
690 // ability to unregister ourselves, which is bad. (However, it also shouldn't
691 // be logically possible for us to get here with a null mDocument; the only
692 // thing that clears that pointer is our Shutdown() method, which first
693 // unregisters and fatally asserts if that fails).
696 "The document was shut down while refresh observer is attached!");
698 if (ipc::ProcessChild::ExpectingShutdown()) {
702 // Wait until an update, we have started, or an interruptible reflow is
703 // finished. We also check the existance of our pres context and root pres
704 // context, since if we can't reach either of these the frame tree is being
706 nsPresContext
* pc
= mPresShell
->GetPresContext();
707 if (mObservingState
== eRefreshProcessing
||
708 mObservingState
== eRefreshProcessingForUpdate
||
709 mPresShell
->IsReflowInterrupted() || !pc
|| !pc
->GetRootPresContext()) {
713 // Process parent's notifications before ours, to get proper ordering between
714 // e.g. tab event and content event.
715 if (WaitingForParent()) {
716 mDocument
->ParentDocument()->mNotificationController
->WillRefresh(aTime
);
717 if (!mDocument
|| ipc::ProcessChild::ExpectingShutdown()) {
722 // Any generic notifications should be queued if we're processing content
723 // insertions or generic notifications.
724 mObservingState
= eRefreshProcessingForUpdate
;
726 // Initial accessible tree construction.
727 if (!mDocument
->HasLoadState(DocAccessible::eTreeConstructed
)) {
728 // (1) If document is not bound to parent at this point, or
729 // (2) the PresShell is not initialized (and it isn't about:blank),
730 // then the document is not ready yet (process notifications later).
731 if (!mDocument
->IsBoundToParent() ||
732 (!mPresShell
->DidInitialize() &&
733 !mDocument
->DocumentNode()->IsInitialDocument())) {
734 mObservingState
= eRefreshObserving
;
739 if (logging::IsEnabled(logging::eTree
)) {
740 logging::MsgBegin("TREE", "initial tree created");
741 logging::Address("document", mDocument
);
746 mDocument
->DoInitialUpdate();
747 if (ipc::ProcessChild::ExpectingShutdown()) {
751 NS_ASSERTION(mContentInsertions
.Count() == 0,
752 "Pending content insertions while initial accessible tree "
756 mDocument
->ProcessPendingUpdates();
758 // Process rendered text change notifications. Even though we want to process
759 // them in the order in which they were queued, we still want to avoid
761 nsTHashSet
<nsIContent
*> textHash
;
762 for (nsIContent
* textNode
: mTextArray
) {
763 if (!textHash
.EnsureInserted(textNode
)) {
764 continue; // Already processed.
766 LocalAccessible
* textAcc
= mDocument
->GetAccessible(textNode
);
768 // If the text node is not in tree or doesn't have a frame, or placed in
769 // another document, then this case should have been handled already by
770 // content removal notifications.
771 nsINode
* containerNode
= textNode
->GetFlattenedTreeParentNode();
772 if (!containerNode
|| textNode
->OwnerDoc() != mDocument
->DocumentNode()) {
774 "Text node was removed but accessible is kept alive!");
778 nsIFrame
* textFrame
= textNode
->GetPrimaryFrame();
781 "Text node isn't rendered but accessible is kept alive!");
786 nsIContent
* containerElm
=
787 containerNode
->IsElement() ? containerNode
->AsElement() : nullptr;
790 nsIFrame::RenderedText text
= textFrame
->GetRenderedText(
791 0, UINT32_MAX
, nsIFrame::TextOffsetType::OffsetsInContentText
,
792 nsIFrame::TrailingWhitespace::DontTrim
);
794 // Remove text accessible if rendered text is empty.
796 if (text
.mString
.IsEmpty()) {
798 if (logging::IsEnabled(logging::eTree
| logging::eText
)) {
799 logging::MsgBegin("TREE", "text node lost its content; doc: %p",
801 logging::Node("container", containerElm
);
802 logging::Node("content", textNode
);
807 mDocument
->ContentRemoved(textAcc
);
811 // Update text of the accessible and fire text change events.
813 if (logging::IsEnabled(logging::eText
)) {
814 logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument
);
815 logging::Node("container", containerElm
);
816 logging::Node("content", textNode
);
819 NS_ConvertUTF16toUTF8(textAcc
->AsTextLeaf()->Text()).get());
820 logging::MsgEntry("new text: '%s'",
821 NS_ConvertUTF16toUTF8(text
.mString
).get());
826 TextUpdater::Run(mDocument
, textAcc
->AsTextLeaf(), text
.mString
);
830 // Append an accessible if rendered text is not empty.
831 if (!text
.mString
.IsEmpty()) {
833 if (logging::IsEnabled(logging::eTree
| logging::eText
)) {
834 logging::MsgBegin("TREE", "text node gains new content; doc: %p",
836 logging::Node("container", containerElm
);
837 logging::Node("content", textNode
);
842 MOZ_ASSERT(mDocument
->AccessibleOrTrueContainer(containerNode
),
843 "Text node having rendered text hasn't accessible document!");
845 LocalAccessible
* container
=
846 mDocument
->AccessibleOrTrueContainer(containerNode
, true);
848 nsTArray
<nsCOMPtr
<nsIContent
>>* list
=
849 mContentInsertions
.GetOrInsertNew(container
);
850 list
->AppendElement(textNode
);
857 // Process content inserted notifications to update the tree.
858 // Processing an insertion can indirectly run script (e.g. querying a XUL
859 // interface), which might result in another insertion being queued.
860 // We don't want to lose any queued insertions if this happens. Therefore, we
861 // move the current insertions into a temporary data structure and process
862 // them from there. Any insertions queued during processing will get handled
863 // in subsequent refresh driver ticks.
864 const auto contentInsertions
= std::move(mContentInsertions
);
865 for (const auto& entry
: contentInsertions
) {
866 mDocument
->ProcessContentInserted(entry
.GetKey(), entry
.GetData().get());
872 // Bind hanging child documents unless we are using IPC and the
873 // document has no IPC actor. If we fail to bind the child doc then
875 uint32_t hangingDocCnt
= mHangingChildDocuments
.Length();
876 nsTArray
<RefPtr
<DocAccessible
>> newChildDocs
;
877 for (uint32_t idx
= 0; idx
< hangingDocCnt
; idx
++) {
878 DocAccessible
* childDoc
= mHangingChildDocuments
[idx
];
879 if (childDoc
->IsDefunct()) {
883 if (IPCAccessibilityActive() && !mDocument
->IPCDoc()) {
884 childDoc
->Shutdown();
888 nsIContent
* ownerContent
= childDoc
->DocumentNode()->GetEmbedderElement();
890 LocalAccessible
* outerDocAcc
= mDocument
->GetAccessible(ownerContent
);
891 if (outerDocAcc
&& outerDocAcc
->AppendChild(childDoc
)) {
892 if (mDocument
->AppendChildDocument(childDoc
)) {
893 newChildDocs
.AppendElement(std::move(mHangingChildDocuments
[idx
]));
897 outerDocAcc
->RemoveChild(childDoc
);
900 // Failed to bind the child document, destroy it.
901 childDoc
->Shutdown();
905 // Clear the hanging documents list, even if we didn't bind them.
906 mHangingChildDocuments
.Clear();
907 MOZ_ASSERT(mDocument
, "Illicit document shutdown");
912 // If the document is ready and all its subdocuments are completely loaded
913 // then process the document load.
914 if (mDocument
->HasLoadState(DocAccessible::eReady
) &&
915 !mDocument
->HasLoadState(DocAccessible::eCompletelyLoaded
) &&
916 hangingDocCnt
== 0) {
917 uint32_t childDocCnt
= mDocument
->ChildDocumentCount(), childDocIdx
= 0;
918 for (; childDocIdx
< childDocCnt
; childDocIdx
++) {
919 DocAccessible
* childDoc
= mDocument
->GetChildDocumentAt(childDocIdx
);
920 if (!childDoc
->HasLoadState(DocAccessible::eCompletelyLoaded
)) {
925 if (childDocIdx
== childDocCnt
) {
926 mDocument
->ProcessLoad();
933 // Process invalidation list of the document after all accessible tree
935 mDocument
->ProcessInvalidationList();
937 // Process relocation list.
938 for (uint32_t idx
= 0; idx
< mRelocations
.Length(); idx
++) {
939 // owner should be in a document and have na associated DOM node (docs
941 if (mRelocations
[idx
]->IsInDocument() &&
942 mRelocations
[idx
]->HasOwnContent()) {
943 mDocument
->DoARIAOwnsRelocation(mRelocations
[idx
]);
946 mRelocations
.Clear();
948 // Process only currently queued generic notifications.
949 // These are used for processing aria-activedescendant, DOMMenuItemActive,
950 // etc. Therefore, they must be processed after relocations, since relocated
951 // subtrees might not have been created before relocation processing and the
952 // target might be inside a relocated subtree.
953 const nsTArray
<RefPtr
<Notification
>> notifications
=
954 std::move(mNotifications
);
956 uint32_t notificationCount
= notifications
.Length();
957 for (uint32_t idx
= 0; idx
< notificationCount
; idx
++) {
958 notifications
[idx
]->Process();
964 if (ipc::ProcessChild::ExpectingShutdown()) {
968 // If a generic notification occurs after this point then we may be allowed to
969 // process it synchronously. However we do not want to reenter if fireing
970 // events causes script to run.
971 mObservingState
= eRefreshProcessing
;
973 mDocument
->SendAccessiblesWillMove();
975 // Send any queued cache updates before we fire any mutation events so the
976 // cache is up to date when mutation events are fired. We do this after
977 // insertions (but not their events) so that cache updates dependent on the
978 // tree work correctly; e.g. line start calculation.
979 if (IPCAccessibilityActive() && mDocument
) {
980 mDocument
->ProcessQueuedCacheUpdates();
983 CoalesceMutationEvents();
984 ProcessMutationEvents();
986 // When firing mutation events, mObservingState is set to
987 // eRefreshProcessing. Any calls to ScheduleProcessing() that
988 // occur before mObservingState is reset will be dropped because we only
989 // schedule a tick if mObservingState == eNotObservingRefresh.
990 // This sometimes results in our viewport cache being out-of-date after
991 // processing mutation events. Call ProcessQueuedCacheUpdates again to
992 // ensure it is updated.
993 if (IPCAccessibilityActive() && mDocument
) {
994 mDocument
->ProcessQueuedCacheUpdates();
998 mDocument
->ClearMutationData();
1001 if (ipc::ProcessChild::ExpectingShutdown()) {
1005 ProcessEventQueue();
1007 if (IPCAccessibilityActive()) {
1008 size_t newDocCount
= newChildDocs
.Length();
1009 for (size_t i
= 0; i
< newDocCount
; i
++) {
1010 DocAccessible
* childDoc
= newChildDocs
[i
];
1011 if (childDoc
->IsDefunct()) {
1015 LocalAccessible
* parent
= childDoc
->LocalParent();
1016 DocAccessibleChild
* parentIPCDoc
= mDocument
->IPCDoc();
1017 MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc
);
1018 uint64_t id
= reinterpret_cast<uintptr_t>(parent
->UniqueID());
1019 MOZ_DIAGNOSTIC_ASSERT(id
);
1020 DocAccessibleChild
* ipcDoc
= childDoc
->IPCDoc();
1022 parentIPCDoc
->SendBindChildDoc(WrapNotNull(ipcDoc
), id
);
1026 ipcDoc
= new DocAccessibleChild(childDoc
, parentIPCDoc
->Manager());
1027 childDoc
->SetIPCDoc(ipcDoc
);
1029 nsCOMPtr
<nsIBrowserChild
> browserChild
=
1030 do_GetInterface(mDocument
->DocumentNode()->GetDocShell());
1032 static_cast<BrowserChild
*>(browserChild
.get())
1033 ->SendPDocAccessibleConstructor(
1034 ipcDoc
, parentIPCDoc
, id
,
1035 childDoc
->DocumentNode()->GetBrowsingContext());
1041 // A null mDocument means we've gotten a Shutdown() call (presumably via
1042 // some script that we triggered above), and that means we're done here.
1043 // Note: in this case, it's important that don't modify mObservingState;
1044 // Shutdown() will have *unregistered* us as a refresh observer, and we
1045 // don't want to mistakenly overwrite mObservingState and fool ourselves
1046 // into thinking we've re-registered when we really haven't!
1047 MOZ_ASSERT(mObservingState
== eNotObservingRefresh
,
1048 "We've been shutdown, which means we should've been "
1049 "unregistered as a refresh observer");
1052 mObservingState
= eRefreshObserving
;
1054 // Stop further processing if there are no new notifications of any kind or
1055 // events and document load is processed.
1056 if (mContentInsertions
.Count() == 0 && mNotifications
.IsEmpty() &&
1057 !mFocusEvent
&& mEvents
.IsEmpty() && mTextArray
.IsEmpty() &&
1058 mHangingChildDocuments
.IsEmpty() &&
1059 mDocument
->HasLoadState(DocAccessible::eCompletelyLoaded
) &&
1060 mPresShell
->RemoveRefreshObserver(this, FlushType::Display
)) {
1061 mObservingState
= eNotObservingRefresh
;
1065 void NotificationController::EventMap::PutEvent(AccTreeMutationEvent
* aEvent
) {
1066 EventType type
= GetEventType(aEvent
);
1067 uint64_t addr
= reinterpret_cast<uintptr_t>(aEvent
->GetAccessible());
1068 MOZ_ASSERT((addr
& 0x3) == 0, "accessible is not 4 byte aligned");
1070 mTable
.InsertOrUpdate(addr
, RefPtr
{aEvent
});
1073 AccTreeMutationEvent
* NotificationController::EventMap::GetEvent(
1074 LocalAccessible
* aTarget
, EventType aType
) {
1075 uint64_t addr
= reinterpret_cast<uintptr_t>(aTarget
);
1076 MOZ_ASSERT((addr
& 0x3) == 0, "target is not 4 byte aligned");
1079 return mTable
.GetWeak(addr
);
1082 void NotificationController::EventMap::RemoveEvent(
1083 AccTreeMutationEvent
* aEvent
) {
1084 EventType type
= GetEventType(aEvent
);
1085 uint64_t addr
= reinterpret_cast<uintptr_t>(aEvent
->GetAccessible());
1086 MOZ_ASSERT((addr
& 0x3) == 0, "accessible is not 4 byte aligned");
1089 MOZ_ASSERT(mTable
.GetWeak(addr
) == aEvent
, "mTable has the wrong event");
1090 mTable
.Remove(addr
);
1093 NotificationController::EventMap::EventType
1094 NotificationController::EventMap::GetEventType(AccTreeMutationEvent
* aEvent
) {
1095 switch (aEvent
->GetEventType()) {
1096 case nsIAccessibleEvent::EVENT_SHOW
:
1098 case nsIAccessibleEvent::EVENT_HIDE
:
1100 case nsIAccessibleEvent::EVENT_REORDER
:
1101 case nsIAccessibleEvent::EVENT_INNER_REORDER
:
1102 return ReorderEvent
;
1104 MOZ_ASSERT_UNREACHABLE("event has invalid type");