diff --git a/packages/happy-dom/src/html-parser/HTMLParser.ts b/packages/happy-dom/src/html-parser/HTMLParser.ts index e664d399..6dc2ed98 100755 --- a/packages/happy-dom/src/html-parser/HTMLParser.ts +++ b/packages/happy-dom/src/html-parser/HTMLParser.ts @@ -186,6 +186,31 @@ export default class HTMLParser { }; } + if (this.rootNode instanceof this.window.HTMLHtmlElement) { + const head = this.rootDocument.createElement('head'); + const body = this.rootDocument.createElement('body'); + while (this.rootNode[PropertySymbol.nodeArray].length > 0) { + this.rootNode[PropertySymbol.removeChild]( + this.rootNode[PropertySymbol.nodeArray][ + this.rootNode[PropertySymbol.nodeArray].length - 1 + ] + ); + } + + this.rootNode[PropertySymbol.appendChild](head); + this.rootNode[PropertySymbol.appendChild](body); + + this.documentStructure = { + nodes: { + doctype: null, + documentElement: this.rootNode, + head, + body + }, + level: HTMLDocumentStructureLevelEnum.documentElement + }; + } + let match: RegExpExecArray; let lastIndex = 0; diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index 85e862ce..a345c776 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -1701,14 +1701,14 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal this[PropertySymbol.mutationObservers] = []; // Disconnects nodes from the document, so that they can be garbage collected. - const childNodes = this.document.body[PropertySymbol.nodeArray]; + const childNodes = this.document[PropertySymbol.nodeArray]; while (childNodes.length > 0) { // Makes sure that something won't be triggered by the disconnect. if ((childNodes[0]).disconnectedCallback) { delete (childNodes[0]).disconnectedCallback; } - this.document.body.removeChild(childNodes[0]); + this.document[PropertySymbol.removeChild](childNodes[0]); } // Create some empty elements for scripts that are still running. @@ -1717,7 +1717,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal const bodyElement = this.document.createElement('body'); htmlElement.appendChild(headElement); htmlElement.appendChild(bodyElement); - this.document.body.appendChild(htmlElement); + this.document[PropertySymbol.appendChild](htmlElement); if (this.location[PropertySymbol.destroy]) { this.location[PropertySymbol.destroy](); diff --git a/packages/happy-dom/test/browser/BrowserPage.test.ts b/packages/happy-dom/test/browser/BrowserPage.test.ts index 12528155..cc0b5139 100644 --- a/packages/happy-dom/test/browser/BrowserPage.test.ts +++ b/packages/happy-dom/test/browser/BrowserPage.test.ts @@ -127,6 +127,9 @@ describe('BrowserPage', () => { const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); + // Should work even if the body is removed. + frame2.document.body.remove(); + await page.close(); expect(browser.defaultContext.pages.length).toBe(0); diff --git a/packages/happy-dom/test/html-parser/HTMLParser.test.ts b/packages/happy-dom/test/html-parser/HTMLParser.test.ts index 09c5963b..3c9806d8 100644 --- a/packages/happy-dom/test/html-parser/HTMLParser.test.ts +++ b/packages/happy-dom/test/html-parser/HTMLParser.test.ts @@ -2104,5 +2104,23 @@ describe('HTMLParser', () => { ` ); }); + + it('Handles setting documentElement.innerHTML for #1663', () => { + document.documentElement.innerHTML = ''; + expect(document.documentElement.outerHTML).toBe(''); + + document.documentElement.innerHTML = 'Test'; + expect(document.documentElement.outerHTML).toBe( + 'Test' + ); + + document.documentElement.innerHTML = ''; + expect(document.documentElement.outerHTML).toBe(''); + + document.documentElement.innerHTML = 'Test'; + expect(document.documentElement.outerHTML).toBe( + 'Test' + ); + }); }); });