/src/mozilla-central/accessible/base/NotificationController.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 "NotificationController.h" |
7 | | |
8 | | #include "DocAccessible-inl.h" |
9 | | #include "DocAccessibleChild.h" |
10 | | #include "nsEventShell.h" |
11 | | #include "TextLeafAccessible.h" |
12 | | #include "TextUpdater.h" |
13 | | |
14 | | #include "mozilla/dom/TabChild.h" |
15 | | #include "mozilla/dom/Element.h" |
16 | | #include "mozilla/Telemetry.h" |
17 | | |
18 | | using namespace mozilla; |
19 | | using namespace mozilla::a11y; |
20 | | using namespace mozilla::dom; |
21 | | |
22 | | //////////////////////////////////////////////////////////////////////////////// |
23 | | // NotificationCollector |
24 | | //////////////////////////////////////////////////////////////////////////////// |
25 | | |
26 | | NotificationController::NotificationController(DocAccessible* aDocument, |
27 | | nsIPresShell* aPresShell) : |
28 | | EventQueue(aDocument), mObservingState(eNotObservingRefresh), |
29 | | mPresShell(aPresShell), mEventGeneration(0) |
30 | 0 | { |
31 | | #ifdef DEBUG |
32 | | mMoveGuardOnStack = false; |
33 | | #endif |
34 | |
|
35 | 0 | // Schedule initial accessible tree construction. |
36 | 0 | ScheduleProcessing(); |
37 | 0 | } |
38 | | |
39 | | NotificationController::~NotificationController() |
40 | 0 | { |
41 | 0 | NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!"); |
42 | 0 | if (mDocument) |
43 | 0 | Shutdown(); |
44 | 0 | } |
45 | | |
46 | | //////////////////////////////////////////////////////////////////////////////// |
47 | | // NotificationCollector: AddRef/Release and cycle collection |
48 | | |
49 | | NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController) |
50 | | NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController) |
51 | | |
52 | | NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController) |
53 | | |
54 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController) |
55 | 0 | if (tmp->mDocument) |
56 | 0 | tmp->Shutdown(); |
57 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
58 | | |
59 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController) |
60 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments) |
61 | 0 | for (auto it = tmp->mContentInsertions.ConstIter(); !it.Done(); it.Next()) { |
62 | 0 | NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key"); |
63 | 0 | cb.NoteXPCOMChild(it.Key()); |
64 | 0 | nsTArray<nsCOMPtr<nsIContent>>* list = it.UserData(); |
65 | 0 | for (uint32_t i = 0; i < list->Length(); i++) { |
66 | 0 | NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, |
67 | 0 | "mContentInsertions value item"); |
68 | 0 | cb.NoteXPCOMChild(list->ElementAt(i)); |
69 | 0 | } |
70 | 0 | } |
71 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents) |
72 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations) |
73 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
74 | | |
75 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef) |
76 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release) |
77 | | |
78 | | //////////////////////////////////////////////////////////////////////////////// |
79 | | // NotificationCollector: public |
80 | | |
81 | | void |
82 | | NotificationController::Shutdown() |
83 | 0 | { |
84 | 0 | if (mObservingState != eNotObservingRefresh && |
85 | 0 | mPresShell->RemoveRefreshObserver(this, FlushType::Display)) { |
86 | 0 | mObservingState = eNotObservingRefresh; |
87 | 0 | } |
88 | 0 |
|
89 | 0 | // Shutdown handling child documents. |
90 | 0 | int32_t childDocCount = mHangingChildDocuments.Length(); |
91 | 0 | for (int32_t idx = childDocCount - 1; idx >= 0; idx--) { |
92 | 0 | if (!mHangingChildDocuments[idx]->IsDefunct()) |
93 | 0 | mHangingChildDocuments[idx]->Shutdown(); |
94 | 0 | } |
95 | 0 |
|
96 | 0 | mHangingChildDocuments.Clear(); |
97 | 0 |
|
98 | 0 | mDocument = nullptr; |
99 | 0 | mPresShell = nullptr; |
100 | 0 |
|
101 | 0 | mTextHash.Clear(); |
102 | 0 | mContentInsertions.Clear(); |
103 | 0 | mNotifications.Clear(); |
104 | 0 | mEvents.Clear(); |
105 | 0 | mRelocations.Clear(); |
106 | 0 | mEventTree.Clear(); |
107 | 0 | } |
108 | | |
109 | | EventTree* |
110 | | NotificationController::QueueMutation(Accessible* aContainer) |
111 | 0 | { |
112 | 0 | EventTree* tree = mEventTree.FindOrInsert(aContainer); |
113 | 0 | if (tree) { |
114 | 0 | ScheduleProcessing(); |
115 | 0 | } |
116 | 0 | return tree; |
117 | 0 | } |
118 | | |
119 | | bool |
120 | | NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent) |
121 | 0 | { |
122 | 0 | // We have to allow there to be a hide and then a show event for a target |
123 | 0 | // because of targets getting moved. However we need to coalesce a show and |
124 | 0 | // then a hide for a target which means we need to check for that here. |
125 | 0 | if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE && |
126 | 0 | aEvent->GetAccessible()->ShowEventTarget()) { |
127 | 0 | AccTreeMutationEvent* showEvent = mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent); |
128 | 0 | DropMutationEvent(showEvent); |
129 | 0 | return false; |
130 | 0 | } |
131 | 0 | |
132 | 0 | AccMutationEvent* mutEvent = downcast_accEvent(aEvent); |
133 | 0 | mEventGeneration++; |
134 | 0 | mutEvent->SetEventGeneration(mEventGeneration); |
135 | 0 |
|
136 | 0 | if (!mFirstMutationEvent) { |
137 | 0 | mFirstMutationEvent = aEvent; |
138 | 0 | ScheduleProcessing(); |
139 | 0 | } |
140 | 0 |
|
141 | 0 | if (mLastMutationEvent) { |
142 | 0 | NS_ASSERTION(!mLastMutationEvent->NextEvent(), "why isn't the last event the end?"); |
143 | 0 | mLastMutationEvent->SetNextEvent(aEvent); |
144 | 0 | } |
145 | 0 |
|
146 | 0 | aEvent->SetPrevEvent(mLastMutationEvent); |
147 | 0 | mLastMutationEvent = aEvent; |
148 | 0 | mMutationMap.PutEvent(aEvent); |
149 | 0 |
|
150 | 0 | // Because we could be hiding the target of a show event we need to get rid |
151 | 0 | // of any such events. It may be possible to do less than coallesce all |
152 | 0 | // events, however that is easiest. |
153 | 0 | if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) { |
154 | 0 | CoalesceMutationEvents(); |
155 | 0 |
|
156 | 0 | // mLastMutationEvent will point to something other than aEvent if and only |
157 | 0 | // if aEvent was just coalesced away. In that case a parent accessible |
158 | 0 | // must already have the required reorder and text change events so we are |
159 | 0 | // done here. |
160 | 0 | if (mLastMutationEvent != aEvent) { |
161 | 0 | return false; |
162 | 0 | } |
163 | 0 | } |
164 | 0 | |
165 | 0 | // We need to fire a reorder event after all of the events targeted at shown or |
166 | 0 | // hidden children of a container. So either queue a new one, or move an |
167 | 0 | // existing one to the end of the queue if the container already has a |
168 | 0 | // reorder event. |
169 | 0 | Accessible* target = aEvent->GetAccessible(); |
170 | 0 | Accessible* container = aEvent->GetAccessible()->Parent(); |
171 | 0 | RefPtr<AccReorderEvent> reorder; |
172 | 0 | if (!container->ReorderEventTarget()) { |
173 | 0 | reorder = new AccReorderEvent(container); |
174 | 0 | container->SetReorderEventTarget(true); |
175 | 0 | mMutationMap.PutEvent(reorder); |
176 | 0 |
|
177 | 0 | // Since this is the first child of container that is changing, the name of |
178 | 0 | // container may be changing. |
179 | 0 | QueueNameChange(target); |
180 | 0 | } else { |
181 | 0 | AccReorderEvent* event = downcast_accEvent(mMutationMap.GetEvent(container, EventMap::ReorderEvent)); |
182 | 0 | reorder = event; |
183 | 0 | if (mFirstMutationEvent == event) { |
184 | 0 | mFirstMutationEvent = event->NextEvent(); |
185 | 0 | } else { |
186 | 0 | event->PrevEvent()->SetNextEvent(event->NextEvent()); |
187 | 0 | } |
188 | 0 |
|
189 | 0 | event->NextEvent()->SetPrevEvent(event->PrevEvent()); |
190 | 0 | event->SetNextEvent(nullptr); |
191 | 0 | } |
192 | 0 |
|
193 | 0 | reorder->SetEventGeneration(mEventGeneration); |
194 | 0 | reorder->SetPrevEvent(mLastMutationEvent); |
195 | 0 | mLastMutationEvent->SetNextEvent(reorder); |
196 | 0 | mLastMutationEvent = reorder; |
197 | 0 |
|
198 | 0 | // It is not possible to have a text change event for something other than a |
199 | 0 | // hyper text accessible. |
200 | 0 | if (!container->IsHyperText()) { |
201 | 0 | return true; |
202 | 0 | } |
203 | 0 | |
204 | 0 | MOZ_ASSERT(mutEvent); |
205 | 0 |
|
206 | 0 | nsString text; |
207 | 0 | aEvent->GetAccessible()->AppendTextTo(text); |
208 | 0 | if (text.IsEmpty()) { |
209 | 0 | return true; |
210 | 0 | } |
211 | 0 | |
212 | 0 | int32_t offset = container->AsHyperText()->GetChildOffset(target); |
213 | 0 | AccTreeMutationEvent* prevEvent = aEvent->PrevEvent(); |
214 | 0 | while (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) { |
215 | 0 | prevEvent = prevEvent->PrevEvent(); |
216 | 0 | } |
217 | 0 |
|
218 | 0 | if (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE && |
219 | 0 | mutEvent->IsHide()) { |
220 | 0 | AccHideEvent* prevHide = downcast_accEvent(prevEvent); |
221 | 0 | AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent; |
222 | 0 | if (prevTextChange && prevHide->Parent() == mutEvent->Parent()) { |
223 | 0 | if (prevHide->mNextSibling == target) { |
224 | 0 | target->AppendTextTo(prevTextChange->mModifiedText); |
225 | 0 | prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent); |
226 | 0 | } else if (prevHide->mPrevSibling == target) { |
227 | 0 | nsString temp; |
228 | 0 | target->AppendTextTo(temp); |
229 | 0 |
|
230 | 0 | uint32_t extraLen = temp.Length(); |
231 | 0 | temp += prevTextChange->mModifiedText;; |
232 | 0 | prevTextChange->mModifiedText = temp; |
233 | 0 | prevTextChange->mStart -= extraLen; |
234 | 0 | prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent); |
235 | 0 | } |
236 | 0 | } |
237 | 0 | } else if (prevEvent && mutEvent->IsShow() && |
238 | 0 | prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) { |
239 | 0 | AccShowEvent* prevShow = downcast_accEvent(prevEvent); |
240 | 0 | AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent; |
241 | 0 | if (prevTextChange && prevShow->Parent() == target->Parent()) { |
242 | 0 | int32_t index = target->IndexInParent(); |
243 | 0 | int32_t prevIndex = prevShow->GetAccessible()->IndexInParent(); |
244 | 0 | if (prevIndex + 1 == index) { |
245 | 0 | target->AppendTextTo(prevTextChange->mModifiedText); |
246 | 0 | prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent); |
247 | 0 | } else if (index + 1 == prevIndex) { |
248 | 0 | nsString temp; |
249 | 0 | target->AppendTextTo(temp); |
250 | 0 | prevTextChange->mStart -= temp.Length(); |
251 | 0 | temp += prevTextChange->mModifiedText; |
252 | 0 | prevTextChange->mModifiedText = temp; |
253 | 0 | prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent); |
254 | 0 | } |
255 | 0 | } |
256 | 0 | } |
257 | 0 |
|
258 | 0 | if (!mutEvent->mTextChangeEvent) { |
259 | 0 | mutEvent->mTextChangeEvent = |
260 | 0 | new AccTextChangeEvent(container, offset, text, mutEvent->IsShow(), |
261 | 0 | aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput); |
262 | 0 | } |
263 | 0 |
|
264 | 0 | return true; |
265 | 0 | } |
266 | | |
267 | | void |
268 | | NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent) |
269 | 0 | { |
270 | 0 | // unset the event bits since the event isn't being fired any more. |
271 | 0 | if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) { |
272 | 0 | aEvent->GetAccessible()->SetReorderEventTarget(false); |
273 | 0 | } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) { |
274 | 0 | aEvent->GetAccessible()->SetShowEventTarget(false); |
275 | 0 | } else { |
276 | 0 | aEvent->GetAccessible()->SetHideEventTarget(false); |
277 | 0 |
|
278 | 0 | AccHideEvent* hideEvent = downcast_accEvent(aEvent); |
279 | 0 | MOZ_ASSERT(hideEvent); |
280 | 0 |
|
281 | 0 | if (hideEvent->NeedsShutdown()) { |
282 | 0 | mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible()); |
283 | 0 | } |
284 | 0 | } |
285 | 0 |
|
286 | 0 | // Do the work to splice the event out of the list. |
287 | 0 | if (mFirstMutationEvent == aEvent) { |
288 | 0 | mFirstMutationEvent = aEvent->NextEvent(); |
289 | 0 | } else { |
290 | 0 | aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent()); |
291 | 0 | } |
292 | 0 |
|
293 | 0 | if (mLastMutationEvent == aEvent) { |
294 | 0 | mLastMutationEvent = aEvent->PrevEvent(); |
295 | 0 | } else { |
296 | 0 | aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent()); |
297 | 0 | } |
298 | 0 |
|
299 | 0 | aEvent->SetPrevEvent(nullptr); |
300 | 0 | aEvent->SetNextEvent(nullptr); |
301 | 0 | mMutationMap.RemoveEvent(aEvent); |
302 | 0 | } |
303 | | |
304 | | void |
305 | | NotificationController::CoalesceMutationEvents() |
306 | 0 | { |
307 | 0 | AccTreeMutationEvent* event = mFirstMutationEvent; |
308 | 0 | while (event) { |
309 | 0 | AccTreeMutationEvent* nextEvent = event->NextEvent(); |
310 | 0 | uint32_t eventType = event->GetEventType(); |
311 | 0 | if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) { |
312 | 0 | Accessible* acc = event->GetAccessible(); |
313 | 0 | while (acc) { |
314 | 0 | if (acc->IsDoc()) { |
315 | 0 | break; |
316 | 0 | } |
317 | 0 | |
318 | 0 | // if a parent of the reorder event's target is being hidden that |
319 | 0 | // hide event's target must have a parent that is also a reorder event |
320 | 0 | // target. That means we don't need this reorder event. |
321 | 0 | if (acc->HideEventTarget()) { |
322 | 0 | DropMutationEvent(event); |
323 | 0 | break; |
324 | 0 | } |
325 | 0 | |
326 | 0 | Accessible* parent = acc->Parent(); |
327 | 0 | if (parent->ReorderEventTarget()) { |
328 | 0 | AccReorderEvent* reorder = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ReorderEvent)); |
329 | 0 |
|
330 | 0 | // We want to make sure that a reorder event comes after any show or |
331 | 0 | // hide events targeted at the children of its target. We keep the |
332 | 0 | // invariant that event generation goes up as you are farther in the |
333 | 0 | // queue, so we want to use the spot of the event with the higher |
334 | 0 | // generation number, and keep that generation number. |
335 | 0 | if (reorder && reorder->EventGeneration() < event->EventGeneration()) { |
336 | 0 | reorder->SetEventGeneration(event->EventGeneration()); |
337 | 0 |
|
338 | 0 | // It may be true that reorder was before event, and we coalesced |
339 | 0 | // away all the show / hide events between them. In that case |
340 | 0 | // event is already immediately after reorder in the queue and we |
341 | 0 | // do not need to rearrange the list of events. |
342 | 0 | if (event != reorder->NextEvent()) { |
343 | 0 | // There really should be a show or hide event before the first |
344 | 0 | // reorder event. |
345 | 0 | if (reorder->PrevEvent()) { |
346 | 0 | reorder->PrevEvent()->SetNextEvent(reorder->NextEvent()); |
347 | 0 | } else { |
348 | 0 | mFirstMutationEvent = reorder->NextEvent(); |
349 | 0 | } |
350 | 0 |
|
351 | 0 | reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent()); |
352 | 0 | event->PrevEvent()->SetNextEvent(reorder); |
353 | 0 | reorder->SetPrevEvent(event->PrevEvent()); |
354 | 0 | event->SetPrevEvent(reorder); |
355 | 0 | reorder->SetNextEvent(event); |
356 | 0 | } |
357 | 0 | } |
358 | 0 | DropMutationEvent(event); |
359 | 0 | break; |
360 | 0 | } |
361 | 0 |
|
362 | 0 | acc = parent; |
363 | 0 | } |
364 | 0 | } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) { |
365 | 0 | Accessible* parent = event->GetAccessible()->Parent(); |
366 | 0 | while (parent) { |
367 | 0 | if (parent->IsDoc()) { |
368 | 0 | break; |
369 | 0 | } |
370 | 0 | |
371 | 0 | // if the parent of a show event is being either shown or hidden then |
372 | 0 | // we don't need to fire a show event for a subtree of that change. |
373 | 0 | if (parent->ShowEventTarget() || parent->HideEventTarget()) { |
374 | 0 | DropMutationEvent(event); |
375 | 0 | break; |
376 | 0 | } |
377 | 0 | |
378 | 0 | parent = parent->Parent(); |
379 | 0 | } |
380 | 0 | } else { |
381 | 0 | MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE, "mutation event list has an invalid event"); |
382 | 0 |
|
383 | 0 | AccHideEvent* hideEvent = downcast_accEvent(event); |
384 | 0 | Accessible* parent = hideEvent->Parent(); |
385 | 0 | while (parent) { |
386 | 0 | if (parent->IsDoc()) { |
387 | 0 | break; |
388 | 0 | } |
389 | 0 | |
390 | 0 | if (parent->HideEventTarget()) { |
391 | 0 | DropMutationEvent(event); |
392 | 0 | break; |
393 | 0 | } |
394 | 0 | |
395 | 0 | if (parent->ShowEventTarget()) { |
396 | 0 | AccShowEvent* showEvent = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ShowEvent)); |
397 | 0 | if (showEvent->EventGeneration() < hideEvent->EventGeneration()) { |
398 | 0 | DropMutationEvent(hideEvent); |
399 | 0 | break; |
400 | 0 | } |
401 | 0 | } |
402 | 0 | |
403 | 0 | parent = parent->Parent(); |
404 | 0 | } |
405 | 0 | } |
406 | 0 |
|
407 | 0 | event = nextEvent; |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | | void |
412 | | NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument) |
413 | 0 | { |
414 | 0 | // Schedule child document binding to the tree. |
415 | 0 | mHangingChildDocuments.AppendElement(aDocument); |
416 | 0 | ScheduleProcessing(); |
417 | 0 | } |
418 | | |
419 | | void |
420 | | NotificationController::ScheduleContentInsertion(Accessible* aContainer, |
421 | | nsIContent* aStartChildNode, |
422 | | nsIContent* aEndChildNode) |
423 | 0 | { |
424 | 0 | nsTArray<nsCOMPtr<nsIContent>> list; |
425 | 0 |
|
426 | 0 | bool needsProcessing = false; |
427 | 0 | nsIContent* node = aStartChildNode; |
428 | 0 | while (node != aEndChildNode) { |
429 | 0 | // Notification triggers for content insertion even if no content was |
430 | 0 | // actually inserted, check if the given content has a frame to discard |
431 | 0 | // this case early. |
432 | 0 | if (node->GetPrimaryFrame()) { |
433 | 0 | if (list.AppendElement(node)) |
434 | 0 | needsProcessing = true; |
435 | 0 | } |
436 | 0 | node = node->GetNextSibling(); |
437 | 0 | } |
438 | 0 |
|
439 | 0 | if (needsProcessing) { |
440 | 0 | mContentInsertions.LookupOrAdd(aContainer)->AppendElements(list); |
441 | 0 | ScheduleProcessing(); |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | void |
446 | | NotificationController::ScheduleProcessing() |
447 | 0 | { |
448 | 0 | // If notification flush isn't planed yet start notification flush |
449 | 0 | // asynchronously (after style and layout). |
450 | 0 | if (mObservingState == eNotObservingRefresh) { |
451 | 0 | if (mPresShell->AddRefreshObserver(this, FlushType::Display)) |
452 | 0 | mObservingState = eRefreshObserving; |
453 | 0 | } |
454 | 0 | } |
455 | | |
456 | | //////////////////////////////////////////////////////////////////////////////// |
457 | | // NotificationCollector: protected |
458 | | |
459 | | bool |
460 | | NotificationController::IsUpdatePending() |
461 | 0 | { |
462 | 0 | return mPresShell->IsLayoutFlushObserver() || |
463 | 0 | mObservingState == eRefreshProcessingForUpdate || |
464 | 0 | WaitingForParent() || |
465 | 0 | mContentInsertions.Count() != 0 || mNotifications.Length() != 0 || |
466 | 0 | mTextHash.Count() != 0 || |
467 | 0 | !mDocument->HasLoadState(DocAccessible::eTreeConstructed); |
468 | 0 | } |
469 | | |
470 | | bool |
471 | | NotificationController::WaitingForParent() |
472 | 0 | { |
473 | 0 | DocAccessible* parentdoc = mDocument->ParentDocument(); |
474 | 0 | if (!parentdoc) { |
475 | 0 | return false; |
476 | 0 | } |
477 | 0 | |
478 | 0 | NotificationController* parent = parentdoc->mNotificationController; |
479 | 0 | if (!parent || parent == this) { |
480 | 0 | // Do not wait for nothing or ourselves |
481 | 0 | return false; |
482 | 0 | } |
483 | 0 | |
484 | 0 | // Wait for parent's notifications processing |
485 | 0 | return parent->mContentInsertions.Count() != 0 || |
486 | 0 | parent->mNotifications.Length() != 0; |
487 | 0 | } |
488 | | |
489 | | void |
490 | | NotificationController::ProcessMutationEvents() |
491 | 0 | { |
492 | 0 | // there is no reason to fire a hide event for a child of a show event |
493 | 0 | // target. That can happen if something is inserted into the tree and |
494 | 0 | // removed before the next refresh driver tick, but it should not be |
495 | 0 | // observable outside gecko so it should be safe to coalesce away any such |
496 | 0 | // events. This means that it should be fine to fire all of the hide events |
497 | 0 | // first, and then deal with any shown subtrees. |
498 | 0 | for (AccTreeMutationEvent* event = mFirstMutationEvent; |
499 | 0 | event; event = event->NextEvent()) { |
500 | 0 | if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) { |
501 | 0 | continue; |
502 | 0 | } |
503 | 0 | |
504 | 0 | nsEventShell::FireEvent(event); |
505 | 0 | if (!mDocument) { |
506 | 0 | return; |
507 | 0 | } |
508 | 0 | |
509 | 0 | AccMutationEvent* mutEvent = downcast_accEvent(event); |
510 | 0 | if (mutEvent->mTextChangeEvent) { |
511 | 0 | nsEventShell::FireEvent(mutEvent->mTextChangeEvent); |
512 | 0 | if (!mDocument) { |
513 | 0 | return; |
514 | 0 | } |
515 | 0 | } |
516 | 0 | |
517 | 0 | // Fire menupopup end event before a hide event if a menu goes away. |
518 | 0 | |
519 | 0 | // XXX: We don't look into children of hidden subtree to find hiding |
520 | 0 | // menupopup (as we did prior bug 570275) because we don't do that when |
521 | 0 | // menu is showing (and that's impossible until bug 606924 is fixed). |
522 | 0 | // Nevertheless we should do this at least because layout coalesces |
523 | 0 | // the changes before our processing and we may miss some menupopup |
524 | 0 | // events. Now we just want to be consistent in content insertion/removal |
525 | 0 | // handling. |
526 | 0 | if (event->mAccessible->ARIARole() == roles::MENUPOPUP) { |
527 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, |
528 | 0 | event->mAccessible); |
529 | 0 | if (!mDocument) { |
530 | 0 | return; |
531 | 0 | } |
532 | 0 | } |
533 | 0 | |
534 | 0 | AccHideEvent* hideEvent = downcast_accEvent(event); |
535 | 0 | if (hideEvent->NeedsShutdown()) { |
536 | 0 | mDocument->ShutdownChildrenInSubtree(event->mAccessible); |
537 | 0 | } |
538 | 0 | } |
539 | 0 |
|
540 | 0 | // Group the show events by the parent of their target. |
541 | 0 | nsDataHashtable<nsPtrHashKey<Accessible>, nsTArray<AccTreeMutationEvent*>> showEvents; |
542 | 0 | for (AccTreeMutationEvent* event = mFirstMutationEvent; |
543 | 0 | event; event = event->NextEvent()) { |
544 | 0 | if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) { |
545 | 0 | continue; |
546 | 0 | } |
547 | 0 | |
548 | 0 | Accessible* parent = event->GetAccessible()->Parent(); |
549 | 0 | showEvents.GetOrInsert(parent).AppendElement(event); |
550 | 0 | } |
551 | 0 |
|
552 | 0 | // We need to fire show events for the children of an accessible in the order |
553 | 0 | // of their indices at this point. So sort each set of events for the same |
554 | 0 | // container by the index of their target. |
555 | 0 | for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) { |
556 | 0 | struct AccIdxComparator { |
557 | 0 | bool LessThan(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const |
558 | 0 | { |
559 | 0 | int32_t aIdx = a->GetAccessible()->IndexInParent(); |
560 | 0 | int32_t bIdx = b->GetAccessible()->IndexInParent(); |
561 | 0 | MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx); |
562 | 0 | return aIdx < bIdx; |
563 | 0 | } |
564 | 0 | bool Equals(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const |
565 | 0 | { |
566 | 0 | DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent(); |
567 | 0 | DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent(); |
568 | 0 | MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx); |
569 | 0 | return false; |
570 | 0 | } |
571 | 0 | }; |
572 | 0 |
|
573 | 0 | nsTArray<AccTreeMutationEvent*>& events = iter.Data(); |
574 | 0 | events.Sort(AccIdxComparator()); |
575 | 0 | for (AccTreeMutationEvent* event: events) { |
576 | 0 | nsEventShell::FireEvent(event); |
577 | 0 | if (!mDocument) { |
578 | 0 | return; |
579 | 0 | } |
580 | 0 | |
581 | 0 | AccMutationEvent* mutEvent = downcast_accEvent(event); |
582 | 0 | if (mutEvent->mTextChangeEvent) { |
583 | 0 | nsEventShell::FireEvent(mutEvent->mTextChangeEvent); |
584 | 0 | if (!mDocument) { |
585 | 0 | return; |
586 | 0 | } |
587 | 0 | } |
588 | 0 | } |
589 | 0 | } |
590 | 0 |
|
591 | 0 | // Now we can fire the reorder events after all the show and hide events. |
592 | 0 | for (AccTreeMutationEvent* event = mFirstMutationEvent; |
593 | 0 | event; event = event->NextEvent()) { |
594 | 0 | if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) { |
595 | 0 | continue; |
596 | 0 | } |
597 | 0 | |
598 | 0 | nsEventShell::FireEvent(event); |
599 | 0 | if (!mDocument) { |
600 | 0 | return; |
601 | 0 | } |
602 | 0 | |
603 | 0 | Accessible* target = event->GetAccessible(); |
604 | 0 | target->Document()->MaybeNotifyOfValueChange(target); |
605 | 0 | if (!mDocument) { |
606 | 0 | return; |
607 | 0 | } |
608 | 0 | } |
609 | 0 | } |
610 | | |
611 | | //////////////////////////////////////////////////////////////////////////////// |
612 | | // NotificationCollector: private |
613 | | |
614 | | void |
615 | | NotificationController::WillRefresh(mozilla::TimeStamp aTime) |
616 | 0 | { |
617 | 0 | Telemetry::AutoTimer<Telemetry::A11Y_TREE_UPDATE_TIMING_MS> timer; |
618 | 0 |
|
619 | 0 | AUTO_PROFILER_LABEL("NotificationController::WillRefresh", OTHER); |
620 | 0 |
|
621 | 0 | // If the document accessible that notification collector was created for is |
622 | 0 | // now shut down, don't process notifications anymore. |
623 | 0 | NS_ASSERTION(mDocument, |
624 | 0 | "The document was shut down while refresh observer is attached!"); |
625 | 0 | if (!mDocument) |
626 | 0 | return; |
627 | 0 | |
628 | 0 | // Wait until an update, we have started, or an interruptible reflow is |
629 | 0 | // finished. |
630 | 0 | if (mObservingState == eRefreshProcessing || |
631 | 0 | mObservingState == eRefreshProcessingForUpdate || |
632 | 0 | mPresShell->IsReflowInterrupted()) { |
633 | 0 | return; |
634 | 0 | } |
635 | 0 | |
636 | 0 | // Process parent's notifications before ours, to get proper ordering between |
637 | 0 | // e.g. tab event and content event. |
638 | 0 | if (WaitingForParent()) { |
639 | 0 | mDocument->ParentDocument()->mNotificationController->WillRefresh(aTime); |
640 | 0 | if (!mDocument) { |
641 | 0 | return; |
642 | 0 | } |
643 | 0 | } |
644 | 0 | |
645 | 0 | // Any generic notifications should be queued if we're processing content |
646 | 0 | // insertions or generic notifications. |
647 | 0 | mObservingState = eRefreshProcessingForUpdate; |
648 | 0 |
|
649 | 0 | // Initial accessible tree construction. |
650 | 0 | if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) { |
651 | 0 | // If document is not bound to parent at this point then the document is not |
652 | 0 | // ready yet (process notifications later). |
653 | 0 | if (!mDocument->IsBoundToParent()) { |
654 | 0 | mObservingState = eRefreshObserving; |
655 | 0 | return; |
656 | 0 | } |
657 | 0 | |
658 | 0 | #ifdef A11Y_LOG |
659 | 0 | if (logging::IsEnabled(logging::eTree)) { |
660 | 0 | logging::MsgBegin("TREE", "initial tree created"); |
661 | 0 | logging::Address("document", mDocument); |
662 | 0 | logging::MsgEnd(); |
663 | 0 | } |
664 | 0 | #endif |
665 | 0 |
|
666 | 0 | mDocument->DoInitialUpdate(); |
667 | 0 |
|
668 | 0 | NS_ASSERTION(mContentInsertions.Count() == 0, |
669 | 0 | "Pending content insertions while initial accessible tree isn't created!"); |
670 | 0 | } |
671 | 0 |
|
672 | 0 | // Initialize scroll support if needed. |
673 | 0 | if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized)) |
674 | 0 | mDocument->AddScrollListener(); |
675 | 0 |
|
676 | 0 | // Process rendered text change notifications. |
677 | 0 | for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) { |
678 | 0 | nsCOMPtrHashKey<nsIContent>* entry = iter.Get(); |
679 | 0 | nsIContent* textNode = entry->GetKey(); |
680 | 0 | Accessible* textAcc = mDocument->GetAccessible(textNode); |
681 | 0 |
|
682 | 0 | // If the text node is not in tree or doesn't have a frame, or placed in |
683 | 0 | // another document, then this case should have been handled already by |
684 | 0 | // content removal notifications. |
685 | 0 | nsINode* containerNode = textNode->GetFlattenedTreeParentNode(); |
686 | 0 | if (!containerNode || textNode->OwnerDoc() != mDocument->DocumentNode()) { |
687 | 0 | MOZ_ASSERT(!textAcc, |
688 | 0 | "Text node was removed but accessible is kept alive!"); |
689 | 0 | continue; |
690 | 0 | } |
691 | 0 |
|
692 | 0 | nsIFrame* textFrame = textNode->GetPrimaryFrame(); |
693 | 0 | if (!textFrame) { |
694 | 0 | MOZ_ASSERT(!textAcc, |
695 | 0 | "Text node isn't rendered but accessible is kept alive!"); |
696 | 0 | continue; |
697 | 0 | } |
698 | 0 |
|
699 | 0 | #ifdef A11Y_LOG |
700 | 0 | nsIContent* containerElm = containerNode->IsElement() ? |
701 | 0 | containerNode->AsElement() : nullptr; |
702 | 0 | #endif |
703 | 0 |
|
704 | 0 | nsIFrame::RenderedText text = textFrame->GetRenderedText(0, |
705 | 0 | UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, |
706 | 0 | nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); |
707 | 0 |
|
708 | 0 | // Remove text accessible if rendered text is empty. |
709 | 0 | if (textAcc) { |
710 | 0 | if (text.mString.IsEmpty()) { |
711 | 0 | #ifdef A11Y_LOG |
712 | 0 | if (logging::IsEnabled(logging::eTree | logging::eText)) { |
713 | 0 | logging::MsgBegin("TREE", "text node lost its content; doc: %p", mDocument); |
714 | 0 | logging::Node("container", containerElm); |
715 | 0 | logging::Node("content", textNode); |
716 | 0 | logging::MsgEnd(); |
717 | 0 | } |
718 | 0 | #endif |
719 | 0 |
|
720 | 0 | mDocument->ContentRemoved(textAcc); |
721 | 0 | continue; |
722 | 0 | } |
723 | 0 |
|
724 | 0 | // Update text of the accessible and fire text change events. |
725 | 0 | #ifdef A11Y_LOG |
726 | 0 | if (logging::IsEnabled(logging::eText)) { |
727 | 0 | logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument); |
728 | 0 | logging::Node("container", containerElm); |
729 | 0 | logging::Node("content", textNode); |
730 | 0 | logging::MsgEntry("old text '%s'", |
731 | 0 | NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get()); |
732 | 0 | logging::MsgEntry("new text: '%s'", |
733 | 0 | NS_ConvertUTF16toUTF8(text.mString).get()); |
734 | 0 | logging::MsgEnd(); |
735 | 0 | } |
736 | 0 | #endif |
737 | 0 |
|
738 | 0 | TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString); |
739 | 0 | continue; |
740 | 0 | } |
741 | 0 |
|
742 | 0 | // Append an accessible if rendered text is not empty. |
743 | 0 | if (!text.mString.IsEmpty()) { |
744 | 0 | #ifdef A11Y_LOG |
745 | 0 | if (logging::IsEnabled(logging::eTree | logging::eText)) { |
746 | 0 | logging::MsgBegin("TREE", "text node gains new content; doc: %p", mDocument); |
747 | 0 | logging::Node("container", containerElm); |
748 | 0 | logging::Node("content", textNode); |
749 | 0 | logging::MsgEnd(); |
750 | 0 | } |
751 | 0 | #endif |
752 | 0 |
|
753 | 0 | MOZ_ASSERT(mDocument->AccessibleOrTrueContainer(containerNode), |
754 | 0 | "Text node having rendered text hasn't accessible document!"); |
755 | 0 |
|
756 | 0 | Accessible* container = mDocument->AccessibleOrTrueContainer( |
757 | 0 | containerNode, DocAccessible::eNoContainerIfARIAHidden); |
758 | 0 | if (container) { |
759 | 0 | nsTArray<nsCOMPtr<nsIContent>>* list = |
760 | 0 | mContentInsertions.LookupOrAdd(container); |
761 | 0 | list->AppendElement(textNode); |
762 | 0 | } |
763 | 0 | } |
764 | 0 | } |
765 | 0 | mTextHash.Clear(); |
766 | 0 |
|
767 | 0 | // Process content inserted notifications to update the tree. |
768 | 0 | for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) { |
769 | 0 | mDocument->ProcessContentInserted(iter.Key(), iter.UserData()); |
770 | 0 | if (!mDocument) { |
771 | 0 | return; |
772 | 0 | } |
773 | 0 | } |
774 | 0 | mContentInsertions.Clear(); |
775 | 0 |
|
776 | 0 | // Bind hanging child documents unless we are using IPC and the |
777 | 0 | // document has no IPC actor. If we fail to bind the child doc then |
778 | 0 | // shut it down. |
779 | 0 | uint32_t hangingDocCnt = mHangingChildDocuments.Length(); |
780 | 0 | nsTArray<RefPtr<DocAccessible>> newChildDocs; |
781 | 0 | for (uint32_t idx = 0; idx < hangingDocCnt; idx++) { |
782 | 0 | DocAccessible* childDoc = mHangingChildDocuments[idx]; |
783 | 0 | if (childDoc->IsDefunct()) |
784 | 0 | continue; |
785 | 0 | |
786 | 0 | if (IPCAccessibilityActive() && !mDocument->IPCDoc()) { |
787 | 0 | childDoc->Shutdown(); |
788 | 0 | continue; |
789 | 0 | } |
790 | 0 | |
791 | 0 | nsIContent* ownerContent = mDocument->DocumentNode()-> |
792 | 0 | FindContentForSubDocument(childDoc->DocumentNode()); |
793 | 0 | if (ownerContent) { |
794 | 0 | Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent); |
795 | 0 | if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) { |
796 | 0 | if (mDocument->AppendChildDocument(childDoc)) { |
797 | 0 | newChildDocs.AppendElement(std::move(mHangingChildDocuments[idx])); |
798 | 0 | continue; |
799 | 0 | } |
800 | 0 | |
801 | 0 | outerDocAcc->RemoveChild(childDoc); |
802 | 0 | } |
803 | 0 |
|
804 | 0 | // Failed to bind the child document, destroy it. |
805 | 0 | childDoc->Shutdown(); |
806 | 0 | } |
807 | 0 | } |
808 | 0 |
|
809 | 0 | // Clear the hanging documents list, even if we didn't bind them. |
810 | 0 | mHangingChildDocuments.Clear(); |
811 | 0 | MOZ_ASSERT(mDocument, "Illicit document shutdown"); |
812 | 0 | if (!mDocument) { |
813 | 0 | return; |
814 | 0 | } |
815 | 0 | |
816 | 0 | // If the document is ready and all its subdocuments are completely loaded |
817 | 0 | // then process the document load. |
818 | 0 | if (mDocument->HasLoadState(DocAccessible::eReady) && |
819 | 0 | !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && |
820 | 0 | hangingDocCnt == 0) { |
821 | 0 | uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0; |
822 | 0 | for (; childDocIdx < childDocCnt; childDocIdx++) { |
823 | 0 | DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx); |
824 | 0 | if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) |
825 | 0 | break; |
826 | 0 | } |
827 | 0 |
|
828 | 0 | if (childDocIdx == childDocCnt) { |
829 | 0 | mDocument->ProcessLoad(); |
830 | 0 | if (!mDocument) |
831 | 0 | return; |
832 | 0 | } |
833 | 0 | } |
834 | 0 | |
835 | 0 | // Process only currently queued generic notifications. |
836 | 0 | nsTArray < RefPtr<Notification> > notifications; |
837 | 0 | notifications.SwapElements(mNotifications); |
838 | 0 |
|
839 | 0 | uint32_t notificationCount = notifications.Length(); |
840 | 0 | for (uint32_t idx = 0; idx < notificationCount; idx++) { |
841 | 0 | notifications[idx]->Process(); |
842 | 0 | if (!mDocument) |
843 | 0 | return; |
844 | 0 | } |
845 | 0 |
|
846 | 0 | // Process invalidation list of the document after all accessible tree |
847 | 0 | // modification are done. |
848 | 0 | mDocument->ProcessInvalidationList(); |
849 | 0 |
|
850 | 0 | // Process relocation list. |
851 | 0 | for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) { |
852 | 0 | // owner should be in a document and have na associated DOM node (docs sometimes don't) |
853 | 0 | if (mRelocations[idx]->IsInDocument() && mRelocations[idx]->HasOwnContent()) { |
854 | 0 | mDocument->DoARIAOwnsRelocation(mRelocations[idx]); |
855 | 0 | } |
856 | 0 | } |
857 | 0 | mRelocations.Clear(); |
858 | 0 |
|
859 | 0 | // If a generic notification occurs after this point then we may be allowed to |
860 | 0 | // process it synchronously. However we do not want to reenter if fireing |
861 | 0 | // events causes script to run. |
862 | 0 | mObservingState = eRefreshProcessing; |
863 | 0 |
|
864 | 0 | CoalesceMutationEvents(); |
865 | 0 | ProcessMutationEvents(); |
866 | 0 | mEventGeneration = 0; |
867 | 0 |
|
868 | 0 | // Now that we are done with them get rid of the events we fired. |
869 | 0 | RefPtr<AccTreeMutationEvent> mutEvent = std::move(mFirstMutationEvent); |
870 | 0 | mLastMutationEvent = nullptr; |
871 | 0 | mFirstMutationEvent = nullptr; |
872 | 0 | while (mutEvent) { |
873 | 0 | RefPtr<AccTreeMutationEvent> nextEvent = mutEvent->NextEvent(); |
874 | 0 | Accessible* target = mutEvent->GetAccessible(); |
875 | 0 |
|
876 | 0 | // We need to be careful here, while it may seem that we can simply 0 all |
877 | 0 | // the pending event bits that is not true. Because accessibles may be |
878 | 0 | // reparented they may be the target of both a hide event and a show event |
879 | 0 | // at the same time. |
880 | 0 | if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) { |
881 | 0 | target->SetShowEventTarget(false); |
882 | 0 | } |
883 | 0 |
|
884 | 0 | if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) { |
885 | 0 | target->SetHideEventTarget(false); |
886 | 0 | } |
887 | 0 |
|
888 | 0 | // However it is not possible for a reorder event target to also be the |
889 | 0 | // target of a show or hide, so we can just zero that. |
890 | 0 | target->SetReorderEventTarget(false); |
891 | 0 |
|
892 | 0 | mutEvent->SetPrevEvent(nullptr); |
893 | 0 | mutEvent->SetNextEvent(nullptr); |
894 | 0 | mMutationMap.RemoveEvent(mutEvent); |
895 | 0 | mutEvent = nextEvent; |
896 | 0 | } |
897 | 0 |
|
898 | 0 | ProcessEventQueue(); |
899 | 0 |
|
900 | 0 | if (IPCAccessibilityActive()) { |
901 | 0 | size_t newDocCount = newChildDocs.Length(); |
902 | 0 | for (size_t i = 0; i < newDocCount; i++) { |
903 | 0 | DocAccessible* childDoc = newChildDocs[i]; |
904 | 0 | if (childDoc->IsDefunct()) { |
905 | 0 | continue; |
906 | 0 | } |
907 | 0 | |
908 | 0 | Accessible* parent = childDoc->Parent(); |
909 | 0 | DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc(); |
910 | 0 | MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc); |
911 | 0 | uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID()); |
912 | 0 | MOZ_DIAGNOSTIC_ASSERT(id); |
913 | 0 | DocAccessibleChild* ipcDoc = childDoc->IPCDoc(); |
914 | 0 | if (ipcDoc) { |
915 | 0 | parentIPCDoc->SendBindChildDoc(ipcDoc, id); |
916 | 0 | continue; |
917 | 0 | } |
918 | 0 | |
919 | 0 | ipcDoc = new DocAccessibleChild(childDoc, parentIPCDoc->Manager()); |
920 | 0 | childDoc->SetIPCDoc(ipcDoc); |
921 | 0 |
|
922 | | #if defined(XP_WIN) |
923 | | parentIPCDoc->ConstructChildDocInParentProcess(ipcDoc, id, |
924 | | AccessibleWrap::GetChildIDFor(childDoc)); |
925 | | #else |
926 | | nsCOMPtr<nsITabChild> tabChild = |
927 | 0 | do_GetInterface(mDocument->DocumentNode()->GetDocShell()); |
928 | 0 | if (tabChild) { |
929 | 0 | static_cast<TabChild*>(tabChild.get())-> |
930 | 0 | SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0); |
931 | 0 | } |
932 | 0 | #endif |
933 | 0 | } |
934 | 0 | } |
935 | 0 |
|
936 | 0 | mObservingState = eRefreshObserving; |
937 | 0 | if (!mDocument) |
938 | 0 | return; |
939 | 0 | |
940 | 0 | // Stop further processing if there are no new notifications of any kind or |
941 | 0 | // events and document load is processed. |
942 | 0 | if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() && |
943 | 0 | mEvents.IsEmpty() && mTextHash.Count() == 0 && |
944 | 0 | mHangingChildDocuments.IsEmpty() && |
945 | 0 | mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && |
946 | 0 | mPresShell->RemoveRefreshObserver(this, FlushType::Display)) { |
947 | 0 | mObservingState = eNotObservingRefresh; |
948 | 0 | } |
949 | 0 | } |
950 | | |
951 | | void |
952 | | NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent) |
953 | 0 | { |
954 | 0 | EventType type = GetEventType(aEvent); |
955 | 0 | uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible()); |
956 | 0 | MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned"); |
957 | 0 | addr |= type; |
958 | 0 | mTable.Put(addr, aEvent); |
959 | 0 | } |
960 | | |
961 | | AccTreeMutationEvent* |
962 | | NotificationController::EventMap::GetEvent(Accessible* aTarget, EventType aType) |
963 | 0 | { |
964 | 0 | uint64_t addr = reinterpret_cast<uintptr_t>(aTarget); |
965 | 0 | MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned"); |
966 | 0 |
|
967 | 0 | addr |= aType; |
968 | 0 | return mTable.GetWeak(addr); |
969 | 0 | } |
970 | | |
971 | | void |
972 | | NotificationController::EventMap::RemoveEvent(AccTreeMutationEvent* aEvent) |
973 | 0 | { |
974 | 0 | EventType type = GetEventType(aEvent); |
975 | 0 | uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible()); |
976 | 0 | MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned"); |
977 | 0 | addr |= type; |
978 | 0 |
|
979 | 0 | MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event"); |
980 | 0 | mTable.Remove(addr); |
981 | 0 | } |
982 | | |
983 | | NotificationController::EventMap::EventType |
984 | | NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent) |
985 | 0 | { |
986 | 0 | switch(aEvent->GetEventType()) |
987 | 0 | { |
988 | 0 | case nsIAccessibleEvent::EVENT_SHOW: |
989 | 0 | return ShowEvent; |
990 | 0 | case nsIAccessibleEvent::EVENT_HIDE: |
991 | 0 | return HideEvent; |
992 | 0 | case nsIAccessibleEvent::EVENT_REORDER: |
993 | 0 | return ReorderEvent; |
994 | 0 | default: |
995 | 0 | MOZ_ASSERT_UNREACHABLE("event has invalid type"); |
996 | 0 | return ShowEvent; |
997 | 0 | } |
998 | 0 | } |