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