summaryrefslogtreecommitdiffstats
path: root/dom/ipc/jsactor/JSActorManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/ipc/jsactor/JSActorManager.cpp')
-rw-r--r--dom/ipc/jsactor/JSActorManager.cpp248
1 files changed, 248 insertions, 0 deletions
diff --git a/dom/ipc/jsactor/JSActorManager.cpp b/dom/ipc/jsactor/JSActorManager.cpp
new file mode 100644
index 0000000000..dfccff5592
--- /dev/null
+++ b/dom/ipc/jsactor/JSActorManager.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/JSActorManager.h"
+
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/JSActorService.h"
+#include "mozilla/dom/PWindowGlobal.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ScopeExit.h"
+#include "mozJSModuleLoader.h"
+#include "jsapi.h"
+#include "js/CallAndConstruct.h" // JS::Construct
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<JSActor> JSActorManager::GetActor(JSContext* aCx,
+ const nsACString& aName,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ // If our connection has been closed, return an error.
+ mozilla::ipc::IProtocol* nativeActor = AsNativeActor();
+ if (!nativeActor->CanSend()) {
+ aRv.ThrowInvalidStateError(nsPrintfCString(
+ "Cannot get actor '%s'. Native '%s' actor is destroyed.",
+ PromiseFlatCString(aName).get(), nativeActor->GetProtocolName()));
+ return nullptr;
+ }
+
+ // Check if this actor has already been created, and return it if it has.
+ if (RefPtr<JSActor> actor = mJSActors.Get(aName)) {
+ return actor.forget();
+ }
+
+ RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton();
+ if (!actorSvc) {
+ aRv.ThrowInvalidStateError("JSActorService hasn't been initialized");
+ return nullptr;
+ }
+
+ // Check if this actor satisfies the requirements of the protocol
+ // corresponding to `aName`, and get the module which implements it.
+ RefPtr<JSActorProtocol> protocol =
+ MatchingJSActorProtocol(actorSvc, aName, aRv);
+ if (!protocol) {
+ return nullptr;
+ }
+
+ bool isParent = nativeActor->GetSide() == mozilla::ipc::ParentSide;
+ auto& side = isParent ? protocol->Parent() : protocol->Child();
+
+ // We're about to construct the actor, so make sure we're in the JSM realm
+ // while importing etc.
+ JSAutoRealm ar(aCx, xpc::PrivilegedJunkScope());
+
+ // Load the module using mozJSModuleLoader.
+ RefPtr loader = mozJSModuleLoader::Get();
+ MOZ_ASSERT(loader);
+
+ // If a module URI was provided, use it to construct an instance of the actor.
+ JS::Rooted<JSObject*> actorObj(aCx);
+ if (side.mModuleURI || side.mESModuleURI) {
+ JS::Rooted<JSObject*> exports(aCx);
+ if (side.mModuleURI) {
+ JS::Rooted<JSObject*> global(aCx);
+ aRv = loader->Import(aCx, side.mModuleURI.ref(), &global, &exports);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ } else {
+ aRv = loader->ImportESModule(aCx, side.mESModuleURI.ref(), &exports);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ MOZ_ASSERT(exports, "null exports!");
+
+ // Load the specific property from our module.
+ JS::Rooted<JS::Value> ctor(aCx);
+ nsAutoCString ctorName(aName);
+ ctorName.Append(isParent ? "Parent"_ns : "Child"_ns);
+ if (!JS_GetProperty(aCx, exports, ctorName.get(), &ctor)) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!ctor.isObject())) {
+ aRv.ThrowNotFoundError(nsPrintfCString(
+ "Could not find actor constructor '%s'", ctorName.get()));
+ return nullptr;
+ }
+
+ // Invoke the constructor loaded from the module.
+ if (!JS::Construct(aCx, ctor, JS::HandleValueArray::empty(), &actorObj)) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+ }
+
+ // Initialize our newly-constructed actor, and return it.
+ RefPtr<JSActor> actor = InitJSActor(actorObj, aName, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ mJSActors.InsertOrUpdate(aName, RefPtr{actor});
+ return actor.forget();
+}
+
+already_AddRefed<JSActor> JSActorManager::GetExistingActor(
+ const nsACString& aName) {
+ if (!AsNativeActor()->CanSend()) {
+ return nullptr;
+ }
+ return mJSActors.Get(aName);
+}
+
+#define CHILD_DIAGNOSTIC_ASSERT(test, msg) \
+ do { \
+ if (XRE_IsParentProcess()) { \
+ MOZ_ASSERT(test, msg); \
+ } else { \
+ MOZ_DIAGNOSTIC_ASSERT(test, msg); \
+ } \
+ } while (0)
+
+void JSActorManager::ReceiveRawMessage(
+ const JSActorMessageMeta& aMetadata,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ CrashReporter::AutoAnnotateCrashReport autoActorName(
+ CrashReporter::Annotation::JSActorName, aMetadata.actorName());
+ CrashReporter::AutoAnnotateCrashReport autoMessageName(
+ CrashReporter::Annotation::JSActorMessage,
+ NS_LossyConvertUTF16toASCII(aMetadata.messageName()));
+
+ // We're going to be running JS. Enter the privileged junk realm so we can set
+ // up our JS state correctly.
+ AutoEntryScript aes(xpc::PrivilegedJunkScope(), "JSActor message handler");
+ JSContext* cx = aes.cx();
+
+ // Ensure any errors reported to `error` are set on the scope, so they're
+ // reported.
+ ErrorResult error;
+ auto autoSetException =
+ MakeScopeExit([&] { Unused << error.MaybeSetPendingException(cx); });
+
+ // If an async stack was provided, set up our async stack state.
+ JS::Rooted<JSObject*> stack(cx);
+ Maybe<JS::AutoSetAsyncStackForNewCalls> stackSetter;
+ {
+ JS::Rooted<JS::Value> stackVal(cx);
+ if (aStack) {
+ aStack->Read(cx, &stackVal, error);
+ if (error.Failed()) {
+ error.SuppressException();
+ JS_ClearPendingException(cx);
+ stackVal.setUndefined();
+ }
+ }
+
+ if (stackVal.isObject()) {
+ stack = &stackVal.toObject();
+ if (!js::IsSavedFrame(stack)) {
+ CHILD_DIAGNOSTIC_ASSERT(false, "Stack must be a SavedFrame object");
+ error.ThrowDataError("Actor async stack must be a SavedFrame object");
+ return;
+ }
+ stackSetter.emplace(cx, stack, "JSActor query");
+ }
+ }
+
+ RefPtr<JSActor> actor = GetActor(cx, aMetadata.actorName(), error);
+ if (error.Failed()) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> data(cx);
+ if (aData) {
+ aData->Read(cx, &data, error);
+ if (error.Failed()) {
+ CHILD_DIAGNOSTIC_ASSERT(false, "Should not receive non-decodable data");
+ return;
+ }
+ }
+
+ switch (aMetadata.kind()) {
+ case JSActorMessageKind::QueryResolve:
+ case JSActorMessageKind::QueryReject:
+ actor->ReceiveQueryReply(cx, aMetadata, data, error);
+ break;
+
+ case JSActorMessageKind::Message:
+ actor->ReceiveMessage(cx, aMetadata, data, error);
+ break;
+
+ case JSActorMessageKind::Query:
+ actor->ReceiveQuery(cx, aMetadata, data, error);
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+}
+
+void JSActorManager::JSActorWillDestroy() {
+ for (const auto& entry : mJSActors.Values()) {
+ entry->StartDestroy();
+ }
+}
+
+void JSActorManager::JSActorDidDestroy() {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ CrashReporter::AutoAnnotateCrashReport autoMessageName(
+ CrashReporter::Annotation::JSActorMessage, "<DidDestroy>"_ns);
+
+ // Swap the table with `mJSActors` so that we don't invalidate it while
+ // iterating.
+ const nsRefPtrHashtable<nsCStringHashKey, JSActor> actors =
+ std::move(mJSActors);
+ for (const auto& entry : actors.Values()) {
+ CrashReporter::AutoAnnotateCrashReport autoActorName(
+ CrashReporter::Annotation::JSActorName, entry->Name());
+ // Do not risk to run script very late in shutdown
+ if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
+ entry->AfterDestroy();
+ }
+ }
+}
+
+void JSActorManager::JSActorUnregister(const nsACString& aName) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ RefPtr<JSActor> actor;
+ if (mJSActors.Remove(aName, getter_AddRefs(actor))) {
+ actor->AfterDestroy();
+ }
+}
+
+} // namespace mozilla::dom