Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1207@patch: Fixes problem related to exception thrown when creating … #1208

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DocumentType from '../nodes/document-type/DocumentType.js';
import * as PropertySymbol from '../PropertySymbol.js';
import IDocument from '../nodes/document/IDocument.js';
import NodeCreationOwnerDocument from '../nodes/document/NodeCreationOwnerDocument.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.
Expand Down Expand Up @@ -46,9 +46,10 @@ export default class DOMImplementation {
publicId: string,
systemId: string
): DocumentType {
NodeCreationOwnerDocument.ownerDocument = this.#document;
const documentType = new this.#document[PropertySymbol.defaultView].DocumentType();
NodeCreationOwnerDocument.ownerDocument = null;
const documentType = NodeFactory.createNode<DocumentType>(
this.#document,
this.#document[PropertySymbol.defaultView].DocumentType
);
documentType.name = qualifiedName;
documentType.publicId = publicId;
documentType.systemId = systemId;
Expand Down
34 changes: 34 additions & 0 deletions packages/happy-dom/src/nodes/NodeFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import IDocument from '../nodes/document/IDocument.js';

/**
* Node factory used for setting the owner document to nodes.
*/
export default class NodeFactory {
public static ownerDocuments: IDocument[] = [];

/**
* 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<T>(
ownerDocument: IDocument,
nodeClass: new (...args) => T,
...args: any[]
): T {
this.ownerDocuments.push(ownerDocument);
return new nodeClass(...args);
}

/**
* Pulls an owner document from the queue.
*
* @returns Document.
*/
public static pullOwnerDocument(): IDocument {
return this.ownerDocuments.pop();
}
}
34 changes: 13 additions & 21 deletions packages/happy-dom/src/nodes/document/Document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import VisibilityStateEnum from './VisibilityStateEnum.js';
import NodeTypeEnum from '../node/NodeTypeEnum.js';
import CookieStringUtility from '../../cookie/urilities/CookieStringUtility.js';
import IBrowserFrame from '../../browser/types/IBrowserFrame.js';
import NodeCreationOwnerDocument from './NodeCreationOwnerDocument.js';
import NodeFactory from '../NodeFactory.js';

const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/;

Expand Down Expand Up @@ -835,10 +835,8 @@ export default class Document extends Node implements IDocument {
this[PropertySymbol.defaultView][ElementTag[tagName]] ||
HTMLUnknownElement;

NodeCreationOwnerDocument.ownerDocument = this;
const element = new elementClass();
NodeCreationOwnerDocument.ownerDocument = null;
element.tagName = tagName;
const element = NodeFactory.createNode<IElement>(this, elementClass);
(<string>element.tagName) = tagName;

(<string>element.namespaceURI) = namespaceURI;
if (element instanceof Element && options && options.is) {
Expand All @@ -857,10 +855,7 @@ export default class Document extends Node implements IDocument {
* @returns Text node.
*/
public createTextNode(data?: string): IText {
NodeCreationOwnerDocument.ownerDocument = this;
const node = new this[PropertySymbol.defaultView].Text(data);
NodeCreationOwnerDocument.ownerDocument = null;
return node;
return NodeFactory.createNode<IText>(this, this[PropertySymbol.defaultView].Text, data);
}

/**
Expand All @@ -870,10 +865,7 @@ export default class Document extends Node implements IDocument {
* @returns Text node.
*/
public createComment(data?: string): IComment {
NodeCreationOwnerDocument.ownerDocument = this;
const node = new this[PropertySymbol.defaultView].Comment(data);
NodeCreationOwnerDocument.ownerDocument = null;
return node;
return NodeFactory.createNode<IComment>(this, this[PropertySymbol.defaultView].Comment, data);
}

/**
Expand Down Expand Up @@ -943,9 +935,7 @@ export default class Document extends Node implements IDocument {
* @returns Element.
*/
public createAttributeNS(namespaceURI: string, qualifiedName: string): IAttr {
NodeCreationOwnerDocument.ownerDocument = this;
const attribute = new this[PropertySymbol.defaultView].Attr();
NodeCreationOwnerDocument.ownerDocument = null;
const attribute = NodeFactory.createNode<IAttr>(this, this[PropertySymbol.defaultView].Attr);
attribute.namespaceURI = namespaceURI;
attribute.name = qualifiedName;
return <IAttr>attribute;
Expand Down Expand Up @@ -1016,9 +1006,9 @@ export default class Document extends Node implements IDocument {
/**
* Creates a Processing Instruction node.
*
* @param target Target.
* @param data Data.
* @returns IProcessingInstruction.
* @param target
* @param data
*/
public createProcessingInstruction(target: string, data: string): IProcessingInstruction {
if (!target || !PROCESSING_INSTRUCTION_TARGET_REGEXP.test(target)) {
Expand All @@ -1031,9 +1021,11 @@ export default class Document extends Node implements IDocument {
`Failed to execute 'createProcessingInstruction' on 'Document': The data provided ('?>') contains '?>'`
);
}
NodeCreationOwnerDocument.ownerDocument = this;
const processingInstruction = new this[PropertySymbol.defaultView].ProcessingInstruction(data);
NodeCreationOwnerDocument.ownerDocument = null;
const processingInstruction = NodeFactory.createNode<IProcessingInstruction>(
this,
this[PropertySymbol.defaultView].ProcessingInstruction,
data
);
processingInstruction.target = target;
return processingInstruction;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/happy-dom/src/nodes/document/IDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,9 @@ export default interface IDocument extends IParentNode {
/**
* Creates a Processing Instruction node.
*
* @param target Target.
* @param data Data.
* @returns IProcessingInstruction.
* @param target
* @param data
*/
createProcessingInstruction(target: string, data: string): IProcessingInstruction;
}

This file was deleted.

26 changes: 14 additions & 12 deletions packages/happy-dom/src/nodes/element/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import ElementNamedNodeMap from './ElementNamedNodeMap.js';
import WindowErrorUtility from '../../window/WindowErrorUtility.js';
import WindowBrowserSettingsReader from '../../window/WindowBrowserSettingsReader.js';
import BrowserErrorCaptureEnum from '../../browser/enums/BrowserErrorCaptureEnum.js';
import NodeCreationOwnerDocument from '../document/NodeCreationOwnerDocument.js';
import NodeFactory from '../NodeFactory.js';

/**
* Element.
Expand Down Expand Up @@ -688,17 +688,19 @@ export default class Element extends Node implements IElement {
throw new DOMException('Shadow root has already been attached.');
}

NodeCreationOwnerDocument.ownerDocument = this.ownerDocument;
(<IShadowRoot>this[PropertySymbol.shadowRoot]) = new this.ownerDocument[
PropertySymbol.defaultView
].ShadowRoot();
NodeCreationOwnerDocument.ownerDocument = null;
(<Element>this[PropertySymbol.shadowRoot].host) = this;
(<string>this[PropertySymbol.shadowRoot].mode) = init.mode;
(<ShadowRoot>this[PropertySymbol.shadowRoot])[PropertySymbol.connectToNode](this);

if (this[PropertySymbol.shadowRoot].mode === 'open') {
(<IShadowRoot>this.shadowRoot) = this[PropertySymbol.shadowRoot];
const shadowRoot = NodeFactory.createNode<IShadowRoot>(
this.ownerDocument,
this.ownerDocument[PropertySymbol.defaultView].ShadowRoot
);

(<IShadowRoot>this[PropertySymbol.shadowRoot]) = shadowRoot;

(<Element>shadowRoot.host) = this;
(<string>shadowRoot.mode) = init.mode;
(<ShadowRoot>shadowRoot)[PropertySymbol.connectToNode](this);

if (shadowRoot.mode === 'open') {
(<IShadowRoot>this.shadowRoot) = shadowRoot;
}

return this[PropertySymbol.shadowRoot];
Expand Down
25 changes: 13 additions & 12 deletions packages/happy-dom/src/nodes/node/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import NodeUtility from './NodeUtility.js';
import IAttr from '../attr/IAttr.js';
import NodeList from './NodeList.js';
import INodeList from './INodeList.js';
import NodeCreationOwnerDocument from '../document/NodeCreationOwnerDocument.js';
import NodeFactory from '../NodeFactory.js';

/**
* Node.
*/
export default class Node extends EventTarget implements INode {
// Can be set before the Node is created.
public static [PropertySymbol.ownerDocument]: IDocument | null = null;
public static [PropertySymbol.ownerDocument]: IDocument | null;

// Public properties
public static readonly ELEMENT_NODE = NodeTypeEnum.elementNode;
Expand Down Expand Up @@ -71,13 +71,16 @@ export default class Node extends EventTarget implements INode {
*/
constructor() {
super();
if (
NodeCreationOwnerDocument.ownerDocument ||
(<typeof Node>this.constructor)[PropertySymbol.ownerDocument]
) {
this.ownerDocument =
NodeCreationOwnerDocument.ownerDocument ||
(<typeof Node>this.constructor)[PropertySymbol.ownerDocument];
if ((<typeof Node>this.constructor)[PropertySymbol.ownerDocument] !== undefined) {
this.ownerDocument = (<typeof Node>this.constructor)[PropertySymbol.ownerDocument];
} else {
const ownerDocument = NodeFactory.pullOwnerDocument();
if (!ownerDocument) {
throw new Error(
'Failed to construct "Node": No owner document in queue. Please use "NodeFactory" to create instances of a Node.'
);
}
this.ownerDocument = ownerDocument;
}
}

Expand Down Expand Up @@ -279,9 +282,7 @@ export default class Node extends EventTarget implements INode {
* @returns Cloned node.
*/
public cloneNode(deep = false): INode {
NodeCreationOwnerDocument.ownerDocument = this.ownerDocument;
const clone = new (<typeof Node>this.constructor)();
NodeCreationOwnerDocument.ownerDocument = null;
const clone = NodeFactory.createNode<Node>(this.ownerDocument, <typeof Node>this.constructor);

// Document has childNodes directly when it is created
if (clone[PropertySymbol.childNodes].length) {
Expand Down
6 changes: 6 additions & 0 deletions packages/happy-dom/src/window/BrowserWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,12 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow
this.XMLDocument = XMLDocument;
this.SVGDocument = SVGDocument;

// Override owner document
this.Document[PropertySymbol.ownerDocument] = null;
this.HTMLDocument[PropertySymbol.ownerDocument] = null;
this.XMLDocument[PropertySymbol.ownerDocument] = null;
this.SVGDocument[PropertySymbol.ownerDocument] = null;

// Document
this.document = new HTMLDocument();
(<IBrowserWindow>this.document.defaultView) = this;
Expand Down
3 changes: 3 additions & 0 deletions packages/happy-dom/test/CustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export default class CustomElement extends HTMLElement {
constructor() {
super();
this.internalShadowRoot = this.attachShadow({ mode: CustomElement.shadowRootMode });

// Test to create a node while constructing this node.
this.ownerDocument.createElement('div');
}

/**
Expand Down
7 changes: 5 additions & 2 deletions packages/happy-dom/test/event/events/SubmitEvent.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import Event from '../../../src/event/Event.js';
import SubmitEvent from '../../../src/event/events/SubmitEvent.js';
import HTMLButtonElement from '../../../src/nodes/html-button-element/HTMLButtonElement.js';
import IHTMLButtonElement from '../../../src/nodes/html-button-element/IHTMLButtonElement.js';
import Window from '../../../src/window/Window.js';
import { describe, it, expect } from 'vitest';

describe('SubmitEvent', () => {
describe('constructor', () => {
it('Creates a submit event.', () => {
const submitter = new HTMLButtonElement();
const window = new Window();
const document = window.document;
const submitter = document.createElement('button');
const event = new SubmitEvent('submit', { bubbles: true, submitter });
expect(event).toBeInstanceOf(Event);
expect(event.bubbles).toBe(true);
Expand Down
Loading