summaryrefslogtreecommitdiffstats
path: root/accessible/base/NotificationController.h
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/base/NotificationController.h')
-rw-r--r--accessible/base/NotificationController.h439
1 files changed, 439 insertions, 0 deletions
diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h
new file mode 100644
index 000000000..a909fc63d
--- /dev/null
+++ b/accessible/base/NotificationController.h
@@ -0,0 +1,439 @@
+/* -*- 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 "EventTree.h"
+
+#include "mozilla/IndexSequence.h"
+#include "mozilla/Tuple.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefreshDriver.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+namespace mozilla {
+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(typename IndexSequenceFor<Args...>::Type()); }
+
+private:
+ TNotification(const TNotification&);
+ TNotification& operator = (const TNotification&);
+
+ template <size_t... Indices>
+ void ProcessHelper(IndexSequence<Indices...>)
+ {
+ (mInstance->*mCallback)(Get<Indices>(mArgs)...);
+ }
+
+ Class* mInstance;
+ Callback mCallback;
+ 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, nsIPresShell* 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();
+ }
+ }
+
+ /**
+ * Creates and adds a name change event into the queue for a container of
+ * the given accessible, if the accessible is a part of name computation of
+ * the container.
+ */
+ void QueueNameChange(Accessible* aChangeTarget)
+ {
+ if (PushNameChange(aChangeTarget)) {
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Returns existing event tree for the given the accessible or creates one if
+ * it doesn't exists yet.
+ */
+ EventTree* QueueMutation(Accessible* aContainer);
+
+ class MoveGuard final {
+ public:
+ explicit MoveGuard(NotificationController* aController) :
+ mController(aController)
+ {
+#ifdef DEBUG
+ MOZ_ASSERT(!mController->mMoveGuardOnStack,
+ "Move guard is on stack already!");
+ mController->mMoveGuardOnStack = true;
+#endif
+ }
+ ~MoveGuard() {
+#ifdef DEBUG
+ MOZ_ASSERT(mController->mMoveGuardOnStack, "No move guard on stack!");
+ mController->mMoveGuardOnStack = false;
+#endif
+ mController->mPrecedingEvents.Clear();
+ }
+
+ private:
+ NotificationController* mController;
+ };
+
+#ifdef A11Y_LOG
+ const EventTree& RootEventTree() const { return mEventTree; };
+#endif
+
+ /**
+ * 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");
+
+ mTextHash.PutEntry(aTextNode);
+ ScheduleProcessing();
+ }
+
+ /**
+ * Pend accessible tree update for content insertion.
+ */
+ void ScheduleContentInsertion(Accessible* aContainer,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode);
+
+ /**
+ * Pend an accessible subtree relocation.
+ */
+ void ScheduleRelocation(Accessible* aOwner)
+ {
+ if (!mRelocations.Contains(aOwner) && 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 Arg>
+ inline void HandleNotification(Class* aInstance,
+ typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg)
+ {
+ if (!IsUpdatePending()) {
+#ifdef A11Y_LOG
+ if (mozilla::a11y::logging::IsEnabled(mozilla::a11y::logging::eNotifications))
+ mozilla::a11y::logging::Text("sync notification processing");
+#endif
+ (aInstance->*aMethod)(aArg);
+ return;
+ }
+
+ RefPtr<Notification> notification =
+ new TNotification<Class, Arg>(aInstance, aMethod, aArg);
+ if (notification && 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 && 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();
+
+private:
+ NotificationController(const NotificationController&);
+ NotificationController& operator = (const NotificationController&);
+
+ // nsARefreshObserver
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override;
+
+ /**
+ * Set and returns a hide event, paired with a show event, for the move.
+ */
+ void WithdrawPrecedingEvents(nsTArray<RefPtr<AccHideEvent>>* aEvs)
+ {
+ if (mPrecedingEvents.Length() > 0) {
+ aEvs->AppendElements(mozilla::Move(mPrecedingEvents));
+ }
+ }
+ void StorePrecedingEvent(AccHideEvent* aEv)
+ {
+ MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+ mPrecedingEvents.AppendElement(aEv);
+ }
+ void StorePrecedingEvents(nsTArray<RefPtr<AccHideEvent>>&& aEvs)
+ {
+ MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+ mPrecedingEvents.InsertElementsAt(0, aEvs);
+ }
+
+private:
+ /**
+ * 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.
+ */
+ nsIPresShell* 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<Accessible>,
+ 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)) {}
+ explicit nsCOMPtrHashKey(const nsPtrHashKey<T> &aToCopy) : mKey(aToCopy.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.
+ */
+ nsTHashtable<nsCOMPtrHashKey<nsIContent> > mTextHash;
+
+ /**
+ * 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<Accessible> > mRelocations;
+
+ /**
+ * Holds all mutation events.
+ */
+ EventTree mEventTree;
+
+ /**
+ * A temporary collection of hide events that should be fired before related
+ * show event. Used by EventTree.
+ */
+ nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
+
+#ifdef DEBUG
+ bool mMoveGuardOnStack;
+#endif
+
+ friend class MoveGuard;
+ friend class EventTree;
+
+ /**
+ * 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(Accessible* 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_