зеркало из https://github.com/mozilla/gecko-dev.git
238 строки
7.6 KiB
C++
238 строки
7.6 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/AutoEntryScript.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 "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 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.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());
|
|
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
|