Bug 1733869 [wpt PR 30916] - Add a note about counterintuitive send_keys() code point...
[gecko.git] / accessible / base / NotificationController.cpp
blobc83c7a3342d013263779222684bbd10dc7804303
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),
36 mEventGeneration(0) {
37 #ifdef DEBUG
38 mMoveGuardOnStack = false;
39 #endif
41 // Schedule initial accessible tree construction.
42 ScheduleProcessing();
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();
99 mDocument = nullptr;
100 mPresShell = nullptr;
102 mTextHash.Clear();
103 mContentInsertions.Clear();
104 mNotifications.Clear();
105 mEvents.Clear();
106 mRelocations.Clear();
107 mEventTree.Clear();
110 EventTree* NotificationController::QueueMutation(LocalAccessible* aContainer) {
111 EventTree* tree = mEventTree.FindOrInsert(aContainer);
112 if (tree) {
113 ScheduleProcessing();
115 return tree;
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);
127 return false;
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()) {
135 return false;
139 AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
140 mEventGeneration++;
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
167 // done here.
168 if (mLastMutationEvent != aEvent) {
169 return false;
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
176 // reorder event.
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();
190 } else {
191 AccReorderEvent* event = downcast_accEvent(
192 mMutationMap.GetEvent(container, EventMap::ReorderEvent));
193 reorder = event;
194 if (mFirstMutationEvent == event) {
195 mFirstMutationEvent = event->NextEvent();
196 } else {
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()) {
212 return true;
215 MOZ_ASSERT(mutEvent);
217 nsString text;
218 aEvent->GetAccessible()->AppendTextTo(text);
219 if (text.IsEmpty()) {
220 return true;
223 int32_t offset = container->AsHyperText()->GetChildOffset(target);
224 AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
225 while (prevEvent &&
226 prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
227 prevEvent = prevEvent->PrevEvent();
230 if (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) {
240 nsString temp;
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) {
262 nsString temp;
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);
278 return true;
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);
287 } else {
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();
301 } else {
302 aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
305 if (mLastMutationEvent == aEvent) {
306 mLastMutationEvent = aEvent->PrevEvent();
307 } else {
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;
318 while (event) {
319 AccTreeMutationEvent* nextEvent = event->NextEvent();
320 uint32_t eventType = event->GetEventType();
321 if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
322 LocalAccessible* acc = event->GetAccessible();
323 while (acc) {
324 if (acc->IsDoc()) {
325 break;
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);
333 break;
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.
346 if (reorder &&
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
356 // reorder event.
357 if (reorder->PrevEvent()) {
358 reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
359 } else {
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);
371 break;
374 acc = parent;
376 } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
377 LocalAccessible* parent = event->GetAccessible()->LocalParent();
378 while (parent) {
379 if (parent->IsDoc()) {
380 break;
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);
387 break;
390 parent = parent->LocalParent();
392 } else {
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();
398 while (parent) {
399 if (parent->IsDoc()) {
400 break;
403 if (parent->HideEventTarget()) {
404 DropMutationEvent(event);
405 break;
408 if (parent->ShowEventTarget()) {
409 AccShowEvent* showEvent = downcast_accEvent(
410 mMutationMap.GetEvent(parent, EventMap::ShowEvent));
411 if (showEvent->EventGeneration() < hideEvent->EventGeneration()) {
412 DropMutationEvent(hideEvent);
413 break;
417 parent = parent->LocalParent();
421 event = nextEvent;
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();
463 if (!parentdoc) {
464 return false;
467 NotificationController* parent = parentdoc->mNotificationController;
468 if (!parent || parent == this) {
469 // Do not wait for nothing or ourselves
470 return false;
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) {
488 continue;
491 nsEventShell::FireEvent(event);
492 if (!mDocument) {
493 return;
496 AccMutationEvent* mutEvent = downcast_accEvent(event);
497 if (mutEvent->mTextChangeEvent) {
498 nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
499 if (!mDocument) {
500 return;
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
512 // handling.
513 if (event->mAccessible->ARIARole() == roles::MENUPOPUP) {
514 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
515 event->mAccessible);
516 if (!mDocument) {
517 return;
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*>>
529 showEvents;
530 for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
531 event = event->NextEvent()) {
532 if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
533 continue;
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);
550 return 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);
557 return false;
561 nsTArray<AccTreeMutationEvent*>& events = iter.Data();
562 events.Sort(AccIdxComparator());
563 for (AccTreeMutationEvent* event : events) {
564 nsEventShell::FireEvent(event);
565 if (!mDocument) {
566 return;
569 AccMutationEvent* mutEvent = downcast_accEvent(event);
570 if (mutEvent->mTextChangeEvent) {
571 nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
572 if (!mDocument) {
573 return;
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) {
583 continue;
586 nsEventShell::FireEvent(event);
587 if (!mDocument) {
588 return;
591 LocalAccessible* target = event->GetAccessible();
592 target->Document()->MaybeNotifyOfValueChange(target);
593 if (!mDocument) {
594 return;
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.
609 NS_ASSERTION(
610 mDocument,
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
615 // finished.
616 if (mObservingState == eRefreshProcessing ||
617 mObservingState == eRefreshProcessingForUpdate ||
618 mPresShell->IsReflowInterrupted()) {
619 return;
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);
626 if (!mDocument) {
627 return;
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;
641 return;
644 #ifdef A11Y_LOG
645 if (logging::IsEnabled(logging::eTree)) {
646 logging::MsgBegin("TREE", "initial tree created");
647 logging::Address("document", mDocument);
648 logging::MsgEnd();
650 #endif
652 mDocument->DoInitialUpdate();
654 NS_ASSERTION(mContentInsertions.Count() == 0,
655 "Pending content insertions while initial accessible tree "
656 "isn't created!");
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()) {
668 MOZ_ASSERT(!textAcc,
669 "Text node was removed but accessible is kept alive!");
670 continue;
673 nsIFrame* textFrame = textNode->GetPrimaryFrame();
674 if (!textFrame) {
675 MOZ_ASSERT(!textAcc,
676 "Text node isn't rendered but accessible is kept alive!");
677 continue;
680 #ifdef A11Y_LOG
681 nsIContent* containerElm =
682 containerNode->IsElement() ? containerNode->AsElement() : nullptr;
683 #endif
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.
690 if (textAcc) {
691 if (text.mString.IsEmpty()) {
692 #ifdef A11Y_LOG
693 if (logging::IsEnabled(logging::eTree | logging::eText)) {
694 logging::MsgBegin("TREE", "text node lost its content; doc: %p",
695 mDocument);
696 logging::Node("container", containerElm);
697 logging::Node("content", textNode);
698 logging::MsgEnd();
700 #endif
702 mDocument->ContentRemoved(textAcc);
703 continue;
706 // Update text of the accessible and fire text change events.
707 #ifdef A11Y_LOG
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);
712 logging::MsgEntry(
713 "old text '%s'",
714 NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
715 logging::MsgEntry("new text: '%s'",
716 NS_ConvertUTF16toUTF8(text.mString).get());
717 logging::MsgEnd();
719 #endif
721 TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
722 continue;
725 // Append an accessible if rendered text is not empty.
726 if (!text.mString.IsEmpty()) {
727 #ifdef A11Y_LOG
728 if (logging::IsEnabled(logging::eTree | logging::eText)) {
729 logging::MsgBegin("TREE", "text node gains new content; doc: %p",
730 mDocument);
731 logging::Node("container", containerElm);
732 logging::Node("content", textNode);
733 logging::MsgEnd();
735 #endif
737 MOZ_ASSERT(mDocument->AccessibleOrTrueContainer(containerNode),
738 "Text node having rendered text hasn't accessible document!");
740 LocalAccessible* container =
741 mDocument->AccessibleOrTrueContainer(containerNode, true);
742 if (container) {
743 nsTArray<nsCOMPtr<nsIContent>>* list =
744 mContentInsertions.GetOrInsertNew(container);
745 list->AppendElement(textNode);
749 mTextHash.Clear();
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());
761 if (!mDocument) {
762 return;
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
768 // shut it down.
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();
777 continue;
780 nsIContent* ownerContent = childDoc->DocumentNode()->GetEmbedderElement();
781 if (ownerContent) {
782 LocalAccessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
783 if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
784 if (mDocument->AppendChildDocument(childDoc)) {
785 newChildDocs.AppendElement(std::move(mHangingChildDocuments[idx]));
786 continue;
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");
800 if (!mDocument) {
801 return;
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
822 // mutation is done.
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
828 // sometimes don't)
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;
863 while (mutEvent) {
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
870 // at the same time.
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;
889 ProcessEventQueue();
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()) {
896 continue;
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();
905 if (ipcDoc) {
906 parentIPCDoc->SendBindChildDoc(ipcDoc, id);
907 continue;
910 ipcDoc = new DocAccessibleChild(childDoc, parentIPCDoc->Manager());
911 childDoc->SetIPCDoc(ipcDoc);
913 #if defined(XP_WIN)
914 parentIPCDoc->ConstructChildDocInParentProcess(
915 ipcDoc, id,
916 StaticPrefs::accessibility_cache_enabled_AtStartup()
918 : MsaaAccessible::GetChildIDFor(childDoc));
919 #else
920 nsCOMPtr<nsIBrowserChild> browserChild =
921 do_GetInterface(mDocument->DocumentNode()->GetDocShell());
922 if (browserChild) {
923 static_cast<BrowserChild*>(browserChild.get())
924 ->SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
925 ipcDoc->SendPDocAccessiblePlatformExtConstructor();
927 #endif
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");
949 addr |= type;
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");
958 addr |= aType;
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");
967 addr |= type;
969 MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
970 mTable.Remove(addr);
973 NotificationController::EventMap::EventType
974 NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent) {
975 switch (aEvent->GetEventType()) {
976 case nsIAccessibleEvent::EVENT_SHOW:
977 return ShowEvent;
978 case nsIAccessibleEvent::EVENT_HIDE:
979 return HideEvent;
980 case nsIAccessibleEvent::EVENT_REORDER:
981 return ReorderEvent;
982 default:
983 MOZ_ASSERT_UNREACHABLE("event has invalid type");
984 return ShowEvent;