/* -*- 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_extensions_ExtensionEventListener_h #define mozilla_extensions_ExtensionEventListener_h #include "js/Promise.h" // JS::IsPromiseObject #include "mozIExtensionAPIRequestHandling.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/StructuredCloneHolder.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/dom/WorkerPrivate.h" #include "ExtensionBrowser.h" class nsIGlobalObject; namespace mozilla { namespace dom { class Function; } // namespace dom namespace extensions { #define SLOT_SEND_RESPONSE_CALLBACK_INSTANCE 0 // A class that represents a callback parameter passed to WebExtensions API // addListener / removeListener methods. // // Instances of this class are sent to the mozIExtensionAPIRequestHandler as // a property of the mozIExtensionAPIRequest. // // The mozIExtensionEventListener xpcom interface provides methods that allow // the mozIExtensionAPIRequestHandler running in the Main Thread to call the // underlying callback Function on its owning thread. class ExtensionEventListener final : public mozIExtensionEventListener { public: NS_DECL_MOZIEXTENSIONEVENTLISTENER NS_DECL_THREADSAFE_ISUPPORTS using CleanupCallback = std::function; using ListenerCallOptions = mozIExtensionListenerCallOptions; using APIObjectType = ListenerCallOptions::APIObjectType; using CallbackType = ListenerCallOptions::CallbackType; static already_AddRefed Create( nsIGlobalObject* aGlobal, ExtensionBrowser* aExtensionBrowser, dom::Function* aCallback, CleanupCallback&& aCleanupCallback, ErrorResult& aRv); static bool IsPromise(JSContext* aCx, JS::Handle aValue) { if (!aValue.isObject()) { return false; } JS::Rooted obj(aCx, &aValue.toObject()); return JS::IsPromiseObject(obj); } dom::WorkerPrivate* GetWorkerPrivate() const; RefPtr GetCallback() const { return mCallback; } nsCOMPtr GetGlobalObject() const { nsCOMPtr global = do_QueryReferent(mGlobal); return global; } ExtensionBrowser* GetExtensionBrowser() const { return mExtensionBrowser; } void Cleanup() { if (mWorkerRef) { MutexAutoLock lock(mMutex); mWorkerRef->Private()->AssertIsOnWorkerThread(); mWorkerRef = nullptr; } mGlobal = nullptr; mCallback = nullptr; mExtensionBrowser = nullptr; } private: ExtensionEventListener(nsIGlobalObject* aGlobal, ExtensionBrowser* aExtensionBrowser, dom::Function* aCallback) : mGlobal(do_GetWeakReference(aGlobal)), mExtensionBrowser(aExtensionBrowser), mCallback(aCallback), mMutex("ExtensionEventListener::mMutex") { MOZ_ASSERT(aGlobal); MOZ_ASSERT(aExtensionBrowser); MOZ_ASSERT(aCallback); }; static UniquePtr SerializeCallArguments( const nsTArray& aArgs, JSContext* aCx, ErrorResult& aRv); ~ExtensionEventListener() { Cleanup(); }; // Accessed on the main and on the owning threads. RefPtr mWorkerRef; // Accessed only on the owning thread. nsWeakPtr mGlobal; RefPtr mExtensionBrowser; RefPtr mCallback; // Used to make sure we are not going to release the // instance on the worker thread, while we are in the // process of forwarding a call from the main thread. Mutex mMutex MOZ_UNANNOTATED; }; // A WorkerRunnable subclass used to call an ExtensionEventListener // in the thread that owns the dom::Function wrapped by the // ExtensionEventListener class. class ExtensionListenerCallWorkerRunnable final : public dom::WorkerRunnable { friend class ExtensionListenerCallPromiseResultHandler; public: using ListenerCallOptions = mozIExtensionListenerCallOptions; using APIObjectType = ListenerCallOptions::APIObjectType; using CallbackType = ListenerCallOptions::CallbackType; ExtensionListenerCallWorkerRunnable( const RefPtr& aExtensionEventListener, UniquePtr aArgsHolder, ListenerCallOptions* aCallOptions, RefPtr aPromiseRetval = nullptr) : WorkerRunnable(aExtensionEventListener->GetWorkerPrivate(), "ExtensionListenerCallWorkerRunnable", WorkerThread), mListener(aExtensionEventListener), mArgsHolder(std::move(aArgsHolder)), mPromiseResult(std::move(aPromiseRetval)), mAPIObjectType(APIObjectType::NONE), mCallbackArgType(CallbackType::CALLBACK_NONE) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aExtensionEventListener); if (aCallOptions) { aCallOptions->GetApiObjectType(&mAPIObjectType); aCallOptions->GetApiObjectPrepended(&mAPIObjectPrepended); aCallOptions->GetCallbackType(&mCallbackArgType); } } MOZ_CAN_RUN_SCRIPT_BOUNDARY bool WorkerRun(JSContext* aCx, dom::WorkerPrivate* aWorkerPrivate) override; bool IsCallResultCancelled() { return mIsCallResultCancelled; } private: ~ExtensionListenerCallWorkerRunnable() { NS_ReleaseOnMainThread("~ExtensionListenerCallWorkerRunnable", mPromiseResult.forget()); ReleaseArgsHolder(); mListener = nullptr; } void ReleaseArgsHolder() { if (NS_IsMainThread()) { mArgsHolder = nullptr; } else { auto releaseArgsHolder = [argsHolder = std::move(mArgsHolder)]() {}; nsCOMPtr runnable = NS_NewRunnableFunction(__func__, std::move(releaseArgsHolder)); NS_DispatchToMainThread(runnable); } } void DeserializeCallArguments(JSContext* aCx, dom::Sequence& aArg, ErrorResult& aRv); RefPtr mListener; UniquePtr mArgsHolder; RefPtr mPromiseResult; bool mIsCallResultCancelled = false; // Call Options. bool mAPIObjectPrepended; APIObjectType mAPIObjectType; CallbackType mCallbackArgType; }; // A class attached to the promise that should be resolved once the extension // event listener call has been handled, responsible for serializing resolved // values or rejected errors on the listener's owning thread and sending them to // the extension event listener caller running on the main thread. class ExtensionListenerCallPromiseResultHandler : public dom::PromiseNativeHandler { public: NS_DECL_THREADSAFE_ISUPPORTS static void Create( const RefPtr& aPromise, const RefPtr& aWorkerRunnable, dom::ThreadSafeWorkerRef* aWorkerRef); // PromiseNativeHandler void ResolvedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) override; void RejectedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) override; enum class PromiseCallbackType { Resolve, Reject }; private: ExtensionListenerCallPromiseResultHandler( dom::ThreadSafeWorkerRef* aWorkerRef, RefPtr aWorkerRunnable) : mWorkerRef(aWorkerRef), mWorkerRunnable(std::move(aWorkerRunnable)) {} ~ExtensionListenerCallPromiseResultHandler() = default; void WorkerRunCallback(JSContext* aCx, JS::Handle aValue, PromiseCallbackType aCallbackType); // Set and accessed only on the owning worker thread. RefPtr mWorkerRef; // Reference to the runnable created on and owned by the main thread, // accessed on the worker thread and released on the owning thread. RefPtr mWorkerRunnable; }; } // namespace extensions } // namespace mozilla #endif // mozilla_extensions_ExtensionEventListener_h