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