y
'), + ]).toEqual([ + 'x', + 'y
', + ]); + }); + + // Every open element that called onEndTag() keeps a pending handler, and + // each end tag used to scan all of them: n nested elements cost n^2/2 + // visits (n = 100k, a 700 KB document, took 5.3 s in a release build). + // The scan now stops once the handlers that end tag activated have run. + // The release size cannot finish inside the spawn timeout with the old + // scan and takes well under a second without it. A debug build spends + // ~0.2 ms of JS per element, so it gets a size that only keeps it short. + it("stays linear when every nested element registers onEndTag", async () => { + const n = isDebug ? 40_000 : 400_000; + const fixture = /* js */ ` + const n = ${n}; + const doc = Buffer.alloc(n * 3, "").toString() + "x" + Buffer.alloc(n * 4, "").toString(); + let ends = 0; + const onEnd = () => { ends++; }; + const out = new HTMLRewriter().on("a", { element(el) { el.onEndTag(onEnd); } }).transform(doc); + console.log(JSON.stringify({ ends, same: out === doc })); + `; + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", fixture], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + timeout: 30_000, + killSignal: "SIGKILL", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect({ stdout: stdout.trim(), stderr, exitCode, signal: proc.signalCode }).toEqual({ + stdout: JSON.stringify({ ends: n, same: true }), + stderr: "", + exitCode: 0, + signal: null, + }); + }, 60_000); +}); From a8bb8d8b7274bfa675ce5292a96734b1d9c9c60d Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:51:10 +0000 Subject: [PATCH 2/3] test: smaller debug size, default per-test timeout, only surface error-looking stderr --- test/js/workerd/html-rewriter.test.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/js/workerd/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js index 1013bda40ec8..304a11c9e077 100644 --- a/test/js/workerd/html-rewriter.test.js +++ b/test/js/workerd/html-rewriter.test.js @@ -3345,7 +3345,7 @@ describe("end tag handler dispatch", () => { // scan and takes well under a second without it. A debug build spends // ~0.2 ms of JS per element, so it gets a size that only keeps it short. it("stays linear when every nested element registers onEndTag", async () => { - const n = isDebug ? 40_000 : 400_000; + const n = isDebug ? 10_000 : 400_000; const fixture = /* js */ ` const n = ${n}; const doc = Buffer.alloc(n * 3, "").toString() + "x" + Buffer.alloc(n * 4, "").toString(); @@ -3363,11 +3363,16 @@ describe("end tag handler dispatch", () => { killSignal: "SIGKILL", }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - expect({ stdout: stdout.trim(), stderr, exitCode, signal: proc.signalCode }).toEqual({ + expect({ + stdout: stdout.trim(), + stderr: /error|panic|assert|abort/i.test(stderr) ? stderr : "", + exitCode, + signal: proc.signalCode, + }).toEqual({ stdout: JSON.stringify({ ends: n, same: true }), stderr: "", exitCode: 0, signal: null, }); - }, 60_000); + }); }); From 41fe1d24b03e64ab96607bbe39cecaae6da678ee Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:36:52 +0000 Subject: [PATCH 3/3] ci: retrigger