From b5bf1ac55cdc8b89324a008d4142f019f326b406 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Wed, 8 Feb 2023 15:51:10 +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. --- lib/internal/bootstrap/browser.js | 145 ------------------ .../bootstrap/web/exposed-wildcard.js | 89 +++++++++++ lib/internal/bootstrap/web/exposed-window.js | 57 +++++++ lib/internal/process/pre_execution.js | 4 +- lib/internal/url.js | 77 ++++++---- lib/internal/util.js | 23 +++ src/api/environment.cc | 13 +- src/env-inl.h | 3 + src/env.cc | 26 +++- src/env.h | 6 + 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 | 45 ++++-- src/node_realm-inl.h | 21 ++- src/node_realm.cc | 23 ++- src/node_realm.h | 23 ++- src/node_shadow_realm.cc | 57 ++++++- src/node_shadow_realm.h | 15 ++ src/util.h | 11 ++ test/common/globals.js | 141 +++++++++++++++++ test/parallel/test-shadow-realm-gc.js | 12 ++ test/parallel/test-shadow-realm-globals.js | 27 ++++ 26 files changed, 669 insertions(+), 260 deletions(-) delete mode 100644 lib/internal/bootstrap/browser.js create mode 100644 lib/internal/bootstrap/web/exposed-wildcard.js create mode 100644 lib/internal/bootstrap/web/exposed-window.js 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/lib/internal/bootstrap/browser.js b/lib/internal/bootstrap/browser.js deleted file mode 100644 index b94ac9891a399c..00000000000000 --- a/lib/internal/bootstrap/browser.js +++ /dev/null @@ -1,145 +0,0 @@ -'use strict'; - -const { - ObjectDefineProperty, - globalThis, -} = primordials; - -const { - defineOperation, - exposeInterface, - lazyDOMExceptionClass, - defineLazyProperties, - defineReplaceableLazyAttribute, - exposeLazyInterfaces, -} = require('internal/util'); -const config = internalBinding('config'); - -// https://console.spec.whatwg.org/#console-namespace -exposeNamespace(globalThis, 'console', - createGlobalConsole()); - -const { URL, URLSearchParams } = require('internal/url'); -// https://url.spec.whatwg.org/#url -exposeInterface(globalThis, 'URL', URL); -// https://url.spec.whatwg.org/#urlsearchparams -exposeInterface(globalThis, 'URLSearchParams', URLSearchParams); -exposeGetterAndSetter(globalThis, - 'DOMException', - lazyDOMExceptionClass, - (value) => { - exposeInterface(globalThis, 'DOMException', value); - }); - -// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope -const timers = require('timers'); -defineOperation(globalThis, 'clearInterval', timers.clearInterval); -defineOperation(globalThis, 'clearTimeout', timers.clearTimeout); -defineOperation(globalThis, 'setInterval', timers.setInterval); -defineOperation(globalThis, 'setTimeout', timers.setTimeout); - -// Lazy ones. -exposeLazyInterfaces(globalThis, 'internal/abort_controller', [ - 'AbortController', 'AbortSignal', -]); -const { - EventTarget, Event, -} = require('internal/event_target'); -exposeInterface(globalThis, 'Event', Event); -exposeInterface(globalThis, 'EventTarget', EventTarget); -exposeLazyInterfaces(globalThis, 'internal/worker/io', [ - 'MessageChannel', 'MessagePort', 'MessageEvent', -]); -defineLazyProperties(globalThis, 'buffer', ['atob', 'btoa']); -// https://www.w3.org/TR/FileAPI/#dfn-Blob -exposeLazyInterfaces(globalThis, 'internal/blob', ['Blob']); -// https://www.w3.org/TR/hr-time-2/#the-performance-attribute -exposeLazyInterfaces(globalThis, 'perf_hooks', [ - 'Performance', 'PerformanceEntry', 'PerformanceMark', 'PerformanceMeasure', - 'PerformanceObserver', 'PerformanceObserverEntryList', 'PerformanceResourceTiming', -]); - -defineReplaceableLazyAttribute(globalThis, 'perf_hooks', ['performance']); - -// https://encoding.spec.whatwg.org/#textencoder -// https://encoding.spec.whatwg.org/#textdecoder -exposeLazyInterfaces(globalThis, - 'internal/encoding', - ['TextEncoder', 'TextDecoder']); - -function createGlobalConsole() { - const consoleFromNode = - require('internal/console/global'); - if (config.hasInspector) { - const inspector = require('internal/util/inspector'); - // TODO(joyeecheung): postpone this until the first time inspector - // is activated. - inspector.wrapConsole(consoleFromNode); - const { setConsoleExtensionInstaller } = internalBinding('inspector'); - // Setup inspector command line API. - setConsoleExtensionInstaller(inspector.installConsoleExtensions); - } - return consoleFromNode; -} - -// https://heycam.github.io/webidl/#es-namespaces -function exposeNamespace(target, name, namespaceObject) { - ObjectDefineProperty(target, name, { - __proto__: null, - writable: true, - enumerable: false, - configurable: true, - value: namespaceObject - }); -} - -function exposeGetterAndSetter(target, name, getter, setter = undefined) { - ObjectDefineProperty(target, name, { - __proto__: null, - enumerable: false, - configurable: true, - get: getter, - set: setter, - }); -} - -// Web Streams API -exposeLazyInterfaces( - globalThis, - 'internal/webstreams/transformstream', - ['TransformStream', 'TransformStreamDefaultController']); - -exposeLazyInterfaces( - globalThis, - 'internal/webstreams/writablestream', - ['WritableStream', 'WritableStreamDefaultController', 'WritableStreamDefaultWriter']); - -exposeLazyInterfaces( - globalThis, - 'internal/webstreams/readablestream', - [ - 'ReadableStream', 'ReadableStreamDefaultReader', - 'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest', - 'ReadableByteStreamController', 'ReadableStreamDefaultController', - ]); - -exposeLazyInterfaces( - globalThis, - 'internal/webstreams/queuingstrategies', - [ - 'ByteLengthQueuingStrategy', 'CountQueuingStrategy', - ]); - -exposeLazyInterfaces( - globalThis, - 'internal/webstreams/encoding', - [ - 'TextEncoderStream', 'TextDecoderStream', - ]); - -exposeLazyInterfaces( - globalThis, - 'internal/webstreams/compression', - [ - 'CompressionStream', 'DecompressionStream', - ]); diff --git a/lib/internal/bootstrap/web/exposed-wildcard.js b/lib/internal/bootstrap/web/exposed-wildcard.js new file mode 100644 index 00000000000000..e8287d0488f5f0 --- /dev/null +++ b/lib/internal/bootstrap/web/exposed-wildcard.js @@ -0,0 +1,89 @@ +'use strict'; + +/** + * This file exposes web interfaces that is defined with the WebIDL + * [Exposed=*] extended attribute. + * See more details at https://webidl.spec.whatwg.org/#Exposed. + */ + +const { + globalThis, +} = primordials; + +const { + exposeInterface, + lazyDOMExceptionClass, + exposeLazyInterfaces, + exposeGetterAndSetter, +} = require('internal/util'); + +const { URL, URLSearchParams } = require('internal/url'); +// https://url.spec.whatwg.org/#url +exposeInterface(globalThis, 'URL', URL); +// https://url.spec.whatwg.org/#urlsearchparams +exposeInterface(globalThis, 'URLSearchParams', URLSearchParams); +exposeGetterAndSetter(globalThis, + 'DOMException', + lazyDOMExceptionClass, + (value) => { + exposeInterface(globalThis, 'DOMException', value); + }); + +// https://dom.spec.whatwg.org/#interface-abortcontroller +// Lazy ones. +exposeLazyInterfaces(globalThis, 'internal/abort_controller', [ + 'AbortController', 'AbortSignal', +]); +// https://dom.spec.whatwg.org/#interface-eventtarget +const { + EventTarget, Event, +} = require('internal/event_target'); +exposeInterface(globalThis, 'Event', Event); +exposeInterface(globalThis, 'EventTarget', EventTarget); + +// https://encoding.spec.whatwg.org/#textencoder +// https://encoding.spec.whatwg.org/#textdecoder +exposeLazyInterfaces(globalThis, + 'internal/encoding', + ['TextEncoder', 'TextDecoder']); + +// Web Streams API +exposeLazyInterfaces( + globalThis, + 'internal/webstreams/transformstream', + ['TransformStream', 'TransformStreamDefaultController']); + +exposeLazyInterfaces( + globalThis, + 'internal/webstreams/writablestream', + ['WritableStream', 'WritableStreamDefaultController', 'WritableStreamDefaultWriter']); + +exposeLazyInterfaces( + globalThis, + 'internal/webstreams/readablestream', + [ + 'ReadableStream', 'ReadableStreamDefaultReader', + 'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest', + 'ReadableByteStreamController', 'ReadableStreamDefaultController', + ]); + +exposeLazyInterfaces( + globalThis, + 'internal/webstreams/queuingstrategies', + [ + 'ByteLengthQueuingStrategy', 'CountQueuingStrategy', + ]); + +exposeLazyInterfaces( + globalThis, + 'internal/webstreams/encoding', + [ + 'TextEncoderStream', 'TextDecoderStream', + ]); + +exposeLazyInterfaces( + globalThis, + 'internal/webstreams/compression', + [ + 'CompressionStream', 'DecompressionStream', + ]); diff --git a/lib/internal/bootstrap/web/exposed-window.js b/lib/internal/bootstrap/web/exposed-window.js new file mode 100644 index 00000000000000..0a8c8c6cc924e1 --- /dev/null +++ b/lib/internal/bootstrap/web/exposed-window.js @@ -0,0 +1,57 @@ +'use strict'; + +const { + globalThis, +} = primordials; + +const { + defineOperation, + defineLazyProperties, + defineReplaceableLazyAttribute, + exposeLazyInterfaces, + exposeNamespace, +} = require('internal/util'); +const config = internalBinding('config'); + +// https://console.spec.whatwg.org/#console-namespace +exposeNamespace(globalThis, 'console', + createGlobalConsole()); + +// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope +const timers = require('timers'); +defineOperation(globalThis, 'clearInterval', timers.clearInterval); +defineOperation(globalThis, 'clearTimeout', timers.clearTimeout); +defineOperation(globalThis, 'setInterval', timers.setInterval); +defineOperation(globalThis, 'setTimeout', timers.setTimeout); + +exposeLazyInterfaces(globalThis, 'internal/worker/io', [ + 'MessageChannel', 'MessagePort', 'MessageEvent', +]); +defineLazyProperties(globalThis, 'buffer', ['atob', 'btoa']); +// https://www.w3.org/TR/FileAPI/#dfn-Blob +exposeLazyInterfaces(globalThis, 'internal/blob', ['Blob']); +// https://www.w3.org/TR/hr-time-2/#the-performance-attribute +exposeLazyInterfaces(globalThis, 'perf_hooks', [ + 'Performance', 'PerformanceEntry', 'PerformanceMark', 'PerformanceMeasure', + 'PerformanceObserver', 'PerformanceObserverEntryList', 'PerformanceResourceTiming', +]); + +defineReplaceableLazyAttribute(globalThis, 'perf_hooks', ['performance']); + +const { installObjectURLMethods } = require('internal/url'); +installObjectURLMethods(); + +function createGlobalConsole() { + const consoleFromNode = + require('internal/console/global'); + if (config.hasInspector) { + const inspector = require('internal/util/inspector'); + // TODO(joyeecheung): postpone this until the first time inspector + // is activated. + inspector.wrapConsole(consoleFromNode); + const { setConsoleExtensionInstaller } = internalBinding('inspector'); + // Setup inspector command line API. + setConsoleExtensionInstaller(inspector.installConsoleExtensions); + } + return consoleFromNode; +} diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index ebc699ef1de87a..3e599b39403e4a 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -262,7 +262,7 @@ function setupFetch() { }); } -// TODO(aduh95): move this to internal/bootstrap/browser when the CLI flag is +// TODO(aduh95): move this to internal/bootstrap/web/* when the CLI flag is // removed. function setupWebCrypto() { if (process.config.variables.node_no_browser_globals || @@ -310,7 +310,7 @@ function setupCodeCoverage() { } } -// TODO(daeyeon): move this to internal/bootstrap/browser when the CLI flag is +// TODO(daeyeon): move this to internal/bootstrap/web/* when the CLI flag is // removed. function setupCustomEvent() { if (process.config.variables.node_no_browser_globals || diff --git a/lib/internal/url.js b/lib/internal/url.js index 23340a3e5c1a01..4474a8c6e92553 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -92,11 +92,6 @@ const { updateUrl, } = internalBinding('url'); -const { - storeDataObject, - revokeDataObject, -} = internalBinding('blob'); - const FORWARD_SLASH = /\//g; const context = Symbol('context'); @@ -799,8 +794,34 @@ class URL { throw new ERR_INVALID_THIS('URL'); return this[context].href; } +} - static createObjectURL(obj) { +ObjectDefineProperties(URL.prototype, { + [kFormat]: { __proto__: null, configurable: false, writable: false }, + [SymbolToStringTag]: { __proto__: null, configurable: true, value: 'URL' }, + toString: kEnumerableProperty, + href: kEnumerableProperty, + origin: kEnumerableProperty, + protocol: kEnumerableProperty, + username: kEnumerableProperty, + password: kEnumerableProperty, + host: kEnumerableProperty, + hostname: kEnumerableProperty, + port: kEnumerableProperty, + pathname: kEnumerableProperty, + search: kEnumerableProperty, + searchParams: kEnumerableProperty, + hash: kEnumerableProperty, + toJSON: kEnumerableProperty, +}); + +function installObjectURLMethods() { + const { + storeDataObject, + revokeDataObject, + } = internalBinding('blob'); + + function createObjectURL(obj) { const cryptoRandom = lazyCryptoRandom(); if (cryptoRandom === undefined) throw new ERR_NO_CRYPTO(); @@ -816,7 +837,7 @@ class URL { return `blob:nodedata:${id}`; } - static revokeObjectURL(url) { + function revokeObjectURL(url) { url = `${url}`; try { // TODO(@anonrig): Remove this try/catch by calling `parse` directly. @@ -828,31 +849,24 @@ class URL { // If there's an error, it's ignored. } } -} - -ObjectDefineProperties(URL.prototype, { - [kFormat]: { __proto__: null, configurable: false, writable: false }, - [SymbolToStringTag]: { __proto__: null, configurable: true, value: 'URL' }, - toString: kEnumerableProperty, - href: kEnumerableProperty, - origin: kEnumerableProperty, - protocol: kEnumerableProperty, - username: kEnumerableProperty, - password: kEnumerableProperty, - host: kEnumerableProperty, - hostname: kEnumerableProperty, - port: kEnumerableProperty, - pathname: kEnumerableProperty, - search: kEnumerableProperty, - searchParams: kEnumerableProperty, - hash: kEnumerableProperty, - toJSON: kEnumerableProperty, -}); -ObjectDefineProperties(URL, { - createObjectURL: kEnumerableProperty, - revokeObjectURL: kEnumerableProperty, -}); + ObjectDefineProperties(URL, { + createObjectURL: { + __proto__: null, + configurable: true, + writable: true, + enumerable: true, + value: createObjectURL, + }, + revokeObjectURL: { + __proto__: null, + configurable: true, + writable: true, + enumerable: true, + value: revokeObjectURL, + }, + }); +} function update(url, params) { if (!url) @@ -1374,6 +1388,7 @@ module.exports = { pathToFileURL, toPathIfFileURL, isURLInstance, + installObjectURLMethods, URL, URLSearchParams, domainToASCII, diff --git a/lib/internal/util.js b/lib/internal/util.js index 5665923a2cd297..3b30f06306e314 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -511,6 +511,27 @@ function exposeInterface(target, name, interfaceObject) { }); } +// https://heycam.github.io/webidl/#es-namespaces +function exposeNamespace(target, name, namespaceObject) { + ObjectDefineProperty(target, name, { + __proto__: null, + writable: true, + enumerable: false, + configurable: true, + value: namespaceObject + }); +} + +function exposeGetterAndSetter(target, name, getter, setter = undefined) { + ObjectDefineProperty(target, name, { + __proto__: null, + enumerable: false, + configurable: true, + get: getter, + set: setter, + }); +} + function defineLazyProperties(target, id, keys, enumerable = true) { const descriptors = { __proto__: null }; let mod; @@ -750,6 +771,8 @@ module.exports = { emitExperimentalWarning, exposeInterface, exposeLazyInterfaces, + exposeNamespace, + exposeGetterAndSetter, filterDuplicateStrings, filterOwnProperties, getConstructorOf, diff --git a/src/api/environment.cc b/src/api/environment.cc index 3d3f864d4e956c..9941d55e4e63d3 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -67,6 +67,15 @@ 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 as the context's security token is + // likely to be different. + 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 +90,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 d84d28c7065822..211e046ff94866 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -789,6 +789,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 { \ @@ -797,7 +798,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 e315e41cedff0b..0f585d03a0a2b7 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_, \ @@ -760,8 +779,11 @@ Environment::Environment(IsolateData* isolate_data, void Environment::InitializeMainContext(Local context, const EnvSerializeInfo* env_info) { - principal_realm_ = std::make_unique( - this, context, MAYBE_FIELD_PTR(env_info, principal_realm)); + principal_realm_ = + std::make_unique(this, + context, + Realm::kPrincipal, + MAYBE_FIELD_PTR(env_info, principal_realm)); AssignToContext(context, principal_realm_.get(), ContextInfo("")); if (env_info != nullptr) { DeserializeProperties(env_info); diff --git a/src/env.h b/src/env.h index 677c03d952d04e..402157e8f7bffe 100644 --- a/src/env.h +++ b/src/env.h @@ -146,12 +146,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 @@ -183,6 +186,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) \ @@ -191,9 +195,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 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 db607ea298edf5..af1091d9b3b634 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 b144b95ab41a25..6cd7afaadb5c20 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1254,11 +1254,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 edc578d1b89ea7..1a723481015c5a 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -28,6 +28,7 @@ using v8::Local; using v8::MaybeLocal; using v8::Number; using v8::Object; +using v8::ObjectTemplate; using v8::PropertyAttribute; using v8::ReadOnly; using v8::String; @@ -96,7 +97,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) @@ -104,9 +108,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 @@ -281,15 +287,23 @@ 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) { + Local proto = target->PrototypeTemplate(); + + HistogramBase::Initialize(isolate_data, proto); +} + +void CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); PerformanceState* state = env->performance_state(); @@ -362,12 +376,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) { @@ -387,6 +397,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..c082e448f0622d 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,18 +119,31 @@ int64_t Realm::base_object_count() const { return base_object_count_; } +// 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 so that they are kept alive when the context is still +// reachable. #define V(PropertyName, TypeName) \ inline v8::Local Realm::PropertyName() const { \ - return PersistentToLocal::Strong(PropertyName##_); \ + return PersistentToLocal::StrongUnChecked(PropertyName##_); \ } \ inline void Realm::set_##PropertyName(v8::Local value) { \ PropertyName##_.Reset(isolate(), value); \ + if (!value.IsEmpty()) { \ + PropertyName##_.SetWeak(); \ + v8::Local ctx = context(); \ + ctx->Global() \ + ->SetPrivate(ctx, isolate_data()->per_realm_##PropertyName(), value) \ + .ToChecked(); \ + } \ } PER_REALM_STRONG_PERSISTENT_VALUES(V) #undef V +// Sub-realms' can set the context persistent handle as weak. v8::Local Realm::context() const { - return PersistentToLocal::Strong(context_); + return PersistentToLocal::Default(isolate_, context_); } void Realm::AddCleanupHook(CleanupQueue::Callback fn, void* arg) { diff --git a/src/node_realm.cc b/src/node_realm.cc index 187323397bc733..6c2b1c366d393a 100644 --- a/src/node_realm.cc +++ b/src/node_realm.cc @@ -21,8 +21,9 @@ using v8::Value; Realm::Realm(Environment* env, v8::Local context, + Kind kind, const RealmSerializeInfo* realm_info) - : env_(env), isolate_(context->GetIsolate()) { + : env_(env), isolate_(context->GetIsolate()), kind_(kind) { context_.Reset(isolate_, context); // Create properties if not deserializing from snapshot. @@ -214,7 +215,7 @@ MaybeLocal Realm::BootstrapInternalLoaders() { return scope.Escape(loader_exports); } -MaybeLocal Realm::BootstrapNode() { +MaybeLocal Realm::BootstrapRealm() { EscapableHandleScope scope(isolate_); MaybeLocal result = ExecuteBootstrapper("internal/bootstrap/node"); @@ -224,7 +225,13 @@ MaybeLocal Realm::BootstrapNode() { } if (!env_->no_browser_globals()) { - result = ExecuteBootstrapper("internal/bootstrap/browser"); + result = ExecuteBootstrapper("internal/bootstrap/web/exposed-wildcard"); + + if (result.IsEmpty()) { + return MaybeLocal(); + } + + result = ExecuteBootstrapper("internal/bootstrap/web/exposed-window"); if (result.IsEmpty()) { return MaybeLocal(); @@ -272,7 +279,7 @@ MaybeLocal Realm::RunBootstrapping() { } Local result; - if (!BootstrapNode().ToLocal(&result)) { + if (!BootstrapRealm().ToLocal(&result)) { return MaybeLocal(); } @@ -289,8 +296,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; @@ -317,7 +326,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()); } diff --git a/src/node_realm.h b/src/node_realm.h index 04129eec47d551..cc61c42da18185 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( @@ -52,6 +57,7 @@ class Realm : public MemoryRetainer { Realm(Environment* env, v8::Local context, + Kind kind, const RealmSerializeInfo* realm_info); ~Realm(); @@ -69,8 +75,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,6 +91,7 @@ class Realm : public MemoryRetainer { inline IsolateData* isolate_data() const; inline Environment* env() const; inline v8::Isolate* isolate() const; + inline Kind kind() const; inline v8::Local context() const; inline bool has_run_bootstrapping_code() const; @@ -127,15 +132,21 @@ 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: + v8::MaybeLocal BootstrapInternalLoaders(); + virtual v8::MaybeLocal BootstrapRealm(); Environment* env_; // Shorthand for isolate pointer. v8::Isolate* isolate_; v8::Global context_; + + 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; diff --git a/src/node_shadow_realm.cc b/src/node_shadow_realm.cc index 2ccd62edc315d7..b24d8d5a4decb5 100644 --- a/src/node_shadow_realm.cc +++ b/src/node_shadow_realm.cc @@ -1,15 +1,70 @@ #include "node_shadow_realm.h" +#include "env-inl.h" namespace node { namespace shadow_realm { using v8::Context; +using v8::EscapableHandleScope; 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, nullptr) { + context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); +} + +ShadowRealm::~ShadowRealm() { + while (HasCleanupHooks()) { + RunCleanup(); + } +} + +v8::MaybeLocal ShadowRealm::BootstrapRealm() { + EscapableHandleScope scope(isolate_); + MaybeLocal result; + + // 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..37bd9928914193 100644 --- a/src/node_shadow_realm.h +++ b/src/node_shadow_realm.h @@ -3,11 +3,26 @@ #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); + + 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/src/util.h b/src/util.h index 9be4aef5686d3a..ceefa1269cc90b 100644 --- a/src/util.h +++ b/src/util.h @@ -826,6 +826,17 @@ class PersistentToLocal { static inline v8::Local Strong( const v8::PersistentBase& persistent) { DCHECK(!persistent.IsWeak()); + return StrongUnChecked(persistent); + } + + // Unchecked conversion from a non-weak Persistent to Local, + // use with care! + // + // Do not call persistent.Reset() while the returned Local is still in + // scope, it will destroy the reference to the object. + template + static inline v8::Local StrongUnChecked( + const v8::PersistentBase& persistent) { return *reinterpret_cast*>( const_cast*>(&persistent)); } diff --git a/test/common/globals.js b/test/common/globals.js new file mode 100644 index 00000000000000..f905c7503738e1 --- /dev/null +++ b/test/common/globals.js @@ -0,0 +1,141 @@ +'use strict'; + +const intrinsics = [ + '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.push('gc'); +} + +// v8 exposes console in the global scope. +intrinsics.push('console'); + +const webIdlExposedWildcard = [ + '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 = [ + 'console', + 'BroadcastChannel', + 'queueMicrotask', + 'structuredClone', + 'MessageChannel', + 'MessagePort', + 'MessageEvent', + 'clearInterval', + 'clearTimeout', + 'setInterval', + 'setTimeout', + 'atob', + 'btoa', + 'Blob', + 'Performance', + 'performance', + 'fetch', + 'FormData', + 'Headers', + 'Request', + 'Response', +]; + +const nodeGlobals = [ + '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..c51810e9287c39 --- /dev/null +++ b/test/parallel/test-shadow-realm-gc.js @@ -0,0 +1,12 @@ +// Flags: --experimental-shadow-realm --max-old-space-size=10 +'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..bf828359f86208 --- /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.indexOf(item) >= 0) { + continue; + } + if (webIdlExposedWildcard.indexOf(item) >= 0) { + continue; + } + leaks.push(item); +} + +assert.deepStrictEqual(leaks, []);