diff --git a/packages/happy-dom/src/dom-implementation/DOMImplementation.ts b/packages/happy-dom/src/dom-implementation/DOMImplementation.ts index 2c772e888..dbf9870b9 100644 --- a/packages/happy-dom/src/dom-implementation/DOMImplementation.ts +++ b/packages/happy-dom/src/dom-implementation/DOMImplementation.ts @@ -1,6 +1,7 @@ import DocumentType from '../nodes/document-type/DocumentType.js'; import * as PropertySymbol from '../PropertySymbol.js'; import Document from '../nodes/document/Document.js'; +import NodeFactory from '../nodes/NodeFactory.js'; /** * The DOMImplementation interface represents an object providing methods which are not dependent on any particular document. Such an object is returned by the. @@ -45,12 +46,15 @@ export default class DOMImplementation { publicId: string, systemId: string ): DocumentType { - const documentType = new this.#document[PropertySymbol.ownerWindow].DocumentType( - this.#document + const documentType = NodeFactory.createNode( + this.#document, + this.#document[PropertySymbol.ownerWindow].DocumentType ); + documentType[PropertySymbol.name] = qualifiedName; documentType[PropertySymbol.publicId] = publicId; documentType[PropertySymbol.systemId] = systemId; + return documentType; } } diff --git a/packages/happy-dom/src/nodes/NodeFactory.ts b/packages/happy-dom/src/nodes/NodeFactory.ts new file mode 100644 index 000000000..120419569 --- /dev/null +++ b/packages/happy-dom/src/nodes/NodeFactory.ts @@ -0,0 +1,38 @@ +import Document from '../nodes/document/Document.js'; +import Node from './node/Node.js'; +import * as PropertySymbol from '../PropertySymbol.js'; + +/** + * Node factory used for setting the owner document to nodes. + */ +export default class NodeFactory { + public static ownerDocuments: Document[] = []; + + /** + * Creates a node instance with the given owner document. + * + * @param ownerDocument Owner document. + * @param nodeClass Node class. + * @param [args] Node arguments. + * @returns Node instance. + */ + public static createNode( + ownerDocument: Document, + nodeClass: new (...args) => T, + ...args: any[] + ): T { + if (!nodeClass[PropertySymbol.ownerDocument]) { + this.ownerDocuments.push(ownerDocument); + } + return new nodeClass(...args); + } + + /** + * Pulls an owner document from the queue. + * + * @returns Document. + */ + public static pullOwnerDocument(): Document { + return this.ownerDocuments.pop(); + } +} diff --git a/packages/happy-dom/src/nodes/character-data/CharacterData.ts b/packages/happy-dom/src/nodes/character-data/CharacterData.ts index 83ab3fb1e..9b2f27cb7 100644 --- a/packages/happy-dom/src/nodes/character-data/CharacterData.ts +++ b/packages/happy-dom/src/nodes/character-data/CharacterData.ts @@ -22,6 +22,17 @@ export default abstract class CharacterData public [PropertySymbol.data] = ''; public declare cloneNode: (deep?: boolean) => CharacterData; + /** + * Constructor. + * + * @param [data] Data. + */ + constructor(data?: string) { + super(); + + this[PropertySymbol.data] = data !== undefined ? String(data) : ''; + } + /** * Returns text content. * diff --git a/packages/happy-dom/src/nodes/comment/Comment.ts b/packages/happy-dom/src/nodes/comment/Comment.ts index 3887d941e..87a4f8bc1 100644 --- a/packages/happy-dom/src/nodes/comment/Comment.ts +++ b/packages/happy-dom/src/nodes/comment/Comment.ts @@ -1,7 +1,6 @@ import CharacterData from '../character-data/CharacterData.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import NodeTypeEnum from '../node/NodeTypeEnum.js'; -import Document from '../document/Document.js'; /** * Comment node. @@ -10,18 +9,6 @@ export default class Comment extends CharacterData { public [PropertySymbol.nodeType] = NodeTypeEnum.commentNode; public declare cloneNode: (deep?: boolean) => Comment; - /** - * Constructor. - * - * @param [ownerDocument] Owner document. - * @param [data] Data. - */ - constructor(ownerDocument?: Document, data?: string) { - super(ownerDocument); - - this[PropertySymbol.data] = data !== undefined ? String(data) : ''; - } - /** * Node name. * diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index c26c4419b..a8170ca10 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -47,6 +47,7 @@ import HTMLHeadElement from '../html-head-element/HTMLHeadElement.js'; import HTMLBaseElement from '../html-base-element/HTMLBaseElement.js'; import ICachedResult from '../node/ICachedResult.js'; import HTMLTitleElement from '../html-title-element/HTMLTitleElement.js'; +import NodeFactory from '../NodeFactory.js'; const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/; @@ -54,6 +55,9 @@ const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/; * Document. */ export default class Document extends Node { + // Static properties + public static [PropertySymbol.ownerDocument]: Document = {}; + // Internal properties public [PropertySymbol.children]: HTMLCollection | null = null; public [PropertySymbol.activeElement]: HTMLElement | SVGElement = null; @@ -201,7 +205,7 @@ export default class Document extends Node { * @param injected.window Window. */ constructor(injected: { browserFrame: IBrowserFrame; window: BrowserWindow }) { - super({}); + super(); this.#browserFrame = injected.browserFrame; this[PropertySymbol.ownerWindow] = injected.window; this[PropertySymbol.ownerDocument] = null; @@ -1074,15 +1078,19 @@ export default class Document extends Node { // SVG element if (namespaceURI === NamespaceURI.svg) { - const element = + const elementClass = qualifiedName === 'svg' - ? new this[PropertySymbol.ownerWindow].SVGSVGElement(this) - : new this[PropertySymbol.ownerWindow].SVGElement(this); + ? this[PropertySymbol.ownerWindow].SVGSVGElement + : this[PropertySymbol.ownerWindow].SVGElement; + + const element = NodeFactory.createNode(this, elementClass); + element[PropertySymbol.tagName] = qualifiedName; element[PropertySymbol.localName] = qualifiedName; element[PropertySymbol.namespaceURI] = namespaceURI; element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null; - return (element); + + return element; } // Custom HTML element @@ -1107,7 +1115,7 @@ export default class Document extends Node { // Known HTML element if (elementClass) { - const element = new elementClass(this); + const element = NodeFactory.createNode(this, elementClass); element[PropertySymbol.tagName] = qualifiedName.toUpperCase(); element[PropertySymbol.localName] = localName; @@ -1118,16 +1126,18 @@ export default class Document extends Node { } // Unknown HTML element - const element = localName.includes('-') - ? new this[PropertySymbol.ownerWindow].HTMLElement(this) - : new this[PropertySymbol.ownerWindow].HTMLUnknownElement(this); + const unknownElementClass = localName.includes('-') + ? this[PropertySymbol.ownerWindow].HTMLElement + : this[PropertySymbol.ownerWindow].HTMLUnknownElement; + + const element = NodeFactory.createNode(this, unknownElementClass); element[PropertySymbol.tagName] = qualifiedName.toUpperCase(); element[PropertySymbol.localName] = localName; element[PropertySymbol.namespaceURI] = namespaceURI; element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null; - return element; + return element; } /* eslint-enable jsdoc/valid-types */ @@ -1225,13 +1235,15 @@ export default class Document extends Node { * @returns Element. */ public createAttributeNS(namespaceURI: string, qualifiedName: string): Attr { - const attribute = new this[PropertySymbol.ownerWindow].Attr(this); + const attribute = NodeFactory.createNode(this, this[PropertySymbol.ownerWindow].Attr); + const parts = qualifiedName.split(':'); attribute[PropertySymbol.namespaceURI] = namespaceURI; attribute[PropertySymbol.name] = qualifiedName; attribute[PropertySymbol.localName] = parts[1] ?? qualifiedName; attribute[PropertySymbol.prefix] = parts[0] ?? null; - return attribute; + + return attribute; } /** @@ -1325,12 +1337,16 @@ export default class Document extends Node { `Failed to execute 'createProcessingInstruction' on 'Document': The data provided ('?>') contains '?>'` ); } - const processingInstruction = new this[PropertySymbol.ownerWindow].ProcessingInstruction(this); - processingInstruction[PropertySymbol.data] = data; - processingInstruction[PropertySymbol.target] = target; + const element = NodeFactory.createNode( + this, + this[PropertySymbol.ownerWindow].ProcessingInstruction + ); + + element[PropertySymbol.data] = data; + element[PropertySymbol.target] = target; - return processingInstruction; + return element; } /** diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index 27681e39b..3a9932faa 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -33,6 +33,7 @@ import NamespaceURI from '../../config/NamespaceURI.js'; import NodeList from '../node/NodeList.js'; import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration.js'; import NamedNodeMapProxyFactory from './NamedNodeMapProxyFactory.js'; +import NodeFactory from '../NodeFactory.js'; type InsertAdjacentPosition = 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend'; @@ -848,9 +849,10 @@ export default class Element ); } - const shadowRoot = new this[PropertySymbol.ownerDocument][ - PropertySymbol.ownerWindow - ].ShadowRoot(this[PropertySymbol.ownerDocument]); + const shadowRoot = NodeFactory.createNode( + this[PropertySymbol.ownerDocument], + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].ShadowRoot + ); this[PropertySymbol.shadowRoot] = shadowRoot; @@ -862,7 +864,7 @@ export default class Element shadowRoot[PropertySymbol.slotAssignment] = init.slotAssignment === 'manual' ? 'manual' : 'named'; - (shadowRoot)[PropertySymbol.connectedToNode](); + shadowRoot[PropertySymbol.connectedToNode](); return this[PropertySymbol.shadowRoot]; } diff --git a/packages/happy-dom/src/nodes/html-audio-element/Audio.ts b/packages/happy-dom/src/nodes/html-audio-element/Audio.ts index a71397c37..3fca58915 100644 --- a/packages/happy-dom/src/nodes/html-audio-element/Audio.ts +++ b/packages/happy-dom/src/nodes/html-audio-element/Audio.ts @@ -1,4 +1,3 @@ -import Document from '../document/Document.js'; import HTMLAudioElement from './HTMLAudioElement.js'; /** @@ -11,11 +10,10 @@ export default class Audio extends HTMLAudioElement { /** * Constructor. * - * @param [ownerDocument] Owner document. * @param [url] source URL. */ - constructor(ownerDocument?: Document, url: string = null) { - super(ownerDocument); + constructor(url: string = null) { + super(); if (url !== null) { this.src = url; diff --git a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts index 1fe74af43..0e23882ea 100644 --- a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts +++ b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts @@ -17,7 +17,6 @@ import Element from '../element/Element.js'; import EventTarget from '../../event/EventTarget.js'; import Node from '../node/Node.js'; import ClassMethodBinder from '../../ClassMethodBinder.js'; -import Document from '../document/Document.js'; /** * HTML Form Element. @@ -44,14 +43,12 @@ export default class HTMLFormElement extends HTMLElement { /** * Constructor. * - * @param injected Injected properties. - * @param injected.browserFrame Browser frame. - * @param injected.ownerDocument Owner document. + * @param browserFrame Browser frame. */ - constructor(injected: { browserFrame: IBrowserFrame; ownerDocument: Document }) { - super(injected.ownerDocument); + constructor(browserFrame) { + super(); - this.#browserFrame = injected.browserFrame; + this.#browserFrame = browserFrame; ClassMethodBinder.bindMethods( this, diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts index bb750f030..fc33922e7 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts @@ -58,14 +58,12 @@ export default class HTMLIFrameElement extends HTMLElement { /** * Constructor. * - * @param injected Injected properties. - * @param injected.browserFrame Browser frame. - * @param injected.ownerDocument Owner document. + * @param browserFrame Browser frame. */ - constructor(injected: { browserFrame: IBrowserFrame; ownerDocument: Document }) { - super(injected.ownerDocument); + constructor(browserFrame) { + super(); - this.#browserFrame = injected.browserFrame; + this.#browserFrame = browserFrame; } /** diff --git a/packages/happy-dom/src/nodes/html-image-element/Image.ts b/packages/happy-dom/src/nodes/html-image-element/Image.ts index 81c5e3e33..f8e028129 100644 --- a/packages/happy-dom/src/nodes/html-image-element/Image.ts +++ b/packages/happy-dom/src/nodes/html-image-element/Image.ts @@ -1,4 +1,3 @@ -import Document from '../document/Document.js'; import HTMLImageElement from './HTMLImageElement.js'; /** @@ -11,12 +10,11 @@ export default class Image extends HTMLImageElement { /** * Constructor. * - * @param [ownerDocument] Owner document. * @param [width] Width. * @param [height] Height. */ - constructor(ownerDocument?: Document, width: number = null, height: number = null) { - super(ownerDocument); + constructor(width: number = null, height: number = null) { + super(); if (width !== null) { this.width = width; diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts index 3e9d43de2..989be7572 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts @@ -11,7 +11,6 @@ import DOMException from '../../exception/DOMException.js'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import ResourceFetch from '../../fetch/ResourceFetch.js'; import DocumentReadyStateManager from '../document/DocumentReadyStateManager.js'; -import Document from '../document/Document.js'; /** * HTML Link Element. @@ -34,14 +33,12 @@ export default class HTMLLinkElement extends HTMLElement { /** * Constructor. * - * @param injected Injected properties. - * @param injected.browserFrame Browser frame. - * @param injected.ownerDocument Owner document. + * @param browserFrame Browser frame. */ - constructor(injected: { browserFrame: IBrowserFrame; ownerDocument: Document }) { - super(injected.ownerDocument); + constructor(browserFrame) { + super(); - this.#browserFrame = injected.browserFrame; + this.#browserFrame = browserFrame; } /** diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts index f6bcd5073..853a0ba14 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts @@ -11,7 +11,6 @@ import DOMException from '../../exception/DOMException.js'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import ResourceFetch from '../../fetch/ResourceFetch.js'; import DocumentReadyStateManager from '../document/DocumentReadyStateManager.js'; -import Document from '../document/Document.js'; /** * HTML Script Element. @@ -37,14 +36,12 @@ export default class HTMLScriptElement extends HTMLElement { /** * Constructor. * - * @param injected Injected properties. - * @param injected.browserFrame Browser frame. - * @param injected.ownerDocument Owner document. + * @param browserFrame Browser frame. */ - constructor(injected: { browserFrame: IBrowserFrame; ownerDocument: Document }) { - super(injected.ownerDocument); + constructor(browserFrame) { + super(); - this.#browserFrame = injected.browserFrame; + this.#browserFrame = browserFrame; } /** diff --git a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts index 2191ac88f..18588d037 100644 --- a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts +++ b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts @@ -16,7 +16,6 @@ import ClassMethodBinder from '../../ClassMethodBinder.js'; import Element from '../element/Element.js'; import Node from '../node/Node.js'; import EventTarget from '../../event/EventTarget.js'; -import Document from '../document/Document.js'; /** * HTML Select Element. @@ -38,11 +37,9 @@ export default class HTMLSelectElement extends HTMLElement { /** * Constructor. - * - * @param ownerDocument Owner document. */ - constructor(ownerDocument?: Document) { - super(ownerDocument); + constructor() { + super(); ClassMethodBinder.bindMethods( this, diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index 494700c4b..f6a743f22 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -26,6 +26,7 @@ import HTMLFormElement from '../html-form-element/HTMLFormElement.js'; import HTMLSelectElement from '../html-select-element/HTMLSelectElement.js'; import HTMLTextAreaElement from '../html-text-area-element/HTMLTextAreaElement.js'; import HTMLSlotElement from '../html-slot-element/HTMLSlotElement.js'; +import NodeFactory from '../NodeFactory.js'; /** * Node. @@ -109,21 +110,22 @@ export default class Node extends EventTarget { /** * Constructor. - * - * @param [ownerDocument] Owner document. */ - constructor(ownerDocument?: Document) { + constructor() { super(); - if (!ownerDocument) { - ownerDocument = (this.constructor)[PropertySymbol.ownerDocument]; - } + const definedOwnerDocument = (this.constructor)[PropertySymbol.ownerDocument]; - if (!ownerDocument) { - throw new TypeError('Illegal constructor'); - } + if (definedOwnerDocument) { + this[PropertySymbol.ownerDocument] = definedOwnerDocument; + } else { + const ownerDocument = NodeFactory.pullOwnerDocument(); - this[PropertySymbol.ownerDocument] = ownerDocument; + if (!ownerDocument) { + throw new TypeError('Illegal constructor'); + } + this[PropertySymbol.ownerDocument] = ownerDocument; + } } /** @@ -445,19 +447,10 @@ export default class Node extends EventTarget { * @returns Cloned node. */ public [PropertySymbol.cloneNode](deep = false): Node { - // Can be a custom element, which should keep the ownerDocument - const hasOwnerDocument = - (this.constructor)[PropertySymbol.ownerDocument] === - this[PropertySymbol.ownerDocument]; - - (this.constructor)[PropertySymbol.ownerDocument] = - this[PropertySymbol.ownerDocument]; - - const clone = new (this.constructor)(); - - if (!hasOwnerDocument) { - (this.constructor)[PropertySymbol.ownerDocument] = null; - } + const clone = NodeFactory.createNode( + this[PropertySymbol.ownerDocument], + this.constructor + ); // Document has childNodes directly when it is created if (clone[PropertySymbol.nodeArray].length) { diff --git a/packages/happy-dom/src/nodes/text/Text.ts b/packages/happy-dom/src/nodes/text/Text.ts index beec5c999..0170bd264 100644 --- a/packages/happy-dom/src/nodes/text/Text.ts +++ b/packages/happy-dom/src/nodes/text/Text.ts @@ -5,7 +5,6 @@ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import HTMLTextAreaElement from '../html-text-area-element/HTMLTextAreaElement.js'; import NodeTypeEnum from '../node/NodeTypeEnum.js'; import HTMLStyleElement from '../html-style-element/HTMLStyleElement.js'; -import Document from '../document/Document.js'; /** * Text node. @@ -16,18 +15,6 @@ export default class Text extends CharacterData { public override [PropertySymbol.textAreaNode]: HTMLTextAreaElement | null = null; public override [PropertySymbol.styleNode]: HTMLStyleElement | null = null; - /** - * Constructor. - * - * @param [ownerDocument] Owner document. - * @param [data] Data. - */ - constructor(ownerDocument?: Document, data?: string) { - super(ownerDocument); - - this[PropertySymbol.data] = data !== undefined ? String(data) : ''; - } - /** * Node name. * diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index 3f9a3dcc8..1ef95fab0 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -252,11 +252,11 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal public readonly HTMLDocument: new () => HTMLDocumentImplementation; public readonly XMLDocument: new () => XMLDocumentImplementation; public readonly SVGDocument: new () => SVGDocumentImplementation; - public readonly Text: new (data?: string) => TextImplementation; - public readonly Comment: new (data?: string) => CommentImplementation; - public readonly Image: new (width?: number, height?: number) => ImageImplementation; + public readonly Text: typeof TextImplementation; + public readonly Comment: typeof CommentImplementation; + public readonly Image: typeof ImageImplementation; public readonly DocumentFragment: typeof DocumentFragmentImplementation; - public readonly Audio: new (url?: string) => AudioImplementation; + public readonly Audio: typeof AudioImplementation; // Element classes public readonly HTMLAnchorElement: typeof HTMLAnchorElement = HTMLAnchorElement; @@ -618,7 +618,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal this[PropertySymbol.setupVMContext](); // Class overrides - // For classes that need to be bound to the correct context. + // For classes that need to be bound to the correct context to be instantiable using the "new" keyword. /* eslint-disable jsdoc/require-jsdoc */ @@ -660,22 +660,22 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal } class HTMLScriptElement extends HTMLScriptElementImplementation { constructor() { - super({ browserFrame, ownerDocument: window.document }); + super(browserFrame); } } class HTMLLinkElement extends HTMLLinkElementImplementation { constructor() { - super({ browserFrame, ownerDocument: window.document }); + super(browserFrame); } } class HTMLIFrameElement extends HTMLIFrameElementImplementation { constructor() { - super({ browserFrame, ownerDocument: window.document }); + super(browserFrame); } } class HTMLFormElement extends HTMLFormElementImplementation { constructor() { - super({ browserFrame, ownerDocument: window.document }); + super(browserFrame); } } class Document extends DocumentImplementation { @@ -698,31 +698,12 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal super({ window, browserFrame }); } } - class Audio extends AudioImplementation { - constructor(url: string = null) { - super(window.document, url); - } - } - class Image extends ImageImplementation { - constructor(width: number = null, height: number = null) { - super(window.document, width, height); - } - } - class DocumentFragment extends DocumentFragmentImplementation { - constructor() { - super(window.document); - } - } - class Text extends TextImplementation { - constructor(data: string) { - super(window.document, data); - } - } - class Comment extends CommentImplementation { - constructor(data: string) { - super(window.document, data); - } - } + + class Audio extends AudioImplementation {} + class Image extends ImageImplementation {} + class DocumentFragment extends DocumentFragmentImplementation {} + class Text extends TextImplementation {} + class Comment extends CommentImplementation {} /* eslint-enable jsdoc/require-jsdoc */ @@ -751,6 +732,13 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal this.document = new HTMLDocument(); this.document[PropertySymbol.defaultView] = this; + // Override owner document + this.Audio[PropertySymbol.ownerDocument] = this.document; + this.Image[PropertySymbol.ownerDocument] = this.document; + this.DocumentFragment[PropertySymbol.ownerDocument] = this.document; + this.Text[PropertySymbol.ownerDocument] = this.document; + this.Comment[PropertySymbol.ownerDocument] = this.document; + // Ready state manager this[PropertySymbol.readyStateManager].waitUntilComplete().then(() => { this.document[PropertySymbol.readyState] = DocumentReadyStateEnum.complete; @@ -1514,6 +1502,12 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal this[PropertySymbol.asyncTaskManager] = null; this[PropertySymbol.mutationObservers] = []; + this.Audio[PropertySymbol.ownerDocument] = null; + this.Image[PropertySymbol.ownerDocument] = null; + this.DocumentFragment[PropertySymbol.ownerDocument] = null; + this.Text[PropertySymbol.ownerDocument] = null; + this.Comment[PropertySymbol.ownerDocument] = null; + // Disconnects nodes from the document, so that they can be garbage collected. const childNodes = this.document[PropertySymbol.nodeArray]; diff --git a/packages/happy-dom/test/fetch/Fetch.test.ts b/packages/happy-dom/test/fetch/Fetch.test.ts index 78fcd187b..ed97c5c81 100644 --- a/packages/happy-dom/test/fetch/Fetch.test.ts +++ b/packages/happy-dom/test/fetch/Fetch.test.ts @@ -3659,7 +3659,7 @@ describe('Fetch', () => { }); const text1 = await response1.text(); - await new Promise((resolve) => setTimeout(resolve, 20)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = await window.fetch(url); const text2 = await response2.text(); @@ -3989,7 +3989,7 @@ describe('Fetch', () => { }); const text1 = await response1.text(); - await new Promise((resolve) => setTimeout(resolve, 20)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = await window.fetch(url, { method: 'HEAD' @@ -4158,7 +4158,7 @@ describe('Fetch', () => { }); const text1 = await response1.text(); - await new Promise((resolve) => setTimeout(resolve, 20)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = await window.fetch(url); const text2 = await response2.text(); diff --git a/packages/happy-dom/test/nodes/node/Node.test.ts b/packages/happy-dom/test/nodes/node/Node.test.ts index 6c61aa422..d8ab7e09e 100644 --- a/packages/happy-dom/test/nodes/node/Node.test.ts +++ b/packages/happy-dom/test/nodes/node/Node.test.ts @@ -12,6 +12,7 @@ import ErrorEvent from '../../../src/event/events/ErrorEvent.js'; import { beforeEach, describe, it, expect } from 'vitest'; import ShadowRoot from '../../../src/nodes/shadow-root/ShadowRoot.js'; import * as PropertySymbol from '../../../src/PropertySymbol.js'; +import NodeFactory from '../../../src/nodes/NodeFactory.js'; describe('Node', () => { let window: Window; @@ -80,6 +81,22 @@ describe('Node', () => { window.customElements.define('custom-button', CustomButtonElement); }); + describe('constructor', () => { + it('Throws an exception if called without using the NodeFactory or define "ownerDocument" as a property on the class', () => { + expect(() => new Node()).toThrow('Illegal constructor'); + }); + + it('Doesn\'t throw an exception if "ownerDocument" is defined as a property on the class', () => { + Node[PropertySymbol.ownerDocument] = document; + expect(() => new Node()).not.toThrow(); + Node[PropertySymbol.ownerDocument] = null; + }); + + it("Doesn't throw an exception if NodeFactory is used", () => { + expect(() => NodeFactory.createNode(document, Node)).not.toThrow(); + }); + }); + describe('get isConnected()', () => { it('Returns "true" if the node is connected to the document.', () => { const div = document.createElement('div'); @@ -126,13 +143,13 @@ describe('Node', () => { describe('get nodeValue()', () => { it('Returns null.', () => { - expect(new Node(document).nodeValue).toBe(null); + expect(NodeFactory.createNode(document, Node).nodeValue).toBe(null); }); }); describe('get nodeName()', () => { it('Returns emptry string.', () => { - expect(new Node(document).nodeName).toBe(''); + expect(NodeFactory.createNode(document, Node).nodeName).toBe(''); }); });