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 "nsEventShell.h"
11 #include "TextLeafAccessible.h"
12 #include "TextUpdater.h"
14 #include "nsIContentInlines.h"
16 #include "mozilla/dom/BrowserChild.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/PresShell.h"
19 #include "mozilla/ProfilerLabels.h"
20 #include "mozilla/StaticPrefs_accessibility.h"
21 #include "mozilla/Telemetry.h"
23 using namespace mozilla
;
24 using namespace mozilla::a11y
;
25 using namespace mozilla::dom
;
27 ////////////////////////////////////////////////////////////////////////////////
28 // NotificationCollector
29 ////////////////////////////////////////////////////////////////////////////////
31 NotificationController::NotificationController(DocAccessible
* aDocument
,
32 PresShell
* aPresShell
)
33 : EventQueue(aDocument
),
34 mObservingState(eNotObservingRefresh
),
35 mPresShell(aPresShell
),
38 mMoveGuardOnStack
= false;
41 // Schedule initial accessible tree construction.
45 NotificationController::~NotificationController() {
46 NS_ASSERTION(!mDocument
, "Controller wasn't shutdown properly!");
47 if (mDocument
) Shutdown();
50 ////////////////////////////////////////////////////////////////////////////////
51 // NotificationCollector: AddRef/Release and cycle collection
53 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController
)
54 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController
)
56 NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController
)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController
)
59 if (tmp
->mDocument
) tmp
->Shutdown();
60 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
62 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController
)
63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments
)
64 for (const auto& entry
: tmp
->mContentInsertions
) {
65 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mContentInsertions key");
66 cb
.NoteXPCOMChild(entry
.GetKey());
67 nsTArray
<nsCOMPtr
<nsIContent
>>* list
= entry
.GetData().get();
68 for (uint32_t i
= 0; i
< list
->Length(); i
++) {
69 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mContentInsertions value item");
70 cb
.NoteXPCOMChild(list
->ElementAt(i
));
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents
)
74 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations
)
75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
77 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController
, AddRef
)
78 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController
, Release
)
80 ////////////////////////////////////////////////////////////////////////////////
81 // NotificationCollector: public
83 void NotificationController::Shutdown() {
84 if (mObservingState
!= eNotObservingRefresh
&&
85 mPresShell
->RemoveRefreshObserver(this, FlushType::Display
)) {
86 mObservingState
= eNotObservingRefresh
;
89 // Shutdown handling child documents.
90 int32_t childDocCount
= mHangingChildDocuments
.Length();
91 for (int32_t idx
= childDocCount
- 1; idx
>= 0; idx
--) {
92 if (!mHangingChildDocuments
[idx
]->IsDefunct()) {
93 mHangingChildDocuments
[idx
]->Shutdown();
97 mHangingChildDocuments
.Clear();
100 mPresShell
= nullptr;
103 mContentInsertions
.Clear();
104 mNotifications
.Clear();
106 mRelocations
.Clear();
110 EventTree
* NotificationController::QueueMutation(LocalAccessible
* aContainer
) {
111 EventTree
* tree
= mEventTree
.FindOrInsert(aContainer
);
113 ScheduleProcessing();
118 bool NotificationController::QueueMutationEvent(AccTreeMutationEvent
* aEvent
) {
119 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
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 if (aEvent
->GetAccessible()->ShowEventTarget()) {
124 AccTreeMutationEvent
* showEvent
=
125 mMutationMap
.GetEvent(aEvent
->GetAccessible(), EventMap::ShowEvent
);
126 DropMutationEvent(showEvent
);
130 // If this is an additional hide event, the accessible may be hidden, or
131 // moved again after a move. Preserve the original hide event since
132 // its properties are consistent with the tree that existed before
133 // the next batch of mutation events is processed.
134 if (aEvent
->GetAccessible()->HideEventTarget()) {
139 AccMutationEvent
* mutEvent
= downcast_accEvent(aEvent
);
141 mutEvent
->SetEventGeneration(mEventGeneration
);
143 if (!mFirstMutationEvent
) {
144 mFirstMutationEvent
= aEvent
;
145 ScheduleProcessing();
148 if (mLastMutationEvent
) {
149 NS_ASSERTION(!mLastMutationEvent
->NextEvent(),
150 "why isn't the last event the end?");
151 mLastMutationEvent
->SetNextEvent(aEvent
);
154 aEvent
->SetPrevEvent(mLastMutationEvent
);
155 mLastMutationEvent
= aEvent
;
156 mMutationMap
.PutEvent(aEvent
);
158 // Because we could be hiding the target of a show event we need to get rid
159 // of any such events. It may be possible to do less than coallesce all
160 // events, however that is easiest.
161 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
162 CoalesceMutationEvents();
164 // mLastMutationEvent will point to something other than aEvent if and only
165 // if aEvent was just coalesced away. In that case a parent accessible
166 // must already have the required reorder and text change events so we are
168 if (mLastMutationEvent
!= aEvent
) {
173 // We need to fire a reorder event after all of the events targeted at shown
174 // or hidden children of a container. So either queue a new one, or move an
175 // existing one to the end of the queue if the container already has a
177 LocalAccessible
* target
= aEvent
->GetAccessible();
178 LocalAccessible
* container
= aEvent
->GetAccessible()->LocalParent();
179 RefPtr
<AccReorderEvent
> reorder
;
180 if (!container
->ReorderEventTarget()) {
181 reorder
= new AccReorderEvent(container
);
182 container
->SetReorderEventTarget(true);
183 mMutationMap
.PutEvent(reorder
);
185 // Since this is the first child of container that is changing, the name
186 // and/or description of dependent Accessibles may be changing.
187 if (PushNameOrDescriptionChange(target
)) {
188 ScheduleProcessing();
191 AccReorderEvent
* event
= downcast_accEvent(
192 mMutationMap
.GetEvent(container
, EventMap::ReorderEvent
));
194 if (mFirstMutationEvent
== event
) {
195 mFirstMutationEvent
= event
->NextEvent();
197 event
->PrevEvent()->SetNextEvent(event
->NextEvent());
200 event
->NextEvent()->SetPrevEvent(event
->PrevEvent());
201 event
->SetNextEvent(nullptr);
204 reorder
->SetEventGeneration(mEventGeneration
);
205 reorder
->SetPrevEvent(mLastMutationEvent
);
206 mLastMutationEvent
->SetNextEvent(reorder
);
207 mLastMutationEvent
= reorder
;
209 // It is not possible to have a text change event for something other than a
210 // hyper text accessible.
211 if (!container
->IsHyperText()) {
215 MOZ_ASSERT(mutEvent
);
218 aEvent
->GetAccessible()->AppendTextTo(text
);
219 if (text
.IsEmpty()) {
223 int32_t offset
= container
->AsHyperText()->GetChildOffset(target
);
224 AccTreeMutationEvent
* prevEvent
= aEvent
->PrevEvent();
226 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
227 prevEvent
= prevEvent
->PrevEvent();
231 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
&&
232 mutEvent
->IsHide()) {
233 AccHideEvent
* prevHide
= downcast_accEvent(prevEvent
);
234 AccTextChangeEvent
* prevTextChange
= prevHide
->mTextChangeEvent
;
235 if (prevTextChange
&& prevHide
->LocalParent() == mutEvent
->LocalParent()) {
236 if (prevHide
->mNextSibling
== target
) {
237 target
->AppendTextTo(prevTextChange
->mModifiedText
);
238 prevHide
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
239 } else if (prevHide
->mPrevSibling
== target
) {
241 target
->AppendTextTo(temp
);
243 uint32_t extraLen
= temp
.Length();
244 temp
+= prevTextChange
->mModifiedText
;
246 prevTextChange
->mModifiedText
= temp
;
247 prevTextChange
->mStart
-= extraLen
;
248 prevHide
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
251 } else if (prevEvent
&& mutEvent
->IsShow() &&
252 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_SHOW
) {
253 AccShowEvent
* prevShow
= downcast_accEvent(prevEvent
);
254 AccTextChangeEvent
* prevTextChange
= prevShow
->mTextChangeEvent
;
255 if (prevTextChange
&& prevShow
->LocalParent() == target
->LocalParent()) {
256 int32_t index
= target
->IndexInParent();
257 int32_t prevIndex
= prevShow
->GetAccessible()->IndexInParent();
258 if (prevIndex
+ 1 == index
) {
259 target
->AppendTextTo(prevTextChange
->mModifiedText
);
260 prevShow
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
261 } else if (index
+ 1 == prevIndex
) {
263 target
->AppendTextTo(temp
);
264 prevTextChange
->mStart
-= temp
.Length();
265 temp
+= prevTextChange
->mModifiedText
;
266 prevTextChange
->mModifiedText
= temp
;
267 prevShow
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
272 if (!mutEvent
->mTextChangeEvent
) {
273 mutEvent
->mTextChangeEvent
= new AccTextChangeEvent(
274 container
, offset
, text
, mutEvent
->IsShow(),
275 aEvent
->mIsFromUserInput
? eFromUserInput
: eNoUserInput
);
281 void NotificationController::DropMutationEvent(AccTreeMutationEvent
* aEvent
) {
282 // unset the event bits since the event isn't being fired any more.
283 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
284 aEvent
->GetAccessible()->SetReorderEventTarget(false);
285 } else if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_SHOW
) {
286 aEvent
->GetAccessible()->SetShowEventTarget(false);
288 aEvent
->GetAccessible()->SetHideEventTarget(false);
290 AccHideEvent
* hideEvent
= downcast_accEvent(aEvent
);
291 MOZ_ASSERT(hideEvent
);
293 if (hideEvent
->NeedsShutdown()) {
294 mDocument
->ShutdownChildrenInSubtree(aEvent
->GetAccessible());
298 // Do the work to splice the event out of the list.
299 if (mFirstMutationEvent
== aEvent
) {
300 mFirstMutationEvent
= aEvent
->NextEvent();
302 aEvent
->PrevEvent()->SetNextEvent(aEvent
->NextEvent());
305 if (mLastMutationEvent
== aEvent
) {
306 mLastMutationEvent
= aEvent
->PrevEvent();
308 aEvent
->NextEvent()->SetPrevEvent(aEvent
->PrevEvent());
311 aEvent
->SetPrevEvent(nullptr);
312 aEvent
->SetNextEvent(nullptr);
313 mMutationMap
.RemoveEvent(aEvent
);
316 void NotificationController::CoalesceMutationEvents() {
317 AccTreeMutationEvent
* event
= mFirstMutationEvent
;
319 AccTreeMutationEvent
* nextEvent
= event
->NextEvent();
320 uint32_t eventType
= event
->GetEventType();
321 if (event
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
322 LocalAccessible
* acc
= event
->GetAccessible();
328 // if a parent of the reorder event's target is being hidden that
329 // hide event's target must have a parent that is also a reorder event
330 // target. That means we don't need this reorder event.
331 if (acc
->HideEventTarget()) {
332 DropMutationEvent(event
);
336 LocalAccessible
* parent
= acc
->LocalParent();
337 if (parent
&& parent
->ReorderEventTarget()) {
338 AccReorderEvent
* reorder
= downcast_accEvent(
339 mMutationMap
.GetEvent(parent
, EventMap::ReorderEvent
));
341 // We want to make sure that a reorder event comes after any show or
342 // hide events targeted at the children of its target. We keep the
343 // invariant that event generation goes up as you are farther in the
344 // queue, so we want to use the spot of the event with the higher
345 // generation number, and keep that generation number.
347 reorder
->EventGeneration() < event
->EventGeneration()) {
348 reorder
->SetEventGeneration(event
->EventGeneration());
350 // It may be true that reorder was before event, and we coalesced
351 // away all the show / hide events between them. In that case
352 // event is already immediately after reorder in the queue and we
353 // do not need to rearrange the list of events.
354 if (event
!= reorder
->NextEvent()) {
355 // There really should be a show or hide event before the first
357 if (reorder
->PrevEvent()) {
358 reorder
->PrevEvent()->SetNextEvent(reorder
->NextEvent());
360 mFirstMutationEvent
= reorder
->NextEvent();
363 reorder
->NextEvent()->SetPrevEvent(reorder
->PrevEvent());
364 event
->PrevEvent()->SetNextEvent(reorder
);
365 reorder
->SetPrevEvent(event
->PrevEvent());
366 event
->SetPrevEvent(reorder
);
367 reorder
->SetNextEvent(event
);
370 DropMutationEvent(event
);
376 } else if (eventType
== nsIAccessibleEvent::EVENT_SHOW
) {
377 LocalAccessible
* parent
= event
->GetAccessible()->LocalParent();
379 if (parent
->IsDoc()) {
383 // if the parent of a show event is being either shown or hidden then
384 // we don't need to fire a show event for a subtree of that change.
385 if (parent
->ShowEventTarget() || parent
->HideEventTarget()) {
386 DropMutationEvent(event
);
390 parent
= parent
->LocalParent();
393 MOZ_ASSERT(eventType
== nsIAccessibleEvent::EVENT_HIDE
,
394 "mutation event list has an invalid event");
396 AccHideEvent
* hideEvent
= downcast_accEvent(event
);
397 LocalAccessible
* parent
= hideEvent
->LocalParent();
399 if (parent
->IsDoc()) {
403 if (parent
->HideEventTarget()) {
404 DropMutationEvent(event
);
408 if (parent
->ShowEventTarget()) {
409 AccShowEvent
* showEvent
= downcast_accEvent(
410 mMutationMap
.GetEvent(parent
, EventMap::ShowEvent
));
411 if (showEvent
->EventGeneration() < hideEvent
->EventGeneration()) {
412 DropMutationEvent(hideEvent
);
417 parent
= parent
->LocalParent();
425 void NotificationController::ScheduleChildDocBinding(DocAccessible
* aDocument
) {
426 // Schedule child document binding to the tree.
427 mHangingChildDocuments
.AppendElement(aDocument
);
428 ScheduleProcessing();
431 void NotificationController::ScheduleContentInsertion(
432 LocalAccessible
* aContainer
, nsTArray
<nsCOMPtr
<nsIContent
>>& aInsertions
) {
433 if (!aInsertions
.IsEmpty()) {
434 mContentInsertions
.GetOrInsertNew(aContainer
)->AppendElements(aInsertions
);
435 ScheduleProcessing();
439 void NotificationController::ScheduleProcessing() {
440 // If notification flush isn't planed yet start notification flush
441 // asynchronously (after style and layout).
442 if (mObservingState
== eNotObservingRefresh
) {
443 if (mPresShell
->AddRefreshObserver(this, FlushType::Display
,
444 "Accessibility notifications")) {
445 mObservingState
= eRefreshObserving
;
450 ////////////////////////////////////////////////////////////////////////////////
451 // NotificationCollector: protected
453 bool NotificationController::IsUpdatePending() {
454 return mPresShell
->IsLayoutFlushObserver() ||
455 mObservingState
== eRefreshProcessingForUpdate
|| WaitingForParent() ||
456 mContentInsertions
.Count() != 0 || mNotifications
.Length() != 0 ||
457 mTextHash
.Count() != 0 ||
458 !mDocument
->HasLoadState(DocAccessible::eTreeConstructed
);
461 bool NotificationController::WaitingForParent() {
462 DocAccessible
* parentdoc
= mDocument
->ParentDocument();
467 NotificationController
* parent
= parentdoc
->mNotificationController
;
468 if (!parent
|| parent
== this) {
469 // Do not wait for nothing or ourselves
473 // Wait for parent's notifications processing
474 return parent
->mContentInsertions
.Count() != 0 ||
475 parent
->mNotifications
.Length() != 0;
478 void NotificationController::ProcessMutationEvents() {
479 // there is no reason to fire a hide event for a child of a show event
480 // target. That can happen if something is inserted into the tree and
481 // removed before the next refresh driver tick, but it should not be
482 // observable outside gecko so it should be safe to coalesce away any such
483 // events. This means that it should be fine to fire all of the hide events
484 // first, and then deal with any shown subtrees.
485 for (AccTreeMutationEvent
* event
= mFirstMutationEvent
; event
;
486 event
= event
->NextEvent()) {
487 if (event
->GetEventType() != nsIAccessibleEvent::EVENT_HIDE
) {
491 nsEventShell::FireEvent(event
);
496 AccMutationEvent
* mutEvent
= downcast_accEvent(event
);
497 if (mutEvent
->mTextChangeEvent
) {
498 nsEventShell::FireEvent(mutEvent
->mTextChangeEvent
);
504 // Fire menupopup end event before a hide event if a menu goes away.
506 // XXX: We don't look into children of hidden subtree to find hiding
507 // menupopup (as we did prior bug 570275) because we don't do that when
508 // menu is showing (and that's impossible until bug 606924 is fixed).
509 // Nevertheless we should do this at least because layout coalesces
510 // the changes before our processing and we may miss some menupopup
511 // events. Now we just want to be consistent in content insertion/removal
513 if (event
->mAccessible
->ARIARole() == roles::MENUPOPUP
) {
514 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END
,
521 AccHideEvent
* hideEvent
= downcast_accEvent(event
);
522 if (hideEvent
->NeedsShutdown()) {
523 mDocument
->ShutdownChildrenInSubtree(event
->mAccessible
);
527 // Group the show events by the parent of their target.
528 nsTHashMap
<nsPtrHashKey
<LocalAccessible
>, nsTArray
<AccTreeMutationEvent
*>>
530 for (AccTreeMutationEvent
* event
= mFirstMutationEvent
; event
;
531 event
= event
->NextEvent()) {
532 if (event
->GetEventType() != nsIAccessibleEvent::EVENT_SHOW
) {
536 LocalAccessible
* parent
= event
->GetAccessible()->LocalParent();
537 showEvents
.LookupOrInsert(parent
).AppendElement(event
);
540 // We need to fire show events for the children of an accessible in the order
541 // of their indices at this point. So sort each set of events for the same
542 // container by the index of their target.
543 for (auto iter
= showEvents
.Iter(); !iter
.Done(); iter
.Next()) {
544 struct AccIdxComparator
{
545 bool LessThan(const AccTreeMutationEvent
* a
,
546 const AccTreeMutationEvent
* b
) const {
547 int32_t aIdx
= a
->GetAccessible()->IndexInParent();
548 int32_t bIdx
= b
->GetAccessible()->IndexInParent();
549 MOZ_ASSERT(aIdx
>= 0 && bIdx
>= 0 && aIdx
!= bIdx
);
552 bool Equals(const AccTreeMutationEvent
* a
,
553 const AccTreeMutationEvent
* b
) const {
554 DebugOnly
<int32_t> aIdx
= a
->GetAccessible()->IndexInParent();
555 DebugOnly
<int32_t> bIdx
= b
->GetAccessible()->IndexInParent();
556 MOZ_ASSERT(aIdx
>= 0 && bIdx
>= 0 && aIdx
!= bIdx
);
561 nsTArray
<AccTreeMutationEvent
*>& events
= iter
.Data();
562 events
.Sort(AccIdxComparator());
563 for (AccTreeMutationEvent
* event
: events
) {
564 nsEventShell::FireEvent(event
);
569 AccMutationEvent
* mutEvent
= downcast_accEvent(event
);
570 if (mutEvent
->mTextChangeEvent
) {
571 nsEventShell::FireEvent(mutEvent
->mTextChangeEvent
);
579 // Now we can fire the reorder events after all the show and hide events.
580 for (AccTreeMutationEvent
* event
= mFirstMutationEvent
; event
;
581 event
= event
->NextEvent()) {
582 if (event
->GetEventType() != nsIAccessibleEvent::EVENT_REORDER
) {
586 nsEventShell::FireEvent(event
);
591 LocalAccessible
* target
= event
->GetAccessible();
592 target
->Document()->MaybeNotifyOfValueChange(target
);
599 ////////////////////////////////////////////////////////////////////////////////
600 // NotificationCollector: private
602 void NotificationController::WillRefresh(mozilla::TimeStamp aTime
) {
603 Telemetry::AutoTimer
<Telemetry::A11Y_TREE_UPDATE_TIMING_MS
> timer
;
605 AUTO_PROFILER_LABEL("NotificationController::WillRefresh", OTHER
);
607 // If the document accessible that notification collector was created for is
608 // now shut down, don't process notifications anymore.
611 "The document was shut down while refresh observer is attached!");
612 if (!mDocument
) return;
614 // Wait until an update, we have started, or an interruptible reflow is
616 if (mObservingState
== eRefreshProcessing
||
617 mObservingState
== eRefreshProcessingForUpdate
||
618 mPresShell
->IsReflowInterrupted()) {
622 // Process parent's notifications before ours, to get proper ordering between
623 // e.g. tab event and content event.
624 if (WaitingForParent()) {
625 mDocument
->ParentDocument()->mNotificationController
->WillRefresh(aTime
);
631 // Any generic notifications should be queued if we're processing content
632 // insertions or generic notifications.
633 mObservingState
= eRefreshProcessingForUpdate
;
635 // Initial accessible tree construction.
636 if (!mDocument
->HasLoadState(DocAccessible::eTreeConstructed
)) {
637 // If document is not bound to parent at this point then the document is not
638 // ready yet (process notifications later).
639 if (!mDocument
->IsBoundToParent()) {
640 mObservingState
= eRefreshObserving
;
645 if (logging::IsEnabled(logging::eTree
)) {
646 logging::MsgBegin("TREE", "initial tree created");
647 logging::Address("document", mDocument
);
652 mDocument
->DoInitialUpdate();
654 NS_ASSERTION(mContentInsertions
.Count() == 0,
655 "Pending content insertions while initial accessible tree "
659 // Process rendered text change notifications.
660 for (nsIContent
* textNode
: mTextHash
) {
661 LocalAccessible
* textAcc
= mDocument
->GetAccessible(textNode
);
663 // If the text node is not in tree or doesn't have a frame, or placed in
664 // another document, then this case should have been handled already by
665 // content removal notifications.
666 nsINode
* containerNode
= textNode
->GetFlattenedTreeParentNode();
667 if (!containerNode
|| textNode
->OwnerDoc() != mDocument
->DocumentNode()) {
669 "Text node was removed but accessible is kept alive!");
673 nsIFrame
* textFrame
= textNode
->GetPrimaryFrame();
676 "Text node isn't rendered but accessible is kept alive!");
681 nsIContent
* containerElm
=
682 containerNode
->IsElement() ? containerNode
->AsElement() : nullptr;
685 nsIFrame::RenderedText text
= textFrame
->GetRenderedText(
686 0, UINT32_MAX
, nsIFrame::TextOffsetType::OffsetsInContentText
,
687 nsIFrame::TrailingWhitespace::DontTrim
);
689 // Remove text accessible if rendered text is empty.
691 if (text
.mString
.IsEmpty()) {
693 if (logging::IsEnabled(logging::eTree
| logging::eText
)) {
694 logging::MsgBegin("TREE", "text node lost its content; doc: %p",
696 logging::Node("container", containerElm
);
697 logging::Node("content", textNode
);
702 mDocument
->ContentRemoved(textAcc
);
706 // Update text of the accessible and fire text change events.
708 if (logging::IsEnabled(logging::eText
)) {
709 logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument
);
710 logging::Node("container", containerElm
);
711 logging::Node("content", textNode
);
714 NS_ConvertUTF16toUTF8(textAcc
->AsTextLeaf()->Text()).get());
715 logging::MsgEntry("new text: '%s'",
716 NS_ConvertUTF16toUTF8(text
.mString
).get());
721 TextUpdater::Run(mDocument
, textAcc
->AsTextLeaf(), text
.mString
);
725 // Append an accessible if rendered text is not empty.
726 if (!text
.mString
.IsEmpty()) {
728 if (logging::IsEnabled(logging::eTree
| logging::eText
)) {
729 logging::MsgBegin("TREE", "text node gains new content; doc: %p",
731 logging::Node("container", containerElm
);
732 logging::Node("content", textNode
);
737 MOZ_ASSERT(mDocument
->AccessibleOrTrueContainer(containerNode
),
738 "Text node having rendered text hasn't accessible document!");
740 LocalAccessible
* container
=
741 mDocument
->AccessibleOrTrueContainer(containerNode
, true);
743 nsTArray
<nsCOMPtr
<nsIContent
>>* list
=
744 mContentInsertions
.GetOrInsertNew(container
);
745 list
->AppendElement(textNode
);
751 // Process content inserted notifications to update the tree.
752 // Processing an insertion can indirectly run script (e.g. querying a XUL
753 // interface), which might result in another insertion being queued.
754 // We don't want to lose any queued insertions if this happens. Therefore, we
755 // move the current insertions into a temporary data structure and process
756 // them from there. Any insertions queued during processing will get handled
757 // in subsequent refresh driver ticks.
758 const auto contentInsertions
= std::move(mContentInsertions
);
759 for (const auto& entry
: contentInsertions
) {
760 mDocument
->ProcessContentInserted(entry
.GetKey(), entry
.GetData().get());
766 // Bind hanging child documents unless we are using IPC and the
767 // document has no IPC actor. If we fail to bind the child doc then
769 uint32_t hangingDocCnt
= mHangingChildDocuments
.Length();
770 nsTArray
<RefPtr
<DocAccessible
>> newChildDocs
;
771 for (uint32_t idx
= 0; idx
< hangingDocCnt
; idx
++) {
772 DocAccessible
* childDoc
= mHangingChildDocuments
[idx
];
773 if (childDoc
->IsDefunct()) continue;
775 if (IPCAccessibilityActive() && !mDocument
->IPCDoc()) {
776 childDoc
->Shutdown();
780 nsIContent
* ownerContent
= childDoc
->DocumentNode()->GetEmbedderElement();
782 LocalAccessible
* outerDocAcc
= mDocument
->GetAccessible(ownerContent
);
783 if (outerDocAcc
&& outerDocAcc
->AppendChild(childDoc
)) {
784 if (mDocument
->AppendChildDocument(childDoc
)) {
785 newChildDocs
.AppendElement(std::move(mHangingChildDocuments
[idx
]));
789 outerDocAcc
->RemoveChild(childDoc
);
792 // Failed to bind the child document, destroy it.
793 childDoc
->Shutdown();
797 // Clear the hanging documents list, even if we didn't bind them.
798 mHangingChildDocuments
.Clear();
799 MOZ_ASSERT(mDocument
, "Illicit document shutdown");
804 // If the document is ready and all its subdocuments are completely loaded
805 // then process the document load.
806 if (mDocument
->HasLoadState(DocAccessible::eReady
) &&
807 !mDocument
->HasLoadState(DocAccessible::eCompletelyLoaded
) &&
808 hangingDocCnt
== 0) {
809 uint32_t childDocCnt
= mDocument
->ChildDocumentCount(), childDocIdx
= 0;
810 for (; childDocIdx
< childDocCnt
; childDocIdx
++) {
811 DocAccessible
* childDoc
= mDocument
->GetChildDocumentAt(childDocIdx
);
812 if (!childDoc
->HasLoadState(DocAccessible::eCompletelyLoaded
)) break;
815 if (childDocIdx
== childDocCnt
) {
816 mDocument
->ProcessLoad();
817 if (!mDocument
) return;
821 // Process invalidation list of the document after all accessible tree
823 mDocument
->ProcessInvalidationList();
825 // Process relocation list.
826 for (uint32_t idx
= 0; idx
< mRelocations
.Length(); idx
++) {
827 // owner should be in a document and have na associated DOM node (docs
829 if (mRelocations
[idx
]->IsInDocument() &&
830 mRelocations
[idx
]->HasOwnContent()) {
831 mDocument
->DoARIAOwnsRelocation(mRelocations
[idx
]);
834 mRelocations
.Clear();
836 // Process only currently queued generic notifications.
837 // These are used for processing aria-activedescendant, DOMMenuItemActive,
838 // etc. Therefore, they must be processed after relocations, since relocated
839 // subtrees might not have been created before relocation processing and the
840 // target might be inside a relocated subtree.
841 const nsTArray
<RefPtr
<Notification
>> notifications
=
842 std::move(mNotifications
);
844 uint32_t notificationCount
= notifications
.Length();
845 for (uint32_t idx
= 0; idx
< notificationCount
; idx
++) {
846 notifications
[idx
]->Process();
847 if (!mDocument
) return;
850 // If a generic notification occurs after this point then we may be allowed to
851 // process it synchronously. However we do not want to reenter if fireing
852 // events causes script to run.
853 mObservingState
= eRefreshProcessing
;
855 CoalesceMutationEvents();
856 ProcessMutationEvents();
857 mEventGeneration
= 0;
859 // Now that we are done with them get rid of the events we fired.
860 RefPtr
<AccTreeMutationEvent
> mutEvent
= std::move(mFirstMutationEvent
);
861 mLastMutationEvent
= nullptr;
862 mFirstMutationEvent
= nullptr;
864 RefPtr
<AccTreeMutationEvent
> nextEvent
= mutEvent
->NextEvent();
865 LocalAccessible
* target
= mutEvent
->GetAccessible();
867 // We need to be careful here, while it may seem that we can simply 0 all
868 // the pending event bits that is not true. Because accessibles may be
869 // reparented they may be the target of both a hide event and a show event
871 if (mutEvent
->GetEventType() == nsIAccessibleEvent::EVENT_SHOW
) {
872 target
->SetShowEventTarget(false);
875 if (mutEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
876 target
->SetHideEventTarget(false);
879 // However it is not possible for a reorder event target to also be the
880 // target of a show or hide, so we can just zero that.
881 target
->SetReorderEventTarget(false);
883 mutEvent
->SetPrevEvent(nullptr);
884 mutEvent
->SetNextEvent(nullptr);
885 mMutationMap
.RemoveEvent(mutEvent
);
886 mutEvent
= nextEvent
;
891 if (IPCAccessibilityActive()) {
892 size_t newDocCount
= newChildDocs
.Length();
893 for (size_t i
= 0; i
< newDocCount
; i
++) {
894 DocAccessible
* childDoc
= newChildDocs
[i
];
895 if (childDoc
->IsDefunct()) {
899 LocalAccessible
* parent
= childDoc
->LocalParent();
900 DocAccessibleChild
* parentIPCDoc
= mDocument
->IPCDoc();
901 MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc
);
902 uint64_t id
= reinterpret_cast<uintptr_t>(parent
->UniqueID());
903 MOZ_DIAGNOSTIC_ASSERT(id
);
904 DocAccessibleChild
* ipcDoc
= childDoc
->IPCDoc();
906 parentIPCDoc
->SendBindChildDoc(ipcDoc
, id
);
910 ipcDoc
= new DocAccessibleChild(childDoc
, parentIPCDoc
->Manager());
911 childDoc
->SetIPCDoc(ipcDoc
);
914 parentIPCDoc
->ConstructChildDocInParentProcess(
916 StaticPrefs::accessibility_cache_enabled_AtStartup()
918 : MsaaAccessible::GetChildIDFor(childDoc
));
920 nsCOMPtr
<nsIBrowserChild
> browserChild
=
921 do_GetInterface(mDocument
->DocumentNode()->GetDocShell());
923 static_cast<BrowserChild
*>(browserChild
.get())
924 ->SendPDocAccessibleConstructor(ipcDoc
, parentIPCDoc
, id
, 0, 0);
925 ipcDoc
->SendPDocAccessiblePlatformExtConstructor();
931 mObservingState
= eRefreshObserving
;
932 if (!mDocument
) return;
934 // Stop further processing if there are no new notifications of any kind or
935 // events and document load is processed.
936 if (mContentInsertions
.Count() == 0 && mNotifications
.IsEmpty() &&
937 mEvents
.IsEmpty() && mTextHash
.Count() == 0 &&
938 mHangingChildDocuments
.IsEmpty() &&
939 mDocument
->HasLoadState(DocAccessible::eCompletelyLoaded
) &&
940 mPresShell
->RemoveRefreshObserver(this, FlushType::Display
)) {
941 mObservingState
= eNotObservingRefresh
;
945 void NotificationController::EventMap::PutEvent(AccTreeMutationEvent
* aEvent
) {
946 EventType type
= GetEventType(aEvent
);
947 uint64_t addr
= reinterpret_cast<uintptr_t>(aEvent
->GetAccessible());
948 MOZ_ASSERT((addr
& 0x3) == 0, "accessible is not 4 byte aligned");
950 mTable
.InsertOrUpdate(addr
, RefPtr
{aEvent
});
953 AccTreeMutationEvent
* NotificationController::EventMap::GetEvent(
954 LocalAccessible
* aTarget
, EventType aType
) {
955 uint64_t addr
= reinterpret_cast<uintptr_t>(aTarget
);
956 MOZ_ASSERT((addr
& 0x3) == 0, "target is not 4 byte aligned");
959 return mTable
.GetWeak(addr
);
962 void NotificationController::EventMap::RemoveEvent(
963 AccTreeMutationEvent
* aEvent
) {
964 EventType type
= GetEventType(aEvent
);
965 uint64_t addr
= reinterpret_cast<uintptr_t>(aEvent
->GetAccessible());
966 MOZ_ASSERT((addr
& 0x3) == 0, "accessible is not 4 byte aligned");
969 MOZ_ASSERT(mTable
.GetWeak(addr
) == aEvent
, "mTable has the wrong event");
973 NotificationController::EventMap::EventType
974 NotificationController::EventMap::GetEventType(AccTreeMutationEvent
* aEvent
) {
975 switch (aEvent
->GetEventType()) {
976 case nsIAccessibleEvent::EVENT_SHOW
:
978 case nsIAccessibleEvent::EVENT_HIDE
:
980 case nsIAccessibleEvent::EVENT_REORDER
:
983 MOZ_ASSERT_UNREACHABLE("event has invalid type");