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