1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 #include "mozilla/dom/JSActor.h"
8 #include "mozilla/dom/JSActorBinding.h"
9 
10 #include "chrome/common/ipc_channel.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/FunctionRef.h"
13 #include "mozilla/dom/AutoEntryScript.h"
14 #include "mozilla/dom/ClonedErrorHolder.h"
15 #include "mozilla/dom/ClonedErrorHolderBinding.h"
16 #include "mozilla/dom/DOMException.h"
17 #include "mozilla/dom/DOMExceptionBinding.h"
18 #include "mozilla/dom/JSActorManager.h"
19 #include "mozilla/dom/MessageManagerBinding.h"
20 #include "mozilla/dom/PWindowGlobal.h"
21 #include "mozilla/dom/Promise.h"
22 #include "mozilla/dom/RootedDictionary.h"
23 #include "mozilla/dom/ipc/StructuredCloneData.h"
24 #include "js/Promise.h"
25 #include "xpcprivate.h"
26 #include "nsFrameMessageManager.h"
27 #include "nsICrashReporter.h"
28 
29 namespace mozilla::dom {
30 
31 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor)
32   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
33   NS_INTERFACE_MAP_ENTRY(nsISupports)
34 NS_INTERFACE_MAP_END
35 
36 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor)
37 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor)
38 
39 NS_IMPL_CYCLE_COLLECTION_CLASS(JSActor)
40 
41 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSActor)
42   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
43   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWrappedJS)
44   tmp->mPendingQueries.Clear();
45   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
46 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
47 
48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSActor)
49   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
50   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWrappedJS)
51   for (const auto& query : tmp->mPendingQueries.Values()) {
52     CycleCollectionNoteChild(cb, query.mPromise.get(), "Pending Query Promise");
53   }
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
55 
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(JSActor)56 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(JSActor)
57 
58 JSActor::JSActor(nsISupports* aGlobal) {
59   mGlobal = do_QueryInterface(aGlobal);
60   if (!mGlobal) {
61     mGlobal = xpc::NativeGlobal(xpc::PrivilegedJunkScope());
62   }
63 }
64 
StartDestroy()65 void JSActor::StartDestroy() { mCanSend = false; }
66 
AfterDestroy()67 void JSActor::AfterDestroy() {
68   mCanSend = false;
69 
70   // Take our queries out, in case somehow rejecting promises can trigger
71   // additions or removals.
72   const nsTHashMap<nsUint64HashKey, PendingQuery> pendingQueries =
73       std::move(mPendingQueries);
74   for (const auto& entry : pendingQueries.Values()) {
75     nsPrintfCString message(
76         "Actor '%s' destroyed before query '%s' was resolved", mName.get(),
77         NS_LossyConvertUTF16toASCII(entry.mMessageName).get());
78     entry.mPromise->MaybeRejectWithAbortError(message);
79   }
80 
81   InvokeCallback(CallbackFunction::DidDestroy);
82   ClearManager();
83 }
84 
InvokeCallback(CallbackFunction callback)85 void JSActor::InvokeCallback(CallbackFunction callback) {
86   MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
87 
88   AutoEntryScript aes(GetParentObject(), "JSActor destroy callback");
89   JSContext* cx = aes.cx();
90   MozJSActorCallbacks callbacksHolder;
91   JS::Rooted<JS::Value> val(cx, JS::ObjectOrNullValue(GetWrapper()));
92   if (NS_WARN_IF(!callbacksHolder.Init(cx, val))) {
93     return;
94   }
95 
96   // Destroy callback is optional.
97   if (callback == CallbackFunction::DidDestroy) {
98     if (callbacksHolder.mDidDestroy.WasPassed()) {
99       callbacksHolder.mDidDestroy.Value()->Call(this);
100     }
101   } else {
102     if (callbacksHolder.mActorCreated.WasPassed()) {
103       callbacksHolder.mActorCreated.Value()->Call(this);
104     }
105   }
106 }
107 
QueryInterfaceActor(const nsIID & aIID,void ** aPtr)108 nsresult JSActor::QueryInterfaceActor(const nsIID& aIID, void** aPtr) {
109   MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
110   if (!GetWrapperPreserveColor()) {
111     // If we have no preserved wrapper, we won't implement any interfaces.
112     return NS_NOINTERFACE;
113   }
114 
115   if (!mWrappedJS) {
116     AutoEntryScript aes(GetParentObject(), "JSActor query interface");
117     JSContext* cx = aes.cx();
118 
119     JS::Rooted<JSObject*> self(cx, GetWrapper());
120     JSAutoRealm ar(cx, self);
121 
122     RefPtr<nsXPCWrappedJS> wrappedJS;
123     nsresult rv = nsXPCWrappedJS::GetNewOrUsed(
124         cx, self, NS_GET_IID(nsISupports), getter_AddRefs(wrappedJS));
125     NS_ENSURE_SUCCESS(rv, rv);
126 
127     mWrappedJS = do_QueryInterface(wrappedJS);
128     MOZ_ASSERT(mWrappedJS);
129   }
130 
131   return mWrappedJS->QueryInterface(aIID, aPtr);
132 }
133 
134 /* static */
AllowMessage(const JSActorMessageMeta & aMetadata,size_t aDataLength)135 bool JSActor::AllowMessage(const JSActorMessageMeta& aMetadata,
136                            size_t aDataLength) {
137   // A message includes more than structured clone data, so subtract
138   // 20KB to make it more likely that a message within this bound won't
139   // result in an overly large IPC message.
140   static const size_t kMaxMessageSize =
141       IPC::Channel::kMaximumMessageSize - 20 * 1024;
142   if (aDataLength < kMaxMessageSize) {
143     return true;
144   }
145 
146   return false;
147 }
148 
SetName(const nsACString & aName)149 void JSActor::SetName(const nsACString& aName) {
150   MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!");
151   mName = aName;
152 }
153 
ThrowStateErrorForGetter(const char * aName,ErrorResult & aRv) const154 void JSActor::ThrowStateErrorForGetter(const char* aName,
155                                        ErrorResult& aRv) const {
156   if (mName.IsEmpty()) {
157     aRv.ThrowInvalidStateError(nsPrintfCString(
158         "Cannot access property '%s' before actor is initialized", aName));
159   } else {
160     aRv.ThrowInvalidStateError(nsPrintfCString(
161         "Cannot access property '%s' after actor '%s' has been destroyed",
162         aName, mName.get()));
163   }
164 }
165 
TryClone(JSContext * aCx,JS::Handle<JS::Value> aValue)166 static Maybe<ipc::StructuredCloneData> TryClone(JSContext* aCx,
167                                                 JS::Handle<JS::Value> aValue) {
168   Maybe<ipc::StructuredCloneData> data{std::in_place};
169 
170   // Try to directly serialize the passed-in data, and return it to our caller.
171   IgnoredErrorResult rv;
172   data->Write(aCx, aValue, rv);
173   if (rv.Failed()) {
174     // Serialization failed, return `Nothing()` instead.
175     JS_ClearPendingException(aCx);
176     data.reset();
177   }
178   return data;
179 }
180 
CloneJSStack(JSContext * aCx,JS::Handle<JSObject * > aStack)181 static Maybe<ipc::StructuredCloneData> CloneJSStack(
182     JSContext* aCx, JS::Handle<JSObject*> aStack) {
183   JS::Rooted<JS::Value> stackVal(aCx, JS::ObjectOrNullValue(aStack));
184   return TryClone(aCx, stackVal);
185 }
186 
CaptureJSStack(JSContext * aCx)187 static Maybe<ipc::StructuredCloneData> CaptureJSStack(JSContext* aCx) {
188   JS::Rooted<JSObject*> stack(aCx, nullptr);
189   if (JS::IsAsyncStackCaptureEnabledForRealm(aCx) &&
190       !JS::CaptureCurrentStack(aCx, &stack)) {
191     JS_ClearPendingException(aCx);
192   }
193 
194   return CloneJSStack(aCx, stack);
195 }
196 
SendAsyncMessage(JSContext * aCx,const nsAString & aMessageName,JS::Handle<JS::Value> aObj,ErrorResult & aRv)197 void JSActor::SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
198                                JS::Handle<JS::Value> aObj, ErrorResult& aRv) {
199   Maybe<ipc::StructuredCloneData> data{std::in_place};
200   if (!nsFrameMessageManager::GetParamsForMessage(
201           aCx, aObj, JS::UndefinedHandleValue, *data)) {
202     aRv.ThrowDataCloneError(nsPrintfCString(
203         "Failed to serialize message '%s::%s'",
204         NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
205     return;
206   }
207 
208   JSActorMessageMeta meta;
209   meta.actorName() = mName;
210   meta.messageName() = aMessageName;
211   meta.kind() = JSActorMessageKind::Message;
212 
213   SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
214 }
215 
SendQuery(JSContext * aCx,const nsAString & aMessageName,JS::Handle<JS::Value> aObj,ErrorResult & aRv)216 already_AddRefed<Promise> JSActor::SendQuery(JSContext* aCx,
217                                              const nsAString& aMessageName,
218                                              JS::Handle<JS::Value> aObj,
219                                              ErrorResult& aRv) {
220   Maybe<ipc::StructuredCloneData> data{std::in_place};
221   if (!nsFrameMessageManager::GetParamsForMessage(
222           aCx, aObj, JS::UndefinedHandleValue, *data)) {
223     aRv.ThrowDataCloneError(nsPrintfCString(
224         "Failed to serialize message '%s::%s'",
225         NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
226     return nullptr;
227   }
228 
229   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
230   if (NS_WARN_IF(!global)) {
231     aRv.ThrowUnknownError("Unable to get current native global");
232     return nullptr;
233   }
234 
235   RefPtr<Promise> promise = Promise::Create(global, aRv);
236   if (NS_WARN_IF(aRv.Failed())) {
237     return nullptr;
238   }
239 
240   JSActorMessageMeta meta;
241   meta.actorName() = mName;
242   meta.messageName() = aMessageName;
243   meta.queryId() = mNextQueryId++;
244   meta.kind() = JSActorMessageKind::Query;
245 
246   mPendingQueries.InsertOrUpdate(meta.queryId(),
247                                  PendingQuery{promise, meta.messageName()});
248 
249   SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
250   return promise.forget();
251 }
252 
CallReceiveMessage(JSContext * aCx,const JSActorMessageMeta & aMetadata,JS::Handle<JS::Value> aData,JS::MutableHandle<JS::Value> aRetVal,ErrorResult & aRv)253 void JSActor::CallReceiveMessage(JSContext* aCx,
254                                  const JSActorMessageMeta& aMetadata,
255                                  JS::Handle<JS::Value> aData,
256                                  JS::MutableHandle<JS::Value> aRetVal,
257                                  ErrorResult& aRv) {
258   // The argument which we want to pass to IPC.
259   RootedDictionary<ReceiveMessageArgument> argument(aCx);
260   argument.mTarget = this;
261   argument.mName = aMetadata.messageName();
262   argument.mData = aData;
263   argument.mJson = aData;
264   argument.mSync = false;
265 
266   if (GetWrapperPreserveColor()) {
267     // Invoke the actual callback.
268     JS::Rooted<JSObject*> global(aCx, JS::GetNonCCWObjectGlobal(GetWrapper()));
269     RefPtr<MessageListener> messageListener =
270         new MessageListener(GetWrapper(), global, nullptr, nullptr);
271     messageListener->ReceiveMessage(argument, aRetVal, aRv,
272                                     "JSActor receive message",
273                                     MessageListener::eRethrowExceptions);
274   } else {
275     aRv.ThrowTypeError<MSG_NOT_CALLABLE>("Property 'receiveMessage'");
276   }
277 }
278 
ReceiveMessage(JSContext * aCx,const JSActorMessageMeta & aMetadata,JS::Handle<JS::Value> aData,ErrorResult & aRv)279 void JSActor::ReceiveMessage(JSContext* aCx,
280                              const JSActorMessageMeta& aMetadata,
281                              JS::Handle<JS::Value> aData, ErrorResult& aRv) {
282   MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Message);
283   JS::Rooted<JS::Value> retval(aCx);
284   CallReceiveMessage(aCx, aMetadata, aData, &retval, aRv);
285 }
286 
ReceiveQuery(JSContext * aCx,const JSActorMessageMeta & aMetadata,JS::Handle<JS::Value> aData,ErrorResult & aRv)287 void JSActor::ReceiveQuery(JSContext* aCx, const JSActorMessageMeta& aMetadata,
288                            JS::Handle<JS::Value> aData, ErrorResult& aRv) {
289   MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Query);
290 
291   // This promise will be resolved or rejected once the listener has been
292   // called. Our listener on this promise will then send the reply.
293   RefPtr<Promise> promise = Promise::Create(GetParentObject(), aRv);
294   if (NS_WARN_IF(aRv.Failed())) {
295     return;
296   }
297 
298   RefPtr<QueryHandler> handler = new QueryHandler(this, aMetadata, promise);
299   promise->AppendNativeHandler(handler);
300 
301   ErrorResult error;
302   JS::Rooted<JS::Value> retval(aCx);
303   CallReceiveMessage(aCx, aMetadata, aData, &retval, error);
304 
305   // If we have a promise, resolve or reject it respectively.
306   if (error.Failed()) {
307     if (error.IsUncatchableException()) {
308       promise->MaybeRejectWithTimeoutError(
309           "Message handler threw uncatchable exception");
310     } else {
311       promise->MaybeReject(std::move(error));
312     }
313   } else {
314     promise->MaybeResolve(retval);
315   }
316   error.SuppressException();
317 }
318 
ReceiveQueryReply(JSContext * aCx,const JSActorMessageMeta & aMetadata,JS::Handle<JS::Value> aData,ErrorResult & aRv)319 void JSActor::ReceiveQueryReply(JSContext* aCx,
320                                 const JSActorMessageMeta& aMetadata,
321                                 JS::Handle<JS::Value> aData, ErrorResult& aRv) {
322   if (NS_WARN_IF(aMetadata.actorName() != mName)) {
323     aRv.ThrowUnknownError("Mismatched actor name for query reply");
324     return;
325   }
326 
327   Maybe<PendingQuery> query = mPendingQueries.Extract(aMetadata.queryId());
328   if (NS_WARN_IF(!query)) {
329     aRv.ThrowUnknownError("Received reply for non-pending query");
330     return;
331   }
332 
333   Promise* promise = query->mPromise;
334   JSAutoRealm ar(aCx, promise->PromiseObj());
335   JS::RootedValue data(aCx, aData);
336   if (NS_WARN_IF(!JS_WrapValue(aCx, &data))) {
337     aRv.NoteJSContextException(aCx);
338     return;
339   }
340 
341   if (aMetadata.kind() == JSActorMessageKind::QueryResolve) {
342     promise->MaybeResolve(data);
343   } else {
344     promise->MaybeReject(data);
345   }
346 }
347 
SendRawMessageInProcess(const JSActorMessageMeta & aMeta,Maybe<ipc::StructuredCloneData> && aData,Maybe<ipc::StructuredCloneData> && aStack,OtherSideCallback && aGetOtherSide)348 void JSActor::SendRawMessageInProcess(const JSActorMessageMeta& aMeta,
349                                       Maybe<ipc::StructuredCloneData>&& aData,
350                                       Maybe<ipc::StructuredCloneData>&& aStack,
351                                       OtherSideCallback&& aGetOtherSide) {
352   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
353   NS_DispatchToMainThread(NS_NewRunnableFunction(
354       "JSActor Async Message",
355       [aMeta, data{std::move(aData)}, stack{std::move(aStack)},
356        getOtherSide{std::move(aGetOtherSide)}]() mutable {
357         if (RefPtr<JSActorManager> otherSide = getOtherSide()) {
358           otherSide->ReceiveRawMessage(aMeta, std::move(data),
359                                        std::move(stack));
360         }
361       }));
362 }
363 
364 // Native handler for our generated promise which is used to handle Queries and
365 // send the reply when their promises have been resolved.
QueryHandler(JSActor * aActor,const JSActorMessageMeta & aMetadata,Promise * aPromise)366 JSActor::QueryHandler::QueryHandler(JSActor* aActor,
367                                     const JSActorMessageMeta& aMetadata,
368                                     Promise* aPromise)
369     : mActor(aActor),
370       mPromise(aPromise),
371       mMessageName(aMetadata.messageName()),
372       mQueryId(aMetadata.queryId()) {}
373 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)374 void JSActor::QueryHandler::RejectedCallback(JSContext* aCx,
375                                              JS::Handle<JS::Value> aValue) {
376   if (!mActor) {
377     // Make sure that this rejection is reported. See comment below.
378     if (!JS::CallOriginalPromiseReject(aCx, aValue)) {
379       JS_ClearPendingException(aCx);
380     }
381     return;
382   }
383 
384   JS::Rooted<JS::Value> value(aCx, aValue);
385   if (value.isObject()) {
386     JS::Rooted<JSObject*> error(aCx, &value.toObject());
387     if (RefPtr<ClonedErrorHolder> ceh =
388             ClonedErrorHolder::Create(aCx, error, IgnoreErrors())) {
389       JS::RootedObject obj(aCx);
390       // Note: We can't use `ToJSValue` here because ClonedErrorHolder isn't
391       // wrapper cached.
392       if (ceh->WrapObject(aCx, nullptr, &obj)) {
393         value.setObject(*obj);
394       } else {
395         JS_ClearPendingException(aCx);
396       }
397     } else {
398       JS_ClearPendingException(aCx);
399     }
400   }
401 
402   Maybe<ipc::StructuredCloneData> data = TryClone(aCx, value);
403   if (!data) {
404     // Failed to clone the rejection value. Make sure that this
405     // rejection is reported, despite being "handled". This is done by
406     // creating a new promise in the rejected state, and throwing it
407     // away. This will be reported as an unhandled rejected promise.
408     if (!JS::CallOriginalPromiseReject(aCx, aValue)) {
409       JS_ClearPendingException(aCx);
410     }
411   }
412 
413   SendReply(aCx, JSActorMessageKind::QueryReject, std::move(data));
414 }
415 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)416 void JSActor::QueryHandler::ResolvedCallback(JSContext* aCx,
417                                              JS::Handle<JS::Value> aValue) {
418   if (!mActor) {
419     return;
420   }
421 
422   Maybe<ipc::StructuredCloneData> data{std::in_place};
423   data->InitScope(JS::StructuredCloneScope::DifferentProcess);
424 
425   IgnoredErrorResult error;
426   data->Write(aCx, aValue, error);
427   if (NS_WARN_IF(error.Failed())) {
428     JS_ClearPendingException(aCx);
429 
430     nsAutoCString msg;
431     msg.Append(mActor->Name());
432     msg.Append(':');
433     msg.Append(NS_LossyConvertUTF16toASCII(mMessageName));
434     msg.AppendLiteral(": message reply cannot be cloned.");
435 
436     auto exc = MakeRefPtr<Exception>(msg, NS_ERROR_FAILURE, "DataCloneError"_ns,
437                                      nullptr, nullptr);
438 
439     JS::Rooted<JS::Value> val(aCx);
440     if (ToJSValue(aCx, exc, &val)) {
441       RejectedCallback(aCx, val);
442     } else {
443       JS_ClearPendingException(aCx);
444     }
445     return;
446   }
447 
448   SendReply(aCx, JSActorMessageKind::QueryResolve, std::move(data));
449 }
450 
SendReply(JSContext * aCx,JSActorMessageKind aKind,Maybe<ipc::StructuredCloneData> && aData)451 void JSActor::QueryHandler::SendReply(JSContext* aCx, JSActorMessageKind aKind,
452                                       Maybe<ipc::StructuredCloneData>&& aData) {
453   MOZ_ASSERT(mActor);
454 
455   JSActorMessageMeta meta;
456   meta.actorName() = mActor->Name();
457   meta.messageName() = mMessageName;
458   meta.queryId() = mQueryId;
459   meta.kind() = aKind;
460 
461   JS::Rooted<JSObject*> promise(aCx, mPromise->PromiseObj());
462   JS::Rooted<JSObject*> stack(aCx, JS::GetPromiseResolutionSite(promise));
463 
464   mActor->SendRawMessage(meta, std::move(aData), CloneJSStack(aCx, stack),
465                          IgnoreErrors());
466   mActor = nullptr;
467   mPromise = nullptr;
468 }
469 
470 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor::QueryHandler)
471   NS_INTERFACE_MAP_ENTRY(nsISupports)
472 NS_INTERFACE_MAP_END
473 
474 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor::QueryHandler)
475 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor::QueryHandler)
476 
477 NS_IMPL_CYCLE_COLLECTION(JSActor::QueryHandler, mActor, mPromise)
478 
479 }  // namespace mozilla::dom
480