diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index 7e8ddecce2ac..afe4787eb87d 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` to test a branch. * From https://github.com/oven-sh/WebKit releases. */ -export const WEBKIT_VERSION = "2e2aa2290fac856d6f451ceacb58f7f5b44dd057"; +export const WEBKIT_VERSION = "autobuild-preview-pr-590-c7520f66"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/test/js/bun/jsc/shadow.test.js b/test/js/bun/jsc/shadow.test.js index 3fdca5391413..3e25d72eaab5 100644 --- a/test/js/bun/jsc/shadow.test.js +++ b/test/js/bun/jsc/shadow.test.js @@ -8,3 +8,52 @@ it("shadow realm works", () => { expect(globalThis.someValue).toBe(1); expect(result).toBe(2); }); + +// A callable that a wrapped function returns is wrapped for the caller's realm, +// so its prototype is the caller's Function.prototype. The engine uses a +// separate call path for a target that is not a plain function, so check one +// target of each kind. +it.each([ + ["a plain function", `() => () => 1`], + ["a bound function", `(() => () => 1).bind(undefined)`], + ["the realm's Function constructor", `Function`], + ["a callable Proxy", `new Proxy(() => () => 1, {})`], + ["a Proxy with an apply trap", `new Proxy(() => {}, { apply: () => () => 1 })`], +])("a callable returned through %s belongs to the caller's realm", (name, source) => { + const wrapped = new ShadowRealm().evaluate(source); + const returned = wrapped(); + expect(typeof returned).toBe("function"); + expect(Object.getPrototypeOf(returned)).toBe(Function.prototype); +}); + +it("a returned wrapper does not expose the shadow realm's global object", () => { + const realm = new ShadowRealm(); + const made = realm.evaluate(`Function`)("return 1"); + + // The wrapper's prototype is the caller's Function.prototype, so the + // constructor found on it compiles code in the caller's realm. + const constructor = Object.getPrototypeOf(made).constructor; + expect(constructor).toBe(Function); + expect(constructor("return globalThis")()).toBe(globalThis); + + // The realm's global stays out of reach, so the two globals share no state. + globalThis.injectedByShadowTest = { list: [] }; + try { + expect(realm.evaluate(`typeof globalThis.injectedByShadowTest`)).toBe("undefined"); + } finally { + delete globalThis.injectedByShadowTest; + } +}); + +it("a callable returned to the shadow realm belongs to the shadow realm", () => { + const realm = new ShadowRealm(); + const fromRealm = realm.evaluate(`(f) => { + const proto = Object.getPrototypeOf(f()); + if (proto !== Function.prototype) return typeof proto.constructor("return globalThis")().Bun; + return "ok"; + }`); + + expect(fromRealm(new Proxy(() => () => 1, {}))).toBe("ok"); + expect(fromRealm(() => () => 1)).toBe("ok"); + expect(fromRealm(Function)).toBe("ok"); +});