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