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 "CacheConstants.h"
9 #include "DocAccessible-inl.h"
10 #include "DocAccessibleChild.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/PresShell.h"
20 #include "mozilla/ProfilerLabels.h"
21 #include "mozilla/StaticPrefs_accessibility.h"
22 #include "mozilla/Telemetry.h"
24 using namespace mozilla
;
25 using namespace mozilla::a11y
;
26 using namespace mozilla::dom
;
28 ////////////////////////////////////////////////////////////////////////////////
29 // NotificationCollector
30 ////////////////////////////////////////////////////////////////////////////////
32 NotificationController::NotificationController(DocAccessible
* aDocument
,
33 PresShell
* aPresShell
)
34 : EventQueue(aDocument
),
35 mObservingState(eNotObservingRefresh
),
36 mPresShell(aPresShell
),
39 mMoveGuardOnStack
= false;
42 // Schedule initial accessible tree construction.
46 NotificationController::~NotificationController() {
47 NS_ASSERTION(!mDocument
, "Controller wasn't shutdown properly!");
48 if (mDocument
) Shutdown();
51 ////////////////////////////////////////////////////////////////////////////////
52 // NotificationCollector: AddRef/Release and cycle collection
54 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController
)
55 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController
)
57 NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController
)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController
)
60 if (tmp
->mDocument
) tmp
->Shutdown();
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController
)
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments
)
65 for (const auto& entry
: tmp
->mContentInsertions
) {
66 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mContentInsertions key");
67 cb
.NoteXPCOMChild(entry
.GetKey());
68 nsTArray
<nsCOMPtr
<nsIContent
>>* list
= entry
.GetData().get();
69 for (uint32_t i
= 0; i
< list
->Length(); i
++) {
70 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mContentInsertions value item");
71 cb
.NoteXPCOMChild(list
->ElementAt(i
));
74 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusEvent
)
75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents
)
76 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations
)
77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
79 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController
, AddRef
)
80 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController
, Release
)
82 ////////////////////////////////////////////////////////////////////////////////
83 // NotificationCollector: public
85 void NotificationController::Shutdown() {
86 if (mObservingState
!= eNotObservingRefresh
&&
87 mPresShell
->RemoveRefreshObserver(this, FlushType::Display
)) {
88 mObservingState
= eNotObservingRefresh
;
91 // Shutdown handling child documents.
92 int32_t childDocCount
= mHangingChildDocuments
.Length();
93 for (int32_t idx
= childDocCount
- 1; idx
>= 0; idx
--) {
94 if (!mHangingChildDocuments
[idx
]->IsDefunct()) {
95 mHangingChildDocuments
[idx
]->Shutdown();
99 mHangingChildDocuments
.Clear();
102 mPresShell
= nullptr;
105 mContentInsertions
.Clear();
106 mNotifications
.Clear();
107 mFocusEvent
= nullptr;
109 mRelocations
.Clear();
113 EventTree
* NotificationController::QueueMutation(LocalAccessible
* aContainer
) {
114 EventTree
* tree
= mEventTree
.FindOrInsert(aContainer
);
116 ScheduleProcessing();
121 void NotificationController::CoalesceHideEvent(AccHideEvent
* aHideEvent
) {
122 LocalAccessible
* parent
= aHideEvent
->LocalParent();
124 if (parent
->IsDoc()) {
128 if (parent
->HideEventTarget()) {
129 DropMutationEvent(aHideEvent
);
133 if (parent
->ShowEventTarget()) {
134 AccShowEvent
* showEvent
=
135 downcast_accEvent(mMutationMap
.GetEvent(parent
, EventMap::ShowEvent
));
136 if (showEvent
->EventGeneration() < aHideEvent
->EventGeneration()) {
137 DropMutationEvent(aHideEvent
);
142 parent
= parent
->LocalParent();
146 bool NotificationController::QueueMutationEvent(AccTreeMutationEvent
* aEvent
) {
147 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
148 // We have to allow there to be a hide and then a show event for a target
149 // because of targets getting moved. However we need to coalesce a show and
150 // then a hide for a target which means we need to check for that here.
151 if (aEvent
->GetAccessible()->ShowEventTarget()) {
152 AccTreeMutationEvent
* showEvent
=
153 mMutationMap
.GetEvent(aEvent
->GetAccessible(), EventMap::ShowEvent
);
154 DropMutationEvent(showEvent
);
158 // If this is an additional hide event, the accessible may be hidden, or
159 // moved again after a move. Preserve the original hide event since
160 // its properties are consistent with the tree that existed before
161 // the next batch of mutation events is processed.
162 if (aEvent
->GetAccessible()->HideEventTarget()) {
167 AccMutationEvent
* mutEvent
= downcast_accEvent(aEvent
);
169 mutEvent
->SetEventGeneration(mEventGeneration
);
171 if (!mFirstMutationEvent
) {
172 mFirstMutationEvent
= aEvent
;
173 ScheduleProcessing();
176 if (mLastMutationEvent
) {
177 NS_ASSERTION(!mLastMutationEvent
->NextEvent(),
178 "why isn't the last event the end?");
179 mLastMutationEvent
->SetNextEvent(aEvent
);
182 aEvent
->SetPrevEvent(mLastMutationEvent
);
183 mLastMutationEvent
= aEvent
;
184 mMutationMap
.PutEvent(aEvent
);
186 // Because we could be hiding the target of a show event we need to get rid
187 // of any such events.
188 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
189 CoalesceHideEvent(downcast_accEvent(aEvent
));
191 // mLastMutationEvent will point to something other than aEvent if and only
192 // if aEvent was just coalesced away. In that case a parent accessible
193 // must already have the required reorder and text change events so we are
195 if (mLastMutationEvent
!= aEvent
) {
200 // We need to fire a reorder event after all of the events targeted at shown
201 // or hidden children of a container. So either queue a new one, or move an
202 // existing one to the end of the queue if the container already has a
204 LocalAccessible
* target
= aEvent
->GetAccessible();
205 LocalAccessible
* container
= aEvent
->GetAccessible()->LocalParent();
206 RefPtr
<AccReorderEvent
> reorder
;
207 if (!container
->ReorderEventTarget()) {
208 reorder
= new AccReorderEvent(container
);
209 container
->SetReorderEventTarget(true);
210 mMutationMap
.PutEvent(reorder
);
212 // Since this is the first child of container that is changing, the name
213 // and/or description of dependent Accessibles may be changing.
214 if (PushNameOrDescriptionChange(target
)) {
215 ScheduleProcessing();
218 AccReorderEvent
* event
= downcast_accEvent(
219 mMutationMap
.GetEvent(container
, EventMap::ReorderEvent
));
221 if (mFirstMutationEvent
== event
) {
222 mFirstMutationEvent
= event
->NextEvent();
224 event
->PrevEvent()->SetNextEvent(event
->NextEvent());
227 event
->NextEvent()->SetPrevEvent(event
->PrevEvent());
228 event
->SetNextEvent(nullptr);
231 reorder
->SetEventGeneration(mEventGeneration
);
232 reorder
->SetPrevEvent(mLastMutationEvent
);
233 mLastMutationEvent
->SetNextEvent(reorder
);
234 mLastMutationEvent
= reorder
;
236 // It is not possible to have a text change event for something other than a
237 // hyper text accessible.
238 if (!container
->IsHyperText()) {
242 MOZ_ASSERT(mutEvent
);
245 aEvent
->GetAccessible()->AppendTextTo(text
);
246 if (text
.IsEmpty()) {
250 int32_t offset
= container
->AsHyperText()->GetChildOffset(target
);
251 AccTreeMutationEvent
* prevEvent
= aEvent
->PrevEvent();
253 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
254 prevEvent
= prevEvent
->PrevEvent();
258 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
&&
259 mutEvent
->IsHide()) {
260 AccHideEvent
* prevHide
= downcast_accEvent(prevEvent
);
261 AccTextChangeEvent
* prevTextChange
= prevHide
->mTextChangeEvent
;
262 if (prevTextChange
&& prevHide
->LocalParent() == mutEvent
->LocalParent()) {
263 if (prevHide
->mNextSibling
== target
) {
264 target
->AppendTextTo(prevTextChange
->mModifiedText
);
265 prevHide
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
266 } else if (prevHide
->mPrevSibling
== target
) {
268 target
->AppendTextTo(temp
);
270 uint32_t extraLen
= temp
.Length();
271 temp
+= prevTextChange
->mModifiedText
;
273 prevTextChange
->mModifiedText
= temp
;
274 prevTextChange
->mStart
-= extraLen
;
275 prevHide
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
278 } else if (prevEvent
&& mutEvent
->IsShow() &&
279 prevEvent
->GetEventType() == nsIAccessibleEvent::EVENT_SHOW
) {
280 AccShowEvent
* prevShow
= downcast_accEvent(prevEvent
);
281 AccTextChangeEvent
* prevTextChange
= prevShow
->mTextChangeEvent
;
282 if (prevTextChange
&& prevShow
->LocalParent() == target
->LocalParent()) {
283 int32_t index
= target
->IndexInParent();
284 int32_t prevIndex
= prevShow
->GetAccessible()->IndexInParent();
285 if (prevIndex
+ 1 == index
) {
286 target
->AppendTextTo(prevTextChange
->mModifiedText
);
287 prevShow
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
288 } else if (index
+ 1 == prevIndex
) {
290 target
->AppendTextTo(temp
);
291 prevTextChange
->mStart
-= temp
.Length();
292 temp
+= prevTextChange
->mModifiedText
;
293 prevTextChange
->mModifiedText
= temp
;
294 prevShow
->mTextChangeEvent
.swap(mutEvent
->mTextChangeEvent
);
299 if (!mutEvent
->mTextChangeEvent
) {
300 mutEvent
->mTextChangeEvent
= new AccTextChangeEvent(
301 container
, offset
, text
, mutEvent
->IsShow(),
302 aEvent
->mIsFromUserInput
? eFromUserInput
: eNoUserInput
);
308 void NotificationController::DropMutationEvent(AccTreeMutationEvent
* aEvent
) {
309 if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
310 // We don't fully drop reorder events, we just change them to inner reorder
312 AccReorderEvent
* reorderEvent
= downcast_accEvent(aEvent
);
314 MOZ_ASSERT(reorderEvent
);
315 reorderEvent
->SetInner();
317 } else if (aEvent
->GetEventType() == nsIAccessibleEvent::EVENT_SHOW
) {
318 // unset the event bits since the event isn't being fired any more.
319 aEvent
->GetAccessible()->SetShowEventTarget(false);
321 // unset the event bits since the event isn't being fired any more.
322 aEvent
->GetAccessible()->SetHideEventTarget(false);
324 AccHideEvent
* hideEvent
= downcast_accEvent(aEvent
);
325 MOZ_ASSERT(hideEvent
);
327 if (hideEvent
->NeedsShutdown()) {
328 mDocument
->ShutdownChildrenInSubtree(aEvent
->GetAccessible());
332 // Do the work to splice the event out of the list.
333 if (mFirstMutationEvent
== aEvent
) {
334 mFirstMutationEvent
= aEvent
->NextEvent();
336 aEvent
->PrevEvent()->SetNextEvent(aEvent
->NextEvent());
339 if (mLastMutationEvent
== aEvent
) {
340 mLastMutationEvent
= aEvent
->PrevEvent();
342 aEvent
->NextEvent()->SetPrevEvent(aEvent
->PrevEvent());
345 aEvent
->SetPrevEvent(nullptr);
346 aEvent
->SetNextEvent(nullptr);
347 mMutationMap
.RemoveEvent(aEvent
);
350 void NotificationController::CoalesceMutationEvents() {
351 AccTreeMutationEvent
* event
= mFirstMutationEvent
;
353 AccTreeMutationEvent
* nextEvent
= event
->NextEvent();
354 uint32_t eventType
= event
->GetEventType();
355 if (event
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
) {
356 LocalAccessible
* acc
= event
->GetAccessible();
362 // if a parent of the reorder event's target is being hidden that
363 // hide event's target must have a parent that is also a reorder event
364 // target. That means we don't need this reorder event.
365 if (acc
->HideEventTarget()) {
366 DropMutationEvent(event
);
370 LocalAccessible
* parent
= acc
->LocalParent();
371 if (parent
&& parent
->ReorderEventTarget()) {
372 AccReorderEvent
* reorder
= downcast_accEvent(
373 mMutationMap
.GetEvent(parent
, EventMap::ReorderEvent
));
375 // We want to make sure that a reorder event comes after any show or
376 // hide events targeted at the children of its target. We keep the
377 // invariant that event generation goes up as you are farther in the
378 // queue, so we want to use the spot of the event with the higher
379 // generation number, and keep that generation number.
381 reorder
->EventGeneration() < event
->EventGeneration()) {
382 reorder
->SetEventGeneration(event
->EventGeneration());
384 // It may be true that reorder was before event, and we coalesced
385 // away all the show / hide events between them. In that case
386 // event is already immediately after reorder in the queue and we
387 // do not need to rearrange the list of events.
388 if (event
!= reorder
->NextEvent()) {
389 // There really should be a show or hide event before the first
391 if (reorder
->PrevEvent()) {
392 reorder
->PrevEvent()->SetNextEvent(reorder
->NextEvent());
394 mFirstMutationEvent
= reorder
->NextEvent();
397 reorder
->NextEvent()->SetPrevEvent(reorder
->PrevEvent());
398 event
->PrevEvent()->SetNextEvent(reorder
);
399 reorder
->SetPrevEvent(event
->PrevEvent());
400 event
->SetPrevEvent(reorder
);
401 reorder
->SetNextEvent(event
);
404 DropMutationEvent(event
);
410 } else if (eventType
== nsIAccessibleEvent::EVENT_SHOW
) {
411 LocalAccessible
* parent
= event
->GetAccessible()->LocalParent();
413 if (parent
->IsDoc()) {
417 // if the parent of a show event is being either shown or hidden then
418 // we don't need to fire a show event for a subtree of that change.
419 if (parent
->ShowEventTarget() || parent
->HideEventTarget()) {
420 DropMutationEvent(event
);
424 parent
= parent
->LocalParent();
426 } else if (eventType
== nsIAccessibleEvent::EVENT_HIDE
) {
427 MOZ_ASSERT(eventType
== nsIAccessibleEvent::EVENT_HIDE
,
428 "mutation event list has an invalid event");
430 AccHideEvent
* hideEvent
= downcast_accEvent(event
);
431 CoalesceHideEvent(hideEvent
);
438 void NotificationController::ScheduleChildDocBinding(DocAccessible
* aDocument
) {
439 // Schedule child document binding to the tree.
440 mHangingChildDocuments
.AppendElement(aDocument
);
441 ScheduleProcessing();
444 void NotificationController::ScheduleContentInsertion(
445 LocalAccessible
* aContainer
, nsTArray
<nsCOMPtr
<nsIContent
>>& aInsertions
) {
446 if (!aInsertions
.IsEmpty()) {
447 mContentInsertions
.GetOrInsertNew(aContainer
)->AppendElements(aInsertions
);
448 ScheduleProcessing();
452 void NotificationController::ScheduleProcessing() {
453 // If notification flush isn't planed yet start notification flush
454 // asynchronously (after style and layout).
455 if (mObservingState
== eNotObservingRefresh
) {
456 if (mPresShell
->AddRefreshObserver(this, FlushType::Display
,
457 "Accessibility notifications")) {
458 mObservingState
= eRefreshObserving
;
463 ////////////////////////////////////////////////////////////////////////////////
464 // NotificationCollector: protected
466 bool NotificationController::IsUpdatePending() {
467 return mPresShell
->IsLayoutFlushObserver() ||
468 mObservingState
== eRefreshProcessingForUpdate
|| WaitingForParent() ||
469 mContentInsertions
.Count() != 0 || mNotifications
.Length() != 0 ||
470 mTextHash
.Count() != 0 ||
471 !mDocument
->HasLoadState(DocAccessible::eTreeConstructed
);
474 bool NotificationController::WaitingForParent() {
475 DocAccessible
* parentdoc
= mDocument
->ParentDocument();
480 NotificationController
* parent
= parentdoc
->mNotificationController
;
481 if (!parent
|| parent
== this) {
482 // Do not wait for nothing or ourselves
486 // Wait for parent's notifications processing
487 return parent
->mContentInsertions
.Count() != 0 ||
488 parent
->mNotifications
.Length() != 0;
491 void NotificationController::ProcessMutationEvents() {
492 // there is no reason to fire a hide event for a child of a show event
493 // target. That can happen if something is inserted into the tree and
494 // removed before the next refresh driver tick, but it should not be
495 // observable outside gecko so it should be safe to coalesce away any such
496 // events. This means that it should be fine to fire all of the hide events
497 // first, and then deal with any shown subtrees.
498 for (AccTreeMutationEvent
* event
= mFirstMutationEvent
; event
;
499 event
= event
->NextEvent()) {
500 if (event
->GetEventType() != nsIAccessibleEvent::EVENT_HIDE
) {
504 nsEventShell::FireEvent(event
);
509 AccMutationEvent
* mutEvent
= downcast_accEvent(event
);
510 if (mutEvent
->mTextChangeEvent
) {
511 nsEventShell::FireEvent(mutEvent
->mTextChangeEvent
);
517 // Fire menupopup end event before a hide event if a menu goes away.
519 // XXX: We don't look into children of hidden subtree to find hiding
520 // menupopup (as we did prior bug 570275) because we don't do that when
521 // menu is showing (and that's impossible until bug 606924 is fixed).
522 // Nevertheless we should do this at least because layout coalesces
523 // the changes before our processing and we may miss some menupopup
524 // events. Now we just want to be consistent in content insertion/removal
526 if (event
->mAccessible
->ARIARole() == roles::MENUPOPUP
) {
527 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END
,
534 AccHideEvent
* hideEvent
= downcast_accEvent(event
);
535 if (hideEvent
->NeedsShutdown()) {
536 mDocument
->ShutdownChildrenInSubtree(event
->mAccessible
);
540 // Group the show events by the parent of their target.
541 nsTHashMap
<nsPtrHashKey
<LocalAccessible
>, nsTArray
<AccTreeMutationEvent
*>>
543 for (AccTreeMutationEvent
* event
= mFirstMutationEvent
; event
;
544 event
= event
->NextEvent()) {
545 if (event
->GetEventType() != nsIAccessibleEvent::EVENT_SHOW
) {
549 LocalAccessible
* parent
= event
->GetAccessible()->LocalParent();
550 showEvents
.LookupOrInsert(parent
).AppendElement(event
);
553 // We need to fire show events for the children of an accessible in the order
554 // of their indices at this point. So sort each set of events for the same
555 // container by the index of their target.
556 for (auto iter
= showEvents
.Iter(); !iter
.Done(); iter
.Next()) {
557 struct AccIdxComparator
{
558 bool LessThan(const AccTreeMutationEvent
* a
,
559 const AccTreeMutationEvent
* b
) const {
560 int32_t aIdx
= a
->GetAccessible()->IndexInParent();
561 int32_t bIdx
= b
->GetAccessible()->IndexInParent();
562 MOZ_ASSERT(aIdx
>= 0 && bIdx
>= 0 && aIdx
!= bIdx
);
565 bool Equals(const AccTreeMutationEvent
* a
,
566 const AccTreeMutationEvent
* b
) const {
567 DebugOnly
<int32_t> aIdx
= a
->GetAccessible()->IndexInParent();
568 DebugOnly
<int32_t> bIdx
= b
->GetAccessible()->IndexInParent();
569 MOZ_ASSERT(aIdx
>= 0 && bIdx
>= 0 && aIdx
!= bIdx
);
574 nsTArray
<AccTreeMutationEvent
*>& events
= iter
.Data();
575 events
.Sort(AccIdxComparator());
576 for (AccTreeMutationEvent
* event
: events
) {
577 nsEventShell::FireEvent(event
);
582 AccMutationEvent
* mutEvent
= downcast_accEvent(event
);
583 if (mutEvent
->mTextChangeEvent
) {
584 nsEventShell::FireEvent(mutEvent
->mTextChangeEvent
);
592 // Now we can fire the reorder events after all the show and hide events.
593 for (const uint32_t reorderType
: {nsIAccessibleEvent::EVENT_INNER_REORDER
,
594 nsIAccessibleEvent::EVENT_REORDER
}) {
595 for (AccTreeMutationEvent
* event
= mFirstMutationEvent
; event
;
596 event
= event
->NextEvent()) {
597 if (event
->GetEventType() != reorderType
) {
601 if (event
->GetAccessible()->IsDefunct()) {
602 // An inner reorder target may have been hidden itself and no
603 // longer bound to the document.
604 MOZ_ASSERT(reorderType
== nsIAccessibleEvent::EVENT_INNER_REORDER
,
605 "An 'outer' reorder target should not be defunct");
609 nsEventShell::FireEvent(event
);
614 LocalAccessible
* target
= event
->GetAccessible();
615 target
->Document()->MaybeNotifyOfValueChange(target
);
623 ////////////////////////////////////////////////////////////////////////////////
624 // NotificationCollector: private
626 void NotificationController::WillRefresh(mozilla::TimeStamp aTime
) {
627 Telemetry::AutoTimer
<Telemetry::A11Y_TREE_UPDATE_TIMING_MS
> timer
;
629 AUTO_PROFILER_LABEL("NotificationController::WillRefresh", OTHER
);
631 // If the document accessible that notification collector was created for is
632 // now shut down, don't process notifications anymore.
635 "The document was shut down while refresh observer is attached!");
636 if (!mDocument
) return;
638 // Wait until an update, we have started, or an interruptible reflow is
640 if (mObservingState
== eRefreshProcessing
||
641 mObservingState
== eRefreshProcessingForUpdate
||
642 mPresShell
->IsReflowInterrupted()) {
646 // Process parent's notifications before ours, to get proper ordering between
647 // e.g. tab event and content event.
648 if (WaitingForParent()) {
649 mDocument
->ParentDocument()->mNotificationController
->WillRefresh(aTime
);
655 // Any generic notifications should be queued if we're processing content
656 // insertions or generic notifications.
657 mObservingState
= eRefreshProcessingForUpdate
;
659 // Initial accessible tree construction.
660 if (!mDocument
->HasLoadState(DocAccessible::eTreeConstructed
)) {
661 // If document is not bound to parent at this point then the document is not
662 // ready yet (process notifications later).
663 if (!mDocument
->IsBoundToParent()) {
664 mObservingState
= eRefreshObserving
;
669 if (logging::IsEnabled(logging::eTree
)) {
670 logging::MsgBegin("TREE", "initial tree created");
671 logging::Address("document", mDocument
);
676 mDocument
->DoInitialUpdate();
678 NS_ASSERTION(mContentInsertions
.Count() == 0,
679 "Pending content insertions while initial accessible tree "
683 mDocument
->ProcessPendingUpdates();
685 // Process rendered text change notifications.
686 for (nsIContent
* textNode
: mTextHash
) {
687 LocalAccessible
* textAcc
= mDocument
->GetAccessible(textNode
);
689 // If the text node is not in tree or doesn't have a frame, or placed in
690 // another document, then this case should have been handled already by
691 // content removal notifications.
692 nsINode
* containerNode
= textNode
->GetFlattenedTreeParentNode();
693 if (!containerNode
|| textNode
->OwnerDoc() != mDocument
->DocumentNode()) {
695 "Text node was removed but accessible is kept alive!");
699 nsIFrame
* textFrame
= textNode
->GetPrimaryFrame();
702 "Text node isn't rendered but accessible is kept alive!");
707 nsIContent
* containerElm
=
708 containerNode
->IsElement() ? containerNode
->AsElement() : nullptr;
711 nsIFrame::RenderedText text
= textFrame
->GetRenderedText(
712 0, UINT32_MAX
, nsIFrame::TextOffsetType::OffsetsInContentText
,
713 nsIFrame::TrailingWhitespace::DontTrim
);
715 // Remove text accessible if rendered text is empty.
717 if (text
.mString
.IsEmpty()) {
719 if (logging::IsEnabled(logging::eTree
| logging::eText
)) {
720 logging::MsgBegin("TREE", "text node lost its content; doc: %p",
722 logging::Node("container", containerElm
);
723 logging::Node("content", textNode
);
728 mDocument
->ContentRemoved(textAcc
);
732 // Update text of the accessible and fire text change events.
734 if (logging::IsEnabled(logging::eText
)) {
735 logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument
);
736 logging::Node("container", containerElm
);
737 logging::Node("content", textNode
);
740 NS_ConvertUTF16toUTF8(textAcc
->AsTextLeaf()->Text()).get());
741 logging::MsgEntry("new text: '%s'",
742 NS_ConvertUTF16toUTF8(text
.mString
).get());
747 TextUpdater::Run(mDocument
, textAcc
->AsTextLeaf(), text
.mString
);
748 if (IPCAccessibilityActive() &&
749 StaticPrefs::accessibility_cache_enabled_AtStartup()) {
750 mDocument
->QueueCacheUpdate(textAcc
, CacheDomain::Text
);
755 // Append an accessible if rendered text is not empty.
756 if (!text
.mString
.IsEmpty()) {
758 if (logging::IsEnabled(logging::eTree
| logging::eText
)) {
759 logging::MsgBegin("TREE", "text node gains new content; doc: %p",
761 logging::Node("container", containerElm
);
762 logging::Node("content", textNode
);
767 MOZ_ASSERT(mDocument
->AccessibleOrTrueContainer(containerNode
),
768 "Text node having rendered text hasn't accessible document!");
770 LocalAccessible
* container
=
771 mDocument
->AccessibleOrTrueContainer(containerNode
, true);
773 nsTArray
<nsCOMPtr
<nsIContent
>>* list
=
774 mContentInsertions
.GetOrInsertNew(container
);
775 list
->AppendElement(textNode
);
781 // Process content inserted notifications to update the tree.
782 // Processing an insertion can indirectly run script (e.g. querying a XUL
783 // interface), which might result in another insertion being queued.
784 // We don't want to lose any queued insertions if this happens. Therefore, we
785 // move the current insertions into a temporary data structure and process
786 // them from there. Any insertions queued during processing will get handled
787 // in subsequent refresh driver ticks.
788 const auto contentInsertions
= std::move(mContentInsertions
);
789 for (const auto& entry
: contentInsertions
) {
790 mDocument
->ProcessContentInserted(entry
.GetKey(), entry
.GetData().get());
796 // Bind hanging child documents unless we are using IPC and the
797 // document has no IPC actor. If we fail to bind the child doc then
799 uint32_t hangingDocCnt
= mHangingChildDocuments
.Length();
800 nsTArray
<RefPtr
<DocAccessible
>> newChildDocs
;
801 for (uint32_t idx
= 0; idx
< hangingDocCnt
; idx
++) {
802 DocAccessible
* childDoc
= mHangingChildDocuments
[idx
];
803 if (childDoc
->IsDefunct()) continue;
805 if (IPCAccessibilityActive() && !mDocument
->IPCDoc()) {
806 childDoc
->Shutdown();
810 nsIContent
* ownerContent
= childDoc
->DocumentNode()->GetEmbedderElement();
812 LocalAccessible
* outerDocAcc
= mDocument
->GetAccessible(ownerContent
);
813 if (outerDocAcc
&& outerDocAcc
->AppendChild(childDoc
)) {
814 if (mDocument
->AppendChildDocument(childDoc
)) {
815 newChildDocs
.AppendElement(std::move(mHangingChildDocuments
[idx
]));
819 outerDocAcc
->RemoveChild(childDoc
);
822 // Failed to bind the child document, destroy it.
823 childDoc
->Shutdown();
827 // Clear the hanging documents list, even if we didn't bind them.
828 mHangingChildDocuments
.Clear();
829 MOZ_ASSERT(mDocument
, "Illicit document shutdown");
834 // If the document is ready and all its subdocuments are completely loaded
835 // then process the document load.
836 if (mDocument
->HasLoadState(DocAccessible::eReady
) &&
837 !mDocument
->HasLoadState(DocAccessible::eCompletelyLoaded
) &&
838 hangingDocCnt
== 0) {
839 uint32_t childDocCnt
= mDocument
->ChildDocumentCount(), childDocIdx
= 0;
840 for (; childDocIdx
< childDocCnt
; childDocIdx
++) {
841 DocAccessible
* childDoc
= mDocument
->GetChildDocumentAt(childDocIdx
);
842 if (!childDoc
->HasLoadState(DocAccessible::eCompletelyLoaded
)) break;
845 if (childDocIdx
== childDocCnt
) {
846 mDocument
->ProcessLoad();
847 if (!mDocument
) return;
851 // Process invalidation list of the document after all accessible tree
853 mDocument
->ProcessInvalidationList();
855 // Process relocation list.
856 for (uint32_t idx
= 0; idx
< mRelocations
.Length(); idx
++) {
857 // owner should be in a document and have na associated DOM node (docs
859 if (mRelocations
[idx
]->IsInDocument() &&
860 mRelocations
[idx
]->HasOwnContent()) {
861 mDocument
->DoARIAOwnsRelocation(mRelocations
[idx
]);
864 mRelocations
.Clear();
866 // Process only currently queued generic notifications.
867 // These are used for processing aria-activedescendant, DOMMenuItemActive,
868 // etc. Therefore, they must be processed after relocations, since relocated
869 // subtrees might not have been created before relocation processing and the
870 // target might be inside a relocated subtree.
871 const nsTArray
<RefPtr
<Notification
>> notifications
=
872 std::move(mNotifications
);
874 uint32_t notificationCount
= notifications
.Length();
875 for (uint32_t idx
= 0; idx
< notificationCount
; idx
++) {
876 notifications
[idx
]->Process();
877 if (!mDocument
) return;
880 // If a generic notification occurs after this point then we may be allowed to
881 // process it synchronously. However we do not want to reenter if fireing
882 // events causes script to run.
883 mObservingState
= eRefreshProcessing
;
885 mDocument
->SendAccessiblesWillMove();
887 // Send any queued cache updates before we fire any mutation events so the
888 // cache is up to date when mutation events are fired. We do this after
889 // insertions (but not their events) so that cache updates dependent on the
890 // tree work correctly; e.g. line start calculation.
891 if (IPCAccessibilityActive() && mDocument
) {
892 mDocument
->ProcessQueuedCacheUpdates();
895 CoalesceMutationEvents();
896 ProcessMutationEvents();
897 mEventGeneration
= 0;
899 // Now that we are done with them get rid of the events we fired.
900 RefPtr
<AccTreeMutationEvent
> mutEvent
= std::move(mFirstMutationEvent
);
901 mLastMutationEvent
= nullptr;
902 mFirstMutationEvent
= nullptr;
904 RefPtr
<AccTreeMutationEvent
> nextEvent
= mutEvent
->NextEvent();
905 LocalAccessible
* target
= mutEvent
->GetAccessible();
907 // We need to be careful here, while it may seem that we can simply 0 all
908 // the pending event bits that is not true. Because accessibles may be
909 // reparented they may be the target of both a hide event and a show event
911 if (mutEvent
->GetEventType() == nsIAccessibleEvent::EVENT_SHOW
) {
912 target
->SetShowEventTarget(false);
915 if (mutEvent
->GetEventType() == nsIAccessibleEvent::EVENT_HIDE
) {
916 target
->SetHideEventTarget(false);
919 // However it is not possible for a reorder event target to also be the
920 // target of a show or hide, so we can just zero that.
921 target
->SetReorderEventTarget(false);
923 mutEvent
->SetPrevEvent(nullptr);
924 mutEvent
->SetNextEvent(nullptr);
925 mMutationMap
.RemoveEvent(mutEvent
);
926 mutEvent
= nextEvent
;
930 mDocument
->ClearMovedAccessibles();
935 if (IPCAccessibilityActive()) {
936 size_t newDocCount
= newChildDocs
.Length();
937 for (size_t i
= 0; i
< newDocCount
; i
++) {
938 DocAccessible
* childDoc
= newChildDocs
[i
];
939 if (childDoc
->IsDefunct()) {
943 LocalAccessible
* parent
= childDoc
->LocalParent();
944 DocAccessibleChild
* parentIPCDoc
= mDocument
->IPCDoc();
945 MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc
);
946 uint64_t id
= reinterpret_cast<uintptr_t>(parent
->UniqueID());
947 MOZ_DIAGNOSTIC_ASSERT(id
);
948 DocAccessibleChild
* ipcDoc
= childDoc
->IPCDoc();
950 parentIPCDoc
->SendBindChildDoc(ipcDoc
, id
);
954 ipcDoc
= new DocAccessibleChild(childDoc
, parentIPCDoc
->Manager());
955 childDoc
->SetIPCDoc(ipcDoc
);
958 parentIPCDoc
->ConstructChildDocInParentProcess(
960 StaticPrefs::accessibility_cache_enabled_AtStartup()
962 : MsaaAccessible::GetChildIDFor(childDoc
));
964 nsCOMPtr
<nsIBrowserChild
> browserChild
=
965 do_GetInterface(mDocument
->DocumentNode()->GetDocShell());
967 static_cast<BrowserChild
*>(browserChild
.get())
968 ->SendPDocAccessibleConstructor(ipcDoc
, parentIPCDoc
, id
, 0, 0);
969 ipcDoc
->SendPDocAccessiblePlatformExtConstructor();
975 mObservingState
= eRefreshObserving
;
976 if (!mDocument
) return;
978 // Stop further processing if there are no new notifications of any kind or
979 // events and document load is processed.
980 if (mContentInsertions
.Count() == 0 && mNotifications
.IsEmpty() &&
981 !mFocusEvent
&& mEvents
.IsEmpty() && mTextHash
.Count() == 0 &&
982 mHangingChildDocuments
.IsEmpty() &&
983 mDocument
->HasLoadState(DocAccessible::eCompletelyLoaded
) &&
984 mPresShell
->RemoveRefreshObserver(this, FlushType::Display
)) {
985 mObservingState
= eNotObservingRefresh
;
989 void NotificationController::EventMap::PutEvent(AccTreeMutationEvent
* aEvent
) {
990 EventType type
= GetEventType(aEvent
);
991 uint64_t addr
= reinterpret_cast<uintptr_t>(aEvent
->GetAccessible());
992 MOZ_ASSERT((addr
& 0x3) == 0, "accessible is not 4 byte aligned");
994 mTable
.InsertOrUpdate(addr
, RefPtr
{aEvent
});
997 AccTreeMutationEvent
* NotificationController::EventMap::GetEvent(
998 LocalAccessible
* aTarget
, EventType aType
) {
999 uint64_t addr
= reinterpret_cast<uintptr_t>(aTarget
);
1000 MOZ_ASSERT((addr
& 0x3) == 0, "target is not 4 byte aligned");
1003 return mTable
.GetWeak(addr
);
1006 void NotificationController::EventMap::RemoveEvent(
1007 AccTreeMutationEvent
* aEvent
) {
1008 EventType type
= GetEventType(aEvent
);
1009 uint64_t addr
= reinterpret_cast<uintptr_t>(aEvent
->GetAccessible());
1010 MOZ_ASSERT((addr
& 0x3) == 0, "accessible is not 4 byte aligned");
1013 MOZ_ASSERT(mTable
.GetWeak(addr
) == aEvent
, "mTable has the wrong event");
1014 mTable
.Remove(addr
);
1017 NotificationController::EventMap::EventType
1018 NotificationController::EventMap::GetEventType(AccTreeMutationEvent
* aEvent
) {
1019 switch (aEvent
->GetEventType()) {
1020 case nsIAccessibleEvent::EVENT_SHOW
:
1022 case nsIAccessibleEvent::EVENT_HIDE
:
1024 case nsIAccessibleEvent::EVENT_REORDER
:
1025 case nsIAccessibleEvent::EVENT_INNER_REORDER
:
1026 return ReorderEvent
;
1028 MOZ_ASSERT_UNREACHABLE("event has invalid type");