diff --git a/bun.lock b/bun.lock index 854a2ea9ac0..fdbaca4a389 100644 --- a/bun.lock +++ b/bun.lock @@ -69,7 +69,7 @@ "express": "^5.1.0", "fix-path": "5.0.0", "ghostty-web": "^0.4.0-next.7.g03ead6e", - "happy-dom": "^20.0.10", + "happy-dom": "20.0.11", "ignore": "^7.0.5", "json-schema-to-typescript": "^15.0.4", "jsonc-parser": "^3.3.1", diff --git a/package.json b/package.json index 7a6646864bb..730202b3176 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "express": "^5.1.0", "fix-path": "5.0.0", "ghostty-web": "^0.4.0-next.7.g03ead6e", - "happy-dom": "^20.0.10", + "happy-dom": "20.0.11", "ignore": "^7.0.5", "json-schema-to-typescript": "^15.0.4", "jsonc-parser": "^3.3.1", diff --git a/src/browser/happyDomMutationObserver.test.ts b/src/browser/happyDomMutationObserver.test.ts new file mode 100644 index 00000000000..35710bf68fb --- /dev/null +++ b/src/browser/happyDomMutationObserver.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from "bun:test"; +import { Window } from "happy-dom"; + +/** + * happy-dom 20.0.11 holds each MutationObserver's report callback only through a WeakRef, so after + * a garbage collection the observer stops reporting without an error (a cause of the #5989 + * flake). patches/happy-dom@20.0.11.patch holds it strongly. bun applies that patch only while + * the installed version matches its key, and says nothing when it does not (#6348), so this + * test fails loudly if happy-dom moves to an unpatched version. An upgrade to a version that + * holds the callback upstream (20.11.15 or later) passes without the patch. + */ +test("a happy-dom MutationObserver keeps reporting after a garbage collection", async () => { + const window = new Window(); + const target = window.document.createElement("div"); + const batches: number[] = []; + const observer = new window.MutationObserver((records) => batches.push(records.length)); + observer.observe(target, { childList: true }); + try { + // A WeakRef keeps its target alive until the current job ends, so collect after this one. + await new Promise((resolve) => setTimeout(resolve, 0)); + Bun.gc(true); + target.appendChild(window.document.createElement("span")); + await new Promise((resolve) => setTimeout(resolve, 10)); + expect(batches).toEqual([1]); + } finally { + observer.disconnect(); + await window.happyDOM.close(); + } +});