diff --git a/src/jsc/JSValue.rs b/src/jsc/JSValue.rs index e41e0015940c..719f579d8cc3 100644 --- a/src/jsc/JSValue.rs +++ b/src/jsc/JSValue.rs @@ -1071,6 +1071,10 @@ impl JSValue { self.to_zig_string(&mut out, global)?; Ok(out) } + /// `JSValue::toThis` in strict mode (scope objects become `undefined`). + pub fn to_this_strict(self, global: &JSGlobalObject) -> JSValue { + crate::cpp::Bun__JSValue__toThisStrict(self, global) + } } // ────────────────────────────────────────────────────────────────────────── diff --git a/src/jsc/bindings/BunPlugin.cpp b/src/jsc/bindings/BunPlugin.cpp index 4e16d5246f37..df17049cd87f 100644 --- a/src/jsc/bindings/BunPlugin.cpp +++ b/src/jsc/bindings/BunPlugin.cpp @@ -99,7 +99,7 @@ static JSC::EncodedJSValue jsFunctionAppendOnLoadPluginBody(JSC::JSGlobalObject* plugin.append(vm, filter->regExp(), func.getObject(), namespaceString); callback(ctx, globalObject); - return JSValue::encode(callframe->thisValue()); + return JSValue::encode(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict())); } static EncodedJSValue jsFunctionAppendVirtualModulePluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe) @@ -167,7 +167,7 @@ static EncodedJSValue jsFunctionAppendVirtualModulePluginBody(JSC::JSGlobalObjec moduleLoader->removeEntry(idIdent); } - return JSValue::encode(callframe->thisValue()); + return JSValue::encode(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict())); } static JSC::EncodedJSValue jsFunctionAppendOnResolvePluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, BunPluginTarget target, BunPlugin::Base& plugin, void* ctx, OnAppendPluginCallback callback) @@ -223,7 +223,7 @@ static JSC::EncodedJSValue jsFunctionAppendOnResolvePluginBody(JSC::JSGlobalObje plugin.append(vm, filter->regExp(), uncheckedDowncast(func), namespaceString); callback(ctx, globalObject); - return JSValue::encode(callframe->thisValue()); + return JSValue::encode(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict())); } static JSC::EncodedJSValue jsFunctionAppendOnResolvePluginGlobal(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, BunPluginTarget target) diff --git a/src/jsc/bindings/JSMockFunction.cpp b/src/jsc/bindings/JSMockFunction.cpp index d9d2fdecaa05..c9a5d08af24c 100644 --- a/src/jsc/bindings/JSMockFunction.cpp +++ b/src/jsc/bindings/JSMockFunction.cpp @@ -1439,7 +1439,7 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSetSystemTime, (JSC::JSGlobalObject * globalO // from this value instead of the activation-time clock. Bun__FakeTimers__setSystemTime(ms); - return JSValue::encode(callframe->thisValue()); + return JSValue::encode(callframe->thisValue().toThis(globalObject, ECMAMode::strict())); } BUN_DEFINE_HOST_FUNCTION(JSMock__jsRestoreAllMocks, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe)) diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index 248c256d432a..c9fcd33ee84f 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -6852,6 +6852,11 @@ CPP_DECL [[ZIG_EXPORT(nothrow)]] JSC::EncodedJSValue Bun__JSBoundFunction__bound return JSC::JSValue::encode(boundFunction->boundThis()); } +CPP_DECL [[ZIG_EXPORT(nothrow)]] JSC::EncodedJSValue Bun__JSValue__toThisStrict(JSC::EncodedJSValue value, JSC::JSGlobalObject* globalObject) +{ + return JSC::JSValue::encode(JSC::JSValue::decode(value).toThis(globalObject, JSC::ECMAMode::strict())); +} + CPP_DECL [[ZIG_EXPORT(check_slow)]] void Bun__JSValue__setPrototypeDirect(JSC::EncodedJSValue valueEncoded, JSC::EncodedJSValue prototypeEncoded, JSC::JSGlobalObject* globalObject) { auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); diff --git a/src/jsc/bindings/webcore/JSDOMURL.cpp b/src/jsc/bindings/webcore/JSDOMURL.cpp index b436cb5c4298..1e100d4d7680 100644 --- a/src/jsc/bindings/webcore/JSDOMURL.cpp +++ b/src/jsc/bindings/webcore/JSDOMURL.cpp @@ -759,7 +759,7 @@ JSC_DEFINE_HOST_FUNCTION(jsDOMURLPrototypeFunction_inspectCustom, (JSGlobalObjec { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/JSURLSearchParams.cpp b/src/jsc/bindings/webcore/JSURLSearchParams.cpp index c504e768f653..9c0c84bfeb79 100644 --- a/src/jsc/bindings/webcore/JSURLSearchParams.cpp +++ b/src/jsc/bindings/webcore/JSURLSearchParams.cpp @@ -198,7 +198,7 @@ JSC_DEFINE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_inspectCustom, (JSGl { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSByteLengthQueuingStrategy.cpp b/src/jsc/bindings/webcore/streams/JSByteLengthQueuingStrategy.cpp index 7a2af0ac784e..371aa41c8163 100644 --- a/src/jsc/bindings/webcore/streams/JSByteLengthQueuingStrategy.cpp +++ b/src/jsc/bindings/webcore/streams/JSByteLengthQueuingStrategy.cpp @@ -165,7 +165,7 @@ JSC_DEFINE_HOST_FUNCTION(jsByteLengthQueuingStrategyPrototype_inspectCustom, (JS { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSCountQueuingStrategy.cpp b/src/jsc/bindings/webcore/streams/JSCountQueuingStrategy.cpp index 24ed41cafed6..507197307b8e 100644 --- a/src/jsc/bindings/webcore/streams/JSCountQueuingStrategy.cpp +++ b/src/jsc/bindings/webcore/streams/JSCountQueuingStrategy.cpp @@ -165,7 +165,7 @@ JSC_DEFINE_HOST_FUNCTION(jsCountQueuingStrategyPrototype_inspectCustom, (JSGloba { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSReadableByteStreamController.cpp b/src/jsc/bindings/webcore/streams/JSReadableByteStreamController.cpp index b76a69bc7467..792607a42f26 100644 --- a/src/jsc/bindings/webcore/streams/JSReadableByteStreamController.cpp +++ b/src/jsc/bindings/webcore/streams/JSReadableByteStreamController.cpp @@ -265,7 +265,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableByteStreamControllerPrototype_inspectCustom, { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSReadableStream.cpp b/src/jsc/bindings/webcore/streams/JSReadableStream.cpp index 852d271cdc71..2527842a6d8c 100644 --- a/src/jsc/bindings/webcore/streams/JSReadableStream.cpp +++ b/src/jsc/bindings/webcore/streams/JSReadableStream.cpp @@ -390,7 +390,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPrototype_inspectCustom, (JSGlobalObjec { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBReader.cpp b/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBReader.cpp index 3b2873a81420..5f91b0bc55a0 100644 --- a/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBReader.cpp +++ b/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBReader.cpp @@ -269,7 +269,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamBYOBReaderPrototype_inspectCustom, (JSG { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBRequest.cpp b/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBRequest.cpp index 7c12238d6793..542d5fcf4f05 100644 --- a/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBRequest.cpp +++ b/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBRequest.cpp @@ -107,7 +107,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamBYOBRequestPrototype_inspectCustom, (JS { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultController.cpp b/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultController.cpp index 7888cd3c419d..f0dd51ec649a 100644 --- a/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultController.cpp +++ b/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultController.cpp @@ -214,7 +214,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototype_inspectCusto { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultReader.cpp b/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultReader.cpp index 7f14710ee5d8..f2cc71a86182 100644 --- a/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultReader.cpp +++ b/src/jsc/bindings/webcore/streams/JSReadableStreamDefaultReader.cpp @@ -574,7 +574,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultReaderPrototype_inspectCustom, ( { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSTransformStream.cpp b/src/jsc/bindings/webcore/streams/JSTransformStream.cpp index 333263b7f610..e027c6908f84 100644 --- a/src/jsc/bindings/webcore/streams/JSTransformStream.cpp +++ b/src/jsc/bindings/webcore/streams/JSTransformStream.cpp @@ -196,7 +196,7 @@ JSC_DEFINE_HOST_FUNCTION(jsTransformStreamPrototype_inspectCustom, (JSGlobalObje { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSTransformStreamDefaultController.cpp b/src/jsc/bindings/webcore/streams/JSTransformStreamDefaultController.cpp index ae9582229d55..084a912715fa 100644 --- a/src/jsc/bindings/webcore/streams/JSTransformStreamDefaultController.cpp +++ b/src/jsc/bindings/webcore/streams/JSTransformStreamDefaultController.cpp @@ -176,7 +176,7 @@ JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerPrototype_inspectCust { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSWritableStream.cpp b/src/jsc/bindings/webcore/streams/JSWritableStream.cpp index 65396a7de002..e317cfe3c766 100644 --- a/src/jsc/bindings/webcore/streams/JSWritableStream.cpp +++ b/src/jsc/bindings/webcore/streams/JSWritableStream.cpp @@ -175,7 +175,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototype_inspectCustom, (JSGlobalObjec { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultController.cpp b/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultController.cpp index a743a00b08e3..c586d2be3ded 100644 --- a/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultController.cpp +++ b/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultController.cpp @@ -222,7 +222,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerPrototype_inspectCusto { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultWriter.cpp b/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultWriter.cpp index 75758b15306e..130e3496981c 100644 --- a/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultWriter.cpp +++ b/src/jsc/bindings/webcore/streams/JSWritableStreamDefaultWriter.cpp @@ -295,7 +295,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterPrototype_inspectCustom, ( { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/jsc/bindings/webcrypto/JSCryptoKey.cpp b/src/jsc/bindings/webcrypto/JSCryptoKey.cpp index 80401f071393..d5387a458696 100644 --- a/src/jsc/bindings/webcrypto/JSCryptoKey.cpp +++ b/src/jsc/bindings/webcrypto/JSCryptoKey.cpp @@ -280,7 +280,7 @@ JSC_DEFINE_HOST_FUNCTION(jsCryptoKeyPrototype_inspectCustom, (JSGlobalObject * l { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = callFrame->thisValue(); + JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict()); auto* thisObject = dynamicDowncast(thisValue); if (!thisObject) [[unlikely]] return JSValue::encode(thisValue); diff --git a/src/runtime/test_runner/timers/FakeTimers.rs b/src/runtime/test_runner/timers/FakeTimers.rs index 8d17f5d714b9..3630bbc89341 100644 --- a/src/runtime/test_runner/timers/FakeTimers.rs +++ b/src/runtime/test_runner/timers/FakeTimers.rs @@ -373,7 +373,7 @@ fn use_fake_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResult JsResult J FakeTimers::execute_next(global)?; - Ok(frame.this()) + Ok(frame.this().to_this_strict(global)) } #[bun_jsc::host_fn] @@ -430,7 +430,7 @@ fn advance_timers_by_time(global: &JSGlobalObject, frame: &CallFrame) -> JsResul CURRENT_TIME.set(global, &target, None); advanced?; - Ok(frame.this()) + Ok(frame.this().to_this_strict(global)) } #[bun_jsc::host_fn] @@ -439,7 +439,7 @@ fn run_only_pending_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResu FakeTimers::execute_only_pending_timers(global)?; - Ok(frame.this()) + Ok(frame.this().to_this_strict(global)) } #[bun_jsc::host_fn] @@ -448,7 +448,7 @@ fn run_all_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResult JsResult { expect(mod).toBe("world"); }); +it("builder methods return the builder only when called as methods", () => { + plugin({ + name: "builder receiver", + setup(builder) { + const options = { filter: /.*/, namespace: "builder-receiver" }; + const virtualModule = () => ({ exports: {}, loader: "object" }) as const; + expect([ + builder.onResolve(options, () => undefined), + builder.onLoad(options, () => undefined), + builder.module("builder-receiver-virtual-module", virtualModule), + ]).toEqual([builder, builder, builder]); + + const { onResolve, onLoad, module: defineModule } = builder; + // Closed-over bindings make JSC pass the scope object as the raw receiver + // of these calls; it must not come back as the return value. + const bare = () => [ + onResolve(options, () => undefined), + onLoad(options, () => undefined), + defineModule("builder-receiver-virtual-module-bare", virtualModule), + ]; + expect(bare()).toEqual([undefined, undefined, undefined]); + }, + }); +}); + it("recursion throws stack overflow", () => { expect(() => { require("recursion:recursion"); diff --git a/test/js/bun/test/test-timers.test.ts b/test/js/bun/test/test-timers.test.ts index 8d40f210634c..d47c3c1859ef 100644 --- a/test/js/bun/test/test-timers.test.ts +++ b/test/js/bun/test/test-timers.test.ts @@ -1,3 +1,4 @@ +import { expect, jest, setSystemTime, test } from "bun:test"; import { bunEnv, bunExe } from "harness"; import path from "node:path"; @@ -77,6 +78,38 @@ test("setSystemTime accepts pre-epoch and epoch times and resets with no argumen } }); +test("chainable timer functions return their receiver only when called as a method", () => { + // Inside `bare`, every callee is a closed-over binding (setSystemTime is a module + // binding), so JSC hands the native function the scope object as its raw receiver. + // None of that may leak back out as a return value. + const { + useFakeTimers, + advanceTimersByTime, + advanceTimersToNextTimer, + runOnlyPendingTimers, + runAllTimers, + clearAllTimers, + useRealTimers, + } = jest; + const bare = () => [ + useFakeTimers(), + advanceTimersByTime(0), + advanceTimersToNextTimer(), + runOnlyPendingTimers(), + runAllTimers(), + clearAllTimers(), + setSystemTime(), + useRealTimers(), + ]; + try { + expect(jest.useFakeTimers()).toBe(jest); + expect(jest.setSystemTime()).toBe(jest); + expect(bare()).toEqual(new Array(8).fill(undefined)); + } finally { + expect(jest.useRealTimers()).toBe(jest); + } +}); + test.each(["'x'", "Symbol()", "1n"])("useFakeTimers does not crash when globalThis.setTimeout is %s", async value => { await using proc = Bun.spawn({ cmd: [ diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index c61020fe0a37..aef21fc6a546 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -494,6 +494,37 @@ it("Bun.inspect.custom exists", () => { expect(Bun.inspect.custom).toBe(util.inspect.custom); }); +it("native inspect.custom functions do not return the scope object of a bare call", () => { + const classes = [ + ByteLengthQueuingStrategy, + CountQueuingStrategy, + CryptoKey, + ReadableByteStreamController, + ReadableStream, + ReadableStreamBYOBReader, + ReadableStreamBYOBRequest, + ReadableStreamDefaultController, + ReadableStreamDefaultReader, + TransformStream, + TransformStreamDefaultController, + URL, + URLSearchParams, + WritableStream, + WritableStreamDefaultController, + WritableStreamDefaultWriter, + ]; + const results = classes.map(klass => { + const inspectCustom = klass.prototype[util.inspect.custom]; + // Closing over the binding makes the bare call below pass a scope object as `this`. + const bare = () => inspectCustom(2, {}); + return [klass.name, typeof inspectCustom, bare()]; + }); + expect(results).toEqual(classes.map(klass => [klass.name, "function", undefined])); + + const inspectURL = URL.prototype[util.inspect.custom]; + expect(inspectURL.call(new URL("http://example.com/"), 2, {})).toContain("http://example.com/"); +}); + describe("Functions with names", () => { const closures = [ () => function f() {},