diff options
Diffstat (limited to '')
-rw-r--r-- | dom/ipc/jsactor/JSActorManager.cpp | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/dom/ipc/jsactor/JSActorManager.cpp b/dom/ipc/jsactor/JSActorManager.cpp new file mode 100644 index 0000000000..fda2cf7efc --- /dev/null +++ b/dom/ipc/jsactor/JSActorManager.cpp @@ -0,0 +1,225 @@ +/* -*- 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/JSActorService.h" +#include "mozilla/dom/PWindowGlobal.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ScopeExit.h" +#include "mozJSComponentLoader.h" +#include "jsapi.h" +#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 mozJSComponentLoader. + RefPtr<mozJSComponentLoader> loader = mozJSComponentLoader::Get(); + MOZ_ASSERT(loader); + + // If a module URI was provided, use it to construct an instance of the actor. + JS::RootedObject actorObj(aCx); + if (side.mModuleURI) { + JS::RootedObject global(aCx); + JS::RootedObject exports(aCx); + aRv = loader->Import(aCx, side.mModuleURI.ref(), &global, &exports); + if (aRv.Failed()) { + return nullptr; + } + MOZ_ASSERT(exports, "null exports!"); + + // Load the specific property from our module. + JS::RootedValue 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.Put(aName, RefPtr{actor}); + return actor.forget(); +} + +#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 (auto& entry : mJSActors) { + entry.GetData()->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. + nsRefPtrHashtable<nsCStringHashKey, JSActor> actors; + mJSActors.SwapElements(actors); + for (auto& entry : actors) { + CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::Annotation::JSActorName, entry.GetData()->Name()); + entry.GetData()->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 |