fune/dom/ipc/jsactor/JSActorManager.cpp
Nika Layzell 2d69635263 Bug 1650837 - Part 3: Destroy active actors when they are unregistered, r=kmag
This is needed specifically for tests, which need to be able to unregister &
re-register the same actor with different configuration options. With
JSWindowActors, we can create new windows after each actor is unregistered, but
we can't do this with in-process JSProcessActors.

Differential Revision: https://phabricator.services.mozilla.com/D82450
2020-07-07 22:02:50 +00:00

225 lines
7.5 KiB
C++

/* -*- 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 "mozJSComponentLoader.h"
#include "jsapi.h"
namespace mozilla {
namespace dom {
already_AddRefed<JSActor> JSActorManager::GetActor(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();
// Constructing an actor requires a running script, so push an AutoEntryScript
// onto the stack.
AutoEntryScript aes(xpc::PrivilegedJunkScope(), "JSActor construction");
JSContext* cx = aes.cx();
// 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(cx);
if (side.mModuleURI) {
JS::RootedObject global(cx);
JS::RootedObject exports(cx);
aRv = loader->Import(cx, 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(cx);
nsAutoCString ctorName(aName);
ctorName.Append(isParent ? "Parent"_ns : "Child"_ns);
if (!JS_GetProperty(cx, exports, ctorName.get(), &ctor)) {
aRv.NoteJSContextException(cx);
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(cx, ctor, JS::HandleValueArray::empty(), &actorObj)) {
aRv.NoteJSContextException(cx);
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,
ipc::StructuredCloneData&& aData,
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);
aStack.Read(cx, &stackVal, error);
if (error.Failed()) {
return;
}
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(aMetadata.actorName(), error);
if (error.Failed()) {
return;
}
JS::Rooted<JS::Value> data(cx);
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:
case JSActorMessageKind::Query:
actor->ReceiveMessageOrQuery(cx, aMetadata, data, error);
break;
default:
MOZ_ASSERT_UNREACHABLE();
}
}
void JSActorManager::JSActorWillDestroy() {
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
CrashReporter::AutoAnnotateCrashReport autoMessageName(
CrashReporter::Annotation::JSActorMessage, "<WillDestroy>"_ns);
// Make a copy so that we can avoid potential iterator invalidation when
// calling the user-provided Destroy() methods.
nsTArray<RefPtr<JSActor>> actors(mJSActors.Count());
for (auto& entry : mJSActors) {
actors.AppendElement(entry.GetData());
}
for (auto& actor : actors) {
CrashReporter::AutoAnnotateCrashReport autoActorName(
CrashReporter::Annotation::JSActorName, actor->Name());
actor->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 dom
} // namespace mozilla