1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "EventTree.h"
7 
8 #include "LocalAccessible-inl.h"
9 #include "EmbeddedObjCollector.h"
10 #include "NotificationController.h"
11 #include "nsEventShell.h"
12 #include "DocAccessible.h"
13 #ifdef A11Y_LOG
14 #  include "Logging.h"
15 #endif
16 
17 #include "mozilla/UniquePtr.h"
18 
19 using namespace mozilla;
20 using namespace mozilla::a11y;
21 
22 ////////////////////////////////////////////////////////////////////////////////
23 // TreeMutation class
24 
25 EventTree* const TreeMutation::kNoEventTree = reinterpret_cast<EventTree*>(-1);
26 
TreeMutation(LocalAccessible * aParent,bool aNoEvents)27 TreeMutation::TreeMutation(LocalAccessible* aParent, bool aNoEvents)
28     : mParent(aParent),
29       mStartIdx(UINT32_MAX),
30       mStateFlagsCopy(mParent->mStateFlags),
31       mQueueEvents(!aNoEvents) {
32 #ifdef DEBUG
33   mIsDone = false;
34 #endif
35 
36 #ifdef A11Y_LOG
37   if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
38     logging::MsgBegin("EVENTS_TREE", "reordering tree before");
39     logging::AccessibleInfo("reordering for", mParent);
40     Controller()->RootEventTree().Log();
41     logging::MsgEnd();
42 
43     if (logging::IsEnabled(logging::eVerbose)) {
44       logging::Tree("EVENTS_TREE", "Container tree", mParent->Document(),
45                     PrefixLog, static_cast<void*>(this));
46     }
47   }
48 #endif
49 
50   mParent->mStateFlags |= LocalAccessible::eKidsMutating;
51 }
52 
~TreeMutation()53 TreeMutation::~TreeMutation() {
54   MOZ_ASSERT(mIsDone, "Done() must be called explicitly");
55 }
56 
AfterInsertion(LocalAccessible * aChild)57 void TreeMutation::AfterInsertion(LocalAccessible* aChild) {
58   MOZ_ASSERT(aChild->LocalParent() == mParent);
59 
60   if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
61     mStartIdx = aChild->mIndexInParent + 1;
62   }
63 
64   if (!mQueueEvents) {
65     return;
66   }
67 
68   RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
69   DebugOnly<bool> added = Controller()->QueueMutationEvent(ev);
70   MOZ_ASSERT(added);
71   aChild->SetShowEventTarget(true);
72 }
73 
BeforeRemoval(LocalAccessible * aChild,bool aNoShutdown)74 void TreeMutation::BeforeRemoval(LocalAccessible* aChild, bool aNoShutdown) {
75   MOZ_ASSERT(aChild->LocalParent() == mParent);
76 
77   if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
78     mStartIdx = aChild->mIndexInParent;
79   }
80 
81   if (!mQueueEvents) {
82     return;
83   }
84 
85   RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, !aNoShutdown);
86   if (Controller()->QueueMutationEvent(ev)) {
87     aChild->SetHideEventTarget(true);
88   }
89 }
90 
Done()91 void TreeMutation::Done() {
92   MOZ_ASSERT(mParent->mStateFlags & LocalAccessible::eKidsMutating);
93   mParent->mStateFlags &= ~LocalAccessible::eKidsMutating;
94 
95   uint32_t length = mParent->mChildren.Length();
96 #ifdef DEBUG
97   for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
98     MOZ_ASSERT(
99         mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx),
100         "Wrong index detected");
101   }
102 #endif
103 
104   for (uint32_t idx = mStartIdx; idx < length; idx++) {
105     mParent->mChildren[idx]->mIndexOfEmbeddedChild = -1;
106   }
107 
108   for (uint32_t idx = 0; idx < length; idx++) {
109     mParent->mChildren[idx]->mStateFlags |= LocalAccessible::eGroupInfoDirty;
110   }
111 
112   mParent->mEmbeddedObjCollector = nullptr;
113   mParent->mStateFlags |= mStateFlagsCopy & LocalAccessible::eKidsMutating;
114 
115 #ifdef DEBUG
116   mIsDone = true;
117 #endif
118 
119 #ifdef A11Y_LOG
120   if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
121     logging::MsgBegin("EVENTS_TREE", "reordering tree after");
122     logging::AccessibleInfo("reordering for", mParent);
123     Controller()->RootEventTree().Log();
124     logging::MsgEnd();
125   }
126 #endif
127 }
128 
129 #ifdef A11Y_LOG
PrefixLog(void * aData,LocalAccessible * aAcc)130 const char* TreeMutation::PrefixLog(void* aData, LocalAccessible* aAcc) {
131   TreeMutation* thisObj = reinterpret_cast<TreeMutation*>(aData);
132   if (thisObj->mParent == aAcc) {
133     return "_X_";
134   }
135   const EventTree& ret = thisObj->Controller()->RootEventTree();
136   if (ret.Find(aAcc)) {
137     return "_с_";
138   }
139   return "";
140 }
141 #endif
142 
143 ////////////////////////////////////////////////////////////////////////////////
144 // EventTree
145 
Shown(LocalAccessible * aChild)146 void EventTree::Shown(LocalAccessible* aChild) {
147   RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
148   Controller(aChild)->WithdrawPrecedingEvents(&ev->mPrecedingEvents);
149   Mutated(ev);
150 }
151 
Hidden(LocalAccessible * aChild,bool aNeedsShutdown)152 void EventTree::Hidden(LocalAccessible* aChild, bool aNeedsShutdown) {
153   RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, aNeedsShutdown);
154   if (!aNeedsShutdown) {
155     Controller(aChild)->StorePrecedingEvent(ev);
156   }
157   Mutated(ev);
158 }
159 
Process(const RefPtr<DocAccessible> & aDeathGrip)160 void EventTree::Process(const RefPtr<DocAccessible>& aDeathGrip) {
161   while (mFirst) {
162     // Skip a node and its subtree if its container is not in the document.
163     if (mFirst->mContainer->IsInDocument()) {
164       mFirst->Process(aDeathGrip);
165       if (aDeathGrip->IsDefunct()) {
166         return;
167       }
168     }
169     mFirst = std::move(mFirst->mNext);
170   }
171 
172   MOZ_ASSERT(mContainer || mDependentEvents.IsEmpty(),
173              "No container, no events");
174   MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(),
175              "Processing events for defunct container");
176   MOZ_ASSERT(!mFireReorder || mContainer, "No target for reorder event");
177 
178   // Fire mutation events.
179   uint32_t eventsCount = mDependentEvents.Length();
180   for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
181     AccMutationEvent* mtEvent = mDependentEvents[jdx];
182     MOZ_ASSERT(mtEvent->Document(), "No document for event target");
183 
184     // Fire all hide events that has to be fired before this show event.
185     if (mtEvent->IsShow()) {
186       AccShowEvent* showEv = downcast_accEvent(mtEvent);
187       for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
188         nsEventShell::FireEvent(showEv->mPrecedingEvents[i]);
189         if (aDeathGrip->IsDefunct()) {
190           return;
191         }
192       }
193     }
194 
195     nsEventShell::FireEvent(mtEvent);
196     if (aDeathGrip->IsDefunct()) {
197       return;
198     }
199 
200     if (mtEvent->mTextChangeEvent) {
201       nsEventShell::FireEvent(mtEvent->mTextChangeEvent);
202       if (aDeathGrip->IsDefunct()) {
203         return;
204       }
205     }
206 
207     if (mtEvent->IsHide()) {
208       // Fire menupopup end event before a hide event if a menu goes away.
209 
210       // XXX: We don't look into children of hidden subtree to find hiding
211       // menupopup (as we did prior bug 570275) because we don't do that when
212       // menu is showing (and that's impossible until bug 606924 is fixed).
213       // Nevertheless we should do this at least because layout coalesces
214       // the changes before our processing and we may miss some menupopup
215       // events. Now we just want to be consistent in content insertion/removal
216       // handling.
217       if (mtEvent->mAccessible->ARIARole() == roles::MENUPOPUP) {
218         nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
219                                 mtEvent->mAccessible);
220         if (aDeathGrip->IsDefunct()) {
221           return;
222         }
223       }
224 
225       AccHideEvent* hideEvent = downcast_accEvent(mtEvent);
226       if (hideEvent->NeedsShutdown()) {
227         aDeathGrip->ShutdownChildrenInSubtree(mtEvent->mAccessible);
228       }
229     }
230   }
231 
232   // Fire reorder event at last.
233   if (mFireReorder) {
234     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer);
235     mContainer->Document()->MaybeNotifyOfValueChange(mContainer);
236   }
237 
238   mDependentEvents.Clear();
239 }
240 
FindOrInsert(LocalAccessible * aContainer)241 EventTree* EventTree::FindOrInsert(LocalAccessible* aContainer) {
242   if (!mFirst) {
243     mFirst.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
244     return mFirst.get();
245   }
246 
247   EventTree* prevNode = nullptr;
248   EventTree* node = mFirst.get();
249   do {
250     MOZ_ASSERT(!node->mContainer->IsApplication(),
251                "No event for application accessible is expected here");
252     MOZ_ASSERT(!node->mContainer->IsDefunct(),
253                "An event target has to be alive");
254 
255     // Case of same target.
256     if (node->mContainer == aContainer) {
257       return node;
258     }
259 
260     // Check if the given container is contained by a current node
261     LocalAccessible* top = mContainer ? mContainer : aContainer->Document();
262     LocalAccessible* parent = aContainer;
263     while (parent) {
264       // Reached a top, no match for a current event.
265       if (parent == top) {
266         break;
267       }
268 
269       // We got a match.
270       if (parent->LocalParent() == node->mContainer) {
271         // Reject the node if it's contained by a show/hide event target
272         uint32_t evCount = node->mDependentEvents.Length();
273         for (uint32_t idx = 0; idx < evCount; idx++) {
274           AccMutationEvent* ev = node->mDependentEvents[idx];
275           if (ev->GetAccessible() == parent) {
276 #ifdef A11Y_LOG
277             if (logging::IsEnabled(logging::eEventTree)) {
278               logging::MsgBegin("EVENTS_TREE",
279                                 "Rejecting node contained by show/hide");
280               logging::AccessibleInfo("Node", aContainer);
281               logging::MsgEnd();
282             }
283 #endif
284             // If the node is rejected, then check if it has related hide event
285             // on stack, and if so, then connect it to the parent show event.
286             if (ev->IsShow()) {
287               AccShowEvent* showEv = downcast_accEvent(ev);
288               Controller(aContainer)
289                   ->WithdrawPrecedingEvents(&showEv->mPrecedingEvents);
290             }
291             return nullptr;
292           }
293         }
294 
295         return node->FindOrInsert(aContainer);
296       }
297 
298       parent = parent->LocalParent();
299       MOZ_ASSERT(parent, "Wrong tree");
300     }
301 
302     // If the given container contains a current node
303     // then
304     //   if show or hide of the given node contains a grand parent of the
305     //   current node then ignore the current node and its show and hide events
306     //   otherwise ignore the current node, but not its show and hide events
307     LocalAccessible* curParent = node->mContainer;
308     while (curParent && !curParent->IsDoc()) {
309       if (curParent->LocalParent() != aContainer) {
310         curParent = curParent->LocalParent();
311         continue;
312       }
313 
314       // Insert the tail node into the hierarchy between the current node and
315       // its parent.
316       node->mFireReorder = false;
317       UniquePtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
318       UniquePtr<EventTree> newNode(
319           new EventTree(aContainer, mDependentEvents.IsEmpty()));
320       newNode->mFirst = std::move(nodeOwnerRef);
321       nodeOwnerRef = std::move(newNode);
322       nodeOwnerRef->mNext = std::move(node->mNext);
323 
324       // Check if a next node is contained by the given node too, and move them
325       // under the given node if so.
326       prevNode = nodeOwnerRef.get();
327       node = nodeOwnerRef->mNext.get();
328       UniquePtr<EventTree>* nodeRef = &nodeOwnerRef->mNext;
329       EventTree* insNode = nodeOwnerRef->mFirst.get();
330       while (node) {
331         LocalAccessible* curParent = node->mContainer;
332         while (curParent && !curParent->IsDoc()) {
333           if (curParent->LocalParent() != aContainer) {
334             curParent = curParent->LocalParent();
335             continue;
336           }
337 
338           MOZ_ASSERT(!insNode->mNext);
339 
340           node->mFireReorder = false;
341           insNode->mNext = std::move(*nodeRef);
342           insNode = insNode->mNext.get();
343 
344           prevNode->mNext = std::move(node->mNext);
345           node = prevNode;
346           break;
347         }
348 
349         prevNode = node;
350         nodeRef = &node->mNext;
351         node = node->mNext.get();
352       }
353 
354       return nodeOwnerRef.get();
355     }
356 
357     prevNode = node;
358   } while ((node = node->mNext.get()));
359 
360   MOZ_ASSERT(prevNode, "Nowhere to insert");
361   MOZ_ASSERT(!prevNode->mNext, "Taken by another node");
362 
363   // If 'this' node contains the given container accessible, then
364   //   do not emit a reorder event for the container
365   //   if a dependent show event target contains the given container then do not
366   //   emit show / hide events (see Process() method)
367 
368   prevNode->mNext.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
369   return prevNode->mNext.get();
370 }
371 
Clear()372 void EventTree::Clear() {
373   mFirst = nullptr;
374   mNext = nullptr;
375   mContainer = nullptr;
376 
377   uint32_t eventsCount = mDependentEvents.Length();
378   for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
379     mDependentEvents[jdx]->mEventType = AccEvent::eDoNotEmit;
380     AccHideEvent* ev = downcast_accEvent(mDependentEvents[jdx]);
381     if (ev && ev->NeedsShutdown()) {
382       ev->Document()->ShutdownChildrenInSubtree(ev->mAccessible);
383     }
384   }
385   mDependentEvents.Clear();
386 }
387 
Find(const LocalAccessible * aContainer) const388 const EventTree* EventTree::Find(const LocalAccessible* aContainer) const {
389   const EventTree* et = this;
390   while (et) {
391     if (et->mContainer == aContainer) {
392       return et;
393     }
394 
395     if (et->mFirst) {
396       et = et->mFirst.get();
397       const EventTree* cet = et->Find(aContainer);
398       if (cet) {
399         return cet;
400       }
401     }
402 
403     et = et->mNext.get();
404     const EventTree* cet = et->Find(aContainer);
405     if (cet) {
406       return cet;
407     }
408   }
409 
410   return nullptr;
411 }
412 
413 #ifdef A11Y_LOG
Log(uint32_t aLevel) const414 void EventTree::Log(uint32_t aLevel) const {
415   if (aLevel == UINT32_MAX) {
416     if (mFirst) {
417       mFirst->Log(0);
418     }
419     return;
420   }
421 
422   for (uint32_t i = 0; i < aLevel; i++) {
423     printf("  ");
424   }
425   logging::AccessibleInfo("container", mContainer);
426 
427   for (uint32_t i = 0; i < mDependentEvents.Length(); i++) {
428     AccMutationEvent* ev = mDependentEvents[i];
429     if (ev->IsShow()) {
430       for (uint32_t i = 0; i < aLevel + 1; i++) {
431         printf("  ");
432       }
433       logging::AccessibleInfo("shown", ev->mAccessible);
434 
435       AccShowEvent* showEv = downcast_accEvent(ev);
436       for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
437         for (uint32_t j = 0; j < aLevel + 1; j++) {
438           printf("  ");
439         }
440         logging::AccessibleInfo("preceding",
441                                 showEv->mPrecedingEvents[i]->mAccessible);
442       }
443     } else {
444       for (uint32_t i = 0; i < aLevel + 1; i++) {
445         printf("  ");
446       }
447       logging::AccessibleInfo("hidden", ev->mAccessible);
448     }
449   }
450 
451   if (mFirst) {
452     mFirst->Log(aLevel + 1);
453   }
454 
455   if (mNext) {
456     mNext->Log(aLevel);
457   }
458 }
459 #endif
460 
Mutated(AccMutationEvent * aEv)461 void EventTree::Mutated(AccMutationEvent* aEv) {
462   // If shown or hidden node is a root of previously mutated subtree, then
463   // discard those subtree mutations as we are no longer interested in them.
464   UniquePtr<EventTree>* node = &mFirst;
465   while (*node) {
466     LocalAccessible* cntr = (*node)->mContainer;
467     while (cntr != mContainer) {
468       if (cntr == aEv->mAccessible) {
469 #ifdef A11Y_LOG
470         if (logging::IsEnabled(logging::eEventTree)) {
471           logging::MsgBegin("EVENTS_TREE", "Trim subtree");
472           logging::AccessibleInfo("Show/hide container", aEv->mAccessible);
473           logging::AccessibleInfo("Trimmed subtree root", (*node)->mContainer);
474           logging::MsgEnd();
475         }
476 #endif
477 
478         // If the new hide is part of a move and it contains existing child
479         // shows, then move preceding events from the child shows to the buffer,
480         // so the ongoing show event will pick them up.
481         if (aEv->IsHide()) {
482           AccHideEvent* hideEv = downcast_accEvent(aEv);
483           if (!hideEv->mNeedsShutdown) {
484             for (uint32_t i = 0; i < (*node)->mDependentEvents.Length(); i++) {
485               AccMutationEvent* childEv = (*node)->mDependentEvents[i];
486               if (childEv->IsShow()) {
487                 AccShowEvent* childShowEv = downcast_accEvent(childEv);
488                 if (childShowEv->mPrecedingEvents.Length() > 0) {
489                   Controller(mContainer)
490                       ->StorePrecedingEvents(
491                           std::move(childShowEv->mPrecedingEvents));
492                 }
493               }
494             }
495           }
496         }
497         // If the new show contains existing child shows, then move preceding
498         // events from the child shows to the new show.
499         else if (aEv->IsShow()) {
500           AccShowEvent* showEv = downcast_accEvent(aEv);
501           for (uint32_t i = 0; (*node)->mDependentEvents.Length(); i++) {
502             AccMutationEvent* childEv = (*node)->mDependentEvents[i];
503             if (childEv->IsShow()) {
504               AccShowEvent* showChildEv = downcast_accEvent(childEv);
505               if (showChildEv->mPrecedingEvents.Length() > 0) {
506 #ifdef A11Y_LOG
507                 if (logging::IsEnabled(logging::eEventTree)) {
508                   logging::MsgBegin("EVENTS_TREE", "Adopt preceding events");
509                   logging::AccessibleInfo("Parent", aEv->mAccessible);
510                   for (uint32_t j = 0;
511                        j < showChildEv->mPrecedingEvents.Length(); j++) {
512                     logging::AccessibleInfo(
513                         "Adoptee",
514                         showChildEv->mPrecedingEvents[i]->mAccessible);
515                   }
516                   logging::MsgEnd();
517                 }
518 #endif
519                 showEv->mPrecedingEvents.AppendElements(
520                     showChildEv->mPrecedingEvents);
521               }
522             }
523           }
524         }
525 
526         *node = std::move((*node)->mNext);
527         break;
528       }
529       cntr = cntr->LocalParent();
530     }
531     if (cntr == aEv->mAccessible) {
532       continue;
533     }
534     node = &(*node)->mNext;
535   }
536 
537   AccMutationEvent* prevEvent = mDependentEvents.SafeLastElement(nullptr);
538   mDependentEvents.AppendElement(aEv);
539 
540   // Coalesce text change events from this hide/show event and the previous one.
541   if (prevEvent && aEv->mEventType == prevEvent->mEventType) {
542     if (aEv->IsHide()) {
543       // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
544       // affect the text within the hypertext.
545       AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
546       if (prevTextEvent) {
547         AccHideEvent* hideEvent = downcast_accEvent(aEv);
548         AccHideEvent* prevHideEvent = downcast_accEvent(prevEvent);
549 
550         if (prevHideEvent->mNextSibling == hideEvent->mAccessible) {
551           hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
552         } else if (prevHideEvent->mPrevSibling == hideEvent->mAccessible) {
553           uint32_t oldLen = prevTextEvent->GetLength();
554           hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
555           prevTextEvent->mStart -= prevTextEvent->GetLength() - oldLen;
556         }
557 
558         hideEvent->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
559       }
560     } else {
561       AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
562       if (prevTextEvent) {
563         if (aEv->mAccessible->IndexInParent() ==
564             prevEvent->mAccessible->IndexInParent() + 1) {
565           // If tail target was inserted after this target, i.e. tail target is
566           // next sibling of this target.
567           aEv->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
568         } else if (aEv->mAccessible->IndexInParent() ==
569                    prevEvent->mAccessible->IndexInParent() - 1) {
570           // If tail target was inserted before this target, i.e. tail target is
571           // previous sibling of this target.
572           nsAutoString startText;
573           aEv->mAccessible->AppendTextTo(startText);
574           prevTextEvent->mModifiedText =
575               startText + prevTextEvent->mModifiedText;
576           prevTextEvent->mStart -= startText.Length();
577         }
578 
579         aEv->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
580       }
581     }
582   }
583 
584   // Create a text change event caused by this hide/show event. When a node is
585   // hidden/removed or shown/appended, the text in an ancestor hyper text will
586   // lose or get new characters.
587   if (aEv->mTextChangeEvent || !mContainer->IsHyperText()) {
588     return;
589   }
590 
591   nsAutoString text;
592   aEv->mAccessible->AppendTextTo(text);
593   if (text.IsEmpty()) {
594     return;
595   }
596 
597   int32_t offset = mContainer->AsHyperText()->GetChildOffset(aEv->mAccessible);
598   aEv->mTextChangeEvent = new AccTextChangeEvent(
599       mContainer, offset, text, aEv->IsShow(),
600       aEv->mIsFromUserInput ? eFromUserInput : eNoUserInput);
601 }
602