From e00852c526f128e750752df706a5eacca6d03f41 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 14 May 2024 18:38:55 +0200 Subject: [PATCH] fix: [#1336] Adds check for if Window and MutationObserver has been destroyed when triggering listeners --- .../src/mutation-observer/MutationListener.ts | 10 ++++-- .../MutationObserver.test.ts | 35 ++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/happy-dom/src/mutation-observer/MutationListener.ts b/packages/happy-dom/src/mutation-observer/MutationListener.ts index 789300c5d..6a3fdeb0a 100644 --- a/packages/happy-dom/src/mutation-observer/MutationListener.ts +++ b/packages/happy-dom/src/mutation-observer/MutationListener.ts @@ -13,7 +13,7 @@ export default class MutationListener { #window: BrowserWindow; #observer: MutationObserver; #callback: (record: MutationRecord[], observer: MutationObserver) => void; - #records: MutationRecord[] = []; + #records: MutationRecord[] | null = []; #immediate: NodeJS.Immediate | null = null; /** @@ -46,13 +46,19 @@ export default class MutationListener { * @param record Record. */ public report(record: MutationRecord): void { + if (!this.#records) { + return; + } + this.#records.push(record); + if (this.#immediate) { this.#window.cancelAnimationFrame(this.#immediate); } + this.#immediate = this.#window.requestAnimationFrame(() => { const records = this.#records; - if (records.length > 0) { + if (records?.length > 0) { this.#records = []; this.#callback(records, this.#observer); } diff --git a/packages/happy-dom/test/mutation-observer/MutationObserver.test.ts b/packages/happy-dom/test/mutation-observer/MutationObserver.test.ts index 3afc3a02a..095c02dd0 100644 --- a/packages/happy-dom/test/mutation-observer/MutationObserver.test.ts +++ b/packages/happy-dom/test/mutation-observer/MutationObserver.test.ts @@ -307,13 +307,40 @@ describe('MutationObserver', () => { const observer = new MutationObserver((mutationRecords) => { records = mutationRecords; }); - observer.observe(div, { attributes: true }); + const span = document.createElement('span'); + const text = document.createTextNode('old'); - window.close(); + span.appendChild(text); + div.appendChild(span); - div.setAttribute('attr', 'value'); + document.body.appendChild(div); - await new Promise((resolve) => setTimeout(resolve, 1)); + observer.observe(div, { + attributes: true, + childList: true, + subtree: true, + characterData: true, + attributeOldValue: true, + characterDataOldValue: true + }); + + text.textContent = 'new1'; + div.setAttribute('attr', 'value1'); + + await Promise.all([ + window.happyDOM.close(), + (async () => { + text.textContent = 'new2'; + div.setAttribute('attr', 'value2'); + })(), + new Promise((resolve) => setTimeout(resolve, 10)) + ]); + + text.textContent = 'new3'; + div.removeChild(span); + div.setAttribute('attr', 'value3'); + + await new Promise((resolve) => setTimeout(resolve, 10)); expect(records).toEqual([]); });