/src/mozilla-central/accessible/base/NotificationController.h
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 | | #ifndef mozilla_a11y_NotificationController_h_ |
7 | | #define mozilla_a11y_NotificationController_h_ |
8 | | |
9 | | #include "EventQueue.h" |
10 | | #include "EventTree.h" |
11 | | |
12 | | #include "mozilla/Tuple.h" |
13 | | #include "nsCycleCollectionParticipant.h" |
14 | | #include "nsRefreshDriver.h" |
15 | | |
16 | | #include <utility> |
17 | | |
18 | | #ifdef A11Y_LOG |
19 | | #include "Logging.h" |
20 | | #endif |
21 | | |
22 | | namespace mozilla { |
23 | | namespace a11y { |
24 | | |
25 | | class DocAccessible; |
26 | | |
27 | | /** |
28 | | * Notification interface. |
29 | | */ |
30 | | class Notification |
31 | | { |
32 | | public: |
33 | | NS_INLINE_DECL_REFCOUNTING(mozilla::a11y::Notification) |
34 | | |
35 | | /** |
36 | | * Process notification. |
37 | | */ |
38 | | virtual void Process() = 0; |
39 | | |
40 | | protected: |
41 | 0 | Notification() { } |
42 | | |
43 | | /** |
44 | | * Protected destructor, to discourage deletion outside of Release(): |
45 | | */ |
46 | 0 | virtual ~Notification() { } |
47 | | |
48 | | private: |
49 | | Notification(const Notification&); |
50 | | Notification& operator = (const Notification&); |
51 | | }; |
52 | | |
53 | | |
54 | | /** |
55 | | * Template class for generic notification. |
56 | | * |
57 | | * @note Instance is kept as a weak ref, the caller must guarantee it exists |
58 | | * longer than the document accessible owning the notification controller |
59 | | * that this notification is processed by. |
60 | | */ |
61 | | template<class Class, class ... Args> |
62 | | class TNotification : public Notification |
63 | | { |
64 | | public: |
65 | | typedef void (Class::*Callback)(Args* ...); |
66 | | |
67 | | TNotification(Class* aInstance, Callback aCallback, Args* ... aArgs) : |
68 | 0 | mInstance(aInstance), mCallback(aCallback), mArgs(aArgs...) { } Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::FocusManager, nsINode>::TNotification(mozilla::a11y::FocusManager*, void (mozilla::a11y::FocusManager::*)(nsINode*), nsINode*) Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::SelectionManager, mozilla::a11y::SelData>::TNotification(mozilla::a11y::SelectionManager*, void (mozilla::a11y::SelectionManager::*)(mozilla::a11y::SelData*), mozilla::a11y::SelData*) Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::DocAccessible, mozilla::a11y::Accessible>::TNotification(mozilla::a11y::DocAccessible*, void (mozilla::a11y::DocAccessible::*)(mozilla::a11y::Accessible*), mozilla::a11y::Accessible*) Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::RootAccessible, mozilla::dom::Event>::TNotification(mozilla::a11y::RootAccessible*, void (mozilla::a11y::RootAccessible::*)(mozilla::dom::Event*), mozilla::dom::Event*) |
69 | 0 | virtual ~TNotification() { mInstance = nullptr; } Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::FocusManager, nsINode>::~TNotification() Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::SelectionManager, mozilla::a11y::SelData>::~TNotification() Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::DocAccessible, mozilla::a11y::Accessible>::~TNotification() Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::RootAccessible, mozilla::dom::Event>::~TNotification() |
70 | | |
71 | | virtual void Process() override |
72 | 0 | { ProcessHelper(std::index_sequence_for<Args...>{}); } Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::FocusManager, nsINode>::Process() Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::SelectionManager, mozilla::a11y::SelData>::Process() Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::DocAccessible, mozilla::a11y::Accessible>::Process() Unexecuted instantiation: mozilla::a11y::TNotification<mozilla::a11y::RootAccessible, mozilla::dom::Event>::Process() |
73 | | |
74 | | private: |
75 | | TNotification(const TNotification&); |
76 | | TNotification& operator = (const TNotification&); |
77 | | |
78 | | template <size_t... Indices> |
79 | | void ProcessHelper(std::index_sequence<Indices...>) |
80 | 0 | { |
81 | 0 | (mInstance->*mCallback)(Get<Indices>(mArgs)...); |
82 | 0 | } Unexecuted instantiation: void mozilla::a11y::TNotification<mozilla::a11y::FocusManager, nsINode>::ProcessHelper<0ul>(std::__1::integer_sequence<unsigned long, 0ul>) Unexecuted instantiation: void mozilla::a11y::TNotification<mozilla::a11y::SelectionManager, mozilla::a11y::SelData>::ProcessHelper<0ul>(std::__1::integer_sequence<unsigned long, 0ul>) Unexecuted instantiation: void mozilla::a11y::TNotification<mozilla::a11y::DocAccessible, mozilla::a11y::Accessible>::ProcessHelper<0ul>(std::__1::integer_sequence<unsigned long, 0ul>) Unexecuted instantiation: void mozilla::a11y::TNotification<mozilla::a11y::RootAccessible, mozilla::dom::Event>::ProcessHelper<0ul>(std::__1::integer_sequence<unsigned long, 0ul>) |
83 | | |
84 | | Class* mInstance; |
85 | | Callback mCallback; |
86 | | Tuple<RefPtr<Args> ...> mArgs; |
87 | | }; |
88 | | |
89 | | /** |
90 | | * Used to process notifications from core for the document accessible. |
91 | | */ |
92 | | class NotificationController final : public EventQueue, |
93 | | public nsARefreshObserver |
94 | | { |
95 | | public: |
96 | | NotificationController(DocAccessible* aDocument, nsIPresShell* aPresShell); |
97 | | |
98 | | NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; |
99 | | NS_IMETHOD_(MozExternalRefCountType) Release(void) override; |
100 | | |
101 | | NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController) |
102 | | |
103 | | /** |
104 | | * Shutdown the notification controller. |
105 | | */ |
106 | | void Shutdown(); |
107 | | |
108 | | /** |
109 | | * Add an accessible event into the queue to process it later. |
110 | | */ |
111 | | void QueueEvent(AccEvent* aEvent) |
112 | 0 | { |
113 | 0 | if (PushEvent(aEvent)) { |
114 | 0 | ScheduleProcessing(); |
115 | 0 | } |
116 | 0 | } |
117 | | |
118 | | /** |
119 | | * Creates and adds a name change event into the queue for a container of |
120 | | * the given accessible, if the accessible is a part of name computation of |
121 | | * the container. |
122 | | */ |
123 | | void QueueNameChange(Accessible* aChangeTarget) |
124 | 0 | { |
125 | 0 | if (PushNameChange(aChangeTarget)) { |
126 | 0 | ScheduleProcessing(); |
127 | 0 | } |
128 | 0 | } |
129 | | |
130 | | /** |
131 | | * Returns existing event tree for the given the accessible or creates one if |
132 | | * it doesn't exists yet. |
133 | | */ |
134 | | EventTree* QueueMutation(Accessible* aContainer); |
135 | | |
136 | | class MoveGuard final { |
137 | | public: |
138 | | explicit MoveGuard(NotificationController* aController) : |
139 | | mController(aController) |
140 | 0 | { |
141 | | #ifdef DEBUG |
142 | | MOZ_ASSERT(!mController->mMoveGuardOnStack, |
143 | | "Move guard is on stack already!"); |
144 | | mController->mMoveGuardOnStack = true; |
145 | | #endif |
146 | | } |
147 | 0 | ~MoveGuard() { |
148 | | #ifdef DEBUG |
149 | | MOZ_ASSERT(mController->mMoveGuardOnStack, "No move guard on stack!"); |
150 | | mController->mMoveGuardOnStack = false; |
151 | | #endif |
152 | | mController->mPrecedingEvents.Clear(); |
153 | 0 | } |
154 | | |
155 | | private: |
156 | | NotificationController* mController; |
157 | | }; |
158 | | |
159 | | #ifdef A11Y_LOG |
160 | 0 | const EventTree& RootEventTree() const { return mEventTree; }; |
161 | | #endif |
162 | | |
163 | | /** |
164 | | * Queue a mutation event to emit if not coalesced away. Returns true if the |
165 | | * event was queued and has not yet been coalesced. |
166 | | */ |
167 | | bool QueueMutationEvent(AccTreeMutationEvent* aEvent); |
168 | | |
169 | | /** |
170 | | * Coalesce all queued mutation events. |
171 | | */ |
172 | | void CoalesceMutationEvents(); |
173 | | |
174 | | /** |
175 | | * Schedule binding the child document to the tree of this document. |
176 | | */ |
177 | | void ScheduleChildDocBinding(DocAccessible* aDocument); |
178 | | |
179 | | /** |
180 | | * Schedule the accessible tree update because of rendered text changes. |
181 | | */ |
182 | | inline void ScheduleTextUpdate(nsIContent* aTextNode) |
183 | 0 | { |
184 | 0 | // Make sure we are not called with a node that is not in the DOM tree or |
185 | 0 | // not visible. |
186 | 0 | MOZ_ASSERT(aTextNode->GetParentNode(), "A text node is not in DOM"); |
187 | 0 | MOZ_ASSERT(aTextNode->GetPrimaryFrame(), "A text node doesn't have a frame"); |
188 | 0 | MOZ_ASSERT(aTextNode->GetPrimaryFrame()->StyleVisibility()->IsVisible(), |
189 | 0 | "A text node is not visible"); |
190 | 0 |
|
191 | 0 | mTextHash.PutEntry(aTextNode); |
192 | 0 | ScheduleProcessing(); |
193 | 0 | } |
194 | | |
195 | | /** |
196 | | * Pend accessible tree update for content insertion. |
197 | | */ |
198 | | void ScheduleContentInsertion(Accessible* aContainer, |
199 | | nsIContent* aStartChildNode, |
200 | | nsIContent* aEndChildNode); |
201 | | |
202 | | /** |
203 | | * Pend an accessible subtree relocation. |
204 | | */ |
205 | | void ScheduleRelocation(Accessible* aOwner) |
206 | 0 | { |
207 | 0 | if (!mRelocations.Contains(aOwner) && mRelocations.AppendElement(aOwner)) { |
208 | 0 | ScheduleProcessing(); |
209 | 0 | } |
210 | 0 | } |
211 | | |
212 | | /** |
213 | | * Start to observe refresh to make notifications and events processing after |
214 | | * layout. |
215 | | */ |
216 | | void ScheduleProcessing(); |
217 | | |
218 | | /** |
219 | | * Process the generic notification synchronously if there are no pending |
220 | | * layout changes and no notifications are pending or being processed right |
221 | | * now. Otherwise, queue it up to process asynchronously. |
222 | | * |
223 | | * @note The caller must guarantee that the given instance still exists when |
224 | | * the notification is processed. |
225 | | */ |
226 | | template<class Class, class Arg> |
227 | | inline void HandleNotification(Class* aInstance, |
228 | | typename TNotification<Class, Arg>::Callback aMethod, |
229 | | Arg* aArg) |
230 | 0 | { |
231 | 0 | if (!IsUpdatePending()) { |
232 | 0 | #ifdef A11Y_LOG |
233 | 0 | if (mozilla::a11y::logging::IsEnabled(mozilla::a11y::logging::eNotifications)) |
234 | 0 | mozilla::a11y::logging::Text("sync notification processing"); |
235 | 0 | #endif |
236 | 0 | (aInstance->*aMethod)(aArg); |
237 | 0 | return; |
238 | 0 | } |
239 | 0 |
|
240 | 0 | RefPtr<Notification> notification = |
241 | 0 | new TNotification<Class, Arg>(aInstance, aMethod, aArg); |
242 | 0 | if (notification && mNotifications.AppendElement(notification)) |
243 | 0 | ScheduleProcessing(); |
244 | 0 | } Unexecuted instantiation: void mozilla::a11y::NotificationController::HandleNotification<mozilla::a11y::FocusManager, nsINode>(mozilla::a11y::FocusManager*, mozilla::a11y::TNotification<mozilla::a11y::FocusManager, nsINode>::Callback, nsINode*) Unexecuted instantiation: void mozilla::a11y::NotificationController::HandleNotification<mozilla::a11y::SelectionManager, mozilla::a11y::SelData>(mozilla::a11y::SelectionManager*, mozilla::a11y::TNotification<mozilla::a11y::SelectionManager, mozilla::a11y::SelData>::Callback, mozilla::a11y::SelData*) Unexecuted instantiation: void mozilla::a11y::NotificationController::HandleNotification<mozilla::a11y::RootAccessible, mozilla::dom::Event>(mozilla::a11y::RootAccessible*, mozilla::a11y::TNotification<mozilla::a11y::RootAccessible, mozilla::dom::Event>::Callback, mozilla::dom::Event*) |
245 | | |
246 | | /** |
247 | | * Schedule the generic notification to process asynchronously. |
248 | | * |
249 | | * @note The caller must guarantee that the given instance still exists when |
250 | | * the notification is processed. |
251 | | */ |
252 | | template<class Class> |
253 | | inline void ScheduleNotification(Class* aInstance, |
254 | | typename TNotification<Class>::Callback aMethod) |
255 | | { |
256 | | RefPtr<Notification> notification = |
257 | | new TNotification<Class>(aInstance, aMethod); |
258 | | if (notification && mNotifications.AppendElement(notification)) |
259 | | ScheduleProcessing(); |
260 | | } |
261 | | |
262 | | template<class Class, class Arg> |
263 | | inline void ScheduleNotification(Class* aInstance, |
264 | | typename TNotification<Class, Arg>::Callback aMethod, |
265 | | Arg* aArg) |
266 | 0 | { |
267 | 0 | RefPtr<Notification> notification = |
268 | 0 | new TNotification<Class, Arg>(aInstance, aMethod, aArg); |
269 | 0 | if (notification && mNotifications.AppendElement(notification)) { |
270 | 0 | ScheduleProcessing(); |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | | #ifdef DEBUG |
275 | | bool IsUpdating() const |
276 | | { return mObservingState == eRefreshProcessingForUpdate; } |
277 | | #endif |
278 | | |
279 | | protected: |
280 | | virtual ~NotificationController(); |
281 | | |
282 | | nsCycleCollectingAutoRefCnt mRefCnt; |
283 | | NS_DECL_OWNINGTHREAD |
284 | | |
285 | | /** |
286 | | * Return true if the accessible tree state update is pending. |
287 | | */ |
288 | | bool IsUpdatePending(); |
289 | | |
290 | | /** |
291 | | * Return true if we should wait for processing from the parent before we can |
292 | | * process our own queue. |
293 | | */ |
294 | | bool WaitingForParent(); |
295 | | |
296 | | private: |
297 | | NotificationController(const NotificationController&); |
298 | | NotificationController& operator = (const NotificationController&); |
299 | | |
300 | | // nsARefreshObserver |
301 | | virtual void WillRefresh(mozilla::TimeStamp aTime) override; |
302 | | |
303 | | /** |
304 | | * Set and returns a hide event, paired with a show event, for the move. |
305 | | */ |
306 | | void WithdrawPrecedingEvents(nsTArray<RefPtr<AccHideEvent>>* aEvs) |
307 | 0 | { |
308 | 0 | if (mPrecedingEvents.Length() > 0) { |
309 | 0 | aEvs->AppendElements(std::move(mPrecedingEvents)); |
310 | 0 | } |
311 | 0 | } |
312 | | void StorePrecedingEvent(AccHideEvent* aEv) |
313 | 0 | { |
314 | 0 | MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!"); |
315 | 0 | mPrecedingEvents.AppendElement(aEv); |
316 | 0 | } |
317 | | void StorePrecedingEvents(nsTArray<RefPtr<AccHideEvent>>&& aEvs) |
318 | 0 | { |
319 | 0 | MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!"); |
320 | 0 | mPrecedingEvents.InsertElementsAt(0, aEvs); |
321 | 0 | } |
322 | | |
323 | | private: |
324 | | /** |
325 | | * get rid of a mutation event that is no longer necessary. |
326 | | */ |
327 | | void DropMutationEvent(AccTreeMutationEvent* aEvent); |
328 | | |
329 | | /** |
330 | | * Fire all necessary mutation events. |
331 | | */ |
332 | | void ProcessMutationEvents(); |
333 | | |
334 | | /** |
335 | | * Indicates whether we're waiting on an event queue processing from our |
336 | | * notification controller to flush events. |
337 | | */ |
338 | | enum eObservingState { |
339 | | eNotObservingRefresh, |
340 | | eRefreshObserving, |
341 | | eRefreshProcessing, |
342 | | eRefreshProcessingForUpdate |
343 | | }; |
344 | | eObservingState mObservingState; |
345 | | |
346 | | /** |
347 | | * The presshell of the document accessible. |
348 | | */ |
349 | | nsIPresShell* mPresShell; |
350 | | |
351 | | /** |
352 | | * Child documents that needs to be bound to the tree. |
353 | | */ |
354 | | nsTArray<RefPtr<DocAccessible> > mHangingChildDocuments; |
355 | | |
356 | | /** |
357 | | * Pending accessible tree update notifications for content insertions. |
358 | | */ |
359 | | nsClassHashtable<nsRefPtrHashKey<Accessible>, |
360 | | nsTArray<nsCOMPtr<nsIContent>>> mContentInsertions; |
361 | | |
362 | | template<class T> |
363 | | class nsCOMPtrHashKey : public PLDHashEntryHdr |
364 | | { |
365 | | public: |
366 | | typedef T* KeyType; |
367 | | typedef const T* KeyTypePointer; |
368 | | |
369 | 0 | explicit nsCOMPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {} |
370 | | nsCOMPtrHashKey(nsCOMPtrHashKey<T>&& aOther) |
371 | | : PLDHashEntryHdr(std::move(aOther)) |
372 | | , mKey(std::move(aOther.mKey)) |
373 | | {} |
374 | 0 | ~nsCOMPtrHashKey() { } |
375 | | |
376 | 0 | KeyType GetKey() const { return mKey; } |
377 | 0 | bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } |
378 | | |
379 | 0 | static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } |
380 | | static PLDHashNumber HashKey(KeyTypePointer aKey) |
381 | 0 | { return NS_PTR_TO_INT32(aKey) >> 2; } |
382 | | |
383 | | enum { ALLOW_MEMMOVE = true }; |
384 | | |
385 | | protected: |
386 | | nsCOMPtr<T> mKey; |
387 | | }; |
388 | | |
389 | | /** |
390 | | * Pending accessible tree update notifications for rendered text changes. |
391 | | */ |
392 | | nsTHashtable<nsCOMPtrHashKey<nsIContent> > mTextHash; |
393 | | |
394 | | /** |
395 | | * Other notifications like DOM events. Don't make this an AutoTArray; we |
396 | | * use SwapElements() on it. |
397 | | */ |
398 | | nsTArray<RefPtr<Notification> > mNotifications; |
399 | | |
400 | | /** |
401 | | * Holds all scheduled relocations. |
402 | | */ |
403 | | nsTArray<RefPtr<Accessible> > mRelocations; |
404 | | |
405 | | /** |
406 | | * Holds all mutation events. |
407 | | */ |
408 | | EventTree mEventTree; |
409 | | |
410 | | /** |
411 | | * A temporary collection of hide events that should be fired before related |
412 | | * show event. Used by EventTree. |
413 | | */ |
414 | | nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents; |
415 | | |
416 | | #ifdef DEBUG |
417 | | bool mMoveGuardOnStack; |
418 | | #endif |
419 | | |
420 | | friend class MoveGuard; |
421 | | friend class EventTree; |
422 | | |
423 | | /** |
424 | | * A list of all mutation events we may want to emit. Ordered from the first |
425 | | * event that should be emitted to the last one to emit. |
426 | | */ |
427 | | RefPtr<AccTreeMutationEvent> mFirstMutationEvent; |
428 | | RefPtr<AccTreeMutationEvent> mLastMutationEvent; |
429 | | |
430 | | /** |
431 | | * A class to map an accessible and event type to an event. |
432 | | */ |
433 | | class EventMap |
434 | | { |
435 | | public: |
436 | | enum EventType |
437 | | { |
438 | | ShowEvent = 0x0, |
439 | | HideEvent = 0x1, |
440 | | ReorderEvent = 0x2, |
441 | | }; |
442 | | |
443 | | void PutEvent(AccTreeMutationEvent* aEvent); |
444 | | AccTreeMutationEvent* GetEvent(Accessible* aTarget, EventType aType); |
445 | | void RemoveEvent(AccTreeMutationEvent* aEvent); |
446 | 0 | void Clear() { mTable.Clear(); } |
447 | | |
448 | | private: |
449 | | EventType GetEventType(AccTreeMutationEvent* aEvent); |
450 | | |
451 | | nsRefPtrHashtable<nsUint64HashKey, AccTreeMutationEvent> mTable; |
452 | | }; |
453 | | |
454 | | EventMap mMutationMap; |
455 | | uint32_t mEventGeneration; |
456 | | }; |
457 | | |
458 | | } // namespace a11y |
459 | | } // namespace mozilla |
460 | | |
461 | | #endif // mozilla_a11y_NotificationController_h_ |