/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_a11y_NotificationController_h_ #define mozilla_a11y_NotificationController_h_ #include "EventQueue.h" #include "nsClassHashtable.h" #include "nsCycleCollectionParticipant.h" #include "nsIFrame.h" #include "nsRefreshObservers.h" #include "nsTHashSet.h" #include <utility> #ifdef A11Y_LOG # include "Logging.h" #endif namespace mozilla { class PresShell; namespace a11y { class DocAccessible; /** * Notification interface. */ class Notification { public: NS_INLINE_DECL_REFCOUNTING(mozilla::a11y::Notification) /** * Process notification. */ virtual void Process() = 0; protected: Notification() {} /** * Protected destructor, to discourage deletion outside of Release(): */ virtual ~Notification() {} private: Notification(const Notification&); Notification& operator=(const Notification&); }; /** * Template class for generic notification. * * @note Instance is kept as a weak ref, the caller must guarantee it exists * longer than the document accessible owning the notification controller * that this notification is processed by. */ template <class Class, class... Args> class TNotification : public Notification { public: typedef void (Class::*Callback)(Args*...); TNotification(Class* aInstance, Callback aCallback, Args*... aArgs) : mInstance(aInstance), mCallback(aCallback), mArgs(aArgs...) {} virtual ~TNotification() { mInstance = nullptr; } virtual void Process() override { ProcessHelper(std::index_sequence_for<Args...>{}); } private: TNotification(const TNotification&); TNotification& operator=(const TNotification&); template <size_t... Indices> void ProcessHelper(std::index_sequence<Indices...>) { (mInstance->*mCallback)(std::get<Indices>(mArgs)...); } Class* mInstance; Callback mCallback; std::tuple<RefPtr<Args>...> mArgs; }; /** * Used to process notifications from core for the document accessible. */ class NotificationController final : public EventQueue, public nsARefreshObserver { public: NotificationController(DocAccessible* aDocument, PresShell* aPresShell); NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; NS_IMETHOD_(MozExternalRefCountType) Release(void) override; NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController) /** * Shutdown the notification controller. */ void Shutdown(); /** * Add an accessible event into the queue to process it later. */ void QueueEvent(AccEvent* aEvent) { if (PushEvent(aEvent)) { ScheduleProcessing(); } } /** * Queue a mutation event to emit if not coalesced away. Returns true if the * event was queued and has not yet been coalesced. */ bool QueueMutationEvent(AccTreeMutationEvent* aEvent); /** * Coalesce all queued mutation events. */ void CoalesceMutationEvents(); /** * Schedule binding the child document to the tree of this document. */ void ScheduleChildDocBinding(DocAccessible* aDocument); /** * Schedule the accessible tree update because of rendered text changes. */ inline void ScheduleTextUpdate(nsIContent* aTextNode) { // Make sure we are not called with a node that is not in the DOM tree or // not visible. MOZ_ASSERT(aTextNode->GetParentNode(), "A text node is not in DOM"); MOZ_ASSERT(aTextNode->GetPrimaryFrame(), "A text node doesn't have a frame"); MOZ_ASSERT(aTextNode->GetPrimaryFrame()->StyleVisibility()->IsVisible(), "A text node is not visible"); mTextArray.AppendElement(aTextNode); ScheduleProcessing(); } /** * Pend accessible tree update for content insertion. */ void ScheduleContentInsertion(LocalAccessible* aContainer, nsTArray<nsCOMPtr<nsIContent>>& aInsertions); /** * Pend an accessible subtree relocation. */ void ScheduleRelocation(LocalAccessible* aOwner) { if (!mRelocations.Contains(aOwner)) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier, or change the return type to void. mRelocations.AppendElement(aOwner); ScheduleProcessing(); } } /** * Start to observe refresh to make notifications and events processing after * layout. */ void ScheduleProcessing(); /** * Process the generic notification synchronously if there are no pending * layout changes and no notifications are pending or being processed right * now. Otherwise, queue it up to process asynchronously. * * @note The caller must guarantee that the given instance still exists when * the notification is processed. */ template <class Class, class... Args> inline void HandleNotification( Class* aInstance, typename TNotification<Class, Args...>::Callback aMethod, Args*... aArgs) { if (!IsUpdatePending()) { #ifdef A11Y_LOG if (mozilla::a11y::logging::IsEnabled( mozilla::a11y::logging::eNotifications)) { mozilla::a11y::logging::Text("sync notification processing"); } #endif (aInstance->*aMethod)(aArgs...); return; } RefPtr<Notification> notification = new TNotification<Class, Args...>(aInstance, aMethod, aArgs...); if (notification) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier. mNotifications.AppendElement(notification); ScheduleProcessing(); } } /** * Schedule the generic notification to process asynchronously. * * @note The caller must guarantee that the given instance still exists when * the notification is processed. */ template <class Class> inline void ScheduleNotification( Class* aInstance, typename TNotification<Class>::Callback aMethod) { RefPtr<Notification> notification = new TNotification<Class>(aInstance, aMethod); if (notification) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier. mNotifications.AppendElement(notification); ScheduleProcessing(); } } template <class Class, class Arg> inline void ScheduleNotification( Class* aInstance, typename TNotification<Class, Arg>::Callback aMethod, Arg* aArg) { RefPtr<Notification> notification = new TNotification<Class, Arg>(aInstance, aMethod, aArg); if (notification) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier. mNotifications.AppendElement(notification); ScheduleProcessing(); } } #ifdef DEBUG bool IsUpdating() const { return mObservingState == eRefreshProcessingForUpdate; } #endif protected: virtual ~NotificationController(); nsCycleCollectingAutoRefCnt mRefCnt; NS_DECL_OWNINGTHREAD /** * Return true if the accessible tree state update is pending. */ bool IsUpdatePending(); /** * Return true if we should wait for processing from the parent before we can * process our own queue. */ bool WaitingForParent(); private: NotificationController(const NotificationController&); NotificationController& operator=(const NotificationController&); // nsARefreshObserver virtual void WillRefresh(mozilla::TimeStamp aTime) override; private: /** * Remove a specific hide event if it should not be propagated. */ void CoalesceHideEvent(AccHideEvent* aHideEvent); /** * get rid of a mutation event that is no longer necessary. */ void DropMutationEvent(AccTreeMutationEvent* aEvent); /** * Fire all necessary mutation events. */ void ProcessMutationEvents(); /** * Indicates whether we're waiting on an event queue processing from our * notification controller to flush events. */ enum eObservingState { eNotObservingRefresh, eRefreshObserving, eRefreshProcessing, eRefreshProcessingForUpdate }; eObservingState mObservingState; /** * The presshell of the document accessible. */ PresShell* mPresShell; /** * Child documents that needs to be bound to the tree. */ nsTArray<RefPtr<DocAccessible>> mHangingChildDocuments; /** * Pending accessible tree update notifications for content insertions. */ nsClassHashtable<nsRefPtrHashKey<LocalAccessible>, nsTArray<nsCOMPtr<nsIContent>>> mContentInsertions; template <class T> class nsCOMPtrHashKey : public PLDHashEntryHdr { public: typedef T* KeyType; typedef const T* KeyTypePointer; explicit nsCOMPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {} nsCOMPtrHashKey(nsCOMPtrHashKey<T>&& aOther) : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {} ~nsCOMPtrHashKey() {} KeyType GetKey() const { return mKey; } bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } static PLDHashNumber HashKey(KeyTypePointer aKey) { return NS_PTR_TO_INT32(aKey) >> 2; } enum { ALLOW_MEMMOVE = true }; protected: nsCOMPtr<T> mKey; }; /** * Pending accessible tree update notifications for rendered text changes. * When there are a lot of nearby text insertions (e.g. during a reflow), it * is much more performant to process them in order because we then benefit * from the layout line cursor. Therefore, we use an array here. */ nsTArray<nsCOMPtr<nsIContent>> mTextArray; /** * Other notifications like DOM events. Don't make this an AutoTArray; we * use SwapElements() on it. */ nsTArray<RefPtr<Notification>> mNotifications; /** * Holds all scheduled relocations. */ nsTArray<RefPtr<LocalAccessible>> mRelocations; /** * A list of all mutation events we may want to emit. Ordered from the first * event that should be emitted to the last one to emit. */ RefPtr<AccTreeMutationEvent> mFirstMutationEvent; RefPtr<AccTreeMutationEvent> mLastMutationEvent; /** * A class to map an accessible and event type to an event. */ class EventMap { public: enum EventType { ShowEvent = 0x0, HideEvent = 0x1, ReorderEvent = 0x2, }; void PutEvent(AccTreeMutationEvent* aEvent); AccTreeMutationEvent* GetEvent(LocalAccessible* aTarget, EventType aType); void RemoveEvent(AccTreeMutationEvent* aEvent); void Clear() { mTable.Clear(); } private: EventType GetEventType(AccTreeMutationEvent* aEvent); nsRefPtrHashtable<nsUint64HashKey, AccTreeMutationEvent> mTable; }; EventMap mMutationMap; uint32_t mEventGeneration; }; } // namespace a11y } // namespace mozilla #endif // mozilla_a11y_NotificationController_h_