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