/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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_dom_RequestCallbackManager_h #define mozilla_dom_RequestCallbackManager_h #include #include "mozilla/HashTable.h" #include "mozilla/RefPtr.h" #include "nsTArray.h" #include "nsThreadUtils.h" namespace mozilla::dom { template struct RequestCallbackEntry { RequestCallbackEntry(RequestCallback& aCallback, uint32_t aHandle) : mCallback(&aCallback), mHandle(aHandle) { LogTaskBase::LogDispatch(mCallback); } ~RequestCallbackEntry() = default; // Comparator operators to allow RemoveElementSorted with an // integer argument on arrays of RequestCallback bool operator==(uint32_t aHandle) const { return mHandle == aHandle; } bool operator<(uint32_t aHandle) const { return mHandle < aHandle; } RefPtr mCallback; const uint32_t mHandle; bool mCancelled = false; }; template class RequestCallbackManager { public: RequestCallbackManager() = default; ~RequestCallbackManager() = default; using CallbackList = nsTArray>; nsresult Schedule(RequestCallback& aCallback, uint32_t* aHandle) { if (mCallbackCounter == std::numeric_limits::max()) { // Can't increment without overflowing; bail out return NS_ERROR_NOT_AVAILABLE; } uint32_t newHandle = ++mCallbackCounter; mCallbacks.AppendElement(RequestCallbackEntry(aCallback, newHandle)); *aHandle = newHandle; return NS_OK; } bool Cancel(uint32_t aHandle) { // mCallbacks is stored sorted by handle if (mCallbacks.RemoveElementSorted(aHandle)) { return true; } for (auto* callbacks : mFiringCallbacksOnStack) { auto index = callbacks->mList.BinaryIndexOf(aHandle); if (index != CallbackList::NoIndex) { callbacks->mList.ElementAt(index).mCancelled = true; } } return false; } bool IsEmpty() const { return mCallbacks.IsEmpty(); } // FiringCallbacks takes care of: // * Stealing (and thus "freezing") the current callback list, in preparation // for firing them. // * Registering and unregistering in mFiringCallbacksOnStack, to deal with // cancellation of in-flight callbacks in cases like the first callback on // the list calling cancelAnimationFrame(secondCallbackId) or so. // mList is guaranteed not to reallocate once stolen. Instead if a callback is // cancelled mid-firing, the mCancelled bit is set, see Cancel(). struct MOZ_NON_MEMMOVABLE MOZ_STACK_CLASS FiringCallbacks { explicit FiringCallbacks(RequestCallbackManager& aManager) : mManager(aManager) { mList = std::move(aManager.mCallbacks); aManager.mFiringCallbacksOnStack.AppendElement(this); } ~FiringCallbacks() { MOZ_ASSERT(mManager.mFiringCallbacksOnStack.LastElement() == this); mManager.mFiringCallbacksOnStack.RemoveLastElement(); } RequestCallbackManager& mManager; CallbackList mList; }; void Unlink() { mCallbacks.Clear(); } void Traverse(nsCycleCollectionTraversalCallback& aCB) { for (auto& i : mCallbacks) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( aCB, "RequestCallbackManager::mCallbacks[i]"); aCB.NoteXPCOMChild(i.mCallback); } } private: CallbackList mCallbacks; // The current lists of callbacks that are executing. Used to deal with // cancellation within the same frame. Note this is a list to deal reasonably // with event loop spinning. AutoTArray mFiringCallbacksOnStack; // The current frame request callback handle. uint32_t mCallbackCounter = 0; }; template inline void ImplCycleCollectionUnlink( RequestCallbackManager& aField) { aField.Unlink(); } template inline void ImplCycleCollectionTraverse( nsCycleCollectionTraversalCallback& aCallback, RequestCallbackManager& aField, const char* aName, uint32_t aFlags) { aField.Traverse(aCallback); } } // namespace mozilla::dom #endif