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