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