From cf06a60f2bfab3728a998fe515a4316e14f3e180 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 12 Aug 2026 18:08:35 +0530 Subject: [PATCH] fix(csr): prevent native output recursion --- packages/csr/src/reactive-runtime.ts | 6 ++++++ packages/csr/test/reactive.test.ts | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/csr/src/reactive-runtime.ts b/packages/csr/src/reactive-runtime.ts index 06ef7cd5..0478bdb5 100644 --- a/packages/csr/src/reactive-runtime.ts +++ b/packages/csr/src/reactive-runtime.ts @@ -2512,6 +2512,11 @@ export const REACTIVE_RUNTIME = String.raw` var stmt = attr.value; var listener = function (event) { + // An unbound output falls back to a same-named CustomEvent for + // external consumers. Do not feed that synthetic event back into + // the component's own declarative DOM handler (for example, + // @click="output.click()"), which would recurse indefinitely. + if (event && event.__wrnexusComponentOutput) return; var locals = decodeLoopLocals(node); @@ -4167,6 +4172,7 @@ export const REACTIVE_RUNTIME = String.raw` bubbles: true, detail: detail || {}, }); + event.__wrnexusComponentOutput = true; root.dispatchEvent(event); // Compatibility for applications using the former prefixed contract. root.dispatchEvent(new EventConstructor("wrnexus:" + String(name), { diff --git a/packages/csr/test/reactive.test.ts b/packages/csr/test/reactive.test.ts index f311346c..8c233bc9 100644 --- a/packages/csr/test/reactive.test.ts +++ b/packages/csr/test/reactive.test.ts @@ -781,6 +781,26 @@ test("development runtime allows an output with no parent binding", () => { expect(warnings).toHaveLength(0); }); +test("an unbound native-named output does not re-enter its DOM handler", () => { + const win = mount( + `
` + + `` + + `
`, + ); + const root = win.document.querySelector("[data-wrn-events]") as unknown as HTMLElement; + let outputs = 0; + root.addEventListener("click", (event) => { + if ((event as Event & { __wrnexusComponentOutput?: boolean }).__wrnexusComponentOutput) { + outputs += 1; + } + }); + + expect(() => + (win.document.querySelector("button") as unknown as HTMLElement).click(), + ).not.toThrow(); + expect(outputs).toBe(1); +}); + test("development runtime warns when a component binding names a missing function", () => { const win = new Window() as unknown as Window & Record; win.document.body.innerHTML =