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.h396
1 files changed, 396 insertions, 0 deletions
diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h
new file mode 100644
index 0000000000..137963f117
--- /dev/null
+++ b/accessible/base/NotificationController.h
@@ -0,0 +1,396 @@
+/* -*- 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_