From 8d3d1040d1159a582eaafdf5d63d7c5acdaab73f Mon Sep 17 00:00:00 2001 From: Dawood Date: Mon, 20 Jul 2026 12:08:35 -0400 Subject: [PATCH 1/2] Fix launch JavaScript function execution --- .changeset/clean-dogs-launch.md | 8 +++++ js/app/src/routes/[...catchall]/+page.svelte | 10 ------ js/core/src/Blocks.svelte | 11 +++++- js/core/src/custom_js.test.ts | 36 ++++++++++++++++++++ js/core/src/custom_js.ts | 16 +++++++++ js/spa/src/Index.svelte | 9 ----- 6 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 .changeset/clean-dogs-launch.md create mode 100644 js/core/src/custom_js.test.ts create mode 100644 js/core/src/custom_js.ts diff --git a/.changeset/clean-dogs-launch.md b/.changeset/clean-dogs-launch.md new file mode 100644 index 00000000000..0a3027e0dd0 --- /dev/null +++ b/.changeset/clean-dogs-launch.md @@ -0,0 +1,8 @@ +--- +"@gradio/core": patch +"@self/app": patch +"@self/spa": patch +"gradio": patch +--- + +fix: Execute function strings passed to `Blocks.launch(js=...)` diff --git a/js/app/src/routes/[...catchall]/+page.svelte b/js/app/src/routes/[...catchall]/+page.svelte index 45b3b977868..de0a74a8995 100644 --- a/js/app/src/routes/[...catchall]/+page.svelte +++ b/js/app/src/routes/[...catchall]/+page.svelte @@ -301,16 +301,6 @@ await add_custom_html_head(config.head); - if (config.js) { - try { - const script = document.createElement("script"); - script.textContent = config.js; - document.head.appendChild(script); - } catch (e) { - console.error("Error executing custom JS:", e); - } - } - dispatch("loaded"); if (config.dev_mode) { setTimeout(() => { diff --git a/js/core/src/Blocks.svelte b/js/core/src/Blocks.svelte index 4fc4faf66b0..e144dbe88a7 100644 --- a/js/core/src/Blocks.svelte +++ b/js/core/src/Blocks.svelte @@ -17,6 +17,7 @@ import MountComponents from "./MountComponents.svelte"; import { prefix_css } from "./css"; + import { execute_custom_js } from "./custom_js"; import { reactive_formatter } from "./gradio_helper"; import logo from "./images/logo.svg"; @@ -467,7 +468,15 @@ }); res.observe(root_container); - app_tree.ready.then(() => { + app_tree.ready.then(async () => { + if (js) { + try { + await execute_custom_js(js); + } catch (e) { + console.error("Error executing custom JS:", e); + } + } + ready = true; dep_manager.dispatch_load_events(); }); diff --git a/js/core/src/custom_js.test.ts b/js/core/src/custom_js.test.ts new file mode 100644 index 00000000000..c177375a1b8 --- /dev/null +++ b/js/core/src/custom_js.test.ts @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, test } from "vitest"; +import { execute_custom_js } from "./custom_js"; + +declare global { + var custom_js_result: string | undefined; +} + +afterEach(() => { + delete globalThis.custom_js_result; +}); + +describe("execute_custom_js", () => { + test("invokes a function expression", async () => { + await execute_custom_js( + "() => { globalThis.custom_js_result = 'function'; }" + ); + + expect(globalThis.custom_js_result).toBe("function"); + }); + + test("awaits an async function expression", async () => { + await execute_custom_js( + "async () => { await Promise.resolve(); globalThis.custom_js_result = 'async'; }" + ); + + expect(globalThis.custom_js_result).toBe("async"); + }); + + test("executes raw JavaScript", async () => { + await execute_custom_js( + "const result = 'raw'; globalThis.custom_js_result = result;" + ); + + expect(globalThis.custom_js_result).toBe("raw"); + }); +}); diff --git a/js/core/src/custom_js.ts b/js/core/src/custom_js.ts new file mode 100644 index 00000000000..2ecf3306ddd --- /dev/null +++ b/js/core/src/custom_js.ts @@ -0,0 +1,16 @@ +const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; + +export async function execute_custom_js(js: string): Promise { + let custom_js: () => Promise; + + try { + custom_js = new AsyncFunction(`return (${js});`); + } catch { + custom_js = new AsyncFunction(js); + } + + const result = await custom_js(); + if (typeof result === "function") { + await result(); + } +} diff --git a/js/spa/src/Index.svelte b/js/spa/src/Index.svelte index c1c31398ffe..da27ed69d99 100644 --- a/js/spa/src/Index.svelte +++ b/js/spa/src/Index.svelte @@ -387,15 +387,6 @@ if (config.deep_link_state === "invalid") { pending_deep_link_error = true; } - if (config.js) { - try { - const script = document.createElement("script"); - script.textContent = config.js; - document.head.appendChild(script); - } catch (e) { - console.error("Error executing custom JS:", e); - } - } if (config.dev_mode) { setTimeout(() => { const { host } = new URL(api_url); From 4acd2f60d9fd674323a7177e8774a75dfb0ea338 Mon Sep 17 00:00:00 2001 From: Dawood Date: Tue, 21 Jul 2026 14:21:47 -0400 Subject: [PATCH 2/2] Reuse shared async function constructor --- js/core/src/custom_js.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/core/src/custom_js.ts b/js/core/src/custom_js.ts index 2ecf3306ddd..ee13723ce0b 100644 --- a/js/core/src/custom_js.ts +++ b/js/core/src/custom_js.ts @@ -1,4 +1,4 @@ -const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; +import { AsyncFunction } from "./init_utils"; export async function execute_custom_js(js: string): Promise { let custom_js: () => Promise;