From e6b4d30a2f8ff0b43bbfd98e0e9f3a15438a4952 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Thu, 16 Mar 2023 00:22:02 +0800 Subject: [PATCH] src: bootstrap Web [Exposed=*] APIs in the shadow realm This is the initial work to bootstrap Web interfaces that are defined with extended attributes `[Exposed=*]`. The ShadowRealm instances are garbage-collected once it is unreachable. However, V8 can not infer the reference cycles between the per-realm strong persistent function handles and the realm's context handle. To allow the context to be gc-ed once it is not reachable, the per-realm persistent handles are attached to the context's global object and the persistent handles are set as weak. PR-URL: https://github.com/nodejs/node/pull/46809 Refs: https://github.com/nodejs/node/issues/42528 Reviewed-By: Matteo Collina Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Joyee Cheung --- src/api/environment.cc | 14 +- src/env-inl.h | 3 + src/env.cc | 21 ++- src/env.h | 8 +- src/env_properties.h | 1 - src/histogram.cc | 39 +++--- src/histogram.h | 5 +- src/node_binding.cc | 6 - src/node_binding.h | 12 +- src/node_buffer.cc | 7 +- src/node_i18n.cc | 42 +++--- src/node_perf.cc | 90 ++++++------ src/node_realm-inl.h | 18 +-- src/node_realm.cc | 151 ++++++++++++--------- src/node_realm.h | 60 +++++--- src/node_shadow_realm.cc | 98 ++++++++++++- src/node_shadow_realm.h | 27 ++++ test/common/globals.js | 141 +++++++++++++++++++ test/parallel/test-shadow-realm-gc.js | 12 ++ test/parallel/test-shadow-realm-globals.js | 27 ++++ test/pummel/test-heapdump-env.js | 4 +- 21 files changed, 595 insertions(+), 191 deletions(-) create mode 100644 test/common/globals.js create mode 100644 test/parallel/test-shadow-realm-gc.js create mode 100644 test/parallel/test-shadow-realm-globals.js diff --git a/src/api/environment.cc b/src/api/environment.cc index f56ee8d12bd1c5..12672603fcdc46 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -67,6 +67,16 @@ MaybeLocal PrepareStackTraceCallback(Local context, if (env == nullptr) { return exception->ToString(context).FromMaybe(Local()); } + // TODO(legendecas): Per-realm prepareStackTrace callback. + // If we are in a Realm that is not the principal Realm (e.g. ShadowRealm), + // skip the prepareStackTrace callback to avoid passing the JS objects ( + // the exception and trace) across the realm boundary with the + // `Error.prepareStackTrace` override. + Realm* current_realm = Realm::GetCurrent(context); + if (current_realm != nullptr && + current_realm->kind() != Realm::Kind::kPrincipal) { + return exception->ToString(context).FromMaybe(Local()); + } Local prepare = env->prepare_stack_trace_callback(); if (prepare.IsEmpty()) { return exception->ToString(context).FromMaybe(Local()); @@ -81,8 +91,8 @@ MaybeLocal PrepareStackTraceCallback(Local context, // is what ReThrow gives us). Just returning the empty MaybeLocal would leave // us with a pending exception. TryCatchScope try_catch(env); - MaybeLocal result = prepare->Call( - context, Undefined(env->isolate()), arraysize(args), args); + MaybeLocal result = + prepare->Call(context, Undefined(env->isolate()), arraysize(args), args); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { try_catch.ReThrow(); } diff --git a/src/env-inl.h b/src/env-inl.h index 68dfb3a4d57113..7b135c7b83293a 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -791,6 +791,7 @@ void Environment::set_process_exit_handler( #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) +#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ inline \ v8::Local IsolateData::PropertyName() const { \ @@ -799,7 +800,9 @@ void Environment::set_process_exit_handler( PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VS #undef VY #undef VP diff --git a/src/env.cc b/src/env.cc index efeccad346475a..8ee2c10d089f00 100644 --- a/src/env.cc +++ b/src/env.cc @@ -299,13 +299,16 @@ IsolateDataSerializeInfo IsolateData::Serialize(SnapshotCreator* creator) { #define VP(PropertyName, StringValue) V(Private, PropertyName) #define VY(PropertyName, StringValue) V(Symbol, PropertyName) #define VS(PropertyName, StringValue) V(String, PropertyName) +#define VR(PropertyName, TypeName) V(Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ info.primitive_values.push_back( \ creator->AddData(PropertyName##_.Get(isolate))); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VY #undef VS #undef VP @@ -338,6 +341,7 @@ void IsolateData::DeserializeProperties(const IsolateDataSerializeInfo* info) { #define VP(PropertyName, StringValue) V(Private, PropertyName) #define VY(PropertyName, StringValue) V(Symbol, PropertyName) #define VS(PropertyName, StringValue) V(String, PropertyName) +#define VR(PropertyName, TypeName) V(Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ do { \ MaybeLocal maybe_field = \ @@ -352,7 +356,9 @@ void IsolateData::DeserializeProperties(const IsolateDataSerializeInfo* info) { PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VY #undef VS #undef VP @@ -421,6 +427,19 @@ void IsolateData::CreateProperties() { .ToLocalChecked())); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) #undef V +#define V(PropertyName, TypeName) \ + per_realm_##PropertyName##_.Set( \ + isolate_, \ + Private::New( \ + isolate_, \ + String::NewFromOneByte( \ + isolate_, \ + reinterpret_cast("per_realm_" #PropertyName), \ + NewStringType::kInternalized, \ + sizeof("per_realm_" #PropertyName) - 1) \ + .ToLocalChecked())); + PER_REALM_STRONG_PERSISTENT_VALUES(V) +#undef V #define V(PropertyName, StringValue) \ PropertyName##_.Set( \ isolate_, \ @@ -786,7 +805,7 @@ Environment::Environment(IsolateData* isolate_data, void Environment::InitializeMainContext(Local context, const EnvSerializeInfo* env_info) { - principal_realm_ = std::make_unique( + principal_realm_ = std::make_unique( this, context, MAYBE_FIELD_PTR(env_info, principal_realm)); AssignToContext(context, principal_realm_.get(), ContextInfo("")); if (env_info != nullptr) { diff --git a/src/env.h b/src/env.h index bf16b48fa24b0b..8b3596a6c5b358 100644 --- a/src/env.h +++ b/src/env.h @@ -147,12 +147,15 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) +#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ inline v8::Local PropertyName() const; PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VY #undef VS #undef VP @@ -184,6 +187,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) +#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName) #define VM(PropertyName) V(v8::FunctionTemplate, PropertyName##_binding) #define VT(PropertyName, TypeName) V(TypeName, PropertyName) #define V(TypeName, PropertyName) \ @@ -192,9 +196,11 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) PER_ISOLATE_TEMPLATE_PROPERTIES(VT) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) NODE_BINDINGS_WITH_PER_ISOLATE_INIT(VM) #undef V #undef VM +#undef VR #undef VT #undef VS #undef VY @@ -1139,7 +1145,7 @@ class Environment : public MemoryRetainer { std::function process_exit_handler_{ DefaultProcessExitHandlerInternal}; - std::unique_ptr principal_realm_ = nullptr; + std::unique_ptr principal_realm_ = nullptr; builtins::BuiltinLoader builtin_loader_; StartExecutionCallback embedder_entry_point_; diff --git a/src/env_properties.h b/src/env_properties.h index 9abcb8d9d72d0e..9f55993972ea45 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -414,7 +414,6 @@ V(message_port, v8::Object) \ V(builtin_module_require, v8::Function) \ V(performance_entry_callback, v8::Function) \ - V(performance_entry_template, v8::Function) \ V(prepare_stack_trace_callback, v8::Function) \ V(process_object, v8::Object) \ V(primordials, v8::Object) \ diff --git a/src/histogram.cc b/src/histogram.cc index 3a3228ddc9eb6b..112a8911cfb4af 100644 --- a/src/histogram.cc +++ b/src/histogram.cc @@ -16,6 +16,7 @@ using v8::Local; using v8::Map; using v8::Number; using v8::Object; +using v8::ObjectTemplate; using v8::String; using v8::Uint32; using v8::Value; @@ -213,7 +214,7 @@ void HistogramBase::Add(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - CHECK(GetConstructorTemplate(env)->HasInstance(args[0])); + CHECK(GetConstructorTemplate(env->isolate_data())->HasInstance(args[0])); HistogramBase* other; ASSIGN_OR_RETURN_UNWRAP(&other, args[0]); @@ -225,9 +226,10 @@ BaseObjectPtr HistogramBase::Create( Environment* env, const Histogram::Options& options) { Local obj; - if (!GetConstructorTemplate(env) - ->InstanceTemplate() - ->NewInstance(env->context()).ToLocal(&obj)) { + if (!GetConstructorTemplate(env->isolate_data()) + ->InstanceTemplate() + ->NewInstance(env->context()) + .ToLocal(&obj)) { return BaseObjectPtr(); } @@ -238,9 +240,10 @@ BaseObjectPtr HistogramBase::Create( Environment* env, std::shared_ptr histogram) { Local obj; - if (!GetConstructorTemplate(env) - ->InstanceTemplate() - ->NewInstance(env->context()).ToLocal(&obj)) { + if (!GetConstructorTemplate(env->isolate_data()) + ->InstanceTemplate() + ->NewInstance(env->context()) + .ToLocal(&obj)) { return BaseObjectPtr(); } return MakeBaseObject(env, obj, std::move(histogram)); @@ -278,15 +281,14 @@ void HistogramBase::New(const FunctionCallbackInfo& args) { } Local HistogramBase::GetConstructorTemplate( - Environment* env) { - Local tmpl = env->histogram_ctor_template(); + IsolateData* isolate_data) { + Local tmpl = isolate_data->histogram_ctor_template(); if (tmpl.IsEmpty()) { - Isolate* isolate = env->isolate(); + Isolate* isolate = isolate_data->isolate(); tmpl = NewFunctionTemplate(isolate, New); - Local classname = - FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); + Local classname = FIXED_ONE_BYTE_STRING(isolate, "Histogram"); tmpl->SetClassName(classname); - tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); + tmpl->Inherit(BaseObject::GetConstructorTemplate(isolate_data)); tmpl->InstanceTemplate()->SetInternalFieldCount( HistogramBase::kInternalFieldCount); @@ -311,7 +313,7 @@ Local HistogramBase::GetConstructorTemplate( SetProtoMethod(isolate, tmpl, "record", Record); SetProtoMethod(isolate, tmpl, "recordDelta", RecordDelta); SetProtoMethod(isolate, tmpl, "add", Add); - env->set_histogram_ctor_template(tmpl); + isolate_data->set_histogram_ctor_template(tmpl); } return tmpl; } @@ -339,9 +341,12 @@ void HistogramBase::RegisterExternalReferences( registry->Register(Add); } -void HistogramBase::Initialize(Environment* env, Local target) { - SetConstructorFunction( - env->context(), target, "Histogram", GetConstructorTemplate(env)); +void HistogramBase::Initialize(IsolateData* isolate_data, + Local target) { + SetConstructorFunction(isolate_data->isolate(), + target, + "Histogram", + GetConstructorTemplate(isolate_data)); } BaseObjectPtr HistogramBase::HistogramTransferData::Deserialize( diff --git a/src/histogram.h b/src/histogram.h index d526bba8a1b797..b3df6c975d90b0 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -84,8 +84,9 @@ class HistogramImpl { class HistogramBase : public BaseObject, public HistogramImpl { public: static v8::Local GetConstructorTemplate( - Environment* env); - static void Initialize(Environment* env, v8::Local target); + IsolateData* isolate_data); + static void Initialize(IsolateData* isolate_data, + v8::Local target); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); static BaseObjectPtr Create( diff --git a/src/node_binding.cc b/src/node_binding.cc index 48825171a43feb..bd6ca33fcaf91f 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -14,12 +14,6 @@ #define NODE_BUILTIN_OPENSSL_BINDINGS(V) #endif -#if NODE_HAVE_I18N_SUPPORT -#define NODE_BUILTIN_ICU_BINDINGS(V) V(icu) -#else -#define NODE_BUILTIN_ICU_BINDINGS(V) -#endif - #if HAVE_INSPECTOR #define NODE_BUILTIN_PROFILER_BINDINGS(V) V(profiler) #else diff --git a/src/node_binding.h b/src/node_binding.h index c140297f5ca936..1b024774e120a9 100644 --- a/src/node_binding.h +++ b/src/node_binding.h @@ -24,9 +24,17 @@ static_assert(static_cast(NM_F_LINKED) == static_cast(node::ModuleFlags::kLinked), "NM_F_LINKED != node::ModuleFlags::kLinked"); +#if NODE_HAVE_I18N_SUPPORT +#define NODE_BUILTIN_ICU_BINDINGS(V) V(icu) +#else +#define NODE_BUILTIN_ICU_BINDINGS(V) +#endif + #define NODE_BINDINGS_WITH_PER_ISOLATE_INIT(V) \ V(builtins) \ - V(worker) + V(performance) \ + V(worker) \ + NODE_BUILTIN_ICU_BINDINGS(V) #define NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \ static node::node_module _module = { \ @@ -56,6 +64,8 @@ namespace node { NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_INTERNAL) // Define a per-isolate initialization function for a node internal binding. +// The modname should be registered in the NODE_BINDINGS_WITH_PER_ISOLATE_INIT +// list. #define NODE_BINDING_PER_ISOLATE_INIT(modname, per_isolate_func) \ void _register_isolate_##modname(node::IsolateData* isolate_data, \ v8::Local target) { \ diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 62651f4656b1b9..ff041274f90d24 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1150,11 +1150,14 @@ static void IsAscii(const FunctionCallbackInfo& args) { } void SetBufferPrototype(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); + + // TODO(legendecas): Remove this check once the binding supports sub-realms. + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); CHECK(args[0]->IsObject()); Local proto = args[0].As(); - env->set_buffer_prototype_object(proto); + realm->set_buffer_prototype_object(proto); } void GetZeroFillToggle(const FunctionCallbackInfo& args) { diff --git a/src/node_i18n.cc b/src/node_i18n.cc index bb810632ee6617..cdb264d6202eb3 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -859,36 +859,41 @@ static void GetStringWidth(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(width); } -void Initialize(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - SetMethod(context, target, "toUnicode", ToUnicode); - SetMethod(context, target, "toASCII", ToASCII); - SetMethod(context, target, "getStringWidth", GetStringWidth); +static void CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Isolate* isolate = isolate_data->isolate(); + Local proto = target->PrototypeTemplate(); + + SetMethod(isolate, proto, "toUnicode", ToUnicode); + SetMethod(isolate, proto, "toASCII", ToASCII); + SetMethod(isolate, proto, "getStringWidth", GetStringWidth); // One-shot converters - SetMethod(context, target, "icuErrName", ICUErrorName); - SetMethod(context, target, "transcode", Transcode); + SetMethod(isolate, proto, "icuErrName", ICUErrorName); + SetMethod(isolate, proto, "transcode", Transcode); // ConverterObject { - Local t = NewFunctionTemplate(env->isolate(), nullptr); - t->Inherit(BaseObject::GetConstructorTemplate(env)); + Local t = NewFunctionTemplate(isolate, nullptr); + t->Inherit(BaseObject::GetConstructorTemplate(isolate_data)); t->InstanceTemplate()->SetInternalFieldCount( ConverterObject::kInternalFieldCount); Local converter_string = - FIXED_ONE_BYTE_STRING(env->isolate(), "Converter"); + FIXED_ONE_BYTE_STRING(isolate, "Converter"); t->SetClassName(converter_string); - env->set_i18n_converter_template(t->InstanceTemplate()); + isolate_data->set_i18n_converter_template(t->InstanceTemplate()); } - SetMethod(context, target, "getConverter", ConverterObject::Create); - SetMethod(context, target, "decode", ConverterObject::Decode); - SetMethod(context, target, "hasConverter", ConverterObject::Has); + SetMethod(isolate, proto, "getConverter", ConverterObject::Create); + SetMethod(isolate, proto, "decode", ConverterObject::Decode); + SetMethod(isolate, proto, "hasConverter", ConverterObject::Has); } +void CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) {} + void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(ToUnicode); registry->Register(ToASCII); @@ -903,7 +908,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // namespace i18n } // namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(icu, node::i18n::Initialize) +NODE_BINDING_CONTEXT_AWARE_INTERNAL(icu, node::i18n::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT(icu, node::i18n::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE(icu, node::i18n::RegisterExternalReferences) #endif // NODE_HAVE_I18N_SUPPORT diff --git a/src/node_perf.cc b/src/node_perf.cc index 1a97f25b6ca484..94773fa9e9fed6 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -28,9 +28,9 @@ using v8::Local; using v8::MaybeLocal; using v8::Number; using v8::Object; +using v8::ObjectTemplate; using v8::PropertyAttribute; using v8::ReadOnly; -using v8::String; using v8::Value; // Microseconds in a millisecond, as a float. @@ -99,7 +99,10 @@ void PerformanceState::Mark(PerformanceMilestone milestone, uint64_t ts) { // Allows specific Node.js lifecycle milestones to be set from JavaScript void MarkMilestone(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); + // TODO(legendecas): Remove this check once the sub-realms are supported. + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); + Environment* env = realm->env(); PerformanceMilestone milestone = static_cast(args[0].As()->Value()); if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID) @@ -107,9 +110,11 @@ void MarkMilestone(const FunctionCallbackInfo& args) { } void SetupPerformanceObservers(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); + // TODO(legendecas): Remove this check once the sub-realms are supported. + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); CHECK(args[0]->IsFunction()); - env->set_performance_entry_callback(args[0].As()); + realm->set_performance_entry_callback(args[0].As()); } // Marks the start of a GC cycle @@ -284,15 +289,41 @@ void GetTimeOriginTimeStamp(const FunctionCallbackInfo& args) { } void MarkBootstrapComplete(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - env->performance_state()->Mark( + Realm* realm = Realm::GetCurrent(args); + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); + realm->env()->performance_state()->Mark( performance::NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); } -void Initialize(Local target, - Local unused, - Local context, - void* priv) { +static void CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Isolate* isolate = isolate_data->isolate(); + Local proto = target->PrototypeTemplate(); + + HistogramBase::Initialize(isolate_data, proto); + + SetMethod(isolate, proto, "markMilestone", MarkMilestone); + SetMethod(isolate, proto, "setupObservers", SetupPerformanceObservers); + SetMethod(isolate, + proto, + "installGarbageCollectionTracking", + InstallGarbageCollectionTracking); + SetMethod(isolate, + proto, + "removeGarbageCollectionTracking", + RemoveGarbageCollectionTracking); + SetMethod(isolate, proto, "notify", Notify); + SetMethod(isolate, proto, "loopIdleTime", LoopIdleTime); + SetMethod(isolate, proto, "getTimeOrigin", GetTimeOrigin); + SetMethod(isolate, proto, "getTimeOriginTimestamp", GetTimeOriginTimeStamp); + SetMethod(isolate, proto, "createELDHistogram", CreateELDHistogram); + SetMethod(isolate, proto, "markBootstrapComplete", MarkBootstrapComplete); +} + +void CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); PerformanceState* state = env->performance_state(); @@ -304,32 +335,6 @@ void Initialize(Local target, FIXED_ONE_BYTE_STRING(isolate, "milestones"), state->milestones.GetJSArray()).Check(); - Local performanceEntryString = - FIXED_ONE_BYTE_STRING(isolate, "PerformanceEntry"); - - Local pe = FunctionTemplate::New(isolate); - pe->SetClassName(performanceEntryString); - Local fn = pe->GetFunction(context).ToLocalChecked(); - target->Set(context, performanceEntryString, fn).Check(); - env->set_performance_entry_template(fn); - - SetMethod(context, target, "markMilestone", MarkMilestone); - SetMethod(context, target, "setupObservers", SetupPerformanceObservers); - SetMethod(context, - target, - "installGarbageCollectionTracking", - InstallGarbageCollectionTracking); - SetMethod(context, - target, - "removeGarbageCollectionTracking", - RemoveGarbageCollectionTracking); - SetMethod(context, target, "notify", Notify); - SetMethod(context, target, "loopIdleTime", LoopIdleTime); - SetMethod(context, target, "getTimeOrigin", GetTimeOrigin); - SetMethod(context, target, "getTimeOriginTimestamp", GetTimeOriginTimeStamp); - SetMethod(context, target, "createELDHistogram", CreateELDHistogram); - SetMethod(context, target, "markBootstrapComplete", MarkBootstrapComplete); - Local constants = Object::New(isolate); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MAJOR); @@ -365,12 +370,8 @@ void Initialize(Local target, PropertyAttribute attr = static_cast(ReadOnly | DontDelete); - target->DefineOwnProperty(context, - env->constants_string(), - constants, - attr).ToChecked(); - - HistogramBase::Initialize(env, target); + target->DefineOwnProperty(context, env->constants_string(), constants, attr) + .ToChecked(); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { @@ -390,6 +391,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // namespace performance } // namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(performance, node::performance::Initialize) +NODE_BINDING_CONTEXT_AWARE_INTERNAL( + performance, node::performance::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT(performance, + node::performance::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE(performance, node::performance::RegisterExternalReferences) diff --git a/src/node_realm-inl.h b/src/node_realm-inl.h index 2fc3eef97729d1..f49690714a3ea5 100644 --- a/src/node_realm-inl.h +++ b/src/node_realm-inl.h @@ -42,6 +42,10 @@ inline v8::Isolate* Realm::isolate() const { return isolate_; } +inline Realm::Kind Realm::kind() const { + return kind_; +} + inline bool Realm::has_run_bootstrapping_code() const { return has_run_bootstrapping_code_; } @@ -115,20 +119,6 @@ int64_t Realm::base_object_count() const { return base_object_count_; } -#define V(PropertyName, TypeName) \ - inline v8::Local Realm::PropertyName() const { \ - return PersistentToLocal::Strong(PropertyName##_); \ - } \ - inline void Realm::set_##PropertyName(v8::Local value) { \ - PropertyName##_.Reset(isolate(), value); \ - } -PER_REALM_STRONG_PERSISTENT_VALUES(V) -#undef V - -v8::Local Realm::context() const { - return PersistentToLocal::Strong(context_); -} - void Realm::AddCleanupHook(CleanupQueue::Callback fn, void* arg) { cleanup_queue_.Add(fn, arg); } diff --git a/src/node_realm.cc b/src/node_realm.cc index ad6e22f6b5b35a..a8cd9b9a55da2f 100644 --- a/src/node_realm.cc +++ b/src/node_realm.cc @@ -19,18 +19,9 @@ using v8::SnapshotCreator; using v8::String; using v8::Value; -Realm::Realm(Environment* env, - v8::Local context, - const RealmSerializeInfo* realm_info) - : env_(env), isolate_(context->GetIsolate()) { +Realm::Realm(Environment* env, v8::Local context, Kind kind) + : env_(env), isolate_(context->GetIsolate()), kind_(kind) { context_.Reset(isolate_, context); - - // Create properties if not deserializing from snapshot. - // Or the properties are deserialized with DeserializeProperties() when the - // env drained the deserialize requests. - if (realm_info == nullptr) { - CreateProperties(); - } } Realm::~Realm() { @@ -208,55 +199,6 @@ MaybeLocal Realm::BootstrapInternalLoaders() { return scope.Escape(loader_exports); } -MaybeLocal Realm::BootstrapNode() { - EscapableHandleScope scope(isolate_); - - MaybeLocal result = ExecuteBootstrapper("internal/bootstrap/node"); - - if (result.IsEmpty()) { - return MaybeLocal(); - } - - if (!env_->no_browser_globals()) { - if (ExecuteBootstrapper("internal/bootstrap/web/exposed-wildcard") - .IsEmpty() || - ExecuteBootstrapper("internal/bootstrap/web/exposed-window-or-worker") - .IsEmpty()) { - return MaybeLocal(); - } - } - - // TODO(joyeecheung): skip these in the snapshot building for workers. - auto thread_switch_id = - env_->is_main_thread() ? "internal/bootstrap/switches/is_main_thread" - : "internal/bootstrap/switches/is_not_main_thread"; - result = ExecuteBootstrapper(thread_switch_id); - - if (result.IsEmpty()) { - return MaybeLocal(); - } - - auto process_state_switch_id = - env_->owns_process_state() - ? "internal/bootstrap/switches/does_own_process_state" - : "internal/bootstrap/switches/does_not_own_process_state"; - result = ExecuteBootstrapper(process_state_switch_id); - - if (result.IsEmpty()) { - return MaybeLocal(); - } - - Local env_string = FIXED_ONE_BYTE_STRING(isolate_, "env"); - Local env_proxy; - CreateEnvProxyTemplate(isolate_, env_->isolate_data()); - if (!env_->env_proxy_template()->NewInstance(context()).ToLocal(&env_proxy) || - process_object()->Set(context(), env_string, env_proxy).IsNothing()) { - return MaybeLocal(); - } - - return scope.EscapeMaybe(result); -} - MaybeLocal Realm::RunBootstrapping() { EscapableHandleScope scope(isolate_); @@ -267,7 +209,7 @@ MaybeLocal Realm::RunBootstrapping() { } Local result; - if (!BootstrapNode().ToLocal(&result)) { + if (!BootstrapRealm().ToLocal(&result)) { return MaybeLocal(); } @@ -284,8 +226,10 @@ void Realm::DoneBootstrapping() { // TODO(legendecas): track req_wrap and handle_wrap by realms instead of // environments. - CHECK(env_->req_wrap_queue()->IsEmpty()); - CHECK(env_->handle_wrap_queue()->IsEmpty()); + if (kind_ == kPrincipal) { + CHECK(env_->req_wrap_queue()->IsEmpty()); + CHECK(env_->handle_wrap_queue()->IsEmpty()); + } has_run_bootstrapping_code_ = true; @@ -312,7 +256,7 @@ void Realm::PrintInfoForSnapshot() { << "\n"; }); - fprintf(stderr, "\nnBuiltins without cache:\n"); + fprintf(stderr, "\nBuiltins without cache:\n"); for (const auto& s : builtins_without_cache) { fprintf(stderr, "%s\n", s.c_str()); } @@ -360,4 +304,83 @@ void Realm::VerifyNoStrongBaseObjects() { }); } +v8::Local Realm::context() const { + return PersistentToLocal::Strong(context_); +} + +#define V(PropertyName, TypeName) \ + v8::Local PrincipalRealm::PropertyName() const { \ + return PersistentToLocal::Strong(PropertyName##_); \ + } \ + void PrincipalRealm::set_##PropertyName(v8::Local value) { \ + PropertyName##_.Reset(isolate(), value); \ + } +PER_REALM_STRONG_PERSISTENT_VALUES(V) +#undef V + +PrincipalRealm::PrincipalRealm(Environment* env, + v8::Local context, + const RealmSerializeInfo* realm_info) + : Realm(env, context, kPrincipal) { + // Create properties if not deserializing from snapshot. + // Or the properties are deserialized with DeserializeProperties() when the + // env drained the deserialize requests. + if (realm_info == nullptr) { + CreateProperties(); + } +} + +void PrincipalRealm::MemoryInfo(MemoryTracker* tracker) const { + Realm::MemoryInfo(tracker); +} + +MaybeLocal PrincipalRealm::BootstrapRealm() { + EscapableHandleScope scope(isolate_); + + MaybeLocal result = ExecuteBootstrapper("internal/bootstrap/node"); + + if (result.IsEmpty()) { + return MaybeLocal(); + } + + if (!env_->no_browser_globals()) { + if (ExecuteBootstrapper("internal/bootstrap/web/exposed-wildcard") + .IsEmpty() || + ExecuteBootstrapper("internal/bootstrap/web/exposed-window-or-worker") + .IsEmpty()) { + return MaybeLocal(); + } + } + + // TODO(joyeecheung): skip these in the snapshot building for workers. + auto thread_switch_id = + env_->is_main_thread() ? "internal/bootstrap/switches/is_main_thread" + : "internal/bootstrap/switches/is_not_main_thread"; + result = ExecuteBootstrapper(thread_switch_id); + + if (result.IsEmpty()) { + return MaybeLocal(); + } + + auto process_state_switch_id = + env_->owns_process_state() + ? "internal/bootstrap/switches/does_own_process_state" + : "internal/bootstrap/switches/does_not_own_process_state"; + result = ExecuteBootstrapper(process_state_switch_id); + + if (result.IsEmpty()) { + return MaybeLocal(); + } + + Local env_string = FIXED_ONE_BYTE_STRING(isolate_, "env"); + Local env_proxy; + CreateEnvProxyTemplate(isolate_, env_->isolate_data()); + if (!env_->env_proxy_template()->NewInstance(context()).ToLocal(&env_proxy) || + process_object()->Set(context(), env_string, env_proxy).IsNothing()) { + return MaybeLocal(); + } + + return scope.EscapeMaybe(result); +} + } // namespace node diff --git a/src/node_realm.h b/src/node_realm.h index 04129eec47d551..73ab1718e002de 100644 --- a/src/node_realm.h +++ b/src/node_realm.h @@ -43,6 +43,11 @@ using BindingDataStore = std::array, */ class Realm : public MemoryRetainer { public: + enum Kind { + kPrincipal, + kShadowRealm, + }; + static inline Realm* GetCurrent(v8::Isolate* isolate); static inline Realm* GetCurrent(v8::Local context); static inline Realm* GetCurrent( @@ -50,18 +55,13 @@ class Realm : public MemoryRetainer { template static inline Realm* GetCurrent(const v8::PropertyCallbackInfo& info); - Realm(Environment* env, - v8::Local context, - const RealmSerializeInfo* realm_info); - ~Realm(); + Realm(Environment* env, v8::Local context, Kind kind); Realm(const Realm&) = delete; Realm& operator=(const Realm&) = delete; Realm(Realm&&) = delete; Realm& operator=(Realm&&) = delete; - SET_MEMORY_INFO_NAME(Realm) - SET_SELF_SIZE(Realm) void MemoryInfo(MemoryTracker* tracker) const override; void CreateProperties(); @@ -69,8 +69,6 @@ class Realm : public MemoryRetainer { void DeserializeProperties(const RealmSerializeInfo* info); v8::MaybeLocal ExecuteBootstrapper(const char* id); - v8::MaybeLocal BootstrapInternalLoaders(); - v8::MaybeLocal BootstrapNode(); v8::MaybeLocal RunBootstrapping(); inline void AddCleanupHook(CleanupQueue::Callback cb, void* arg); @@ -87,7 +85,8 @@ class Realm : public MemoryRetainer { inline IsolateData* isolate_data() const; inline Environment* env() const; inline v8::Isolate* isolate() const; - inline v8::Local context() const; + inline Kind kind() const; + virtual v8::Local context() const; inline bool has_run_bootstrapping_code() const; // Methods created using SetMethod(), SetPrototypeMethod(), etc. inside @@ -115,8 +114,8 @@ class Realm : public MemoryRetainer { inline int64_t base_object_created_after_bootstrap() const; #define V(PropertyName, TypeName) \ - inline v8::Local PropertyName() const; \ - inline void set_##PropertyName(v8::Local value); + virtual v8::Local PropertyName() const = 0; \ + virtual void set_##PropertyName(v8::Local value) = 0; PER_REALM_STRONG_PERSISTENT_VALUES(V) #undef V @@ -127,15 +126,27 @@ class Realm : public MemoryRetainer { // it's only used for tests. std::vector builtins_in_snapshot; - private: - void InitializeContext(v8::Local context, - const RealmSerializeInfo* realm_info); - void DoneBootstrapping(); + protected: + ~Realm(); + + v8::MaybeLocal BootstrapInternalLoaders(); + virtual v8::MaybeLocal BootstrapRealm() = 0; Environment* env_; // Shorthand for isolate pointer. v8::Isolate* isolate_; v8::Global context_; + +#define V(PropertyName, TypeName) v8::Global PropertyName##_; + PER_REALM_STRONG_PERSISTENT_VALUES(V) +#undef V + + private: + void InitializeContext(v8::Local context, + const RealmSerializeInfo* realm_info); + void DoneBootstrapping(); + + Kind kind_; bool has_run_bootstrapping_code_ = false; int64_t base_object_count_ = 0; @@ -144,10 +155,27 @@ class Realm : public MemoryRetainer { BindingDataStore binding_data_store_; CleanupQueue cleanup_queue_; +}; -#define V(PropertyName, TypeName) v8::Global PropertyName##_; +class PrincipalRealm : public Realm { + public: + PrincipalRealm(Environment* env, + v8::Local context, + const RealmSerializeInfo* realm_info); + ~PrincipalRealm() = default; + + SET_MEMORY_INFO_NAME(PrincipalRealm) + SET_SELF_SIZE(PrincipalRealm) + void MemoryInfo(MemoryTracker* tracker) const override; + +#define V(PropertyName, TypeName) \ + v8::Local PropertyName() const override; \ + void set_##PropertyName(v8::Local value) override; PER_REALM_STRONG_PERSISTENT_VALUES(V) #undef V + + protected: + v8::MaybeLocal BootstrapRealm() override; }; } // namespace node diff --git a/src/node_shadow_realm.cc b/src/node_shadow_realm.cc index 2ccd62edc315d7..bf5c7c94676c55 100644 --- a/src/node_shadow_realm.cc +++ b/src/node_shadow_realm.cc @@ -1,15 +1,111 @@ #include "node_shadow_realm.h" +#include "env-inl.h" namespace node { namespace shadow_realm { using v8::Context; +using v8::EscapableHandleScope; +using v8::HandleScope; using v8::Local; using v8::MaybeLocal; +using v8::Value; + +// static +ShadowRealm* ShadowRealm::New(Environment* env) { + ShadowRealm* realm = new ShadowRealm(env); + env->AssignToContext(realm->context(), realm, ContextInfo("")); + + if (realm->RunBootstrapping().IsEmpty()) { + delete realm; + return nullptr; + } + return realm; +} // static MaybeLocal HostCreateShadowRealmContextCallback( Local initiator_context) { - return Context::New(initiator_context->GetIsolate()); + Environment* env = Environment::GetCurrent(initiator_context); + ShadowRealm* realm = ShadowRealm::New(env); + if (realm != nullptr) { + return realm->context(); + } + return MaybeLocal(); +} + +// static +void ShadowRealm::WeakCallback(const v8::WeakCallbackInfo& data) { + ShadowRealm* realm = data.GetParameter(); + delete realm; +} + +ShadowRealm::ShadowRealm(Environment* env) + : Realm(env, NewContext(env->isolate()), kShadowRealm) { + context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); + CreateProperties(); +} + +ShadowRealm::~ShadowRealm() { + while (HasCleanupHooks()) { + RunCleanup(); + } +} + +void ShadowRealm::MemoryInfo(MemoryTracker* tracker) const { + Realm::MemoryInfo(tracker); +} + +v8::Local ShadowRealm::context() const { + Local ctx = PersistentToLocal::Default(isolate_, context_); + DCHECK(!ctx.IsEmpty()); + return ctx; +} + +// V8 can not infer reference cycles between global persistent handles, e.g. +// the Realm's Context handle and the per-realm function handles. +// Attach the per-realm strong persistent values' lifetime to the context's +// global object to avoid the strong global references to the per-realm objects +// keep the context alive indefinitely. +#define V(PropertyName, TypeName) \ + v8::Local ShadowRealm::PropertyName() const { \ + return PersistentToLocal::Default(isolate_, PropertyName##_); \ + } \ + void ShadowRealm::set_##PropertyName(v8::Local value) { \ + HandleScope scope(isolate_); \ + PropertyName##_.Reset(isolate_, value); \ + v8::Local ctx = context(); \ + if (value.IsEmpty()) { \ + ctx->Global() \ + ->SetPrivate(ctx, \ + isolate_data()->per_realm_##PropertyName(), \ + v8::Undefined(isolate_)) \ + .ToChecked(); \ + } else { \ + PropertyName##_.SetWeak(); \ + ctx->Global() \ + ->SetPrivate(ctx, isolate_data()->per_realm_##PropertyName(), value) \ + .ToChecked(); \ + } \ + } +PER_REALM_STRONG_PERSISTENT_VALUES(V) +#undef V + +v8::MaybeLocal ShadowRealm::BootstrapRealm() { + EscapableHandleScope scope(isolate_); + MaybeLocal result = v8::True(isolate_); + + // Skip "internal/bootstrap/node" as it installs node globals and per-isolate + // callbacks like PrepareStackTraceCallback. + + if (!env_->no_browser_globals()) { + result = ExecuteBootstrapper("internal/bootstrap/web/exposed-wildcard"); + + if (result.IsEmpty()) { + return MaybeLocal(); + } + } + + return result; } } // namespace shadow_realm diff --git a/src/node_shadow_realm.h b/src/node_shadow_realm.h index a10fc425661f56..cc76cbacdc4842 100644 --- a/src/node_shadow_realm.h +++ b/src/node_shadow_realm.h @@ -3,11 +3,38 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include "node_realm.h" #include "v8.h" namespace node { namespace shadow_realm { +class ShadowRealm : public Realm { + public: + static ShadowRealm* New(Environment* env); + + SET_MEMORY_INFO_NAME(ShadowRealm) + SET_SELF_SIZE(ShadowRealm) + void MemoryInfo(MemoryTracker* tracker) const override; + + v8::Local context() const override; + +#define V(PropertyName, TypeName) \ + v8::Local PropertyName() const override; \ + void set_##PropertyName(v8::Local value) override; + PER_REALM_STRONG_PERSISTENT_VALUES(V) +#undef V + + protected: + v8::MaybeLocal BootstrapRealm() override; + + private: + static void WeakCallback(const v8::WeakCallbackInfo& data); + + explicit ShadowRealm(Environment* env); + ~ShadowRealm(); +}; + v8::MaybeLocal HostCreateShadowRealmContextCallback( v8::Local initiator_context); diff --git a/test/common/globals.js b/test/common/globals.js new file mode 100644 index 00000000000000..f18b358e657a55 --- /dev/null +++ b/test/common/globals.js @@ -0,0 +1,141 @@ +'use strict'; + +const intrinsics = new Set([ + 'Object', + 'Function', + 'Array', + 'Number', + 'parseFloat', + 'parseInt', + 'Infinity', + 'NaN', + 'undefined', + 'Boolean', + 'String', + 'Symbol', + 'Date', + 'Promise', + 'RegExp', + 'Error', + 'AggregateError', + 'EvalError', + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'URIError', + 'globalThis', + 'JSON', + 'Math', + 'Intl', + 'ArrayBuffer', + 'Uint8Array', + 'Int8Array', + 'Uint16Array', + 'Int16Array', + 'Uint32Array', + 'Int32Array', + 'Float32Array', + 'Float64Array', + 'Uint8ClampedArray', + 'BigUint64Array', + 'BigInt64Array', + 'DataView', + 'Map', + 'BigInt', + 'Set', + 'WeakMap', + 'WeakSet', + 'Proxy', + 'Reflect', + 'ShadowRealm', + 'FinalizationRegistry', + 'WeakRef', + 'decodeURI', + 'decodeURIComponent', + 'encodeURI', + 'encodeURIComponent', + 'escape', + 'unescape', + 'eval', + 'isFinite', + 'isNaN', + 'SharedArrayBuffer', + 'Atomics', + 'WebAssembly', +]); + +if (global.gc) { + intrinsics.add('gc'); +} + +// v8 exposes console in the global scope. +intrinsics.add('console'); + +const webIdlExposedWildcard = new Set([ + 'DOMException', + 'TextEncoder', + 'TextDecoder', + 'AbortController', + 'AbortSignal', + 'EventTarget', + 'Event', + 'URL', + 'URLSearchParams', + 'ReadableStream', + 'ReadableStreamDefaultReader', + 'ReadableStreamBYOBReader', + 'ReadableStreamBYOBRequest', + 'ReadableByteStreamController', + 'ReadableStreamDefaultController', + 'TransformStream', + 'TransformStreamDefaultController', + 'WritableStream', + 'WritableStreamDefaultWriter', + 'WritableStreamDefaultController', + 'ByteLengthQueuingStrategy', + 'CountQueuingStrategy', + 'TextEncoderStream', + 'TextDecoderStream', + 'CompressionStream', + 'DecompressionStream', +]); + +const webIdlExposedWindow = new Set([ + 'console', + 'BroadcastChannel', + 'queueMicrotask', + 'structuredClone', + 'MessageChannel', + 'MessagePort', + 'MessageEvent', + 'clearInterval', + 'clearTimeout', + 'setInterval', + 'setTimeout', + 'atob', + 'btoa', + 'Blob', + 'Performance', + 'performance', + 'fetch', + 'FormData', + 'Headers', + 'Request', + 'Response', +]); + +const nodeGlobals = new Set([ + 'process', + 'global', + 'Buffer', + 'clearImmediate', + 'setImmediate', +]); + +module.exports = { + intrinsics, + webIdlExposedWildcard, + webIdlExposedWindow, + nodeGlobals, +}; diff --git a/test/parallel/test-shadow-realm-gc.js b/test/parallel/test-shadow-realm-gc.js new file mode 100644 index 00000000000000..b640c6b8be3f1a --- /dev/null +++ b/test/parallel/test-shadow-realm-gc.js @@ -0,0 +1,12 @@ +// Flags: --experimental-shadow-realm --max-old-space-size=20 +'use strict'; + +/** + * Verifying ShadowRealm instances can be correctly garbage collected. + */ + +require('../common'); + +for (let i = 0; i < 1000; i++) { + new ShadowRealm(); +} diff --git a/test/parallel/test-shadow-realm-globals.js b/test/parallel/test-shadow-realm-globals.js new file mode 100644 index 00000000000000..8e0107597985ad --- /dev/null +++ b/test/parallel/test-shadow-realm-globals.js @@ -0,0 +1,27 @@ +// Flags: --experimental-shadow-realm +'use strict'; + +require('../common'); +const { intrinsics, webIdlExposedWildcard } = require('../common/globals'); +const assert = require('assert'); + +// Validates APIs exposed on the ShadowRealm globalThis. +const shadowRealm = new ShadowRealm(); +const itemsStr = shadowRealm.evaluate(` +(() => { + return Object.getOwnPropertyNames(globalThis).join(','); +})(); +`); +const items = itemsStr.split(','); +const leaks = []; +for (const item of items) { + if (intrinsics.has(item)) { + continue; + } + if (webIdlExposedWildcard.has(item)) { + continue; + } + leaks.push(item); +} + +assert.deepStrictEqual(leaks, []); diff --git a/test/pummel/test-heapdump-env.js b/test/pummel/test-heapdump-env.js index b6d4c3df280396..4c121b095fdb0c 100644 --- a/test/pummel/test-heapdump-env.js +++ b/test/pummel/test-heapdump-env.js @@ -17,7 +17,7 @@ validateSnapshotNodes('Node / Environment', [{ children: [ { node_name: 'Node / CleanupQueue', edge_name: 'cleanup_queue' }, { node_name: 'Node / IsolateData', edge_name: 'isolate_data' }, - { node_name: 'Node / Realm', edge_name: 'principal_realm' }, + { node_name: 'Node / PrincipalRealm', edge_name: 'principal_realm' }, ], }]); @@ -32,7 +32,7 @@ validateSnapshotNodes('Node / CleanupQueue', [ }, ]); -validateSnapshotNodes('Node / Realm', [{ +validateSnapshotNodes('Node / PrincipalRealm', [{ children: [ { node_name: 'process', edge_name: 'process_object' }, ],