1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_extensions_ExtensionEventListener_h 8 #define mozilla_extensions_ExtensionEventListener_h 9 10 #include "js/Promise.h" // JS::IsPromiseObject 11 #include "mozIExtensionAPIRequestHandling.h" 12 #include "mozilla/dom/PromiseNativeHandler.h" 13 #include "mozilla/dom/StructuredCloneHolder.h" 14 #include "mozilla/dom/WorkerRunnable.h" 15 #include "mozilla/dom/WorkerPrivate.h" 16 17 class nsIGlobalObject; 18 19 namespace mozilla { 20 21 namespace dom { 22 class Function; 23 } // namespace dom 24 25 namespace extensions { 26 27 #define SLOT_SEND_RESPONSE_CALLBACK_INSTANCE 0 28 29 // A class that represents a callback parameter passed to WebExtensions API 30 // addListener / removeListener methods. 31 // 32 // Instances of this class are sent to the mozIExtensionAPIRequestHandler as 33 // a property of the mozIExtensionAPIRequest. 34 // 35 // The mozIExtensionEventListener xpcom interface provides methods that allow 36 // the mozIExtensionAPIRequestHandler running in the Main Thread to call the 37 // underlying callback Function on its owning thread. 38 class ExtensionEventListener final : public mozIExtensionEventListener { 39 public: 40 NS_DECL_MOZIEXTENSIONEVENTLISTENER 41 NS_DECL_THREADSAFE_ISUPPORTS 42 43 using CleanupCallback = std::function<void()>; 44 using ListenerCallOptions = mozIExtensionListenerCallOptions; 45 using APIObjectType = ListenerCallOptions::APIObjectType; 46 using CallbackType = ListenerCallOptions::CallbackType; 47 48 static already_AddRefed<ExtensionEventListener> Create( 49 nsIGlobalObject* aGlobal, dom::Function* aCallback, 50 CleanupCallback&& aCleanupCallback, ErrorResult& aRv); 51 IsPromise(JSContext * aCx,JS::Handle<JS::Value> aValue)52 static bool IsPromise(JSContext* aCx, JS::Handle<JS::Value> aValue) { 53 if (!aValue.isObject()) { 54 return false; 55 } 56 57 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject()); 58 return JS::IsPromiseObject(obj); 59 } 60 61 dom::WorkerPrivate* GetWorkerPrivate() const; 62 GetCallback()63 RefPtr<dom::Function> GetCallback() const { return mCallback; } 64 GetGlobalObject()65 nsCOMPtr<nsIGlobalObject> GetGlobalObject() const { 66 nsCOMPtr<nsIGlobalObject> global = do_QueryReferent(mGlobal); 67 return global; 68 } 69 Cleanup()70 void Cleanup() { 71 if (mWorkerRef) { 72 MutexAutoLock lock(mMutex); 73 74 mWorkerRef->Private()->AssertIsOnWorkerThread(); 75 mWorkerRef = nullptr; 76 } 77 78 mGlobal = nullptr; 79 mCallback = nullptr; 80 } 81 82 private: ExtensionEventListener(nsIGlobalObject * aGlobal,dom::Function * aCallback)83 ExtensionEventListener(nsIGlobalObject* aGlobal, dom::Function* aCallback) 84 : mGlobal(do_GetWeakReference(aGlobal)), 85 mCallback(aCallback), 86 mMutex("ExtensionEventListener::mMutex"){}; 87 88 static UniquePtr<dom::StructuredCloneHolder> SerializeCallArguments( 89 const nsTArray<JS::Value>& aArgs, JSContext* aCx, ErrorResult& aRv); 90 ~ExtensionEventListener()91 ~ExtensionEventListener() { Cleanup(); }; 92 93 // Accessed on the main and on the owning threads. 94 RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef; 95 96 // Accessed only on the owning thread. 97 nsWeakPtr mGlobal; 98 RefPtr<dom::Function> mCallback; 99 100 // Used to make sure we are not going to release the 101 // instance on the worker thread, while we are in the 102 // process of forwarding a call from the main thread. 103 Mutex mMutex; 104 }; 105 106 // A WorkerRunnable subclass used to call an ExtensionEventListener 107 // in the thread that owns the dom::Function wrapped by the 108 // ExtensionEventListener class. 109 class ExtensionListenerCallWorkerRunnable : public dom::WorkerRunnable { 110 friend class ExtensionListenerCallPromiseResultHandler; 111 112 public: 113 using ListenerCallOptions = mozIExtensionListenerCallOptions; 114 using APIObjectType = ListenerCallOptions::APIObjectType; 115 using CallbackType = ListenerCallOptions::CallbackType; 116 117 ExtensionListenerCallWorkerRunnable( 118 const RefPtr<ExtensionEventListener>& aExtensionEventListener, 119 UniquePtr<dom::StructuredCloneHolder> aArgsHolder, 120 ListenerCallOptions* aCallOptions, 121 RefPtr<dom::Promise> aPromiseRetval = nullptr) 122 : WorkerRunnable(aExtensionEventListener->GetWorkerPrivate(), 123 WorkerThreadUnchangedBusyCount), 124 mListener(aExtensionEventListener), 125 mArgsHolder(std::move(aArgsHolder)), 126 mPromiseResult(std::move(aPromiseRetval)), 127 mAPIObjectType(APIObjectType::NONE), 128 mCallbackArgType(CallbackType::CALLBACK_NONE) { 129 MOZ_ASSERT(NS_IsMainThread()); 130 MOZ_ASSERT(aExtensionEventListener); 131 132 if (aCallOptions) { 133 aCallOptions->GetApiObjectType(&mAPIObjectType); 134 aCallOptions->GetCallbackType(&mCallbackArgType); 135 } 136 } 137 138 MOZ_CAN_RUN_SCRIPT_BOUNDARY 139 bool WorkerRun(JSContext* aCx, dom::WorkerPrivate* aWorkerPrivate) override; 140 IsCallResultCancelled()141 bool IsCallResultCancelled() { return mIsCallResultCancelled; } 142 143 private: ~ExtensionListenerCallWorkerRunnable()144 ~ExtensionListenerCallWorkerRunnable() { 145 NS_ReleaseOnMainThread(mPromiseResult.forget()); 146 ReleaseArgsHolder(); 147 mListener = nullptr; 148 } 149 ReleaseArgsHolder()150 void ReleaseArgsHolder() { 151 if (NS_IsMainThread()) { 152 mArgsHolder = nullptr; 153 } else { 154 auto releaseArgsHolder = [argsHolder = std::move(mArgsHolder)]() {}; 155 nsCOMPtr<nsIRunnable> runnable = 156 NS_NewRunnableFunction(__func__, std::move(releaseArgsHolder)); 157 NS_DispatchToMainThread(runnable); 158 } 159 } 160 161 void DeserializeCallArguments(JSContext* aCx, dom::Sequence<JS::Value>& aArg, 162 ErrorResult& aRv); 163 164 RefPtr<ExtensionEventListener> mListener; 165 UniquePtr<dom::StructuredCloneHolder> mArgsHolder; 166 RefPtr<dom::Promise> mPromiseResult; 167 bool mIsCallResultCancelled = false; 168 // Call Options. 169 APIObjectType mAPIObjectType; 170 CallbackType mCallbackArgType; 171 }; 172 173 // A class attached to the promise that should be resolved once the extension 174 // event listener call has been handled, responsible for serializing resolved 175 // values or rejected errors on the listener's owning thread and sending them to 176 // the extension event listener caller running on the main thread. 177 class ExtensionListenerCallPromiseResultHandler 178 : public dom::PromiseNativeHandler { 179 public: 180 NS_DECL_THREADSAFE_ISUPPORTS 181 182 static void Create( 183 const RefPtr<dom::Promise>& aPromise, 184 const RefPtr<ExtensionListenerCallWorkerRunnable>& aWorkerRunnable, 185 dom::ThreadSafeWorkerRef* aWorkerRef); 186 187 // PromiseNativeHandler 188 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; 189 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; 190 191 enum class PromiseCallbackType { Resolve, Reject }; 192 193 private: ExtensionListenerCallPromiseResultHandler(dom::ThreadSafeWorkerRef * aWorkerRef,RefPtr<ExtensionListenerCallWorkerRunnable> aWorkerRunnable)194 ExtensionListenerCallPromiseResultHandler( 195 dom::ThreadSafeWorkerRef* aWorkerRef, 196 RefPtr<ExtensionListenerCallWorkerRunnable> aWorkerRunnable) 197 : mWorkerRef(aWorkerRef), mWorkerRunnable(std::move(aWorkerRunnable)) {} 198 199 ~ExtensionListenerCallPromiseResultHandler() = default; 200 201 void WorkerRunCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 202 PromiseCallbackType aCallbackType); 203 204 // Set and accessed only on the owning worker thread. 205 RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef; 206 207 // Reference to the runnable created on and owned by the main thread, 208 // accessed on the worker thread and released on the owning thread. 209 RefPtr<ExtensionListenerCallWorkerRunnable> mWorkerRunnable; 210 }; 211 212 } // namespace extensions 213 } // namespace mozilla 214 215 #endif // mozilla_extensions_ExtensionEventListener_h 216