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