From e470b8e3b97c0ebb5316e9f81b56b774f2d9743b Mon Sep 17 00:00:00 2001 From: David Ortner Date: Sun, 14 Jan 2024 13:51:59 +0100 Subject: [PATCH] #1188@minor: Makes properties getters and setters in Node classes according to spec. --- packages/happy-dom/src/PropertySymbol.ts | 68 +++++- .../utilities/BrowserFrameNavigator.ts | 2 +- .../AbstractCSSStyleDeclaration.ts | 33 +-- .../CSSStyleDeclarationElementStyle.ts | 71 ++++-- .../dom-implementation/DOMImplementation.ts | 12 +- .../happy-dom/src/dom-parser/DOMParser.ts | 4 +- packages/happy-dom/src/event/Event.ts | 6 +- packages/happy-dom/src/event/EventTarget.ts | 8 +- packages/happy-dom/src/fetch/Request.ts | 12 +- packages/happy-dom/src/form-data/FormData.ts | 18 +- .../src/mutation-observer/MutationObserver.ts | 6 +- .../src/named-node-map/NamedNodeMap.ts | 20 +- packages/happy-dom/src/nodes/attr/Attr.ts | 70 +++++- .../src/nodes/character-data/CharacterData.ts | 4 +- .../src/nodes/child-node/ChildNodeUtility.ts | 16 +- .../child-node/NonDocumentChildNodeUtility.ts | 7 +- .../happy-dom/src/nodes/comment/Comment.ts | 5 +- .../document-fragment/DocumentFragment.ts | 12 +- .../src/nodes/document-type/DocumentType.ts | 43 +++- .../happy-dom/src/nodes/document/Document.ts | 152 ++++++++---- .../happy-dom/src/nodes/document/IDocument.ts | 2 +- .../happy-dom/src/nodes/element/Dataset.ts | 40 +-- .../happy-dom/src/nodes/element/Element.ts | 229 +++++++++++++----- .../src/nodes/element/ElementNamedNodeMap.ts | 91 ++++--- .../src/nodes/element/ElementUtility.ts | 45 ++-- .../happy-dom/src/nodes/element/IElement.ts | 6 +- .../html-anchor-element/HTMLAnchorElement.ts | 8 +- .../HTMLAnchorElementNamedNodeMap.ts | 16 +- .../html-base-element/HTMLBaseElement.ts | 3 +- .../html-button-element/HTMLButtonElement.ts | 35 ++- .../HTMLButtonElementNamedNodeMap.ts | 14 +- .../html-dialog-element/HTMLDialogElement.ts | 22 +- .../src/nodes/html-document/HTMLDocument.ts | 3 +- .../src/nodes/html-element/HTMLElement.ts | 194 ++++++++++++--- .../html-element/HTMLElementNamedNodeMap.ts | 9 +- .../nodes/html-element/HTMLElementUtility.ts | 27 ++- .../src/nodes/html-element/IHTMLElement.ts | 3 + .../html-form-element/HTMLFormElement.ts | 94 ++++--- .../html-iframe-element/HTMLIFrameElement.ts | 11 +- .../HTMLIFrameElementNamedNodeMap.ts | 7 +- .../HTMLIFrameElementPageLoader.ts | 4 +- .../html-image-element/HTMLImageElement.ts | 143 ++++++++--- .../html-input-element/HTMLInputElement.ts | 155 +++++++++--- .../HTMLInputElementNamedNodeMap.ts | 14 +- .../html-label-element/HTMLLabelElement.ts | 2 +- .../HTMLLabelElementUtility.ts | 5 +- .../html-link-element/HTMLLinkElement.ts | 20 +- .../HTMLLinkElementNamedNodeMap.ts | 15 +- .../HTMLLinkElementStyleSheetLoader.ts | 18 +- .../html-media-element/HTMLMediaElement.ts | 184 ++++++++++---- .../html-option-element/HTMLOptionElement.ts | 4 +- .../HTMLOptionElementNamedNodeMap.ts | 6 +- .../html-script-element/HTMLScriptElement.ts | 31 ++- .../HTMLScriptElementNamedNodeMap.ts | 8 +- .../HTMLScriptElementScriptLoader.ts | 22 +- .../html-select-element/HTMLSelectElement.ts | 100 +++++--- .../HTMLSelectElementNamedNodeMap.ts | 14 +- .../html-style-element/HTMLStyleElement.ts | 12 +- .../HTMLTemplateElement.ts | 40 ++- .../IHTMLTemplateElement.ts | 2 +- .../HTMLTextAreaElement.ts | 35 ++- .../HTMLTextAreaElementNamedNodeMap.ts | 14 +- .../HTMLUnknownElement.ts | 55 +++-- packages/happy-dom/src/nodes/node/INode.ts | 4 +- packages/happy-dom/src/nodes/node/Node.ts | 185 +++++++++----- .../happy-dom/src/nodes/node/NodeUtility.ts | 81 ++++--- .../nodes/parent-node/ParentNodeUtility.ts | 15 +- .../ProcessingInstruction.ts | 14 +- .../src/nodes/shadow-root/ShadowRoot.ts | 58 ++++- .../src/nodes/svg-element/SVGElement.ts | 11 +- .../svg-element/SVGElementNamedNodeMap.ts | 9 +- packages/happy-dom/src/nodes/text/Text.ts | 10 +- .../src/query-selector/QuerySelector.ts | 4 +- .../src/query-selector/SelectorItem.ts | 36 +-- packages/happy-dom/src/range/Range.ts | 191 ++++++++------- packages/happy-dom/src/range/RangeUtility.ts | 11 +- packages/happy-dom/src/selection/Selection.ts | 30 +-- .../happy-dom/src/tree-walker/TreeWalker.ts | 28 ++- .../src/validity-state/ValidityState.ts | 2 +- .../happy-dom/src/window/BrowserWindow.ts | 4 +- .../src/window/WindowErrorUtility.ts | 4 +- .../src/xml-http-request/XMLHttpRequest.ts | 2 +- .../happy-dom/src/xml-parser/XMLParser.ts | 30 ++- .../src/xml-serializer/XMLSerializer.ts | 42 ++-- .../test/nodes/document/Document.test.ts | 2 +- .../test/nodes/element/Element.test.ts | 6 +- .../nodes/html-element/HTMLElement.test.ts | 14 +- .../test/nodes/node/NodeUtility.test.ts | 16 +- 88 files changed, 2186 insertions(+), 954 deletions(-) diff --git a/packages/happy-dom/src/PropertySymbol.ts b/packages/happy-dom/src/PropertySymbol.ts index d78a074b4..116b41550 100644 --- a/packages/happy-dom/src/PropertySymbol.ts +++ b/packages/happy-dom/src/PropertySymbol.ts @@ -67,7 +67,6 @@ export const setupVMContext = Symbol('setupVMContext'); export const shadowRoot = Symbol('shadowRoot'); export const start = Symbol('start'); export const style = Symbol('style'); -export const styleSheet = Symbol('styleSheet'); export const target = Symbol('target'); export const textAreaNode = Symbol('textAreaNode'); export const unobserve = Symbol('unobserve'); @@ -82,3 +81,70 @@ export const mutationObservers = Symbol('mutationObservers'); export const openerFrame = Symbol('openerFrame'); export const openerWindow = Symbol('openerFrame'); export const popup = Symbol('popup'); +export const isConnected = Symbol('isConnected'); +export const parentNode = Symbol('parentNode'); +export const nodeType = Symbol('nodeType'); +export const tagName = Symbol('tagName'); +export const prefix = Symbol('prefix'); +export const scrollHeight = Symbol('scrollHeight'); +export const scrollWidth = Symbol('scrollWidth'); +export const scrollTop = Symbol('scrollTop'); +export const scrollLeft = Symbol('scrollLeft'); +export const attributes = Symbol('attributes'); +export const namespaceURI = Symbol('namespaceURI'); +export const accessKey = Symbol('accessKey'); +export const accessKeyLabel = Symbol('accessKeyLabel'); +export const contentEditable = Symbol('contentEditable'); +export const isContentEditable = Symbol('isContentEditable'); +export const offsetHeight = Symbol('offsetHeight'); +export const offsetWidth = Symbol('offsetWidth'); +export const offsetLeft = Symbol('offsetLeft'); +export const offsetTop = Symbol('offsetTop'); +export const clientHeight = Symbol('clientHeight'); +export const clientWidth = Symbol('clientWidth'); +export const clientLeft = Symbol('clientLeft'); +export const clientTop = Symbol('clientTop'); +export const name = Symbol('name'); +export const specified = Symbol('specified'); +export const adoptedStyleSheets = Symbol('adoptedStyleSheets'); +export const implementation = Symbol('implementation'); +export const readyState = Symbol('readyState'); +export const ownerWindow = Symbol('ownerWindow'); +export const publicId = Symbol('publicId'); +export const systemId = Symbol('systemId'); +export const validationMessage = Symbol('validationMessage'); +export const validity = Symbol('validity'); +export const returnValue = Symbol('returnValue'); +export const elements = Symbol('elements'); +export const length = Symbol('length'); +export const complete = Symbol('complete'); +export const naturalHeight = Symbol('naturalHeight'); +export const naturalWidth = Symbol('naturalWidth'); +export const loading = Symbol('loading'); +export const x = Symbol('x'); +export const y = Symbol('y'); +export const defaultChecked = Symbol('defaultChecked'); +export const files = Symbol('files'); +export const sheet = Symbol('sheet'); +export const volume = Symbol('volume'); +export const paused = Symbol('paused'); +export const currentTime = Symbol('currentTime'); +export const playbackRate = Symbol('playbackRate'); +export const defaultPlaybackRate = Symbol('defaultPlaybackRate'); +export const muted = Symbol('muted'); +export const defaultMuted = Symbol('defaultMuted'); +export const preservesPitch = Symbol('preservesPitch'); +export const buffered = Symbol('buffered'); +export const duration = Symbol('duration'); +export const error = Symbol('error'); +export const ended = Symbol('ended'); +export const networkState = Symbol('networkState'); +export const textTracks = Symbol('textTracks'); +export const videoTracks = Symbol('videoTracks'); +export const seeking = Symbol('seeking'); +export const seekable = Symbol('seekable'); +export const played = Symbol('played'); +export const options = Symbol('options'); +export const content = Symbol('content'); +export const mode = Symbol('mode'); +export const host = Symbol('host'); diff --git a/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts b/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts index 82e8cd0ac..fc95b2fba 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts @@ -100,7 +100,7 @@ export default class BrowserFrameNavigator { (frame.window.devicePixelRatio) = devicePixelRatio; if (options?.referrer) { - (frame.window.document.referrer) = options.referrer; + frame.window.document[PropertySymbol.referrer] = options.referrer; } if (targetURL.protocol === 'about:') { diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index 1f9942a78..073116e70 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -79,21 +79,21 @@ export default abstract class AbstractCSSStyleDeclaration { if (this.#ownerElement) { const style = new CSSStyleDeclarationPropertyManager({ cssText }); - let styleAttribute = this.#ownerElement.attributes['style']; + let styleAttribute = this.#ownerElement[PropertySymbol.attributes]['style']; if (!styleAttribute) { - styleAttribute = this.#ownerElement.ownerDocument.createAttribute('style'); + styleAttribute = this.#ownerElement[PropertySymbol.ownerDocument].createAttribute('style'); // We use "[PropertySymbol.setNamedItemWithoutConsequences]" here to avoid triggering setting "Element.style.cssText" when setting the "style" attribute. - (this.#ownerElement.attributes)[ + (this.#ownerElement[PropertySymbol.attributes])[ PropertySymbol.setNamedItemWithoutConsequences ](styleAttribute); } - if (this.#ownerElement.isConnected) { - this.#ownerElement.ownerDocument[PropertySymbol.cacheID]++; + if (this.#ownerElement[PropertySymbol.isConnected]) { + this.#ownerElement[PropertySymbol.ownerDocument][PropertySymbol.cacheID]++; } - styleAttribute.value = style.toString(); + styleAttribute[PropertySymbol.value] = style.toString(); } else { this.#style = new CSSStyleDeclarationPropertyManager({ cssText }); } @@ -136,25 +136,25 @@ export default abstract class AbstractCSSStyleDeclaration { if (!stringValue) { this.removeProperty(name); } else if (this.#ownerElement) { - let styleAttribute = this.#ownerElement.attributes['style']; + let styleAttribute = this.#ownerElement[PropertySymbol.attributes]['style']; if (!styleAttribute) { - styleAttribute = this.#ownerElement.ownerDocument.createAttribute('style'); + styleAttribute = this.#ownerElement[PropertySymbol.ownerDocument].createAttribute('style'); // We use "[PropertySymbol.setNamedItemWithoutConsequences]" here to avoid triggering setting "Element.style.cssText" when setting the "style" attribute. - (this.#ownerElement.attributes)[ + (this.#ownerElement[PropertySymbol.attributes])[ PropertySymbol.setNamedItemWithoutConsequences ](styleAttribute); } - if (this.#ownerElement.isConnected) { - this.#ownerElement.ownerDocument[PropertySymbol.cacheID]++; + if (this.#ownerElement[PropertySymbol.isConnected]) { + this.#ownerElement[PropertySymbol.ownerDocument][PropertySymbol.cacheID]++; } const style = this.#elementStyle.getElementStyle(); style.set(name, stringValue, !!priority); - styleAttribute.value = style.toString(); + styleAttribute[PropertySymbol.value] = style.toString(); } else { this.#style.set(name, stringValue, !!priority); } @@ -180,15 +180,16 @@ export default abstract class AbstractCSSStyleDeclaration { style.remove(name); const newCSSText = style.toString(); - if (this.#ownerElement.isConnected) { - this.#ownerElement.ownerDocument[PropertySymbol.cacheID]++; + if (this.#ownerElement[PropertySymbol.isConnected]) { + this.#ownerElement[PropertySymbol.ownerDocument][PropertySymbol.cacheID]++; } if (newCSSText) { - (this.#ownerElement.attributes['style']).value = newCSSText; + (this.#ownerElement[PropertySymbol.attributes]['style'])[PropertySymbol.value] = + newCSSText; } else { // We use "[PropertySymbol.removeNamedItemWithoutConsequences]" here to avoid triggering setting "Element.style.cssText" when setting the "style" attribute. - (this.#ownerElement.attributes)[ + (this.#ownerElement[PropertySymbol.attributes])[ PropertySymbol.removeNamedItemWithoutConsequences ]('style'); } diff --git a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts b/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts index 322763fa3..d6af77314 100644 --- a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts +++ b/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts @@ -65,7 +65,7 @@ export default class CSSStyleDeclarationElementStyle { return this.getComputedElementStyle(); } - const cssText = this.element.attributes['style']?.value; + const cssText = this.element[PropertySymbol.attributes]['style']?.[PropertySymbol.value]; if (cssText) { if (this.cache.propertyManager && this.cache.cssText === cssText) { @@ -94,24 +94,25 @@ export default class CSSStyleDeclarationElementStyle { }; let shadowRootElements: Array = []; - if (!this.element.isConnected) { + if (!this.element[PropertySymbol.isConnected]) { return new CSSStyleDeclarationPropertyManager(); } if ( this.cache.propertyManager && - this.cache.documentCacheID === this.element.ownerDocument[PropertySymbol.cacheID] + this.cache.documentCacheID === + this.element[PropertySymbol.ownerDocument][PropertySymbol.cacheID] ) { return this.cache.propertyManager; } - this.cache.documentCacheID = this.element.ownerDocument[PropertySymbol.cacheID]; + this.cache.documentCacheID = this.element[PropertySymbol.ownerDocument][PropertySymbol.cacheID]; // Walks through all parent elements and stores them in an array with element and matching CSS text. while (styleAndElement.element) { - if (styleAndElement.element.nodeType === NodeTypeEnum.elementNode) { + if (styleAndElement.element[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { const rootNode = styleAndElement.element.getRootNode(); - if (rootNode.nodeType === NodeTypeEnum.documentNode) { + if (rootNode[PropertySymbol.nodeType] === NodeTypeEnum.documentNode) { documentElements.unshift(styleAndElement); } else { shadowRootElements.unshift(styleAndElement); @@ -119,9 +120,11 @@ export default class CSSStyleDeclarationElementStyle { parentElements.unshift(styleAndElement); } - if (styleAndElement.element === this.element.ownerDocument) { + if (styleAndElement.element === this.element[PropertySymbol.ownerDocument]) { const styleSheets = >( - this.element.ownerDocument.querySelectorAll('style,link[rel="stylesheet"]') + this.element[PropertySymbol.ownerDocument].querySelectorAll( + 'style,link[rel="stylesheet"]' + ) ); for (const styleSheet of styleSheets) { @@ -136,7 +139,7 @@ export default class CSSStyleDeclarationElementStyle { styleAndElement = { element: null, cssTexts: [] }; } else if ( - styleAndElement.element.nodeType === NodeTypeEnum.documentFragmentNode && + styleAndElement.element[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode && (styleAndElement.element).host ) { const styleSheets = >( @@ -160,7 +163,10 @@ export default class CSSStyleDeclarationElementStyle { } shadowRootElements = []; } else { - styleAndElement = { element: styleAndElement.element.parentNode, cssTexts: [] }; + styleAndElement = { + element: styleAndElement.element[PropertySymbol.parentNode], + cssTexts: [] + }; } } @@ -175,36 +181,49 @@ export default class CSSStyleDeclarationElementStyle { parentElement.cssTexts.sort((a, b) => a.priorityWeight - b.priorityWeight); let elementCSSText = ''; - if (CSSStyleDeclarationElementDefaultCSS[(parentElement.element).tagName]) { + if ( + CSSStyleDeclarationElementDefaultCSS[ + (parentElement.element)[PropertySymbol.tagName] + ] + ) { if ( - typeof CSSStyleDeclarationElementDefaultCSS[(parentElement.element).tagName] === - 'string' + typeof CSSStyleDeclarationElementDefaultCSS[ + (parentElement.element)[PropertySymbol.tagName] + ] === 'string' ) { elementCSSText += - CSSStyleDeclarationElementDefaultCSS[(parentElement.element).tagName]; + CSSStyleDeclarationElementDefaultCSS[ + (parentElement.element)[PropertySymbol.tagName] + ]; } else { for (const key of Object.keys( - CSSStyleDeclarationElementDefaultCSS[(parentElement.element).tagName] + CSSStyleDeclarationElementDefaultCSS[ + (parentElement.element)[PropertySymbol.tagName] + ] )) { if (key === 'default' || !!parentElement.element[key]) { elementCSSText += - CSSStyleDeclarationElementDefaultCSS[(parentElement.element).tagName][ - key - ]; + CSSStyleDeclarationElementDefaultCSS[ + (parentElement.element)[PropertySymbol.tagName] + ][key]; } } } elementCSSText += - CSSStyleDeclarationElementDefaultCSS[(parentElement.element).tagName]; + CSSStyleDeclarationElementDefaultCSS[ + (parentElement.element)[PropertySymbol.tagName] + ]; } for (const cssText of parentElement.cssTexts) { elementCSSText += cssText.cssText; } - const elementStyleAttribute = (parentElement.element).attributes['style']; + const elementStyleAttribute = (parentElement.element)[PropertySymbol.attributes][ + 'style' + ]; if (elementStyleAttribute) { - elementCSSText += elementStyleAttribute.value; + elementCSSText += elementStyleAttribute[PropertySymbol.value]; } CSSStyleDeclarationCSSParser.parse(elementCSSText, (name, value, important) => { @@ -229,7 +248,7 @@ export default class CSSStyleDeclarationElementStyle { parentFontSize, parentSize: parentFontSize }); - if ((parentElement.element).tagName === 'HTML') { + if ((parentElement.element)[PropertySymbol.tagName] === 'HTML') { rootFontSize = parsedValue; } else if (parentElement !== targetElement) { parentFontSize = parsedValue; @@ -277,7 +296,7 @@ export default class CSSStyleDeclarationElementStyle { return; } - const ownerWindow = this.element.ownerDocument[PropertySymbol.defaultView]; + const ownerWindow = this.element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow]; for (const rule of options.cssRules) { if (rule.type === CSSRuleTypeEnum.styleRule) { @@ -308,7 +327,7 @@ export default class CSSStyleDeclarationElementStyle { new MediaQueryList({ ownerWindow, media: (rule).conditionText, - rootFontSize: this.element.tagName === 'HTML' ? 16 : null + rootFontSize: this.element[PropertySymbol.tagName] === 'HTML' ? 16 : null }).matches ) { this.parseCSSRules({ @@ -362,7 +381,7 @@ export default class CSSStyleDeclarationElementStyle { }): string { if ( WindowBrowserSettingsReader.getSettings( - this.element.ownerDocument[PropertySymbol.defaultView] + this.element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow] ).disableComputedStyleRendering ) { return options.value; @@ -375,7 +394,7 @@ export default class CSSStyleDeclarationElementStyle { while ((match = regexp.exec(options.value)) !== null) { if (match[1] !== 'px') { const valueInPixels = CSSMeasurementConverter.toPixels({ - ownerWindow: this.element.ownerDocument[PropertySymbol.defaultView], + ownerWindow: this.element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow], value: match[0], rootFontSize: options.rootFontSize, parentFontSize: options.parentFontSize, diff --git a/packages/happy-dom/src/dom-implementation/DOMImplementation.ts b/packages/happy-dom/src/dom-implementation/DOMImplementation.ts index 5a5b75224..c7d59e650 100644 --- a/packages/happy-dom/src/dom-implementation/DOMImplementation.ts +++ b/packages/happy-dom/src/dom-implementation/DOMImplementation.ts @@ -24,14 +24,14 @@ export default class DOMImplementation { * TODO: Not fully implemented. */ public createDocument(): IDocument { - return new this.#document[PropertySymbol.defaultView].HTMLDocument(); + return new this.#document[PropertySymbol.ownerWindow].HTMLDocument(); } /** * Creates and returns an HTML Document. */ public createHTMLDocument(): IDocument { - return new this.#document[PropertySymbol.defaultView].HTMLDocument(); + return new this.#document[PropertySymbol.ownerWindow].HTMLDocument(); } /** @@ -48,11 +48,11 @@ export default class DOMImplementation { ): DocumentType { const documentType = NodeFactory.createNode( this.#document, - this.#document[PropertySymbol.defaultView].DocumentType + this.#document[PropertySymbol.ownerWindow].DocumentType ); - documentType.name = qualifiedName; - documentType.publicId = publicId; - documentType.systemId = systemId; + documentType[PropertySymbol.name] = qualifiedName; + documentType[PropertySymbol.publicId] = publicId; + documentType[PropertySymbol.systemId] = systemId; return documentType; } } diff --git a/packages/happy-dom/src/dom-parser/DOMParser.ts b/packages/happy-dom/src/dom-parser/DOMParser.ts index 05019b006..ebb25baa0 100644 --- a/packages/happy-dom/src/dom-parser/DOMParser.ts +++ b/packages/happy-dom/src/dom-parser/DOMParser.ts @@ -1,10 +1,10 @@ import IDocument from '../nodes/document/IDocument.js'; import * as PropertySymbol from '../PropertySymbol.js'; import XMLParser from '../xml-parser/XMLParser.js'; -import Node from '../nodes/node/Node.js'; import DOMException from '../exception/DOMException.js'; import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js'; import IBrowserWindow from '../window/IBrowserWindow.js'; +import NodeTypeEnum from '../nodes/node/NodeTypeEnum.js'; /** * DOM parser. @@ -48,7 +48,7 @@ export default class DOMParser { for (const node of root[PropertySymbol.childNodes]) { if (node['tagName'] === 'HTML') { documentElement = node; - } else if (node.nodeType === Node.DOCUMENT_TYPE_NODE) { + } else if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { documentTypeNode = node; } diff --git a/packages/happy-dom/src/event/Event.ts b/packages/happy-dom/src/event/Event.ts index 28d9476a7..2ed9e5ca9 100644 --- a/packages/happy-dom/src/event/Event.ts +++ b/packages/happy-dom/src/event/Event.ts @@ -94,12 +94,12 @@ export default class Event { eventTarget = ((eventTarget)).parentNode; } else if ( this.composed && - (eventTarget).nodeType === NodeTypeEnum.documentFragmentNode && + (eventTarget)[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode && (eventTarget).host ) { eventTarget = (eventTarget).host; - } else if ((eventTarget).nodeType === NodeTypeEnum.documentNode) { - eventTarget = ((eventTarget))[PropertySymbol.defaultView]; + } else if ((eventTarget)[PropertySymbol.nodeType] === NodeTypeEnum.documentNode) { + eventTarget = ((eventTarget))[PropertySymbol.ownerWindow]; } else { break; } diff --git a/packages/happy-dom/src/event/EventTarget.ts b/packages/happy-dom/src/event/EventTarget.ts index 0db9e4913..0d8453098 100644 --- a/packages/happy-dom/src/event/EventTarget.ts +++ b/packages/happy-dom/src/event/EventTarget.ts @@ -279,11 +279,11 @@ export default abstract class EventTarget implements IEventTarget { * @returns Window. */ #getWindow(): IBrowserWindow | null { - if (((this)).ownerDocument) { - return ((this)).ownerDocument[PropertySymbol.defaultView]; + if (((this))[PropertySymbol.ownerDocument]) { + return ((this))[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow]; } - if (((this))[PropertySymbol.defaultView]) { - return ((this))[PropertySymbol.defaultView]; + if (((this))[PropertySymbol.ownerWindow]) { + return ((this))[PropertySymbol.ownerWindow]; } if (((this)).document) { return (this); diff --git a/packages/happy-dom/src/fetch/Request.ts b/packages/happy-dom/src/fetch/Request.ts index 2d3e399cc..5b3451449 100644 --- a/packages/happy-dom/src/fetch/Request.ts +++ b/packages/happy-dom/src/fetch/Request.ts @@ -44,11 +44,13 @@ export default class Request implements IRequest { public readonly credentials: IRequestCredentials; // Internal properties - public readonly [PropertySymbol.contentLength]: number | null = null; - public readonly [PropertySymbol.contentType]: string | null = null; + public [PropertySymbol.contentLength]: number | null = null; + public [PropertySymbol.contentType]: string | null = null; public [PropertySymbol.referrer]: '' | 'no-referrer' | 'client' | URL = 'client'; - public readonly [PropertySymbol.url]: URL; - public readonly [PropertySymbol.bodyBuffer]: Buffer | null; + public [PropertySymbol.url]: URL; + public [PropertySymbol.bodyBuffer]: Buffer | null; + + // Private properties readonly #window: IBrowserWindow; readonly #asyncTaskManager: AsyncTaskManager; @@ -149,7 +151,7 @@ export default class Request implements IRequest { * Returns owner document. */ protected get [PropertySymbol.ownerDocument](): IDocument { - throw new Error('[PropertySymbol.ownerDocument] needs to be implemented by sub-class.'); + throw new Error('[PropertySymbol.ownerDocument] getter needs to be implemented by sub-class.'); } /** diff --git a/packages/happy-dom/src/form-data/FormData.ts b/packages/happy-dom/src/form-data/FormData.ts index db88dbfc5..aacf094fa 100644 --- a/packages/happy-dom/src/form-data/FormData.ts +++ b/packages/happy-dom/src/form-data/FormData.ts @@ -29,14 +29,14 @@ export default class FormData implements Iterable<[string, string | File]> { constructor(form?: IHTMLFormElement) { if (form) { for (const name of Object.keys( - (form.elements)[PropertySymbol.namedItems] + (form[PropertySymbol.elements])[PropertySymbol.namedItems] )) { - let radioNodeList = (form.elements)[PropertySymbol.namedItems][ - name - ]; + let radioNodeList = (form[PropertySymbol.elements])[ + PropertySymbol.namedItems + ][name]; if ( - radioNodeList[0].tagName === 'INPUT' && + radioNodeList[0][PropertySymbol.tagName] === 'INPUT' && (radioNodeList[0].type === 'checkbox' || radioNodeList[0].type === 'radio') ) { const newRadioNodeList = new RadioNodeList(); @@ -50,12 +50,12 @@ export default class FormData implements Iterable<[string, string | File]> { } for (const node of radioNodeList) { - if (node.name && SUBMITTABLE_ELEMENTS.includes(node.tagName)) { - if (node.tagName === 'INPUT' && node.type === 'file') { - if ((node).files.length === 0) { + if (node.name && SUBMITTABLE_ELEMENTS.includes(node[PropertySymbol.tagName])) { + if (node[PropertySymbol.tagName] === 'INPUT' && node.type === 'file') { + if ((node)[PropertySymbol.files].length === 0) { this.append(node.name, new File([], '', { type: 'application/octet-stream' })); } else { - for (const file of (node).files) { + for (const file of (node)[PropertySymbol.files]) { this.append(node.name, file); } } diff --git a/packages/happy-dom/src/mutation-observer/MutationObserver.ts b/packages/happy-dom/src/mutation-observer/MutationObserver.ts index 95e306e71..620afef55 100644 --- a/packages/happy-dom/src/mutation-observer/MutationObserver.ts +++ b/packages/happy-dom/src/mutation-observer/MutationObserver.ts @@ -46,9 +46,9 @@ export default class MutationObserver { } if (!this.#window) { - this.#window = target.ownerDocument - ? target.ownerDocument[PropertySymbol.defaultView] - : target[PropertySymbol.defaultView]; + this.#window = target[PropertySymbol.ownerDocument] + ? target[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow] + : target[PropertySymbol.ownerWindow]; } // Makes sure that attribute names are lower case. diff --git a/packages/happy-dom/src/named-node-map/NamedNodeMap.ts b/packages/happy-dom/src/named-node-map/NamedNodeMap.ts index 96cb78469..20243f1ff 100644 --- a/packages/happy-dom/src/named-node-map/NamedNodeMap.ts +++ b/packages/happy-dom/src/named-node-map/NamedNodeMap.ts @@ -63,12 +63,16 @@ export default class NamedNodeMap implements INamedNodeMap { public getNamedItemNS(namespace: string, localName: string): IAttr | null { const attribute = this.getNamedItem(localName); - if (attribute && attribute.namespaceURI === namespace && attribute.localName === localName) { + if ( + attribute && + attribute[PropertySymbol.namespaceURI] === namespace && + attribute.localName === localName + ) { return attribute; } for (let i = 0, max = this.length; i < max; i++) { - if (this[i].namespaceURI === namespace && this[i].localName === localName) { + if (this[i][PropertySymbol.namespaceURI] === namespace && this[i].localName === localName) { return this[i]; } } @@ -125,7 +129,7 @@ export default class NamedNodeMap implements INamedNodeMap { public removeNamedItemNS(namespace: string, localName: string): IAttr | null { const attribute = this.getNamedItemNS(namespace, localName); if (attribute) { - return this.removeNamedItem(attribute.name); + return this.removeNamedItem(attribute[PropertySymbol.name]); } return null; } @@ -137,10 +141,10 @@ export default class NamedNodeMap implements INamedNodeMap { * @returns Replaced item. */ public [PropertySymbol.setNamedItemWithoutConsequences](item: IAttr): IAttr | null { - if (item.name) { - const replacedItem = this[PropertySymbol.namedItems][item.name] || null; + if (item[PropertySymbol.name]) { + const replacedItem = this[PropertySymbol.namedItems][item[PropertySymbol.name]] || null; - this[PropertySymbol.namedItems][item.name] = item; + this[PropertySymbol.namedItems][item[PropertySymbol.name]] = item; if (replacedItem) { this[PropertySymbol.removeNamedItemIndex](replacedItem); @@ -149,8 +153,8 @@ export default class NamedNodeMap implements INamedNodeMap { this[this.length] = item; this.length++; - if (this[PropertySymbol.isValidPropertyName](item.name)) { - this[item.name] = item; + if (this[PropertySymbol.isValidPropertyName](item[PropertySymbol.name])) { + this[item[PropertySymbol.name]] = item; } return replacedItem; diff --git a/packages/happy-dom/src/nodes/attr/Attr.ts b/packages/happy-dom/src/nodes/attr/Attr.ts index 9531f8cf8..08da10517 100644 --- a/packages/happy-dom/src/nodes/attr/Attr.ts +++ b/packages/happy-dom/src/nodes/attr/Attr.ts @@ -1,6 +1,8 @@ import IElement from '../element/IElement.js'; import Node from '../node/Node.js'; import IAttr from './IAttr.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import NodeTypeEnum from '../node/NodeTypeEnum.js'; /** * Attribute node interface. @@ -8,20 +10,57 @@ import IAttr from './IAttr.js'; * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Attr. */ export default class Attr extends Node implements IAttr { - public readonly nodeType = Node.ATTRIBUTE_NODE; - public value: string = null; - public name: string = null; - public namespaceURI: string = null; + public [PropertySymbol.nodeType] = NodeTypeEnum.attributeNode; + public [PropertySymbol.namespaceURI]: string | null = null; + public [PropertySymbol.name]: string | null = null; + public [PropertySymbol.value]: string | null = null; + public [PropertySymbol.specified] = true; + public [PropertySymbol.ownerElement]: IElement | null = null; /** - * @deprecated + * Returns specified. + * + * @returns Specified. + */ + public get specified(): boolean { + return this[PropertySymbol.specified]; + } + + /** + * Returns owner element. + * + * @returns Owner element. + */ + public get ownerElement(): IElement | null { + return this[PropertySymbol.ownerElement]; + } + + /** + * Returns value. + * + * @returns Value. + */ + public get value(): string { + return this[PropertySymbol.value]; + } + + /** + * Sets value. + * + * @param value Value. */ - public readonly ownerElement: IElement = null; + public set value(value: string) { + this[PropertySymbol.value] = value; + } /** - * @deprecated + * Returns name. + * + * @returns Name. */ - public readonly specified = true; + public get name(): string { + return this[PropertySymbol.name]; + } /** * Returns local name. @@ -29,7 +68,7 @@ export default class Attr extends Node implements IAttr { * @returns Local name. */ public get localName(): string { - return this.name ? this.name.split(':').reverse()[0] : null; + return this[PropertySymbol.name] ? this[PropertySymbol.name].split(':').reverse()[0] : null; } /** @@ -38,13 +77,22 @@ export default class Attr extends Node implements IAttr { * @returns Prefix. */ public get prefix(): string { - return this.name ? this.name.split(':')[0] : null; + return this[PropertySymbol.name] ? this[PropertySymbol.name].split(':')[0] : null; } /** * @override */ public get textContent(): string { - return this.value; + return this[PropertySymbol.value]; + } + + /** + * Returns namespace URI. + * + * @returns Namespace URI. + */ + public get namespaceURI(): string | null { + return this[PropertySymbol.namespaceURI]; } } diff --git a/packages/happy-dom/src/nodes/character-data/CharacterData.ts b/packages/happy-dom/src/nodes/character-data/CharacterData.ts index 3df313019..a78401e07 100644 --- a/packages/happy-dom/src/nodes/character-data/CharacterData.ts +++ b/packages/happy-dom/src/nodes/character-data/CharacterData.ts @@ -57,8 +57,8 @@ export default abstract class CharacterData extends Node implements ICharacterDa const oldValue = this[PropertySymbol.data]; this[PropertySymbol.data] = String(data); - if (this.isConnected) { - this.ownerDocument[PropertySymbol.cacheID]++; + if (this[PropertySymbol.isConnected]) { + this[PropertySymbol.ownerDocument][PropertySymbol.cacheID]++; } // MutationObserver diff --git a/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts b/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts index 2f494caa6..8c174856f 100644 --- a/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts +++ b/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts @@ -17,8 +17,8 @@ export default class ChildNodeUtility { * @param childNode Child node. */ public static remove(childNode: IChildNode): void { - if (childNode.parentNode) { - childNode.parentNode.removeChild(childNode); + if (childNode[PropertySymbol.parentNode]) { + childNode[PropertySymbol.parentNode].removeChild(childNode); } } @@ -29,7 +29,7 @@ export default class ChildNodeUtility { * @param nodes List of Node or DOMString. */ public static replaceWith(childNode: IChildNode, ...nodes: (INode | string)[]): void { - const parent = childNode.parentNode; + const parent = childNode[PropertySymbol.parentNode]; if (!parent) { throw new DOMException('This element has no parent node.'); @@ -38,7 +38,7 @@ export default class ChildNodeUtility { for (const node of nodes) { if (typeof node === 'string') { const newChildNodes = (( - XMLParser.parse(childNode.ownerDocument, node) + XMLParser.parse(childNode[PropertySymbol.ownerDocument], node) ))[PropertySymbol.childNodes].slice(); for (const newChildNode of newChildNodes) { parent.insertBefore(newChildNode, childNode); @@ -58,7 +58,7 @@ export default class ChildNodeUtility { * @param nodes List of Node or DOMString. */ public static before(childNode: IChildNode, ...nodes: (string | INode)[]): void { - const parent = childNode.parentNode; + const parent = childNode[PropertySymbol.parentNode]; if (!parent) { return; @@ -67,7 +67,7 @@ export default class ChildNodeUtility { for (const node of nodes) { if (typeof node === 'string') { const newChildNodes = (( - XMLParser.parse(childNode.ownerDocument, node) + XMLParser.parse(childNode[PropertySymbol.ownerDocument], node) ))[PropertySymbol.childNodes].slice(); for (const newChildNode of newChildNodes) { parent.insertBefore(newChildNode, childNode); @@ -85,7 +85,7 @@ export default class ChildNodeUtility { * @param nodes List of Node or DOMString. */ public static after(childNode: IChildNode, ...nodes: (string | INode)[]): void { - const parent = childNode.parentNode; + const parent = childNode[PropertySymbol.parentNode]; if (!parent) { return; @@ -96,7 +96,7 @@ export default class ChildNodeUtility { for (const node of nodes) { if (typeof node === 'string') { const newChildNodes = (( - XMLParser.parse(childNode.ownerDocument, node) + XMLParser.parse(childNode[PropertySymbol.ownerDocument], node) ))[PropertySymbol.childNodes].slice(); for (const newChildNode of newChildNodes) { if (!nextSibling) { diff --git a/packages/happy-dom/src/nodes/child-node/NonDocumentChildNodeUtility.ts b/packages/happy-dom/src/nodes/child-node/NonDocumentChildNodeUtility.ts index 3e919fe01..64b510f69 100644 --- a/packages/happy-dom/src/nodes/child-node/NonDocumentChildNodeUtility.ts +++ b/packages/happy-dom/src/nodes/child-node/NonDocumentChildNodeUtility.ts @@ -1,6 +1,7 @@ -import Element from '../element/Element.js'; import IElement from '../element/IElement.js'; +import NodeTypeEnum from '../node/NodeTypeEnum.js'; import INonDocumentTypeChildNode from './INonDocumentTypeChildNode.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * Non Document Child node utility. @@ -14,7 +15,7 @@ export default class NonDocumentChildNodeUtility { */ public static previousElementSibling(childNode: INonDocumentTypeChildNode): IElement { let sibling = childNode.previousSibling; - while (sibling && sibling.nodeType !== Element.ELEMENT_NODE) { + while (sibling && sibling[PropertySymbol.nodeType] !== NodeTypeEnum.elementNode) { sibling = sibling.previousSibling; } return sibling; @@ -28,7 +29,7 @@ export default class NonDocumentChildNodeUtility { */ public static nextElementSibling(childNode: INonDocumentTypeChildNode): IElement { let sibling = childNode.nextSibling; - while (sibling && sibling.nodeType !== Element.ELEMENT_NODE) { + while (sibling && sibling[PropertySymbol.nodeType] !== NodeTypeEnum.elementNode) { sibling = sibling.nextSibling; } return sibling; diff --git a/packages/happy-dom/src/nodes/comment/Comment.ts b/packages/happy-dom/src/nodes/comment/Comment.ts index 9057a5cb0..07d96b5c7 100644 --- a/packages/happy-dom/src/nodes/comment/Comment.ts +++ b/packages/happy-dom/src/nodes/comment/Comment.ts @@ -1,12 +1,13 @@ -import Node from '../node/Node.js'; import CharacterData from '../character-data/CharacterData.js'; import IComment from './IComment.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import NodeTypeEnum from '../node/NodeTypeEnum.js'; /** * Comment node. */ export default class Comment extends CharacterData implements IComment { - public readonly nodeType = Node.COMMENT_NODE; + public [PropertySymbol.nodeType] = NodeTypeEnum.commentNode; /** * Node name. diff --git a/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts b/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts index 920136b14..475b704e2 100644 --- a/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts +++ b/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts @@ -9,14 +9,15 @@ import IHTMLCollection from '../element/IHTMLCollection.js'; import ElementUtility from '../element/ElementUtility.js'; import HTMLCollection from '../element/HTMLCollection.js'; import INodeList from '../node/INodeList.js'; +import NodeTypeEnum from '../node/NodeTypeEnum.js'; /** * DocumentFragment. */ export default class DocumentFragment extends Node implements IDocumentFragment { - public nodeType = Node.DOCUMENT_FRAGMENT_NODE; public readonly [PropertySymbol.children]: IHTMLCollection = new HTMLCollection(); public [PropertySymbol.rootNode]: INode = this; + public [PropertySymbol.nodeType] = NodeTypeEnum.documentFragmentNode; /** * Returns the document fragment children. @@ -60,7 +61,10 @@ export default class DocumentFragment extends Node implements IDocumentFragment public get textContent(): string { let result = ''; for (const childNode of this[PropertySymbol.childNodes]) { - if (childNode.nodeType === Node.ELEMENT_NODE || childNode.nodeType === Node.TEXT_NODE) { + if ( + childNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode || + childNode[PropertySymbol.nodeType] === NodeTypeEnum.textNode + ) { result += childNode.textContent; } } @@ -77,7 +81,7 @@ export default class DocumentFragment extends Node implements IDocumentFragment this.removeChild(child); } if (textContent) { - this.appendChild(this.ownerDocument.createTextNode(textContent)); + this.appendChild(this[PropertySymbol.ownerDocument].createTextNode(textContent)); } } @@ -150,7 +154,7 @@ export default class DocumentFragment extends Node implements IDocumentFragment if (deep) { for (const node of clone[PropertySymbol.childNodes]) { - if (node.nodeType === Node.ELEMENT_NODE) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { clone[PropertySymbol.children].push(node); } } diff --git a/packages/happy-dom/src/nodes/document-type/DocumentType.ts b/packages/happy-dom/src/nodes/document-type/DocumentType.ts index 92121158e..13244147e 100644 --- a/packages/happy-dom/src/nodes/document-type/DocumentType.ts +++ b/packages/happy-dom/src/nodes/document-type/DocumentType.ts @@ -1,13 +1,42 @@ import Node from '../node/Node.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import NodeTypeEnum from '../node/NodeTypeEnum.js'; /** * DocumentType. */ export default class DocumentType extends Node { - public readonly nodeType = Node.DOCUMENT_TYPE_NODE; - public name: string = null; - public publicId = ''; - public systemId = ''; + public [PropertySymbol.nodeType] = NodeTypeEnum.documentTypeNode; + public [PropertySymbol.name] = ''; + public [PropertySymbol.publicId] = ''; + public [PropertySymbol.systemId] = ''; + + /** + * Returns name. + * + * @returns Name. + */ + public get name(): string { + return this[PropertySymbol.name]; + } + + /** + * Returns public ID. + * + * @returns Public ID. + */ + public get publicId(): string { + return this[PropertySymbol.publicId]; + } + + /** + * Returns system ID. + * + * @returns System ID. + */ + public get systemId(): string { + return this[PropertySymbol.systemId]; + } /** * Node name. @@ -36,9 +65,9 @@ export default class DocumentType extends Node { */ public cloneNode(deep = false): DocumentType { const clone = super.cloneNode(deep); - clone.name = this.name; - clone.publicId = this.publicId; - clone.systemId = this.systemId; + clone[PropertySymbol.name] = this[PropertySymbol.name]; + clone[PropertySymbol.publicId] = this[PropertySymbol.publicId]; + clone[PropertySymbol.systemId] = this[PropertySymbol.systemId]; return clone; } } diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index 5486f36f3..b0db19604 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -53,28 +53,27 @@ const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/; * Document. */ export default class Document extends Node implements IDocument { - public nodeType = Node.DOCUMENT_NODE; - public adoptedStyleSheets: CSSStyleSheet[] = []; - public readonly implementation: DOMImplementation; - public readonly readyState = DocumentReadyStateEnum.interactive; - public readonly isConnected: boolean = true; - public readonly defaultView: IBrowserWindow | null = null; - public readonly [PropertySymbol.defaultView]: IBrowserWindow; - public readonly referrer = ''; - public readonly [PropertySymbol.children]: IHTMLCollection = - new HTMLCollection(); + // Internal properties + public [PropertySymbol.children]: IHTMLCollection = new HTMLCollection(); public [PropertySymbol.activeElement]: IHTMLElement = null; public [PropertySymbol.nextActiveElement]: IHTMLElement = null; public [PropertySymbol.currentScript]: IHTMLScriptElement = null; public [PropertySymbol.rootNode] = this; - // Used as an unique identifier which is updated whenever the DOM gets modified. public [PropertySymbol.cacheID] = 0; - public [PropertySymbol.isFirstWrite] = true; public [PropertySymbol.isFirstWriteAfterOpen] = false; - - private [PropertySymbol.selection]: Selection = null; + public [PropertySymbol.nodeType] = NodeTypeEnum.documentNode; + public [PropertySymbol.isConnected] = true; + public [PropertySymbol.adoptedStyleSheets]: CSSStyleSheet[] = []; + public [PropertySymbol.implementation] = new DOMImplementation(this); + public [PropertySymbol.readyState] = DocumentReadyStateEnum.interactive; + public [PropertySymbol.referrer] = ''; + public [PropertySymbol.defaultView]: IBrowserWindow | null = null; + public [PropertySymbol.ownerWindow]: IBrowserWindow; + + // Private properties + #selection: Selection = null; #browserFrame: IBrowserFrame; // Events @@ -198,8 +197,61 @@ export default class Document extends Node implements IDocument { constructor(injected: { browserFrame: IBrowserFrame; window: IBrowserWindow }) { super(); this.#browserFrame = injected.browserFrame; - this[PropertySymbol.defaultView] = injected.window; - this.implementation = new DOMImplementation(this); + this[PropertySymbol.ownerWindow] = injected.window; + } + + /** + * Returns adopted style sheets. + * + * @returns Adopted style sheets. + */ + public get adoptedStyleSheets(): CSSStyleSheet[] { + return this[PropertySymbol.adoptedStyleSheets]; + } + + /** + * Sets adopted style sheets. + * + * @param value Adopted style sheets. + */ + public set adoptedStyleSheets(value: CSSStyleSheet[]) { + this[PropertySymbol.adoptedStyleSheets] = value; + } + + /** + * Returns DOM implementation. + * + * @returns DOM implementation. + */ + public get implementation(): DOMImplementation { + return this[PropertySymbol.implementation]; + } + + /** + * Returns document ready state. + * + * @returns Document ready state. + */ + public get readyState(): DocumentReadyStateEnum { + return this[PropertySymbol.readyState]; + } + + /** + * Returns referrer. + * + * @returns Referrer. + */ + public get referrer(): string { + return this[PropertySymbol.referrer]; + } + + /** + * Returns default view. + * + * @returns Default view. + */ + public get defaultView(): IBrowserWindow | null { + return this[PropertySymbol.defaultView]; } /** @@ -299,7 +351,7 @@ export default class Document extends Node implements IDocument { public get cookie(): string { return CookieStringUtility.cookiesToString( this.#browserFrame.page.context.cookieContainer.getCookies( - this[PropertySymbol.defaultView].location, + this[PropertySymbol.ownerWindow].location, true ) ); @@ -312,7 +364,7 @@ export default class Document extends Node implements IDocument { */ public set cookie(cookie: string) { this.#browserFrame.page.context.cookieContainer.addCookies([ - CookieStringUtility.stringToCookie(this[PropertySymbol.defaultView].location, cookie) + CookieStringUtility.stringToCookie(this[PropertySymbol.ownerWindow].location, cookie) ]); } @@ -391,7 +443,10 @@ export default class Document extends Node implements IDocument { * @returns Active element. */ public get activeElement(): IHTMLElement { - if (this[PropertySymbol.activeElement] && !this[PropertySymbol.activeElement].isConnected) { + if ( + this[PropertySymbol.activeElement] && + !this[PropertySymbol.activeElement][PropertySymbol.isConnected] + ) { this[PropertySymbol.activeElement] = null; } @@ -427,7 +482,7 @@ export default class Document extends Node implements IDocument { * @returns Location. */ public get location(): Location { - return this[PropertySymbol.defaultView].location; + return this[PropertySymbol.ownerWindow].location; } /** @@ -450,7 +505,7 @@ export default class Document extends Node implements IDocument { if (element) { return element.href; } - return this[PropertySymbol.defaultView].location.href; + return this[PropertySymbol.ownerWindow].location.href; } /** @@ -459,7 +514,7 @@ export default class Document extends Node implements IDocument { * @returns the URL of the current document. * */ public get URL(): string { - return this[PropertySymbol.defaultView].location.href; + return this[PropertySymbol.ownerWindow].location.href; } /** @@ -631,7 +686,7 @@ export default class Document extends Node implements IDocument { if (deep) { for (const node of clone[PropertySymbol.childNodes]) { - if (node.nodeType === Node.ELEMENT_NODE) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { clone[PropertySymbol.children].push(node); } } @@ -694,7 +749,7 @@ export default class Document extends Node implements IDocument { for (const node of root[PropertySymbol.childNodes]) { if (node['tagName'] === 'HTML') { documentElement = node; - } else if (node.nodeType === NodeTypeEnum.documentTypeNode) { + } else if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { documentTypeNode = node; } @@ -736,7 +791,10 @@ export default class Document extends Node implements IDocument { const body = ParentNodeUtility.getElementByTagName(this, 'body'); if (body) { for (const child of root[PropertySymbol.childNodes].slice()) { - if (child['tagName'] !== 'HTML' && child.nodeType !== NodeTypeEnum.documentTypeNode) { + if ( + child['tagName'] !== 'HTML' && + child[PropertySymbol.nodeType] !== NodeTypeEnum.documentTypeNode + ) { body.appendChild(child); } } @@ -825,20 +883,20 @@ export default class Document extends Node implements IDocument { let customElementClass; if (options && options.is) { - customElementClass = this[PropertySymbol.defaultView].customElements.get(String(options.is)); + customElementClass = this[PropertySymbol.ownerWindow].customElements.get(String(options.is)); } else { - customElementClass = this[PropertySymbol.defaultView].customElements.get(tagName); + customElementClass = this[PropertySymbol.ownerWindow].customElements.get(tagName); } const elementClass: typeof Element = customElementClass || - this[PropertySymbol.defaultView][ElementTag[tagName]] || + this[PropertySymbol.ownerWindow][ElementTag[tagName]] || HTMLUnknownElement; const element = NodeFactory.createNode(this, elementClass); - (element.tagName) = tagName; + element[PropertySymbol.tagName] = tagName; - (element.namespaceURI) = namespaceURI; + element[PropertySymbol.namespaceURI] = namespaceURI; if (element instanceof Element && options && options.is) { element[PropertySymbol.isValue] = String(options.is); } @@ -855,7 +913,7 @@ export default class Document extends Node implements IDocument { * @returns Text node. */ public createTextNode(data?: string): IText { - return NodeFactory.createNode(this, this[PropertySymbol.defaultView].Text, data); + return NodeFactory.createNode(this, this[PropertySymbol.ownerWindow].Text, data); } /** @@ -865,7 +923,7 @@ export default class Document extends Node implements IDocument { * @returns Text node. */ public createComment(data?: string): IComment { - return NodeFactory.createNode(this, this[PropertySymbol.defaultView].Comment, data); + return NodeFactory.createNode(this, this[PropertySymbol.ownerWindow].Comment, data); } /** @@ -874,7 +932,7 @@ export default class Document extends Node implements IDocument { * @returns Document fragment. */ public createDocumentFragment(): IDocumentFragment { - return new this[PropertySymbol.defaultView].DocumentFragment(); + return new this[PropertySymbol.ownerWindow].DocumentFragment(); } /** @@ -911,8 +969,8 @@ export default class Document extends Node implements IDocument { * @returns Event. */ public createEvent(type: string): Event { - if (typeof this[PropertySymbol.defaultView][type] === 'function') { - return new this[PropertySymbol.defaultView][type]('init'); + if (typeof this[PropertySymbol.ownerWindow][type] === 'function') { + return new this[PropertySymbol.ownerWindow][type]('init'); } return new Event('init'); } @@ -935,9 +993,9 @@ export default class Document extends Node implements IDocument { * @returns Element. */ public createAttributeNS(namespaceURI: string, qualifiedName: string): IAttr { - const attribute = NodeFactory.createNode(this, this[PropertySymbol.defaultView].Attr); - attribute.namespaceURI = namespaceURI; - attribute.name = qualifiedName; + const attribute = NodeFactory.createNode(this, this[PropertySymbol.ownerWindow].Attr); + attribute[PropertySymbol.namespaceURI] = namespaceURI; + attribute[PropertySymbol.name] = qualifiedName; return attribute; } @@ -962,7 +1020,7 @@ export default class Document extends Node implements IDocument { * @returns Range. */ public createRange(): Range { - return new this[PropertySymbol.defaultView].Range(); + return new this[PropertySymbol.ownerWindow].Range(); } /** @@ -976,7 +1034,9 @@ export default class Document extends Node implements IDocument { throw new DOMException('Parameter 1 was not of type Node.'); } - const adopted = node.parentNode ? node.parentNode.removeChild(node) : node; + const adopted = node[PropertySymbol.parentNode] + ? node[PropertySymbol.parentNode].removeChild(node) + : node; const document = this; Object.defineProperty(adopted, 'ownerDocument', { value: document }); return adopted; @@ -988,10 +1048,10 @@ export default class Document extends Node implements IDocument { * @returns Selection. */ public getSelection(): Selection { - if (!this[PropertySymbol.selection]) { - this[PropertySymbol.selection] = new Selection(this); + if (!this.#selection) { + this.#selection = new Selection(this); } - return this[PropertySymbol.selection]; + return this.#selection; } /** @@ -1023,10 +1083,10 @@ export default class Document extends Node implements IDocument { } const processingInstruction = NodeFactory.createNode( this, - this[PropertySymbol.defaultView].ProcessingInstruction, + this[PropertySymbol.ownerWindow].ProcessingInstruction, data ); - processingInstruction.target = target; + processingInstruction[PropertySymbol.target] = target; return processingInstruction; } @@ -1036,7 +1096,7 @@ export default class Document extends Node implements IDocument { * @param node Node. */ #importNode(node: INode): void { - (node.ownerDocument) = this; + node[PropertySymbol.ownerDocument] = this; for (const child of node[PropertySymbol.childNodes]) { this.#importNode(child); diff --git a/packages/happy-dom/src/nodes/document/IDocument.ts b/packages/happy-dom/src/nodes/document/IDocument.ts index dcc4d5b10..0f05c29ee 100644 --- a/packages/happy-dom/src/nodes/document/IDocument.ts +++ b/packages/happy-dom/src/nodes/document/IDocument.ts @@ -29,7 +29,7 @@ import VisibilityStateEnum from './VisibilityStateEnum.js'; */ export default interface IDocument extends IParentNode { readonly defaultView: IBrowserWindow | null; - readonly [PropertySymbol.defaultView]: IBrowserWindow; + readonly [PropertySymbol.ownerWindow]: IBrowserWindow; readonly implementation: DOMImplementation; readonly documentElement: IHTMLElement; readonly doctype: IDocumentType; diff --git a/packages/happy-dom/src/nodes/element/Dataset.ts b/packages/happy-dom/src/nodes/element/Dataset.ts index b8e59d335..e842421e3 100644 --- a/packages/happy-dom/src/nodes/element/Dataset.ts +++ b/packages/happy-dom/src/nodes/element/Dataset.ts @@ -1,4 +1,4 @@ -import Element from '../element/Element.js'; +import Element from './Element.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import HTMLElementNamedNodeMap from '../html-element/HTMLElementNamedNodeMap.js'; @@ -23,11 +23,11 @@ export default class Dataset { // Build the initial dataset record from all data attributes. const dataset: DatasetRecord = {}; - for (let i = 0, max = element.attributes.length; i < max; i++) { - const attribute = element.attributes[i]; - if (attribute.name.startsWith('data-')) { - const key = Dataset.kebabToCamelCase(attribute.name.replace('data-', '')); - dataset[key] = attribute.value; + for (let i = 0, max = element[PropertySymbol.attributes].length; i < max; i++) { + const attribute = element[PropertySymbol.attributes][i]; + if (attribute[PropertySymbol.name].startsWith('data-')) { + const key = Dataset.kebabToCamelCase(attribute[PropertySymbol.name].replace('data-', '')); + dataset[key] = attribute[PropertySymbol.value]; } } @@ -35,9 +35,11 @@ export default class Dataset { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy this.proxy = new Proxy(dataset, { get(dataset: DatasetRecord, key: string): string { - const attribute = element.attributes.getNamedItem('data-' + Dataset.camelCaseToKebab(key)); + const attribute = element[PropertySymbol.attributes].getNamedItem( + 'data-' + Dataset.camelCaseToKebab(key) + ); if (attribute) { - return (dataset[key] = attribute.value); + return (dataset[key] = attribute[PropertySymbol.value]); } delete dataset[key]; return undefined; @@ -48,9 +50,9 @@ export default class Dataset { return true; }, deleteProperty(dataset: DatasetRecord, key: string): boolean { - (element.attributes)[PropertySymbol.removeNamedItem]( - 'data-' + Dataset.camelCaseToKebab(key) - ); + (element[PropertySymbol.attributes])[ + PropertySymbol.removeNamedItem + ]('data-' + Dataset.camelCaseToKebab(key)); return delete dataset[key]; }, ownKeys(dataset: DatasetRecord): string[] { @@ -59,12 +61,14 @@ export default class Dataset { // "The result List must contain the keys of all non-configurable own properties of the target object." const keys = []; const deleteKeys = []; - for (let i = 0, max = element.attributes.length; i < max; i++) { - const attribute = element.attributes[i]; - if (attribute.name.startsWith('data-')) { - const key = Dataset.kebabToCamelCase(attribute.name.replace('data-', '')); + for (let i = 0, max = element[PropertySymbol.attributes].length; i < max; i++) { + const attribute = element[PropertySymbol.attributes][i]; + if (attribute[PropertySymbol.name].startsWith('data-')) { + const key = Dataset.kebabToCamelCase( + attribute[PropertySymbol.name].replace('data-', '') + ); keys.push(key); - dataset[key] = attribute.value; + dataset[key] = attribute[PropertySymbol.value]; if (!dataset[key]) { deleteKeys.push(key); } @@ -76,7 +80,9 @@ export default class Dataset { return keys; }, has(_dataset: DatasetRecord, key: string): boolean { - return !!element.attributes.getNamedItem('data-' + Dataset.camelCaseToKebab(key)); + return !!element[PropertySymbol.attributes].getNamedItem( + 'data-' + Dataset.camelCaseToKebab(key) + ); } }); } diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index 0b101d821..9af69c540 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -33,6 +33,7 @@ import WindowErrorUtility from '../../window/WindowErrorUtility.js'; import WindowBrowserSettingsReader from '../../window/WindowBrowserSettingsReader.js'; import BrowserErrorCaptureEnum from '../../browser/enums/BrowserErrorCaptureEnum.js'; import NodeFactory from '../NodeFactory.js'; +import NodeTypeEnum from '../node/NodeTypeEnum.js'; /** * Element. @@ -42,17 +43,6 @@ export default class Element extends Node implements IElement { // CustomElementRegistry will therefore populate "[PropertySymbol.observedAttributes]" when CustomElementRegistry.define() is called public static [PropertySymbol.observedAttributes]: string[]; public static observedAttributes: string[]; - public tagName: string = null; - public nodeType = Node.ELEMENT_NODE; - public shadowRoot: IShadowRoot | null = null; - public prefix: string = null; - - public scrollHeight = 0; - public scrollWidth = 0; - public scrollTop = 0; - public scrollLeft = 0; - public readonly namespaceURI: string = null; - public readonly attributes: INamedNodeMap = new ElementNamedNodeMap(this); // Events public oncancel: (event: Event) => void | null = null; @@ -92,10 +82,119 @@ export default class Element extends Node implements IElement { // Internal properties public [PropertySymbol.children]: IHTMLCollection = new HTMLCollection(); - public [PropertySymbol.shadowRoot]: IShadowRoot = null; public [PropertySymbol.classList]: DOMTokenList = null; public [PropertySymbol.isValue]: string | null = null; public [PropertySymbol.computedStyle]: CSSStyleDeclaration | null = null; + public [PropertySymbol.nodeType] = NodeTypeEnum.elementNode; + public [PropertySymbol.tagName]: string | null = null; + public [PropertySymbol.prefix]: string | null = null; + public [PropertySymbol.shadowRoot]: IShadowRoot | null = null; + public [PropertySymbol.scrollHeight] = 0; + public [PropertySymbol.scrollWidth] = 0; + public [PropertySymbol.scrollTop] = 0; + public [PropertySymbol.scrollLeft] = 0; + public [PropertySymbol.attributes]: INamedNodeMap = new ElementNamedNodeMap(this); + public [PropertySymbol.namespaceURI]: string | null = null; + + /** + * Returns tag name. + * + * @returns Tag name. + */ + public get tagName(): string { + return this[PropertySymbol.tagName]; + } + + /** + * Returns prefix. + * + * @returns Prefix. + */ + public get prefix(): string | null { + return this[PropertySymbol.prefix]; + } + + /** + * Returns shadow root. + * + * @returns Shadow root. + */ + public get shadowRoot(): IShadowRoot | null { + const shadowRoot = this[PropertySymbol.shadowRoot]; + return shadowRoot && shadowRoot[PropertySymbol.mode] === 'open' ? shadowRoot : null; + } + + /** + * Returns scroll height. + * + * @returns Scroll height. + */ + public get scrollHeight(): number { + return this[PropertySymbol.scrollHeight]; + } + + /** + * Returns scroll width. + * + * @returns Scroll width. + */ + public get scrollWidth(): number { + return this[PropertySymbol.scrollWidth]; + } + + /** + * Returns scroll top. + * + * @returns Scroll top. + */ + public get scrollTop(): number { + return this[PropertySymbol.scrollTop]; + } + + /** + * Sets scroll top. + * + * @param value Scroll top. + */ + public set scrollTop(value: number) { + this[PropertySymbol.scrollTop] = value; + } + + /** + * Returns scroll left. + * + * @returns Scroll left. + */ + public get scrollLeft(): number { + return this[PropertySymbol.scrollLeft]; + } + + /** + * Sets scroll left. + * + * @param value Scroll left. + */ + public set scrollLeft(value: number) { + this[PropertySymbol.scrollLeft] = value; + } + + /** + * Returns attributes. + * + * @returns Attributes. + */ + public get attributes(): INamedNodeMap { + return this[PropertySymbol.attributes]; + } + + /** + * Returns namespace URI. + * + * @returns Namespace URI. + */ + public get namespaceURI(): string | null { + return this[PropertySymbol.namespaceURI]; + } /** * Returns element children. @@ -158,7 +257,7 @@ export default class Element extends Node implements IElement { * @returns Node name. */ public get nodeName(): string { - return this.tagName; + return this[PropertySymbol.tagName]; } /** @@ -167,7 +266,7 @@ export default class Element extends Node implements IElement { * @returns Local name. */ public get localName(): string { - return this.tagName ? this.tagName.toLowerCase() : 'unknown'; + return this[PropertySymbol.tagName] ? this[PropertySymbol.tagName].toLowerCase() : 'unknown'; } /** @@ -214,7 +313,10 @@ export default class Element extends Node implements IElement { public get textContent(): string { let result = ''; for (const childNode of this[PropertySymbol.childNodes]) { - if (childNode.nodeType === Node.ELEMENT_NODE || childNode.nodeType === Node.TEXT_NODE) { + if ( + childNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode || + childNode[PropertySymbol.nodeType] === NodeTypeEnum.textNode + ) { result += childNode.textContent; } } @@ -231,7 +333,7 @@ export default class Element extends Node implements IElement { this.removeChild(child); } if (textContent) { - this.appendChild(this.ownerDocument.createTextNode(textContent)); + this.appendChild(this[PropertySymbol.ownerDocument].createTextNode(textContent)); } } @@ -254,7 +356,7 @@ export default class Element extends Node implements IElement { this.removeChild(child); } - XMLParser.parse(this.ownerDocument, html, { rootNode: this }); + XMLParser.parse(this[PropertySymbol.ownerDocument], html, { rootNode: this }); } /** @@ -362,11 +464,14 @@ export default class Element extends Node implements IElement { public cloneNode(deep = false): IElement { const clone = super.cloneNode(deep); - for (let i = 0, max = this.attributes.length; i < max; i++) { - const attribute = this.attributes[i]; - clone.attributes.setNamedItem( + for (let i = 0, max = this[PropertySymbol.attributes].length; i < max; i++) { + const attribute = this[PropertySymbol.attributes][i]; + clone[PropertySymbol.attributes].setNamedItem( Object.assign( - this.ownerDocument.createAttributeNS(attribute.namespaceURI, attribute.name), + this[PropertySymbol.ownerDocument].createAttributeNS( + attribute[PropertySymbol.namespaceURI], + attribute[PropertySymbol.name] + ), attribute ) ); @@ -374,14 +479,14 @@ export default class Element extends Node implements IElement { if (deep) { for (const node of clone[PropertySymbol.childNodes]) { - if (node.nodeType === Node.ELEMENT_NODE) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { clone[PropertySymbol.children].push(node); } } } - (clone.tagName) = this.tagName; - (clone.namespaceURI) = this.namespaceURI; + clone[PropertySymbol.tagName] = this[PropertySymbol.tagName]; + clone[PropertySymbol.namespaceURI] = this[PropertySymbol.namespaceURI]; return clone; } @@ -513,9 +618,9 @@ export default class Element extends Node implements IElement { * @param text HTML string to insert. */ public insertAdjacentHTML(position: TInsertAdjacentPositions, text: string): void { - for (const node of (XMLParser.parse(this.ownerDocument, text))[ - PropertySymbol.childNodes - ].slice()) { + for (const node of (( + XMLParser.parse(this[PropertySymbol.ownerDocument], text) + ))[PropertySymbol.childNodes].slice()) { this.insertAdjacentElement(position, node); } } @@ -530,7 +635,7 @@ export default class Element extends Node implements IElement { if (!text) { return; } - const textNode = this.ownerDocument.createTextNode(text); + const textNode = this[PropertySymbol.ownerDocument].createTextNode(text); this.insertAdjacentElement(position, textNode); } @@ -541,8 +646,8 @@ export default class Element extends Node implements IElement { * @param value Value. */ public setAttribute(name: string, value: string): void { - const attribute = this.ownerDocument.createAttributeNS(null, name); - attribute.value = String(value); + const attribute = this[PropertySymbol.ownerDocument].createAttributeNS(null, name); + attribute[PropertySymbol.value] = String(value); this.setAttributeNode(attribute); } @@ -554,8 +659,8 @@ export default class Element extends Node implements IElement { * @param value Value. */ public setAttributeNS(namespaceURI: string, name: string, value: string): void { - const attribute = this.ownerDocument.createAttributeNS(namespaceURI, name); - attribute.value = String(value); + const attribute = this[PropertySymbol.ownerDocument].createAttributeNS(namespaceURI, name); + attribute[PropertySymbol.value] = String(value); this.setAttributeNode(attribute); } @@ -566,8 +671,8 @@ export default class Element extends Node implements IElement { */ public getAttributeNames(): string[] { const attributeNames = []; - for (let i = 0, max = this.attributes.length; i < max; i++) { - attributeNames.push(this.attributes[i].name); + for (let i = 0, max = this[PropertySymbol.attributes].length; i < max; i++) { + attributeNames.push(this[PropertySymbol.attributes][i][PropertySymbol.name]); } return attributeNames; } @@ -580,7 +685,7 @@ export default class Element extends Node implements IElement { public getAttribute(name: string): string { const attribute = this.getAttributeNode(name); if (attribute) { - return attribute.value; + return attribute[PropertySymbol.value]; } return null; } @@ -618,7 +723,7 @@ export default class Element extends Node implements IElement { public getAttributeNS(namespace: string | null, localName: string): string { const attribute = this.getAttributeNodeNS(namespace, localName); if (attribute) { - return attribute.value; + return attribute[PropertySymbol.value]; } return null; } @@ -641,7 +746,7 @@ export default class Element extends Node implements IElement { * @returns True if attribute exists, false otherwise. */ public hasAttributeNS(namespace: string | null, localName: string): boolean { - return this.attributes.getNamedItemNS(namespace, localName) !== null; + return this[PropertySymbol.attributes].getNamedItemNS(namespace, localName) !== null; } /** @@ -650,7 +755,7 @@ export default class Element extends Node implements IElement { * @returns "true" if the element has attributes. */ public hasAttributes(): boolean { - return this.attributes.length > 0; + return this[PropertySymbol.attributes].length > 0; } /** @@ -660,7 +765,7 @@ export default class Element extends Node implements IElement { */ public removeAttribute(name: string): void { try { - this.attributes.removeNamedItem(name); + this[PropertySymbol.attributes].removeNamedItem(name); } catch (error) { // Ignore DOMException when the attribute does not exist. } @@ -673,7 +778,7 @@ export default class Element extends Node implements IElement { * @param localName Local name. */ public removeAttributeNS(namespace: string | null, localName: string): void { - this.attributes.removeNamedItemNS(namespace, localName); + this[PropertySymbol.attributes].removeNamedItemNS(namespace, localName); } /** @@ -683,26 +788,22 @@ export default class Element extends Node implements IElement { * @param init.mode Shadow root mode. * @returns Shadow root. */ - public attachShadow(init: { mode: string }): IShadowRoot { + public attachShadow(init: { mode: 'open' | 'closed' }): IShadowRoot { if (this[PropertySymbol.shadowRoot]) { throw new DOMException('Shadow root has already been attached.'); } const shadowRoot = NodeFactory.createNode( - this.ownerDocument, - this.ownerDocument[PropertySymbol.defaultView].ShadowRoot + this[PropertySymbol.ownerDocument], + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].ShadowRoot ); - (this[PropertySymbol.shadowRoot]) = shadowRoot; + this[PropertySymbol.shadowRoot] = shadowRoot; - (shadowRoot.host) = this; - (shadowRoot.mode) = init.mode; + shadowRoot[PropertySymbol.host] = this; + shadowRoot[PropertySymbol.mode] = init.mode; (shadowRoot)[PropertySymbol.connectToNode](this); - if (shadowRoot.mode === 'open') { - (this.shadowRoot) = shadowRoot; - } - return this[PropertySymbol.shadowRoot]; } @@ -824,7 +925,7 @@ export default class Element extends Node implements IElement { * @returns Replaced attribute. */ public setAttributeNode(attribute: IAttr): IAttr | null { - return this.attributes.setNamedItem(attribute); + return this[PropertySymbol.attributes].setNamedItem(attribute); } /** @@ -834,7 +935,7 @@ export default class Element extends Node implements IElement { * @returns Replaced attribute. */ public setAttributeNodeNS(attribute: IAttr): IAttr | null { - return this.attributes.setNamedItemNS(attribute); + return this[PropertySymbol.attributes].setNamedItemNS(attribute); } /** @@ -844,7 +945,7 @@ export default class Element extends Node implements IElement { * @returns Replaced attribute. */ public getAttributeNode(name: string): IAttr | null { - return this.attributes.getNamedItem(name); + return this[PropertySymbol.attributes].getNamedItem(name); } /** @@ -855,7 +956,7 @@ export default class Element extends Node implements IElement { * @returns Replaced attribute. */ public getAttributeNodeNS(namespace: string | null, localName: string): IAttr | null { - return this.attributes.getNamedItemNS(namespace, localName); + return this[PropertySymbol.attributes].getNamedItemNS(namespace, localName); } /** @@ -865,7 +966,7 @@ export default class Element extends Node implements IElement { * @returns Removed attribute. */ public removeAttributeNode(attribute: IAttr): IAttr | null { - return this.attributes.removeNamedItem(attribute.name); + return this[PropertySymbol.attributes].removeNamedItem(attribute[PropertySymbol.name]); } /** @@ -875,7 +976,10 @@ export default class Element extends Node implements IElement { * @returns Removed attribute. */ public removeAttributeNodeNS(attribute: IAttr): IAttr | null { - return this.attributes.removeNamedItemNS(attribute.namespaceURI, attribute.localName); + return this[PropertySymbol.attributes].removeNamedItemNS( + attribute[PropertySymbol.namespaceURI], + attribute.localName + ); } /** @@ -887,7 +991,7 @@ export default class Element extends Node implements IElement { public scroll(x: { top?: number; left?: number; behavior?: string } | number, y?: number): void { if (typeof x === 'object') { if (x.behavior === 'smooth') { - this.ownerDocument[PropertySymbol.defaultView].setTimeout(() => { + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].setTimeout(() => { if (x.top !== undefined) { (this.scrollTop) = x.top; } @@ -928,7 +1032,7 @@ export default class Element extends Node implements IElement { public override dispatchEvent(event: Event): boolean { const returnValue = super.dispatchEvent(event); const browserSettings = WindowBrowserSettingsReader.getSettings( - this.ownerDocument[PropertySymbol.defaultView] + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow] ); if ( @@ -942,17 +1046,18 @@ export default class Element extends Node implements IElement { if (attribute && !event[PropertySymbol.immediatePropagationStopped]) { const code = `//# sourceURL=${ - this.ownerDocument[PropertySymbol.defaultView].location.href + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location.href }\n${attribute}`; if ( browserSettings.disableErrorCapturing || browserSettings.errorCapture !== BrowserErrorCaptureEnum.tryAndCatch ) { - this.ownerDocument[PropertySymbol.defaultView].eval(code); + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].eval(code); } else { - WindowErrorUtility.captureError(this.ownerDocument[PropertySymbol.defaultView], () => - this.ownerDocument[PropertySymbol.defaultView].eval(code) + WindowErrorUtility.captureError( + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow], + () => this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].eval(code) ); } } diff --git a/packages/happy-dom/src/nodes/element/ElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/element/ElementNamedNodeMap.ts index e9d763d7f..1b3a4de9c 100644 --- a/packages/happy-dom/src/nodes/element/ElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/element/ElementNamedNodeMap.ts @@ -45,39 +45,51 @@ export default class ElementNamedNodeMap extends NamedNodeMap { * @override */ public override setNamedItem(item: IAttr): IAttr | null { - if (!item.name) { + if (!item[PropertySymbol.name]) { return null; } - item.name = this[PropertySymbol.getAttributeName](item.name); - (item.ownerElement) = this[PropertySymbol.ownerElement]; + item[PropertySymbol.name] = this[PropertySymbol.getAttributeName](item[PropertySymbol.name]); + (item[PropertySymbol.ownerElement]) = this[PropertySymbol.ownerElement]; const replacedItem = super.setNamedItem(item); - const oldValue = replacedItem ? replacedItem.value : null; + const oldValue = replacedItem ? replacedItem[PropertySymbol.value] : null; - if (this[PropertySymbol.ownerElement].isConnected) { + if (this[PropertySymbol.ownerElement][PropertySymbol.isConnected]) { this[PropertySymbol.ownerElement].ownerDocument[PropertySymbol.cacheID]++; } - if (item.name === 'class' && this[PropertySymbol.ownerElement][PropertySymbol.classList]) { + if ( + item[PropertySymbol.name] === 'class' && + this[PropertySymbol.ownerElement][PropertySymbol.classList] + ) { this[PropertySymbol.ownerElement][PropertySymbol.classList][PropertySymbol.updateIndices](); } - if (item.name === 'id' || item.name === 'name') { + if (item[PropertySymbol.name] === 'id' || item[PropertySymbol.name] === 'name') { if ( - this[PropertySymbol.ownerElement].parentNode && - (this[PropertySymbol.ownerElement].parentNode)[PropertySymbol.children] && - item.value !== oldValue + this[PropertySymbol.ownerElement][PropertySymbol.parentNode] && + (this[PropertySymbol.ownerElement][PropertySymbol.parentNode])[ + PropertySymbol.children + ] && + item[PropertySymbol.value] !== oldValue ) { if (oldValue) { (>( - (this[PropertySymbol.ownerElement].parentNode)[PropertySymbol.children] + (this[PropertySymbol.ownerElement][PropertySymbol.parentNode])[ + PropertySymbol.children + ] ))[PropertySymbol.removeNamedItem](this[PropertySymbol.ownerElement], oldValue); } - if (item.value) { + if (item[PropertySymbol.value]) { (>( - (this[PropertySymbol.ownerElement].parentNode)[PropertySymbol.children] - ))[PropertySymbol.appendNamedItem](this[PropertySymbol.ownerElement], item.value); + (this[PropertySymbol.ownerElement][PropertySymbol.parentNode])[ + PropertySymbol.children + ] + ))[PropertySymbol.appendNamedItem]( + this[PropertySymbol.ownerElement], + item[PropertySymbol.value] + ); } } } @@ -89,9 +101,13 @@ export default class ElementNamedNodeMap extends NamedNodeMap { ] && (this[PropertySymbol.ownerElement].constructor)[ PropertySymbol.observedAttributes - ].includes(item.name) + ].includes(item[PropertySymbol.name]) ) { - this[PropertySymbol.ownerElement].attributeChangedCallback(item.name, oldValue, item.value); + this[PropertySymbol.ownerElement].attributeChangedCallback( + item[PropertySymbol.name], + oldValue, + item[PropertySymbol.value] + ); } // MutationObserver @@ -102,13 +118,13 @@ export default class ElementNamedNodeMap extends NamedNodeMap { if ( observer.options.attributes && (!observer.options.attributeFilter || - observer.options.attributeFilter.includes(item.name)) + observer.options.attributeFilter.includes(item[PropertySymbol.name])) ) { observer.report( new MutationRecord({ target: this[PropertySymbol.ownerElement], type: MutationTypeEnum.attributes, - attributeName: item.name, + attributeName: item[PropertySymbol.name], oldValue: observer.options.attributeOldValue ? oldValue : null }) ); @@ -131,26 +147,33 @@ export default class ElementNamedNodeMap extends NamedNodeMap { return null; } - if (this[PropertySymbol.ownerElement].isConnected) { + if (this[PropertySymbol.ownerElement][PropertySymbol.isConnected]) { this[PropertySymbol.ownerElement].ownerDocument[PropertySymbol.cacheID]++; } if ( - removedItem.name === 'class' && + removedItem[PropertySymbol.name] === 'class' && this[PropertySymbol.ownerElement][PropertySymbol.classList] ) { this[PropertySymbol.ownerElement][PropertySymbol.classList][PropertySymbol.updateIndices](); } - if (removedItem.name === 'id' || removedItem.name === 'name') { + if (removedItem[PropertySymbol.name] === 'id' || removedItem[PropertySymbol.name] === 'name') { if ( - this[PropertySymbol.ownerElement].parentNode && - (this[PropertySymbol.ownerElement].parentNode)[PropertySymbol.children] && - removedItem.value + this[PropertySymbol.ownerElement][PropertySymbol.parentNode] && + (this[PropertySymbol.ownerElement][PropertySymbol.parentNode])[ + PropertySymbol.children + ] && + removedItem[PropertySymbol.value] ) { (>( - (this[PropertySymbol.ownerElement].parentNode)[PropertySymbol.children] - ))[PropertySymbol.removeNamedItem](this[PropertySymbol.ownerElement], removedItem.value); + (this[PropertySymbol.ownerElement][PropertySymbol.parentNode])[ + PropertySymbol.children + ] + ))[PropertySymbol.removeNamedItem]( + this[PropertySymbol.ownerElement], + removedItem[PropertySymbol.value] + ); } } @@ -161,11 +184,11 @@ export default class ElementNamedNodeMap extends NamedNodeMap { ] && (this[PropertySymbol.ownerElement].constructor)[ PropertySymbol.observedAttributes - ].includes(removedItem.name) + ].includes(removedItem[PropertySymbol.name]) ) { this[PropertySymbol.ownerElement].attributeChangedCallback( - removedItem.name, - removedItem.value, + removedItem[PropertySymbol.name], + removedItem[PropertySymbol.value], null ); } @@ -178,14 +201,16 @@ export default class ElementNamedNodeMap extends NamedNodeMap { if ( observer.options.attributes && (!observer.options.attributeFilter || - observer.options.attributeFilter.includes(removedItem.name)) + observer.options.attributeFilter.includes(removedItem[PropertySymbol.name])) ) { observer.report( new MutationRecord({ target: this[PropertySymbol.ownerElement], type: MutationTypeEnum.attributes, - attributeName: removedItem.name, - oldValue: observer.options.attributeOldValue ? removedItem.value : null + attributeName: removedItem[PropertySymbol.name], + oldValue: observer.options.attributeOldValue + ? removedItem[PropertySymbol.value] + : null }) ); } @@ -209,7 +234,7 @@ export default class ElementNamedNodeMap extends NamedNodeMap { * @returns Attribute name based on namespace. */ protected [PropertySymbol.getAttributeName](name): string { - if (this[PropertySymbol.ownerElement].namespaceURI === NamespaceURI.svg) { + if (this[PropertySymbol.ownerElement][PropertySymbol.namespaceURI] === NamespaceURI.svg) { return name; } return name.toLowerCase(); diff --git a/packages/happy-dom/src/nodes/element/ElementUtility.ts b/packages/happy-dom/src/nodes/element/ElementUtility.ts index 8845a4226..08c3e21e1 100644 --- a/packages/happy-dom/src/nodes/element/ElementUtility.ts +++ b/packages/happy-dom/src/nodes/element/ElementUtility.ts @@ -31,7 +31,7 @@ export default class ElementUtility { node: INode, options?: { disableAncestorValidation?: boolean } ): INode { - if (node.nodeType === NodeTypeEnum.elementNode && node !== ancestorNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode && node !== ancestorNode) { if ( !options?.disableAncestorValidation && NodeUtility.isInclusiveAncestor(node, ancestorNode) @@ -41,20 +41,22 @@ export default class ElementUtility { DOMExceptionNameEnum.domException ); } - if (node.parentNode) { + if (node[PropertySymbol.parentNode]) { const parentNodeChildren = >( - (node.parentNode)[PropertySymbol.children] + (node[PropertySymbol.parentNode])[PropertySymbol.children] ); if (parentNodeChildren) { const index = parentNodeChildren.indexOf(node); if (index !== -1) { for (const attributeName of NAMED_ITEM_ATTRIBUTES) { - const attribute = (node).attributes.getNamedItem(attributeName); + const attribute = (node)[PropertySymbol.attributes].getNamedItem( + attributeName + ); if (attribute) { parentNodeChildren[PropertySymbol.removeNamedItem]( node, - attribute.value + attribute[PropertySymbol.value] ); } } @@ -68,9 +70,12 @@ export default class ElementUtility { ); for (const attributeName of NAMED_ITEM_ATTRIBUTES) { - const attribute = (node).attributes.getNamedItem(attributeName); + const attribute = (node)[PropertySymbol.attributes].getNamedItem(attributeName); if (attribute) { - ancestorNodeChildren[PropertySymbol.appendNamedItem](node, attribute.value); + ancestorNodeChildren[PropertySymbol.appendNamedItem]( + node, + attribute[PropertySymbol.value] + ); } } @@ -95,18 +100,18 @@ export default class ElementUtility { ancestorNode: IElement | IDocument | IDocumentFragment, node: INode ): INode { - if (node.nodeType === NodeTypeEnum.elementNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { const ancestorNodeChildren = >( (ancestorNode)[PropertySymbol.children] ); const index = ancestorNodeChildren.indexOf(node); if (index !== -1) { for (const attributeName of NAMED_ITEM_ATTRIBUTES) { - const attribute = (node).attributes.getNamedItem(attributeName); + const attribute = (node)[PropertySymbol.attributes].getNamedItem(attributeName); if (attribute) { ancestorNodeChildren[PropertySymbol.removeNamedItem]( node, - attribute.value + attribute[PropertySymbol.value] ); } } @@ -137,7 +142,7 @@ export default class ElementUtility { options?: { disableAncestorValidation?: boolean } ): INode { // NodeUtility.insertBefore() will call appendChild() for the scenario where "referenceNode" is "null" or "undefined" - if (newNode.nodeType === NodeTypeEnum.elementNode && referenceNode) { + if (newNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode && referenceNode) { if ( !options?.disableAncestorValidation && NodeUtility.isInclusiveAncestor(newNode, ancestorNode) @@ -147,20 +152,22 @@ export default class ElementUtility { DOMExceptionNameEnum.domException ); } - if (newNode.parentNode) { + if (newNode[PropertySymbol.parentNode]) { const parentNodeChildren = >( - (newNode.parentNode)[PropertySymbol.children] + (newNode[PropertySymbol.parentNode])[PropertySymbol.children] ); if (parentNodeChildren) { const index = parentNodeChildren.indexOf(newNode); if (index !== -1) { for (const attributeName of NAMED_ITEM_ATTRIBUTES) { - const attribute = (newNode).attributes.getNamedItem(attributeName); + const attribute = (newNode)[PropertySymbol.attributes].getNamedItem( + attributeName + ); if (attribute) { parentNodeChildren[PropertySymbol.removeNamedItem]( newNode, - attribute.value + attribute[PropertySymbol.value] ); } } @@ -174,7 +181,7 @@ export default class ElementUtility { (ancestorNode)[PropertySymbol.children] ); - if (referenceNode.nodeType === NodeTypeEnum.elementNode) { + if (referenceNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { const index = ancestorNodeChildren.indexOf(referenceNode); if (index !== -1) { ancestorNodeChildren.splice(index, 0, newNode); @@ -186,18 +193,18 @@ export default class ElementUtility { if (node === referenceNode) { ancestorNodeChildren.push(newNode); } - if (node.nodeType === NodeTypeEnum.elementNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { ancestorNodeChildren.push(node); } } } for (const attributeName of NAMED_ITEM_ATTRIBUTES) { - const attribute = (newNode).attributes.getNamedItem(attributeName); + const attribute = (newNode)[PropertySymbol.attributes].getNamedItem(attributeName); if (attribute) { ancestorNodeChildren[PropertySymbol.appendNamedItem]( newNode, - attribute.value + attribute[PropertySymbol.value] ); } } diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts index f671ccfe5..a3fb5143e 100644 --- a/packages/happy-dom/src/nodes/element/IElement.ts +++ b/packages/happy-dom/src/nodes/element/IElement.ts @@ -20,11 +20,11 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, readonly shadowRoot: IShadowRoot; readonly classList: IDOMTokenList; readonly namespaceURI: string; - prefix: string | null; + readonly prefix: string | null; scrollTop: number; scrollLeft: number; - scrollWidth: number; - scrollHeight: number; + readonly scrollWidth: number; + readonly scrollHeight: number; id: string; className: string; role: string; diff --git a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts index 7a2e63946..18fbc8ba2 100644 --- a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts +++ b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts @@ -17,7 +17,9 @@ import EventPhaseEnum from '../../event/EventPhaseEnum.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement. */ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAnchorElement { - public override readonly attributes: INamedNodeMap = new HTMLAnchorElementNamedNodeMap(this); + public override [PropertySymbol.attributes]: INamedNodeMap = new HTMLAnchorElementNamedNodeMap( + this + ); public [PropertySymbol.relList]: DOMTokenList = null; public [PropertySymbol.url]: URL | null = null; @@ -432,11 +434,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho !event.defaultPrevented && this[PropertySymbol.url] ) { - this.ownerDocument[PropertySymbol.defaultView].open( + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].open( this[PropertySymbol.url].toString(), this.target || '_self' ); - if (this.ownerDocument[PropertySymbol.defaultView].closed) { + if (this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].closed) { event.stopImmediatePropagation(); } } diff --git a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts index 7f7ccb0b5..8832b3626 100644 --- a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts @@ -18,12 +18,15 @@ export default class HTMLAnchorElementNamedNodeMap extends HTMLElementNamedNodeM public override setNamedItem(item: IAttr): IAttr | null { const replacedItem = super.setNamedItem(item); - if (item.name === 'rel' && this[PropertySymbol.ownerElement][PropertySymbol.relList]) { + if ( + item[PropertySymbol.name] === 'rel' && + this[PropertySymbol.ownerElement][PropertySymbol.relList] + ) { this[PropertySymbol.ownerElement][PropertySymbol.relList][PropertySymbol.updateIndices](); - } else if (item.name === 'href') { + } else if (item[PropertySymbol.name] === 'href') { this[PropertySymbol.ownerElement][PropertySymbol.url] = HTMLAnchorElementUtility.getUrl( this[PropertySymbol.ownerElement].ownerDocument, - item.value + item[PropertySymbol.value] ); } @@ -37,9 +40,12 @@ export default class HTMLAnchorElementNamedNodeMap extends HTMLElementNamedNodeM const removedItem = super[PropertySymbol.removeNamedItem](name); if (removedItem) { - if (removedItem.name === 'rel' && this[PropertySymbol.ownerElement][PropertySymbol.relList]) { + if ( + removedItem[PropertySymbol.name] === 'rel' && + this[PropertySymbol.ownerElement][PropertySymbol.relList] + ) { this[PropertySymbol.ownerElement][PropertySymbol.relList][PropertySymbol.updateIndices](); - } else if (removedItem.name === 'href') { + } else if (removedItem[PropertySymbol.name] === 'href') { this[PropertySymbol.ownerElement][PropertySymbol.url] = null; } } diff --git a/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts b/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts index 3d58678dc..f4d217a7b 100644 --- a/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts +++ b/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts @@ -1,5 +1,6 @@ import HTMLElement from '../html-element/HTMLElement.js'; import IHTMLBaseElement from './IHTMLBaseElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * HTML Base Element. @@ -18,7 +19,7 @@ export default class HTMLBaseElement extends HTMLElement implements IHTMLBaseEle if (href !== null) { return href; } - return this.ownerDocument.location.href; + return this[PropertySymbol.ownerDocument].location.href; } /** diff --git a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts index f850048bd..ce0c93b53 100644 --- a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts +++ b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts @@ -22,9 +22,29 @@ const BUTTON_TYPES = ['submit', 'reset', 'button', 'menu']; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLButtonElement. */ export default class HTMLButtonElement extends HTMLElement implements IHTMLButtonElement { - public override readonly attributes: INamedNodeMap = new HTMLButtonElementNamedNodeMap(this); - public readonly validationMessage = ''; - public readonly validity = new ValidityState(this); + public override [PropertySymbol.attributes]: INamedNodeMap = new HTMLButtonElementNamedNodeMap( + this + ); + public [PropertySymbol.validationMessage] = ''; + public [PropertySymbol.validity] = new ValidityState(this); + + /** + * Returns validation message. + * + * @returns Validation message. + */ + public get validationMessage(): string { + return this[PropertySymbol.validationMessage]; + } + + /** + * Returns validity. + * + * @returns Validity. + */ + public get validity(): ValidityState { + return this[PropertySymbol.validity]; + } /** * Returns name. @@ -149,7 +169,10 @@ export default class HTMLButtonElement extends HTMLElement implements IHTMLButto */ public checkValidity(): boolean { const valid = - this.disabled || this.type === 'reset' || this.type === 'button' || this.validity.valid; + this.disabled || + this.type === 'reset' || + this.type === 'button' || + this[PropertySymbol.validity].valid; if (!valid) { this.dispatchEvent(new Event('invalid', { bubbles: true, cancelable: true })); } @@ -171,7 +194,7 @@ export default class HTMLButtonElement extends HTMLElement implements IHTMLButto * @param message Message. */ public setCustomValidity(message: string): void { - (this.validationMessage) = String(message); + this[PropertySymbol.validationMessage] = String(message); } /** @@ -189,7 +212,7 @@ export default class HTMLButtonElement extends HTMLElement implements IHTMLButto (event.eventPhase === EventPhaseEnum.atTarget || event.eventPhase === EventPhaseEnum.bubbling) && this[PropertySymbol.formNode] && - this.isConnected + this[PropertySymbol.isConnected] ) { const form = this[PropertySymbol.formNode]; switch (this.type) { diff --git a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElementNamedNodeMap.ts index 24c23ffd5..f58ec815c 100644 --- a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElementNamedNodeMap.ts @@ -19,18 +19,18 @@ export default class HTMLButtonElementNamedNodeMap extends HTMLElementNamedNodeM const replacedItem = super.setNamedItem(item); if ( - (item.name === 'id' || item.name === 'name') && + (item[PropertySymbol.name] === 'id' || item[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { - if (replacedItem?.value) { + if (replacedItem?.[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], replacedItem.value); + ](this[PropertySymbol.ownerElement], replacedItem[PropertySymbol.value]); } - if (item.value) { + if (item[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.appendFormControlItem - ](this[PropertySymbol.ownerElement], item.value); + ](this[PropertySymbol.ownerElement], item[PropertySymbol.value]); } } @@ -45,12 +45,12 @@ export default class HTMLButtonElementNamedNodeMap extends HTMLElementNamedNodeM if ( removedItem && - (removedItem.name === 'id' || removedItem.name === 'name') && + (removedItem[PropertySymbol.name] === 'id' || removedItem[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], removedItem.value); + ](this[PropertySymbol.ownerElement], removedItem[PropertySymbol.value]); } return removedItem; diff --git a/packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts b/packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts index 2b77cc33e..dcbe08af3 100644 --- a/packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts +++ b/packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts @@ -1,6 +1,7 @@ import Event from '../../event/Event.js'; import HTMLElement from '../html-element/HTMLElement.js'; import IHTMLDialogElement from './IHTMLDialogElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * HTML Dialog Element. @@ -9,12 +10,31 @@ import IHTMLDialogElement from './IHTMLDialogElement.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement. */ export default class HTMLDialogElement extends HTMLElement implements IHTMLDialogElement { - public returnValue = ''; + // Internal properties + public [PropertySymbol.returnValue] = ''; // Events public oncancel: (event: Event) => void | null = null; public onclose: (event: Event) => void | null = null; + /** + * Returns return value. + * + * @returns Return value. + */ + public get returnValue(): string { + return this[PropertySymbol.returnValue]; + } + + /** + * Sets return value. + * + * @param value Return value. + */ + public set returnValue(value: string) { + this[PropertySymbol.returnValue] = value; + } + /** * Sets the "open" attribute. * diff --git a/packages/happy-dom/src/nodes/html-document/HTMLDocument.ts b/packages/happy-dom/src/nodes/html-document/HTMLDocument.ts index 871c3e0b4..620e0f85b 100644 --- a/packages/happy-dom/src/nodes/html-document/HTMLDocument.ts +++ b/packages/happy-dom/src/nodes/html-document/HTMLDocument.ts @@ -1,6 +1,7 @@ import IBrowserFrame from '../../browser/types/IBrowserFrame.js'; import IBrowserWindow from '../../window/IBrowserWindow.js'; import Document from '../document/Document.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * Document. @@ -17,7 +18,7 @@ export default class HTMLDocument extends Document { super(injected); // Default document elements - const doctype = this.implementation.createDocumentType('html', '', ''); + const doctype = this[PropertySymbol.implementation].createDocumentType('html', '', ''); const documentElement = this.createElement('html'); const bodyElement = this.createElement('body'); const headElement = this.createElement('head'); diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index 18454fc85..e8802cf8d 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -18,23 +18,6 @@ import HTMLElementNamedNodeMap from './HTMLElementNamedNodeMap.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement. */ export default class HTMLElement extends Element implements IHTMLElement { - public override readonly attributes: INamedNodeMap = new HTMLElementNamedNodeMap(this); - public readonly accessKey = ''; - public readonly accessKeyLabel = ''; - public readonly contentEditable = 'inherit'; - public readonly isContentEditable = false; - public readonly offsetHeight = 0; - public readonly offsetWidth = 0; - public readonly offsetLeft = 0; - public readonly offsetTop = 0; - public readonly clientHeight = 0; - public readonly clientWidth = 0; - public readonly clientLeft = 0; - public readonly clientTop = 0; - - public [PropertySymbol.style]: CSSStyleDeclaration = null; - #dataset: Dataset = null; - // Events public oncopy: (event: Event) => void | null = null; public oncut: (event: Event) => void | null = null; @@ -62,6 +45,141 @@ export default class HTMLElement extends Element implements IHTMLElement { public ontransitionrun: (event: Event) => void | null = null; public ontransitionstart: (event: Event) => void | null = null; + // Internal properties + public override [PropertySymbol.attributes]: INamedNodeMap = new HTMLElementNamedNodeMap(this); + public [PropertySymbol.accessKey] = ''; + public [PropertySymbol.contentEditable] = 'inherit'; + public [PropertySymbol.isContentEditable] = false; + public [PropertySymbol.offsetHeight] = 0; + public [PropertySymbol.offsetWidth] = 0; + public [PropertySymbol.offsetLeft] = 0; + public [PropertySymbol.offsetTop] = 0; + public [PropertySymbol.clientHeight] = 0; + public [PropertySymbol.clientWidth] = 0; + public [PropertySymbol.clientLeft] = 0; + public [PropertySymbol.clientTop] = 0; + public [PropertySymbol.style]: CSSStyleDeclaration = null; + + // Private properties + #dataset: Dataset = null; + + /** + * Returns access key. + * + * @returns Access key. + */ + public get accessKey(): string { + return this[PropertySymbol.accessKey]; + } + + /** + * Sets access key. + * + * @param accessKey Access key. + */ + public set accessKey(accessKey: string) { + this[PropertySymbol.accessKey] = accessKey; + } + + /** + * Returns content editable. + * + * @returns Content editable. + */ + public get contentEditable(): string { + return this[PropertySymbol.contentEditable]; + } + + /** + * Sets content editable. + * + * @param contentEditable Content editable. + */ + public set contentEditable(contentEditable: string) { + this[PropertySymbol.contentEditable] = contentEditable; + } + + /** + * Returns is content editable. + * + * @returns Is content editable. + */ + public get isContentEditable(): boolean { + return this[PropertySymbol.isContentEditable]; + } + + /** + * Returns offset height. + * + * @returns Offset height. + */ + public get offsetHeight(): number { + return this[PropertySymbol.offsetHeight]; + } + + /** + * Returns offset width. + * + * @returns Offset width. + */ + public get offsetWidth(): number { + return this[PropertySymbol.offsetWidth]; + } + + /** + * Returns offset left. + * + * @returns Offset left. + */ + public get offsetLeft(): number { + return this[PropertySymbol.offsetLeft]; + } + + /** + * Returns offset top. + * + * @returns Offset top. + */ + public get offsetTop(): number { + return this[PropertySymbol.offsetTop]; + } + + /** + * Returns client height. + * + * @returns Client height. + */ + public get clientHeight(): number { + return this[PropertySymbol.clientHeight]; + } + + /** + * Returns client width. + * + * @returns Client width. + */ + public get clientWidth(): number { + return this[PropertySymbol.clientWidth]; + } + + /** + * Returns client left. + * + * @returns Client left. + */ + public get clientLeft(): number { + return this[PropertySymbol.clientLeft]; + } + + /** + * Returns client top. + * + * @returns Client top. + */ + public get clientTop(): number { + return this[PropertySymbol.clientTop]; + } + /** * Returns tab index. * @@ -92,19 +210,24 @@ export default class HTMLElement extends Element implements IHTMLElement { * @returns Inner text. */ public get innerText(): string { - if (!this.isConnected) { + if (!this[PropertySymbol.isConnected]) { return this.textContent; } let result = ''; for (const childNode of this[PropertySymbol.childNodes]) { - if (childNode.nodeType === NodeTypeEnum.elementNode) { + if (childNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) { const childElement = childNode; const computedStyle = - this.ownerDocument[PropertySymbol.defaultView].getComputedStyle(childElement); - - if (childElement.tagName !== 'SCRIPT' && childElement.tagName !== 'STYLE') { + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].getComputedStyle( + childElement + ); + + if ( + childElement[PropertySymbol.tagName] !== 'SCRIPT' && + childElement[PropertySymbol.tagName] !== 'STYLE' + ) { const display = computedStyle.display; if (display !== 'none') { const textTransform = computedStyle.textTransform; @@ -130,7 +253,7 @@ export default class HTMLElement extends Element implements IHTMLElement { result += text; } } - } else if (childNode.nodeType === NodeTypeEnum.textNode) { + } else if (childNode[PropertySymbol.nodeType] === NodeTypeEnum.textNode) { result += childNode.textContent.replace(/[\n\r]/, ''); } } @@ -153,9 +276,9 @@ export default class HTMLElement extends Element implements IHTMLElement { for (let i = 0, max = texts.length; i < max; i++) { if (i !== 0) { - this.appendChild(this.ownerDocument.createElement('br')); + this.appendChild(this[PropertySymbol.ownerDocument].createElement('br')); } - this.appendChild(this.ownerDocument.createTextNode(texts[i])); + this.appendChild(this[PropertySymbol.ownerDocument].createTextNode(texts[i])); } } @@ -176,7 +299,7 @@ export default class HTMLElement extends Element implements IHTMLElement { * @param text Text. */ public set outerText(text: string) { - if (!this.parentNode) { + if (!this[PropertySymbol.parentNode]) { throw new DOMException( "Failed to set the 'outerHTML' property on 'Element': This element has no parent node." ); @@ -186,12 +309,18 @@ export default class HTMLElement extends Element implements IHTMLElement { for (let i = 0, max = texts.length; i < max; i++) { if (i !== 0) { - this.parentNode.insertBefore(this.ownerDocument.createElement('br'), this); + this[PropertySymbol.parentNode].insertBefore( + this[PropertySymbol.ownerDocument].createElement('br'), + this + ); } - this.parentNode.insertBefore(this.ownerDocument.createTextNode(texts[i]), this); + this[PropertySymbol.parentNode].insertBefore( + this[PropertySymbol.ownerDocument].createTextNode(texts[i]), + this + ); } - this.parentNode.removeChild(this); + this[PropertySymbol.parentNode].removeChild(this); } /** @@ -334,10 +463,9 @@ export default class HTMLElement extends Element implements IHTMLElement { public cloneNode(deep = false): IHTMLElement { const clone = super.cloneNode(deep); - (clone.accessKey) = this.accessKey; - (clone.accessKeyLabel) = this.accessKeyLabel; - (clone.contentEditable) = this.contentEditable; - (clone.isContentEditable) = this.isContentEditable; + clone[PropertySymbol.accessKey] = this[PropertySymbol.accessKey]; + clone[PropertySymbol.contentEditable] = this[PropertySymbol.contentEditable]; + clone[PropertySymbol.isContentEditable] = this[PropertySymbol.isContentEditable]; if (this[PropertySymbol.style]) { clone.style.cssText = this[PropertySymbol.style].cssText; diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-element/HTMLElementNamedNodeMap.ts index 8fa8cfeb6..b19070914 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElementNamedNodeMap.ts @@ -17,8 +17,11 @@ export default class HTMLElementNamedNodeMap extends ElementNamedNodeMap { public override setNamedItem(item: IAttr): IAttr | null { const replacedItem = super.setNamedItem(item); - if (item.name === 'style' && this[PropertySymbol.ownerElement][PropertySymbol.style]) { - this[PropertySymbol.ownerElement][PropertySymbol.style].cssText = item.value; + if ( + item[PropertySymbol.name] === 'style' && + this[PropertySymbol.ownerElement][PropertySymbol.style] + ) { + this[PropertySymbol.ownerElement][PropertySymbol.style].cssText = item[PropertySymbol.value]; } return replacedItem || null; @@ -32,7 +35,7 @@ export default class HTMLElementNamedNodeMap extends ElementNamedNodeMap { if ( removedItem && - removedItem.name === 'style' && + removedItem[PropertySymbol.name] === 'style' && this[PropertySymbol.ownerElement][PropertySymbol.style] ) { this[PropertySymbol.ownerElement][PropertySymbol.style].cssText = ''; diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElementUtility.ts b/packages/happy-dom/src/nodes/html-element/HTMLElementUtility.ts index c3b8cc7da..ceda0a8d6 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElementUtility.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElementUtility.ts @@ -13,13 +13,17 @@ export default class HTMLElementUtility { * @param element Element. */ public static blur(element: IHTMLElement | ISVGElement): void { - if (element.ownerDocument[PropertySymbol.activeElement] !== element || !element.isConnected) { + if ( + element[PropertySymbol.ownerDocument][PropertySymbol.activeElement] !== element || + !element[PropertySymbol.isConnected] + ) { return; } - const relatedTarget = element.ownerDocument[PropertySymbol.nextActiveElement] ?? null; + const relatedTarget = + element[PropertySymbol.ownerDocument][PropertySymbol.nextActiveElement] ?? null; - element.ownerDocument[PropertySymbol.activeElement] = null; + element[PropertySymbol.ownerDocument][PropertySymbol.activeElement] = null; element.dispatchEvent( new FocusEvent('blur', { @@ -43,23 +47,26 @@ export default class HTMLElementUtility { * @param element Element. */ public static focus(element: IHTMLElement | ISVGElement): void { - if (element.ownerDocument[PropertySymbol.activeElement] === element || !element.isConnected) { + if ( + element[PropertySymbol.ownerDocument][PropertySymbol.activeElement] === element || + !element[PropertySymbol.isConnected] + ) { return; } // Set the next active element so `blur` can use it for `relatedTarget`. - element.ownerDocument[PropertySymbol.nextActiveElement] = element; + element[PropertySymbol.ownerDocument][PropertySymbol.nextActiveElement] = element; - const relatedTarget = element.ownerDocument[PropertySymbol.activeElement]; + const relatedTarget = element[PropertySymbol.ownerDocument][PropertySymbol.activeElement]; - if (element.ownerDocument[PropertySymbol.activeElement] !== null) { - element.ownerDocument[PropertySymbol.activeElement].blur(); + if (element[PropertySymbol.ownerDocument][PropertySymbol.activeElement] !== null) { + element[PropertySymbol.ownerDocument][PropertySymbol.activeElement].blur(); } // Clean up after blur, so it does not affect next blur call. - element.ownerDocument[PropertySymbol.nextActiveElement] = null; + element[PropertySymbol.ownerDocument][PropertySymbol.nextActiveElement] = null; - element.ownerDocument[PropertySymbol.activeElement] = element; + element[PropertySymbol.ownerDocument][PropertySymbol.activeElement] = element; element.dispatchEvent( new FocusEvent('focus', { diff --git a/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts b/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts index f341e351a..93259bc04 100644 --- a/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts @@ -9,6 +9,9 @@ import IElement from '../element/IElement.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement. */ export default interface IHTMLElement extends IElement { + accessKey: string; + contentEditable: string; + isContentEditable: boolean; dataset: { [key: string]: string }; tabIndex: number; offsetHeight: number; 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 a91cb1b27..db067657b 100644 --- a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts +++ b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts @@ -18,17 +18,33 @@ import IHTMLButtonElement from '../html-button-element/IHTMLButtonElement.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement. */ export default class HTMLFormElement extends HTMLElement implements IHTMLFormElement { - // Public properties. - public readonly elements: IHTMLFormControlsCollection = new HTMLFormControlsCollection(); - public readonly length = 0; + // Internal properties. + public [PropertySymbol.elements]: IHTMLFormControlsCollection = new HTMLFormControlsCollection(); + public [PropertySymbol.length] = 0; + public [PropertySymbol.formNode]: INode = this; // Events public onformdata: (event: Event) => void | null = null; public onreset: (event: Event) => void | null = null; public onsubmit: (event: Event) => void | null = null; - // Private properties - public [PropertySymbol.formNode]: INode = this; + /** + * Returns elements. + * + * @returns Elements. + */ + public get elements(): IHTMLFormControlsCollection { + return this[PropertySymbol.elements]; + } + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this[PropertySymbol.length]; + } /** * Returns name. @@ -221,13 +237,16 @@ export default class HTMLFormElement extends HTMLElement implements IHTMLFormEle * Resets form. */ public reset(): void { - for (const element of this.elements) { - if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') { + for (const element of this[PropertySymbol.elements]) { + if ( + element[PropertySymbol.tagName] === 'INPUT' || + element[PropertySymbol.tagName] === 'TEXTAREA' + ) { element[PropertySymbol.value] = null; element[PropertySymbol.checked] = null; - } else if (element.tagName === 'TEXTAREA') { + } else if (element[PropertySymbol.tagName] === 'TEXTAREA') { element[PropertySymbol.value] = null; - } else if (element.tagName === 'SELECT') { + } else if (element[PropertySymbol.tagName] === 'SELECT') { let hasSelectedAttribute = false; for (const option of (element).options) { if (option.hasAttribute('selected')) { @@ -254,8 +273,8 @@ export default class HTMLFormElement extends HTMLElement implements IHTMLFormEle const radioValidationState: { [k: string]: boolean } = {}; let isFormValid = true; - for (const element of this.elements) { - if (element.tagName === 'INPUT' && element.type === 'radio' && element.name) { + for (const element of this[PropertySymbol.elements]) { + if (element[PropertySymbol.tagName] === 'INPUT' && element.type === 'radio' && element.name) { if (!radioValidationState[element.name]) { radioValidationState[element.name] = true; if (!element.checkValidity()) { @@ -300,14 +319,19 @@ export default class HTMLFormElement extends HTMLElement implements IHTMLFormEle node: IHTMLInputElement | IHTMLTextAreaElement | IHTMLSelectElement | IHTMLButtonElement, name: string ): void { - if (!this.elements.includes(node)) { - this[this.elements.length] = node; - this.elements.push(node); - (this.length) = this.elements.length; + const elements = this[PropertySymbol.elements]; + + if (!elements.includes(node)) { + this[elements.length] = node; + elements.push(node); + this[PropertySymbol.length] = elements.length; } - (this.elements)[PropertySymbol.appendNamedItem](node, name); - this[name] = this.elements[name]; + (elements)[PropertySymbol.appendNamedItem](node, name); + + if (this[PropertySymbol.isValidPropertyName](name)) { + this[name] = elements[name]; + } } /** @@ -320,23 +344,39 @@ export default class HTMLFormElement extends HTMLElement implements IHTMLFormEle node: IHTMLInputElement | IHTMLTextAreaElement | IHTMLSelectElement | IHTMLButtonElement, name: string ): void { - const index = this.elements.indexOf(node); + const elements = this[PropertySymbol.elements]; + const index = elements.indexOf(node); if (index !== -1) { - this.elements.splice(index, 1); - for (let i = index; i < this.length; i++) { + elements.splice(index, 1); + for (let i = index; i < this[PropertySymbol.length]; i++) { this[i] = this[i + 1]; } - delete this[this.length - 1]; - (this.length)--; + delete this[this[PropertySymbol.length] - 1]; + this[PropertySymbol.length]--; } - (this.elements)[PropertySymbol.removeNamedItem](node, name); + (elements)[PropertySymbol.removeNamedItem](node, name); - if (this.elements[name]) { - this[name] = this.elements[name]; - } else { - delete this[name]; + if (this[PropertySymbol.isValidPropertyName](name)) { + if (elements[name]) { + this[name] = elements[name]; + } else { + delete this[name]; + } } } + + /** + * Returns "true" if the property name is valid. + * + * @param name Name. + * @returns True if the property name is valid. + */ + protected [PropertySymbol.isValidPropertyName](name: string): boolean { + return ( + !this.constructor.prototype.hasOwnProperty(name) && + (isNaN(Number(name)) || name.includes('.')) + ); + } } 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 930a480b9..f7a337586 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts @@ -18,13 +18,14 @@ import HTMLIFrameElementPageLoader from './HTMLIFrameElementPageLoader.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement. */ export default class HTMLIFrameElement extends HTMLElement implements IHTMLIFrameElement { - public override readonly attributes: INamedNodeMap; - // Events public onload: (event: Event) => void | null = null; public onerror: (event: Event) => void | null = null; // Internal properties + public override [PropertySymbol.attributes]: INamedNodeMap; + + // Private properties #contentWindowContainer: { window: IBrowserWindow | ICrossOriginBrowserWindow | null } = { window: null }; @@ -42,7 +43,7 @@ export default class HTMLIFrameElement extends HTMLElement implements IHTMLIFram contentWindowContainer: this.#contentWindowContainer, browserParentFrame: browserFrame }); - this.attributes = new HTMLIFrameElementNamedNodeMap(this, this.#pageLoader); + this[PropertySymbol.attributes] = new HTMLIFrameElementNamedNodeMap(this, this.#pageLoader); } /** @@ -193,8 +194,8 @@ export default class HTMLIFrameElement extends HTMLElement implements IHTMLIFram * @override */ public override [PropertySymbol.connectToNode](parentNode: INode = null): void { - const isConnected = this.isConnected; - const isParentConnected = parentNode ? parentNode.isConnected : false; + const isConnected = this[PropertySymbol.isConnected]; + const isParentConnected = parentNode ? parentNode[PropertySymbol.isConnected] : false; super[PropertySymbol.connectToNode](parentNode); diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts index ad6181596..798740a25 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts @@ -2,6 +2,7 @@ import IAttr from '../attr/IAttr.js'; import Element from '../element/Element.js'; import HTMLElementNamedNodeMap from '../html-element/HTMLElementNamedNodeMap.js'; import HTMLIFrameElementPageLoader from './HTMLIFrameElementPageLoader.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * Named Node Map. @@ -28,7 +29,11 @@ export default class HTMLIFrameElementNamedNodeMap extends HTMLElementNamedNodeM public override setNamedItem(item: IAttr): IAttr | null { const replacedAttribute = super.setNamedItem(item); - if (item.name === 'src' && item.value && item.value !== replacedAttribute?.value) { + if ( + item[PropertySymbol.name] === 'src' && + item[PropertySymbol.value] && + item[PropertySymbol.value] !== replacedAttribute?.[PropertySymbol.value] + ) { this.#pageLoader.loadPage(); } diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts index 3982d975e..45f49ac88 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts @@ -43,7 +43,7 @@ export default class HTMLIFrameElementPageLoader { * Loads an iframe page. */ public loadPage(): void { - if (!this.#element.isConnected) { + if (!this.#element[PropertySymbol.isConnected]) { if (this.#browserIFrame) { BrowserFrameFactory.destroyFrame(this.#browserIFrame); this.#browserIFrame = null; @@ -52,7 +52,7 @@ export default class HTMLIFrameElementPageLoader { return; } - const window = this.#element.ownerDocument[PropertySymbol.defaultView]; + const window = this.#element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow]; const originURL = this.#browserParentFrame.window.location; const targetURL = BrowserFrameURL.getRelativeURL(this.#browserParentFrame, this.#element.src); diff --git a/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts b/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts index cf72213f2..daffead4b 100644 --- a/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts +++ b/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts @@ -1,5 +1,6 @@ import HTMLElement from '../html-element/HTMLElement.js'; import IHTMLImageElement from './IHTMLImageElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * HTML Image Element. @@ -8,15 +9,101 @@ import IHTMLImageElement from './IHTMLImageElement.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement. */ export default class HTMLImageElement extends HTMLElement implements IHTMLImageElement { - public readonly tagName: string = 'IMG'; - public readonly complete = false; - public readonly naturalHeight = 0; - public readonly naturalWidth = 0; - public crossOrigin = null; - public decoding = 'auto'; - public loading = 'auto'; - public readonly x = 0; - public readonly y = 0; + public [PropertySymbol.tagName] = 'IMG'; + public [PropertySymbol.complete] = false; + public [PropertySymbol.naturalHeight] = 0; + public [PropertySymbol.naturalWidth] = 0; + public [PropertySymbol.loading] = 'auto'; + public [PropertySymbol.x] = 0; + public [PropertySymbol.y] = 0; + + /** + * Returns complete. + * + * @returns Complete. + */ + public get complete(): boolean { + return this[PropertySymbol.complete]; + } + + /** + * Returns natural height. + * + * @returns Natural height. + */ + public get naturalHeight(): number { + return this[PropertySymbol.naturalHeight]; + } + + /** + * Returns natural width. + * + * @returns Natural width. + */ + public get naturalWidth(): number { + return this[PropertySymbol.naturalWidth]; + } + + /** + * Returns loading. + * + * @returns Loading. + */ + public get loading(): string { + return this[PropertySymbol.loading]; + } + + /** + * Returns x. + */ + public get x(): number { + return this[PropertySymbol.x]; + } + + /** + * Returns y. + */ + public get y(): number { + return this[PropertySymbol.y]; + } + + /** + * Returns decoding. + * + * @returns Decoding. + */ + public get decoding(): string { + return this.getAttribute('decoding') || 'auto'; + } + + /** + * Sets decoding. + * + * @param decoding Decoding. + */ + public set decoding(decoding: string) { + this.setAttribute('decoding', decoding); + } + + /** + * Returns cross origin. + * + * @returns Cross origin. + */ + public get crossOrigin(): string | null { + return this.getAttribute('crossOrigin'); + } + + /** + * Sets cross origin. + * + * @param crossOrigin Cross origin. + */ + public set crossOrigin(crossOrigin: string | null) { + if (crossOrigin === 'anonymous' || crossOrigin === 'use-credentials') { + this.setAttribute('crossOrigin', crossOrigin); + } + } /** * Returns alt. @@ -45,6 +132,25 @@ export default class HTMLImageElement extends HTMLElement implements IHTMLImageE return this.src; } + /** + * Returns width. + * + * @returns Width. + */ + public get width(): number { + const width = this.getAttribute('width'); + return width !== null ? Number(width) : 0; + } + + /** + * Sets width. + * + * @param width Width. + */ + public set width(width: number) { + this.setAttribute('width', String(width)); + } + /** * Returns height. * @@ -176,25 +282,6 @@ export default class HTMLImageElement extends HTMLElement implements IHTMLImageE this.setAttribute('usemap', useMap); } - /** - * Returns width. - * - * @returns Width. - */ - public get width(): number { - const width = this.getAttribute('width'); - return width !== null ? Number(width) : 0; - } - - /** - * Sets width. - * - * @param width Width. - */ - public set width(width: number) { - this.setAttribute('width', String(width)); - } - /** * The decode() method of the HTMLImageElement interface returns a Promise that resolves when the image is decoded and it is safe to append the image to the DOM. * diff --git a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts index 2dbd813a9..d8dbc807a 100644 --- a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts +++ b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts @@ -23,6 +23,7 @@ import HTMLInputElementDateUtility from './HTMLInputElementDateUtility.js'; import HTMLLabelElementUtility from '../html-label-element/HTMLLabelElementUtility.js'; import INamedNodeMap from '../../named-node-map/INamedNodeMap.js'; import HTMLInputElementNamedNodeMap from './HTMLInputElementNamedNodeMap.js'; +import { URL } from 'url'; /** * HTML Input Element. @@ -34,39 +35,131 @@ import HTMLInputElementNamedNodeMap from './HTMLInputElementNamedNodeMap.js'; * https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/nodes/nodes/HTMLInputElement-impl.js (MIT licensed). */ export default class HTMLInputElement extends HTMLElement implements IHTMLInputElement { - public override readonly attributes: INamedNodeMap = new HTMLInputElementNamedNodeMap(this); - - // Related to parent form. - public formAction = ''; - public formMethod = ''; + // Events + public oninput: (event: Event) => void | null = null; + public oninvalid: (event: Event) => void | null = null; + public onselectionchange: (event: Event) => void | null = null; - // Any type of input + // Internal properties + public override [PropertySymbol.attributes]: INamedNodeMap = new HTMLInputElementNamedNodeMap( + this + ); public [PropertySymbol.value] = null; public [PropertySymbol.height] = 0; public [PropertySymbol.width] = 0; - - // Type specific: checkbox/radio - public defaultChecked = false; + public [PropertySymbol.defaultChecked] = false; public [PropertySymbol.checked]: boolean | null = null; + public [PropertySymbol.validationMessage] = ''; + public [PropertySymbol.validity] = new ValidityState(this); + public [PropertySymbol.files]: IFileList = new FileList(); - // Type specific: file - public files: IFileList = new FileList(); - - // All fields - public readonly validationMessage = ''; - public readonly validity = new ValidityState(this); - - // Events - public oninput: (event: Event) => void | null = null; - public oninvalid: (event: Event) => void | null = null; - public onselectionchange: (event: Event) => void | null = null; - - // Type specific: text/password/search/tel/url/week/month + // Private properties #selectionStart: number = null; #selectionEnd: number = null; #selectionDirection: HTMLInputElementSelectionDirectionEnum = HTMLInputElementSelectionDirectionEnum.none; + /** + * Returns default checked. + * + * @returns Default checked. + */ + public get defaultChecked(): boolean { + return this[PropertySymbol.defaultChecked]; + } + + /** + * Sets default checked. + * + * @param defaultChecked Default checked. + */ + public set defaultChecked(defaultChecked: boolean) { + this[PropertySymbol.defaultChecked] = defaultChecked; + } + + /** + * Returns files. + * + * @returns Files. + */ + public get files(): IFileList { + return this[PropertySymbol.files]; + } + + /** + * Sets files. + * + * @param files Files. + */ + public set files(files: IFileList) { + this[PropertySymbol.files] = files; + } + + /** + * Returns form action. + * + * @returns URL. + */ + public get formAction(): string { + return ( + this.getAttribute('formaction') || + (this[PropertySymbol.formNode])?.action || + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location.href + ); + } + + /** + * Sets form action. + * + * @param url URL. + */ + public set formAction(url: string) { + try { + new URL(url); + } catch (error) { + return; + } + this.setAttribute('formaction', url); + } + + /** + * Returns form method. + */ + public get formMethod(): string { + return ( + this.getAttribute('formmethod') || + (this[PropertySymbol.formNode])?.method || + '' + ); + } + + /** + * Sets form method. + * + * @param method Method. + */ + public set formMethod(method: string) { + this.setAttribute('formmethod', method); + } + + /** + * Returns validation message. + * + * @returns Validation message. + */ + public get validationMessage(): string { + return this[PropertySymbol.validationMessage]; + } + + /** + * Returns validity. + * + * @returns Validity. + */ + public get validity(): ValidityState { + return this[PropertySymbol.validity]; + } + /** * Returns height. * @@ -594,7 +687,9 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE const attritube = this.getAttribute('value'); return attritube !== null ? attritube : 'on'; case 'file': - return this.files.length > 0 ? '/fake/path/' + this.files[0].name : ''; + return this[PropertySymbol.files].length > 0 + ? '/fake/path/' + this[PropertySymbol.files][0].name + : ''; } if (this[PropertySymbol.value] === null) { @@ -631,7 +726,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE } break; default: - const oldValue = this[PropertySymbol.value]; + const oldValue = this.value; this[PropertySymbol.value] = HTMLInputElementValueSanitizer.sanitize(this, value); if (oldValue !== this[PropertySymbol.value]) { @@ -970,7 +1065,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE * @param message Message. */ public setCustomValidity(message: string): void { - (this.validationMessage) = String(message); + this[PropertySymbol.validationMessage] = String(message); } /** @@ -1102,7 +1197,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE this.type === 'hidden' || this.type === 'reset' || this.type === 'button' || - this.validity.valid; + this[PropertySymbol.validity].valid; if (!valid) { this.dispatchEvent(new Event('invalid', { bubbles: true, cancelable: true })); } @@ -1156,8 +1251,8 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE clone[PropertySymbol.value] = this[PropertySymbol.value]; clone[PropertySymbol.height] = this[PropertySymbol.height]; clone[PropertySymbol.width] = this[PropertySymbol.width]; - clone.defaultChecked = this.defaultChecked; - clone.files = this.files.slice(); + clone[PropertySymbol.defaultChecked] = this[PropertySymbol.defaultChecked]; + clone[PropertySymbol.files] = this[PropertySymbol.files].slice(); clone.#selectionStart = this.#selectionStart; clone.#selectionEnd = this.#selectionEnd; clone.#selectionDirection = this.#selectionDirection; @@ -1196,7 +1291,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE (event.eventPhase === EventPhaseEnum.atTarget || event.eventPhase === EventPhaseEnum.bubbling) && event.type === 'click' && - this.isConnected + this[PropertySymbol.isConnected] ) { const inputType = this.type; if (!this.readOnly || inputType === 'checkbox' || inputType === 'radio') { @@ -1208,7 +1303,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE if (form) { form.requestSubmit(); } - } else if (inputType === 'reset' && this.isConnected) { + } else if (inputType === 'reset' && this[PropertySymbol.isConnected]) { const form = this[PropertySymbol.formNode]; if (form) { form.reset(); diff --git a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElementNamedNodeMap.ts index 4b8d05f1a..22507fe0d 100644 --- a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElementNamedNodeMap.ts @@ -19,18 +19,18 @@ export default class HTMLInputElementNamedNodeMap extends HTMLElementNamedNodeMa const replacedItem = super.setNamedItem(item); if ( - (item.name === 'id' || item.name === 'name') && + (item[PropertySymbol.name] === 'id' || item[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { - if (replacedItem && replacedItem.value) { + if (replacedItem && replacedItem[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], replacedItem.value); + ](this[PropertySymbol.ownerElement], replacedItem[PropertySymbol.value]); } - if (item.value) { + if (item[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.appendFormControlItem - ](this[PropertySymbol.ownerElement], item.value); + ](this[PropertySymbol.ownerElement], item[PropertySymbol.value]); } } @@ -45,12 +45,12 @@ export default class HTMLInputElementNamedNodeMap extends HTMLElementNamedNodeMa if ( removedItem && - (removedItem.name === 'id' || removedItem.name === 'name') && + (removedItem[PropertySymbol.name] === 'id' || removedItem[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], removedItem.value); + ](this[PropertySymbol.ownerElement], removedItem[PropertySymbol.value]); } return removedItem; diff --git a/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts b/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts index 56060e68f..1fe4e02d8 100644 --- a/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts +++ b/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts @@ -44,7 +44,7 @@ export default class HTMLLabelElement extends HTMLElement implements IHTMLLabelE public get control(): IHTMLElement { const htmlFor = this.htmlFor; if (htmlFor) { - const control = this.ownerDocument.getElementById(htmlFor); + const control = this[PropertySymbol.ownerDocument].getElementById(htmlFor); return control !== this ? control : null; } return ( diff --git a/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElementUtility.ts b/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElementUtility.ts index df9a27de3..336b0442a 100644 --- a/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElementUtility.ts +++ b/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElementUtility.ts @@ -4,6 +4,7 @@ import IHTMLLabelElement from './IHTMLLabelElement.js'; import INodeList from '../node/INodeList.js'; import NodeList from '../node/NodeList.js'; import IShadowRoot from '../shadow-root/IShadowRoot.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * Utility for finding labels associated with a form element. @@ -25,13 +26,13 @@ export default class HTMLLabelElementUtility { labels = new NodeList(); } - let parent = element.parentNode; + let parent = element[PropertySymbol.parentNode]; while (parent) { if (parent['tagName'] === 'LABEL') { labels.push(parent); break; } - parent = parent.parentNode; + parent = parent[PropertySymbol.parentNode]; } return labels; 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 82290322f..069aec408 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts @@ -19,10 +19,13 @@ import IBrowserFrame from '../../browser/types/IBrowserFrame.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement. */ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkElement { - public override readonly attributes: INamedNodeMap; + // Events public onerror: (event: ErrorEvent) => void = null; public onload: (event: Event) => void = null; - public readonly sheet: CSSStyleSheet = null; + + // Internal properties + public override [PropertySymbol.attributes]: INamedNodeMap; + public readonly [PropertySymbol.sheet]: CSSStyleSheet = null; public [PropertySymbol.evaluateCSS] = true; public [PropertySymbol.relList]: DOMTokenList = null; #styleSheetLoader: HTMLLinkElementStyleSheetLoader; @@ -40,7 +43,14 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle browserFrame }); - this.attributes = new HTMLLinkElementNamedNodeMap(this, this.#styleSheetLoader); + this[PropertySymbol.attributes] = new HTMLLinkElementNamedNodeMap(this, this.#styleSheetLoader); + } + + /** + * Returns sheet. + */ + public get sheet(): CSSStyleSheet { + return this[PropertySymbol.sheet]; } /** @@ -203,8 +213,8 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle * @override */ public override [PropertySymbol.connectToNode](parentNode: INode = null): void { - const isConnected = this.isConnected; - const isParentConnected = parentNode ? parentNode.isConnected : false; + const isConnected = this[PropertySymbol.isConnected]; + const isParentConnected = parentNode ? parentNode[PropertySymbol.isConnected] : false; super[PropertySymbol.connectToNode](parentNode); diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementNamedNodeMap.ts index 01c473da4..5a368fb1c 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementNamedNodeMap.ts @@ -31,18 +31,21 @@ export default class HTMLLinkElementNamedNodeMap extends HTMLElementNamedNodeMap public override setNamedItem(item: IAttr): IAttr | null { const replacedItem = super.setNamedItem(item); - if (item.name === 'rel' && this[PropertySymbol.ownerElement][PropertySymbol.relList]) { + if ( + item[PropertySymbol.name] === 'rel' && + this[PropertySymbol.ownerElement][PropertySymbol.relList] + ) { this[PropertySymbol.ownerElement][PropertySymbol.relList][PropertySymbol.updateIndices](); } - if (item.name === 'rel') { + if (item[PropertySymbol.name] === 'rel') { this.#styleSheetLoader.loadStyleSheet( this[PropertySymbol.ownerElement].getAttribute('href'), - item.value + item[PropertySymbol.value] ); - } else if (item.name === 'href') { + } else if (item[PropertySymbol.name] === 'href') { this.#styleSheetLoader.loadStyleSheet( - item.value, + item[PropertySymbol.value], this[PropertySymbol.ownerElement].getAttribute('rel') ); } @@ -58,7 +61,7 @@ export default class HTMLLinkElementNamedNodeMap extends HTMLElementNamedNodeMap if ( removedItem && - removedItem.name === 'rel' && + removedItem[PropertySymbol.name] === 'rel' && this[PropertySymbol.ownerElement][PropertySymbol.relList] ) { this[PropertySymbol.ownerElement][PropertySymbol.relList][PropertySymbol.updateIndices](); diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts index 198bbe6cb..427c17ba2 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts @@ -39,13 +39,21 @@ export default class HTMLLinkElementStyleSheetLoader { const element = this.#element; const browserSettings = this.#browserFrame.page.context.browser.settings; - if (!url || !rel || rel.toLowerCase() !== 'stylesheet' || !element.isConnected) { + if ( + !url || + !rel || + rel.toLowerCase() !== 'stylesheet' || + !element[PropertySymbol.isConnected] + ) { return; } let absoluteURL: string; try { - absoluteURL = new URL(url, element.ownerDocument[PropertySymbol.defaultView].location).href; + absoluteURL = new URL( + url, + element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location + ).href; } catch (error) { this.#loadedStyleSheetURL = null; element.dispatchEvent(new Event('error')); @@ -69,10 +77,10 @@ export default class HTMLLinkElementStyleSheetLoader { const resourceFetch = new ResourceFetch({ browserFrame: this.#browserFrame, - window: element.ownerDocument[PropertySymbol.defaultView] + window: element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow] }); const readyStateManager = (<{ [PropertySymbol.readyStateManager]: DocumentReadyStateManager }>( - (element.ownerDocument[PropertySymbol.defaultView]) + (element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow]) ))[PropertySymbol.readyStateManager]; this.#loadedStyleSheetURL = absoluteURL; @@ -95,7 +103,7 @@ export default class HTMLLinkElementStyleSheetLoader { } else { const styleSheet = new CSSStyleSheet(); styleSheet.replaceSync(code); - (element.sheet) = styleSheet; + element[PropertySymbol.sheet] = styleSheet; element.dispatchEvent(new Event('load')); } } diff --git a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts index 7d248b8b0..2775abe06 100644 --- a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts +++ b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts @@ -4,6 +4,7 @@ import DOMException from '../../exception/DOMException.js'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import HTMLElement from '../html-element/HTMLElement.js'; import IHTMLMediaElement, { IMediaError } from './IHTMLMediaElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * @@ -30,19 +31,6 @@ function getTimeRangeDummy(): object { * */ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaElement { - // Public Properties - public readonly buffered = getTimeRangeDummy(); - public readonly duration = NaN; - public readonly error: IMediaError = null; - public readonly ended = false; - public readonly networkState = 0; - public readonly readyState = 0; - public readonly textTracks = []; - public readonly videoTracks = []; - public readonly seeking = false; - public readonly seekable = getTimeRangeDummy(); - public readonly played = getTimeRangeDummy(); - // Events public onabort: (event: Event) => void | null = null; public oncanplay: (event: Event) => void | null = null; @@ -68,14 +56,126 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE public onvolumechange: (event: Event) => void | null = null; public onwaiting: (event: Event) => void | null = null; - #volume = 1; - #paused = true; - #currentTime = 0; - #playbackRate = 1; - #defaultPlaybackRate = 1; - #muted = false; - #defaultMuted = false; - #preservesPitch = true; + // Internal Properties + public [PropertySymbol.volume] = 1; + public [PropertySymbol.paused] = true; + public [PropertySymbol.currentTime] = 0; + public [PropertySymbol.playbackRate] = 1; + public [PropertySymbol.defaultPlaybackRate] = 1; + public [PropertySymbol.muted] = false; + public [PropertySymbol.defaultMuted] = false; + public [PropertySymbol.preservesPitch] = true; + public [PropertySymbol.buffered]: object = getTimeRangeDummy(); + public [PropertySymbol.duration] = NaN; + public [PropertySymbol.error]: IMediaError = null; + public [PropertySymbol.ended] = false; + public [PropertySymbol.networkState] = 0; + public [PropertySymbol.readyState] = 0; + public [PropertySymbol.textTracks]: object[] = []; + public [PropertySymbol.videoTracks]: object[] = []; + public [PropertySymbol.seeking] = false; + public [PropertySymbol.seekable] = getTimeRangeDummy(); + public [PropertySymbol.played] = getTimeRangeDummy(); + + /** + * Returns buffered. + * + * @returns Buffered. + */ + public get buffered(): object { + return this[PropertySymbol.buffered]; + } + + /** + * Returns duration. + * + * @returns Duration. + */ + public get duration(): number { + return this[PropertySymbol.duration]; + } + + /** + * Returns error. + * + * @returns Error. + */ + public get error(): IMediaError { + return this[PropertySymbol.error]; + } + + /** + * Returns ended. + * + * @returns Ended. + */ + public get ended(): boolean { + return this[PropertySymbol.ended]; + } + + /** + * Returns networkState. + * + * @returns NetworkState. + */ + public get networkState(): number { + return this[PropertySymbol.networkState]; + } + + /** + * Returns readyState. + * + * @returns ReadyState. + */ + public get readyState(): number { + return this[PropertySymbol.readyState]; + } + + /** + * Returns textTracks. + * + * @returns TextTracks. + */ + public get textTracks(): object[] { + return this[PropertySymbol.textTracks]; + } + + /** + * Returns videoTracks. + * + * @returns VideoTracks. + */ + public get videoTracks(): object[] { + return this[PropertySymbol.videoTracks]; + } + + /** + * Returns seeking. + * + * @returns Seeking. + */ + public get seeking(): boolean { + return this[PropertySymbol.seeking]; + } + + /** + * Returns seekable. + * + * @returns Seekable. + */ + public get seekable(): object { + return this[PropertySymbol.seekable]; + } + + /** + * Returns played. + * + * @returns Played. + */ + public get played(): object { + return this[PropertySymbol.played]; + } + /** * Returns autoplay. * @@ -147,11 +247,11 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns Muted. */ public get muted(): boolean { - if (this.#muted) { - return this.#muted; + if (this[PropertySymbol.muted]) { + return this[PropertySymbol.muted]; } - if (!this.#defaultMuted) { + if (!this[PropertySymbol.defaultMuted]) { return this.getAttribute('muted') !== null; } @@ -164,8 +264,8 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @param muted Muted. */ public set muted(muted: boolean) { - this.#muted = !!muted; - if (!muted && !this.#defaultMuted) { + this[PropertySymbol.muted] = !!muted; + if (!muted && !this[PropertySymbol.defaultMuted]) { this.removeAttribute('muted'); } else { this.setAttribute('muted', ''); @@ -178,7 +278,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns DefaultMuted. */ public get defaultMuted(): boolean { - return this.#defaultMuted; + return this[PropertySymbol.defaultMuted]; } /** @@ -187,8 +287,8 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @param defaultMuted DefaultMuted. */ public set defaultMuted(defaultMuted: boolean) { - this.#defaultMuted = !!defaultMuted; - if (!this.#defaultMuted && !this.#muted) { + this[PropertySymbol.defaultMuted] = !!defaultMuted; + if (!this[PropertySymbol.defaultMuted] && !this[PropertySymbol.muted]) { this.removeAttribute('muted'); } else { this.setAttribute('muted', ''); @@ -232,7 +332,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns Volume. */ public get volume(): number { - return this.#volume; + return this[PropertySymbol.volume]; } /** @@ -255,7 +355,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE ); } // TODO: volumechange event https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volumechange_event - this.#volume = parsedVolume; + this[PropertySymbol.volume] = parsedVolume; } /** @@ -290,7 +390,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns CurrentTime. */ public get currentTime(): number { - return this.#currentTime; + return this[PropertySymbol.currentTime]; } /** @@ -305,7 +405,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE `Failed to set the 'currentTime' property on 'HTMLMediaElement': The provided double value is non-finite.` ); } - this.#currentTime = parsedCurrentTime; + this[PropertySymbol.currentTime] = parsedCurrentTime; } /** @@ -314,7 +414,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns PlaybackRate. */ public get playbackRate(): number { - return this.#playbackRate; + return this[PropertySymbol.playbackRate]; } /** @@ -329,7 +429,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE `Failed to set the 'playbackRate' property on 'HTMLMediaElement': The provided double value is non-finite.` ); } - this.#playbackRate = parsedPlaybackRate; + this[PropertySymbol.playbackRate] = parsedPlaybackRate; } /** @@ -338,7 +438,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns DefaultPlaybackRate. */ public get defaultPlaybackRate(): number { - return this.#defaultPlaybackRate; + return this[PropertySymbol.defaultPlaybackRate]; } /** @@ -353,7 +453,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE `Failed to set the 'defaultPlaybackRate' property on 'HTMLMediaElement': The provided double value is non-finite.` ); } - this.#defaultPlaybackRate = parsedDefaultPlaybackRate; + this[PropertySymbol.defaultPlaybackRate] = parsedDefaultPlaybackRate; } /** @@ -362,7 +462,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns PlaybackRate. */ public get preservesPitch(): boolean { - return this.#preservesPitch; + return this[PropertySymbol.preservesPitch]; } /** @@ -371,7 +471,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @param preservesPitch PreservesPitch. */ public set preservesPitch(preservesPitch: boolean) { - this.#preservesPitch = Boolean(preservesPitch); + this[PropertySymbol.preservesPitch] = Boolean(preservesPitch); } /** @@ -398,14 +498,14 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns Paused. */ public get paused(): boolean { - return this.#paused; + return this[PropertySymbol.paused]; } /** * Pause played media. */ public pause(): void { - this.#paused = true; + this[PropertySymbol.paused] = true; this.dispatchEvent(new Event('pause', { bubbles: false, cancelable: false })); } @@ -413,7 +513,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * Start playing media. */ public async play(): Promise { - this.#paused = false; + this[PropertySymbol.paused] = false; return Promise.resolve(); } diff --git a/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElement.ts b/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElement.ts index 722923219..7c11b02f3 100644 --- a/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElement.ts +++ b/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElement.ts @@ -15,7 +15,9 @@ import IHTMLOptionElement from './IHTMLOptionElement.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement. */ export default class HTMLOptionElement extends HTMLElement implements IHTMLOptionElement { - public override readonly attributes: INamedNodeMap = new HTMLOptionElementNamedNodeMap(this); + public override [PropertySymbol.attributes]: INamedNodeMap = new HTMLOptionElementNamedNodeMap( + this + ); public [PropertySymbol.selectedness] = false; public [PropertySymbol.dirtyness] = false; diff --git a/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElementNamedNodeMap.ts index 6f31d7ba3..fb99cff3e 100644 --- a/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-option-element/HTMLOptionElementNamedNodeMap.ts @@ -20,8 +20,8 @@ export default class HTMLOptionElementNamedNodeMap extends HTMLElementNamedNodeM if ( !this[PropertySymbol.ownerElement][PropertySymbol.dirtyness] && - item.name === 'selected' && - replacedItem?.value !== item.value + item[PropertySymbol.name] === 'selected' && + replacedItem?.[PropertySymbol.value] !== item[PropertySymbol.value] ) { const selectNode = ( this[PropertySymbol.ownerElement][PropertySymbol.selectNode] @@ -46,7 +46,7 @@ export default class HTMLOptionElementNamedNodeMap extends HTMLElementNamedNodeM if ( removedItem && !this[PropertySymbol.ownerElement][PropertySymbol.dirtyness] && - removedItem.name === 'selected' + removedItem[PropertySymbol.name] === 'selected' ) { const selectNode = ( this[PropertySymbol.ownerElement][PropertySymbol.selectNode] 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 8aef02c4c..20496e98a 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts @@ -19,10 +19,15 @@ import BrowserErrorCaptureEnum from '../../browser/enums/BrowserErrorCaptureEnum * https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement. */ export default class HTMLScriptElement extends HTMLElement implements IHTMLScriptElement { - public override readonly attributes: INamedNodeMap; + // Events public onerror: (event: ErrorEvent) => void = null; public onload: (event: Event) => void = null; + + // Internal properties + public override [PropertySymbol.attributes]: INamedNodeMap; public [PropertySymbol.evaluateScript] = true; + + // Private properties #scriptLoader: HTMLScriptElementScriptLoader; /** @@ -38,7 +43,7 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip browserFrame }); - this.attributes = new HTMLScriptElementNamedNodeMap(this, this.#scriptLoader); + this[PropertySymbol.attributes] = new HTMLScriptElementNamedNodeMap(this, this.#scriptLoader); } /** @@ -190,10 +195,10 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip * @override */ public override [PropertySymbol.connectToNode](parentNode: INode = null): void { - const isConnected = this.isConnected; - const isParentConnected = parentNode ? parentNode.isConnected : false; + const isConnected = this[PropertySymbol.isConnected]; + const isParentConnected = parentNode ? parentNode[PropertySymbol.isConnected] : false; const browserSettings = WindowBrowserSettingsReader.getSettings( - this.ownerDocument[PropertySymbol.defaultView] + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow] ); super[PropertySymbol.connectToNode](parentNode); @@ -217,24 +222,26 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip type === 'application/x-javascript' || type.startsWith('text/javascript')) ) { - this.ownerDocument[PropertySymbol.currentScript] = this; + this[PropertySymbol.ownerDocument][PropertySymbol.currentScript] = this; const code = - `//# sourceURL=${this.ownerDocument[PropertySymbol.defaultView].location.href}\n` + - textContent; + `//# sourceURL=${ + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location.href + }\n` + textContent; if ( browserSettings.disableErrorCapturing || browserSettings.errorCapture !== BrowserErrorCaptureEnum.tryAndCatch ) { - this.ownerDocument[PropertySymbol.defaultView].eval(code); + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].eval(code); } else { - WindowErrorUtility.captureError(this.ownerDocument[PropertySymbol.defaultView], () => - this.ownerDocument[PropertySymbol.defaultView].eval(code) + WindowErrorUtility.captureError( + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow], + () => this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].eval(code) ); } - this.ownerDocument[PropertySymbol.currentScript] = null; + this[PropertySymbol.ownerDocument][PropertySymbol.currentScript] = null; } } } diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementNamedNodeMap.ts index be0c3a9f2..391131687 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementNamedNodeMap.ts @@ -31,11 +31,11 @@ export default class HTMLScriptElementNamedNodeMap extends HTMLElementNamedNodeM const replacedItem = super.setNamedItem(item); if ( - item.name === 'src' && - item.value !== null && - this[PropertySymbol.ownerElement].isConnected + item[PropertySymbol.name] === 'src' && + item[PropertySymbol.value] !== null && + this[PropertySymbol.ownerElement][PropertySymbol.isConnected] ) { - this.#scriptLoader.loadScript(item.value); + this.#scriptLoader.loadScript(item[PropertySymbol.value]); } return replacedItem || null; diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts index 7dfa4fe7b..86d8b3137 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts @@ -39,13 +39,16 @@ export default class HTMLScriptElementScriptLoader { const element = this.#element; const async = element.getAttribute('async') !== null; - if (!url || !element.isConnected) { + if (!url || !element[PropertySymbol.isConnected]) { return; } let absoluteURL: string; try { - absoluteURL = new URL(url, element.ownerDocument[PropertySymbol.defaultView].location).href; + absoluteURL = new URL( + url, + element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location + ).href; } catch (error) { this.#loadedScriptURL = null; element.dispatchEvent(new Event('error')); @@ -72,7 +75,7 @@ export default class HTMLScriptElementScriptLoader { const resourceFetch = new ResourceFetch({ browserFrame: this.#browserFrame, - window: element.ownerDocument[PropertySymbol.defaultView] + window: element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow] }); let code: string | null = null; let error: Error | null = null; @@ -82,7 +85,7 @@ export default class HTMLScriptElementScriptLoader { if (async) { const readyStateManager = (< { [PropertySymbol.readyStateManager]: DocumentReadyStateManager } - >(element.ownerDocument[PropertySymbol.defaultView]))[ + >(element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow]))[ PropertySymbol.readyStateManager ]; @@ -106,20 +109,21 @@ export default class HTMLScriptElementScriptLoader { if (error) { WindowErrorUtility.dispatchError(element, error); } else { - element.ownerDocument[PropertySymbol.currentScript] = element; + element[PropertySymbol.ownerDocument][PropertySymbol.currentScript] = element; code = '//# sourceURL=' + absoluteURL + '\n' + code; if ( browserSettings.disableErrorCapturing || browserSettings.errorCapture !== BrowserErrorCaptureEnum.tryAndCatch ) { - element.ownerDocument[PropertySymbol.defaultView].eval(code); + element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].eval(code); } else { - WindowErrorUtility.captureError(element.ownerDocument[PropertySymbol.defaultView], () => - element.ownerDocument[PropertySymbol.defaultView].eval(code) + WindowErrorUtility.captureError( + element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow], + () => element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].eval(code) ); } - element.ownerDocument[PropertySymbol.currentScript] = null; + element[PropertySymbol.ownerDocument][PropertySymbol.currentScript] = null; element.dispatchEvent(new Event('load')); } } 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 1f0515cf4..89ba3d452 100644 --- a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts +++ b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts @@ -26,21 +26,56 @@ import HTMLSelectElementNamedNodeMap from './HTMLSelectElementNamedNodeMap.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement. */ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelectElement { - public override readonly attributes: INamedNodeMap = new HTMLSelectElementNamedNodeMap(this); - - // Public properties. - public readonly length = 0; - public readonly options: IHTMLOptionsCollection = new HTMLOptionsCollection(this); - public readonly validationMessage = ''; - public readonly validity = new ValidityState(this); - - // Private properties + // Internal properties. + public override [PropertySymbol.attributes]: INamedNodeMap = new HTMLSelectElementNamedNodeMap( + this + ); + public [PropertySymbol.validationMessage] = ''; + public [PropertySymbol.validity] = new ValidityState(this); public [PropertySymbol.selectNode]: INode = this; + public [PropertySymbol.length] = 0; + public [PropertySymbol.options]: IHTMLOptionsCollection = new HTMLOptionsCollection(this); // Events public onchange: (event: Event) => void | null = null; public oninput: (event: Event) => void | null = null; + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this[PropertySymbol.length]; + } + + /** + * Returns options. + * + * @returns Options. + */ + public get options(): IHTMLOptionsCollection { + return this[PropertySymbol.options]; + } + + /** + * Returns validation message. + * + * @returns Validation message. + */ + public get validationMessage(): string { + return this[PropertySymbol.validationMessage]; + } + + /** + * Returns validity. + * + * @returns Validity. + */ + public get validity(): ValidityState { + return this[PropertySymbol.validity]; + } + /** * Returns name. * @@ -162,8 +197,8 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @returns Value. */ public get value(): string { - for (let i = 0, max = this.options.length; i < max; i++) { - const option = this.options[i]; + for (let i = 0, max = this[PropertySymbol.options].length; i < max; i++) { + const option = this[PropertySymbol.options][i]; if (option[PropertySymbol.selectedness]) { return option.value; } @@ -178,8 +213,8 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @param value Value. */ public set value(value: string) { - for (let i = 0, max = this.options.length; i < max; i++) { - const option = this.options[i]; + for (let i = 0, max = this[PropertySymbol.options].length; i < max; i++) { + const option = this[PropertySymbol.options][i]; if (option.value === value) { option[PropertySymbol.selectedness] = true; option[PropertySymbol.dirtyness] = true; @@ -195,8 +230,8 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @returns Value. */ public get selectedIndex(): number { - for (let i = 0, max = this.options.length; i < max; i++) { - if ((this.options[i])[PropertySymbol.selectedness]) { + for (let i = 0, max = this[PropertySymbol.options].length; i < max; i++) { + if ((this[PropertySymbol.options][i])[PropertySymbol.selectedness]) { return i; } } @@ -210,11 +245,11 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec */ public set selectedIndex(selectedIndex: number) { if (typeof selectedIndex === 'number' && !isNaN(selectedIndex)) { - for (let i = 0, max = this.options.length; i < max; i++) { - (this.options[i])[PropertySymbol.selectedness] = false; + for (let i = 0, max = this[PropertySymbol.options].length; i < max; i++) { + (this[PropertySymbol.options][i])[PropertySymbol.selectedness] = false; } - const selectedOption = this.options[selectedIndex]; + const selectedOption = this[PropertySymbol.options][selectedIndex]; if (selectedOption) { selectedOption[PropertySymbol.selectedness] = true; selectedOption[PropertySymbol.dirtyness] = true; @@ -261,7 +296,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @param index Index. */ public item(index: number): IHTMLOptionElement { - return this.options.item(index); + return this[PropertySymbol.options].item(index); } /** @@ -271,7 +306,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @param before HTMLOptionElement or index number. */ public add(element: IHTMLOptionElement, before?: number | IHTMLOptionElement): void { - this.options.add(element, before); + this[PropertySymbol.options].add(element, before); } /** @@ -281,7 +316,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec */ public override remove(index?: number): void { if (typeof index === 'number') { - this.options.remove(index); + this[PropertySymbol.options].remove(index); } else { super.remove(); } @@ -293,7 +328,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @param message Message. */ public setCustomValidity(message: string): void { - (this.validationMessage) = String(message); + this[PropertySymbol.validationMessage] = String(message); } /** @@ -302,7 +337,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @returns "true" if the field is valid. */ public checkValidity(): boolean { - const valid = this.disabled || this.validity.valid; + const valid = this.disabled || this[PropertySymbol.validity].valid; if (!valid) { this.dispatchEvent(new Event('invalid', { bubbles: true, cancelable: true })); } @@ -330,10 +365,13 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec public [PropertySymbol.updateOptionItems](selectedOption?: IHTMLOptionElement): void { const optionElements = >this.getElementsByTagName('option'); - if (optionElements.length < this.options.length) { - this.options.splice(this.options.length - 1, this.options.length - optionElements.length); + if (optionElements.length < this[PropertySymbol.options].length) { + this[PropertySymbol.options].splice( + this[PropertySymbol.options].length - 1, + this[PropertySymbol.options].length - optionElements.length + ); - for (let i = optionElements.length - 1, max = this.length; i < max; i++) { + for (let i = optionElements.length - 1, max = this[PropertySymbol.length]; i < max; i++) { delete this[i]; } } @@ -342,7 +380,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec const selected: HTMLOptionElement[] = []; for (let i = 0; i < optionElements.length; i++) { - this.options[i] = optionElements[i]; + this[PropertySymbol.options][i] = optionElements[i]; this[i] = optionElements[i]; if (!isMultiple) { @@ -357,7 +395,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec } } - (this.length) = optionElements.length; + (this[PropertySymbol.length]) = optionElements.length; const size = this.#getDisplaySize(); @@ -366,11 +404,11 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec const option = optionElements[i]; let disabled = option.hasAttributeNS(null, 'disabled'); - const parentNode = option.parentNode; + const parentNode = option[PropertySymbol.parentNode]; if ( parentNode && - parentNode.nodeType === NodeTypeEnum.elementNode && - parentNode.tagName === 'OPTGROUP' && + parentNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode && + parentNode[PropertySymbol.tagName] === 'OPTGROUP' && parentNode.hasAttributeNS(null, 'disabled') ) { disabled = true; diff --git a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElementNamedNodeMap.ts index c6f5bcf18..667144978 100644 --- a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElementNamedNodeMap.ts @@ -19,18 +19,18 @@ export default class HTMLSelectElementNamedNodeMap extends HTMLElementNamedNodeM const replacedItem = super.setNamedItem(item); if ( - (item.name === 'id' || item.name === 'name') && + (item[PropertySymbol.name] === 'id' || item[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { - if (replacedItem && replacedItem.value) { + if (replacedItem && replacedItem[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], replacedItem.value); + ](this[PropertySymbol.ownerElement], replacedItem[PropertySymbol.value]); } - if (item.value) { + if (item[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.appendFormControlItem - ](this[PropertySymbol.ownerElement], item.value); + ](this[PropertySymbol.ownerElement], item[PropertySymbol.value]); } } @@ -45,12 +45,12 @@ export default class HTMLSelectElementNamedNodeMap extends HTMLElementNamedNodeM if ( removedItem && - (removedItem.name === 'id' || removedItem.name === 'name') && + (removedItem[PropertySymbol.name] === 'id' || removedItem[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], removedItem.value); + ](this[PropertySymbol.ownerElement], removedItem[PropertySymbol.value]); } return removedItem; diff --git a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts index 3f4b9787b..b16da2663 100644 --- a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts +++ b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts @@ -10,7 +10,7 @@ import IHTMLStyleElement from './IHTMLStyleElement.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLStyleElement. */ export default class HTMLStyleElement extends HTMLElement implements IHTMLStyleElement { - private [PropertySymbol.styleSheet]: CSSStyleSheet | null = null; + private [PropertySymbol.sheet]: CSSStyleSheet | null = null; /** * Returns CSS style sheet. @@ -18,14 +18,14 @@ export default class HTMLStyleElement extends HTMLElement implements IHTMLStyleE * @returns CSS style sheet. */ public get sheet(): CSSStyleSheet { - if (!this.isConnected) { + if (!this[PropertySymbol.isConnected]) { return null; } - if (!this[PropertySymbol.styleSheet]) { - this[PropertySymbol.styleSheet] = new CSSStyleSheet(); + if (!this[PropertySymbol.sheet]) { + this[PropertySymbol.sheet] = new CSSStyleSheet(); } - this[PropertySymbol.styleSheet].replaceSync(this.textContent); - return this[PropertySymbol.styleSheet]; + this[PropertySymbol.sheet].replaceSync(this.textContent); + return this[PropertySymbol.sheet]; } /** diff --git a/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts b/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts index 839138e84..8ff4fa083 100644 --- a/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts +++ b/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts @@ -14,7 +14,18 @@ import DocumentFragment from '../document-fragment/DocumentFragment.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement. */ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTemplateElement { - public readonly content: IDocumentFragment = this.ownerDocument.createDocumentFragment(); + // Internal properties + public [PropertySymbol.content]: IDocumentFragment = + this[PropertySymbol.ownerDocument].createDocumentFragment(); + + /** + * Returns content. + * + * @returns Content. + */ + public get content(): IDocumentFragment { + return this[PropertySymbol.content]; + } /** * @override @@ -27,25 +38,29 @@ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTem * @override */ public set innerHTML(html: string) { - for (const child of (this.content)[PropertySymbol.childNodes].slice()) { - this.content.removeChild(child); + const content = this[PropertySymbol.content]; + + for (const child of content[PropertySymbol.childNodes].slice()) { + this[PropertySymbol.content].removeChild(child); } - XMLParser.parse(this.ownerDocument, html, { rootNode: this.content }); + XMLParser.parse(this[PropertySymbol.ownerDocument], html, { + rootNode: this[PropertySymbol.content] + }); } /** * @override */ public get firstChild(): INode { - return this.content.firstChild; + return this[PropertySymbol.content].firstChild; } /** * @override */ public get lastChild(): INode { - return this.content.lastChild; + return this[PropertySymbol.content].lastChild; } /** @@ -56,8 +71,9 @@ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTem includeShadowRoots: options && options.includeShadowRoots, escapeEntities: false }); + const content = this[PropertySymbol.content]; let xml = ''; - for (const node of (this.content)[PropertySymbol.childNodes]) { + for (const node of content[PropertySymbol.childNodes]) { xml += xmlSerializer.serializeToString(node); } return xml; @@ -67,28 +83,28 @@ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTem * @override */ public appendChild(node: INode): INode { - return this.content.appendChild(node); + return this[PropertySymbol.content].appendChild(node); } /** * @override */ public removeChild(node: INode): INode { - return this.content.removeChild(node); + return this[PropertySymbol.content].removeChild(node); } /** * @override */ public insertBefore(newNode: INode, referenceNode: INode): INode { - return this.content.insertBefore(newNode, referenceNode); + return this[PropertySymbol.content].insertBefore(newNode, referenceNode); } /** * @override */ public replaceChild(newChild: INode, oldChild: INode): INode { - return this.content.replaceChild(newChild, oldChild); + return this[PropertySymbol.content].replaceChild(newChild, oldChild); } /** @@ -96,7 +112,7 @@ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTem */ public cloneNode(deep = false): IHTMLTemplateElement { const clone = super.cloneNode(deep); - (clone.content) = this.content.cloneNode(deep); + clone[PropertySymbol.content] = this[PropertySymbol.content].cloneNode(deep); return clone; } } diff --git a/packages/happy-dom/src/nodes/html-template-element/IHTMLTemplateElement.ts b/packages/happy-dom/src/nodes/html-template-element/IHTMLTemplateElement.ts index 19ad62717..2cbb37631 100644 --- a/packages/happy-dom/src/nodes/html-template-element/IHTMLTemplateElement.ts +++ b/packages/happy-dom/src/nodes/html-template-element/IHTMLTemplateElement.ts @@ -8,7 +8,7 @@ import IHTMLElement from '../html-element/IHTMLElement.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement. */ export default interface IHTMLTemplateElement extends IHTMLElement { - content: IDocumentFragment; + readonly content: IDocumentFragment; /** * Clones a node. diff --git a/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts b/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts index 81036d9d0..f81a80a84 100644 --- a/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts +++ b/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts @@ -23,20 +23,43 @@ import HTMLTextAreaElementNamedNodeMap from './HTMLTextAreaElementNamedNodeMap.j * https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement. */ export default class HTMLTextAreaElement extends HTMLElement implements IHTMLTextAreaElement { - public override readonly attributes: INamedNodeMap = new HTMLTextAreaElementNamedNodeMap(this); public readonly type = 'textarea'; - public readonly validationMessage = ''; - public readonly validity = new ValidityState(this); // Events public oninput: (event: Event) => void | null = null; public onselectionchange: (event: Event) => void | null = null; + // Internal properties + public override [PropertySymbol.attributes]: INamedNodeMap = new HTMLTextAreaElementNamedNodeMap( + this + ); + public [PropertySymbol.validationMessage] = ''; + public [PropertySymbol.validity] = new ValidityState(this); public [PropertySymbol.value] = null; + public [PropertySymbol.textAreaNode]: HTMLTextAreaElement = this; + + // Private properties #selectionStart = null; #selectionEnd = null; #selectionDirection = HTMLInputElementSelectionDirectionEnum.none; - public [PropertySymbol.textAreaNode]: HTMLTextAreaElement = this; + + /** + * Returns validation message. + * + * @returns Validation message. + */ + public get validationMessage(): string { + return this[PropertySymbol.validationMessage]; + } + + /** + * Returns validity. + * + * @returns Validity. + */ + public get validity(): ValidityState { + return this[PropertySymbol.validity]; + } /** * Returns the default value. @@ -519,7 +542,7 @@ export default class HTMLTextAreaElement extends HTMLElement implements IHTMLTex * @param message Message. */ public setCustomValidity(message: string): void { - (this.validationMessage) = String(message); + this[PropertySymbol.validationMessage] = String(message); } /** @@ -528,7 +551,7 @@ export default class HTMLTextAreaElement extends HTMLElement implements IHTMLTex * @returns "true" if the field is valid. */ public checkValidity(): boolean { - const valid = this.disabled || this.readOnly || this.validity.valid; + const valid = this.disabled || this.readOnly || this[PropertySymbol.validity].valid; if (!valid) { this.dispatchEvent(new Event('invalid', { bubbles: true, cancelable: true })); } diff --git a/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElementNamedNodeMap.ts index f461fb8ea..b182f16d1 100644 --- a/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElementNamedNodeMap.ts @@ -19,18 +19,18 @@ export default class HTMLTextAreaElementNamedNodeMap extends HTMLElementNamedNod const replacedItem = super.setNamedItem(item); if ( - (item.name === 'id' || item.name === 'name') && + (item[PropertySymbol.name] === 'id' || item[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { - if (replacedItem && replacedItem.value) { + if (replacedItem && replacedItem[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], replacedItem.value); + ](this[PropertySymbol.ownerElement], replacedItem[PropertySymbol.value]); } - if (item.value) { + if (item[PropertySymbol.value]) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.appendFormControlItem - ](this[PropertySymbol.ownerElement], item.value); + ](this[PropertySymbol.ownerElement], item[PropertySymbol.value]); } } @@ -45,12 +45,12 @@ export default class HTMLTextAreaElementNamedNodeMap extends HTMLElementNamedNod if ( removedItem && - (removedItem.name === 'id' || removedItem.name === 'name') && + (removedItem[PropertySymbol.name] === 'id' || removedItem[PropertySymbol.name] === 'name') && this[PropertySymbol.ownerElement][PropertySymbol.formNode] ) { (this[PropertySymbol.ownerElement][PropertySymbol.formNode])[ PropertySymbol.removeFormControlItem - ](this[PropertySymbol.ownerElement], removedItem.value); + ](this[PropertySymbol.ownerElement], removedItem[PropertySymbol.value]); } return removedItem; diff --git a/packages/happy-dom/src/nodes/html-unknown-element/HTMLUnknownElement.ts b/packages/happy-dom/src/nodes/html-unknown-element/HTMLUnknownElement.ts index 74273f8b0..4eedf2bdf 100644 --- a/packages/happy-dom/src/nodes/html-unknown-element/HTMLUnknownElement.ts +++ b/packages/happy-dom/src/nodes/html-unknown-element/HTMLUnknownElement.ts @@ -24,26 +24,32 @@ export default class HTMLUnknownElement extends HTMLElement implements IHTMLElem * @param parentNode Parent node. */ public [PropertySymbol.connectToNode](parentNode: INode = null): void { - const tagName = this.tagName; + const tagName = this[PropertySymbol.tagName]; // This element can potentially be a custom element that has not been defined yet // Therefore we need to register a callback for when it is defined in CustomElementRegistry and replace it with the registered element (see #404) if ( tagName.includes('-') && - this.ownerDocument[PropertySymbol.defaultView].customElements[PropertySymbol.callbacks] + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].customElements[ + PropertySymbol.callbacks + ] ) { const callbacks = - this.ownerDocument[PropertySymbol.defaultView].customElements[PropertySymbol.callbacks]; + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].customElements[ + PropertySymbol.callbacks + ]; if (parentNode && !this.#customElementDefineCallback) { const callback = (): void => { - if (this.parentNode) { - const newElement = this.ownerDocument.createElement(tagName); + if (this[PropertySymbol.parentNode]) { + const newElement = ( + this[PropertySymbol.ownerDocument].createElement(tagName) + ); (>newElement[PropertySymbol.childNodes]) = this[PropertySymbol.childNodes]; (>newElement[PropertySymbol.children]) = this[PropertySymbol.children]; - (newElement.isConnected) = this.isConnected; + (newElement[PropertySymbol.isConnected]) = this[PropertySymbol.isConnected]; newElement[PropertySymbol.rootNode] = this[PropertySymbol.rootNode]; newElement[PropertySymbol.formNode] = this[PropertySymbol.formNode]; @@ -52,8 +58,10 @@ export default class HTMLUnknownElement extends HTMLElement implements IHTMLElem newElement[PropertySymbol.observers] = this[PropertySymbol.observers]; newElement[PropertySymbol.isValue] = this[PropertySymbol.isValue]; - for (let i = 0, max = this.attributes.length; i < max; i++) { - newElement.attributes.setNamedItem(this.attributes[i]); + for (let i = 0, max = this[PropertySymbol.attributes].length; i < max; i++) { + newElement[PropertySymbol.attributes].setNamedItem( + this[PropertySymbol.attributes][i] + ); } (>this[PropertySymbol.childNodes]) = new NodeList(); @@ -64,33 +72,46 @@ export default class HTMLUnknownElement extends HTMLElement implements IHTMLElem this[PropertySymbol.textAreaNode] = null; this[PropertySymbol.observers] = []; this[PropertySymbol.isValue] = null; - (this.attributes) = new HTMLElementNamedNodeMap(this); + (this[PropertySymbol.attributes]) = + new HTMLElementNamedNodeMap(this); for ( - let i = 0, max = (this.parentNode)[PropertySymbol.childNodes].length; + let i = 0, + max = (this[PropertySymbol.parentNode])[PropertySymbol.childNodes] + .length; i < max; i++ ) { - if ((this.parentNode)[PropertySymbol.childNodes][i] === this) { - (this.parentNode)[PropertySymbol.childNodes][i] = newElement; + if ( + (this[PropertySymbol.parentNode])[PropertySymbol.childNodes][i] === + this + ) { + (this[PropertySymbol.parentNode])[PropertySymbol.childNodes][i] = + newElement; break; } } - if ((this.parentNode)[PropertySymbol.children]) { + if ((this[PropertySymbol.parentNode])[PropertySymbol.children]) { for ( - let i = 0, max = (this.parentNode)[PropertySymbol.children].length; + let i = 0, + max = (this[PropertySymbol.parentNode])[PropertySymbol.children] + .length; i < max; i++ ) { - if ((this.parentNode)[PropertySymbol.children][i] === this) { - (this.parentNode)[PropertySymbol.children][i] = newElement; + if ( + (this[PropertySymbol.parentNode])[PropertySymbol.children][i] === + this + ) { + (this[PropertySymbol.parentNode])[PropertySymbol.children][i] = + newElement; break; } } } - if (newElement.isConnected && newElement.connectedCallback) { + if (newElement[PropertySymbol.isConnected] && newElement.connectedCallback) { newElement.connectedCallback(); } diff --git a/packages/happy-dom/src/nodes/node/INode.ts b/packages/happy-dom/src/nodes/node/INode.ts index 0676fd189..a13ab5808 100644 --- a/packages/happy-dom/src/nodes/node/INode.ts +++ b/packages/happy-dom/src/nodes/node/INode.ts @@ -22,8 +22,8 @@ export default interface INode extends IEventTarget { readonly DOCUMENT_POSITION_CONTAINED_BY: NodeDocumentPositionEnum; readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: NodeDocumentPositionEnum; readonly ownerDocument: IDocument; - readonly parentNode: INode; - readonly parentElement: IElement; + readonly parentNode: INode | null; + readonly parentElement: IElement | null; readonly nodeType: number; readonly childNodes: INodeList; readonly isConnected: boolean; diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index 277829fcc..924039645 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -17,7 +17,7 @@ import NodeFactory from '../NodeFactory.js'; * Node. */ export default class Node extends EventTarget implements INode { - // Can be set before the Node is created. + // This is used when overriding a Node class and set it in a owner document context (used in BrowserWindow.constructor()). public static [PropertySymbol.ownerDocument]: IDocument | null; // Public properties @@ -37,28 +37,29 @@ export default class Node extends EventTarget implements INode { public static readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = NodeDocumentPositionEnum.implementationSpecific; public static readonly DOCUMENT_POSITION_PRECEDING = NodeDocumentPositionEnum.preceding; - public readonly ELEMENT_NODE = NodeTypeEnum.elementNode; - public readonly ATTRIBUTE_NODE = NodeTypeEnum.attributeNode; - public readonly TEXT_NODE = NodeTypeEnum.textNode; - public readonly CDATA_SECTION_NODE = NodeTypeEnum.cdataSectionNode; - public readonly COMMENT_NODE = NodeTypeEnum.commentNode; - public readonly DOCUMENT_NODE = NodeTypeEnum.documentNode; - public readonly DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode; - public readonly DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode; - public readonly PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode; - public readonly DOCUMENT_POSITION_CONTAINED_BY = NodeDocumentPositionEnum.containedBy; - public readonly DOCUMENT_POSITION_CONTAINS = NodeDocumentPositionEnum.contains; - public readonly DOCUMENT_POSITION_DISCONNECTED = NodeDocumentPositionEnum.disconnect; - public readonly DOCUMENT_POSITION_FOLLOWING = NodeDocumentPositionEnum.following; - public readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = - NodeDocumentPositionEnum.implementationSpecific; - public readonly DOCUMENT_POSITION_PRECEDING = NodeDocumentPositionEnum.preceding; - public readonly parentNode: INode = null; - public readonly nodeType: number; - public readonly isConnected: boolean = false; - public readonly ownerDocument: IDocument = null; + + // Defined on the prototype + public readonly ELEMENT_NODE; + public readonly ATTRIBUTE_NODE; + public readonly TEXT_NODE; + public readonly CDATA_SECTION_NODE; + public readonly COMMENT_NODE; + public readonly DOCUMENT_NODE; + public readonly DOCUMENT_TYPE_NODE; + public readonly DOCUMENT_FRAGMENT_NODE; + public readonly PROCESSING_INSTRUCTION_NODE; + public readonly DOCUMENT_POSITION_CONTAINED_BY; + public readonly DOCUMENT_POSITION_CONTAINS; + public readonly DOCUMENT_POSITION_DISCONNECTED; + public readonly DOCUMENT_POSITION_FOLLOWING; + public readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; + public readonly DOCUMENT_POSITION_PRECEDING; // Internal properties + public [PropertySymbol.isConnected] = false; + public [PropertySymbol.ownerDocument]: IDocument; + public [PropertySymbol.parentNode]: INode | null = null; + public [PropertySymbol.nodeType]: NodeTypeEnum; public [PropertySymbol.rootNode]: INode = null; public [PropertySymbol.formNode]: INode = null; public [PropertySymbol.selectNode]: INode = null; @@ -72,7 +73,9 @@ export default class Node extends EventTarget implements INode { constructor() { super(); if ((this.constructor)[PropertySymbol.ownerDocument] !== undefined) { - this.ownerDocument = (this.constructor)[PropertySymbol.ownerDocument]; + this[PropertySymbol.ownerDocument] = (this.constructor)[ + PropertySymbol.ownerDocument + ]; } else { const ownerDocument = NodeFactory.pullOwnerDocument(); if (!ownerDocument) { @@ -80,7 +83,7 @@ export default class Node extends EventTarget implements INode { 'Failed to construct "Node": No owner document in queue. Please use "NodeFactory" to create instances of a Node.' ); } - this.ownerDocument = ownerDocument; + this[PropertySymbol.ownerDocument] = ownerDocument; } } @@ -93,6 +96,42 @@ export default class Node extends EventTarget implements INode { return this.constructor.name; } + /** + * Returns connected state. + * + * @returns Connected state. + */ + public get isConnected(): boolean { + return this[PropertySymbol.isConnected]; + } + + /** + * Returns owner document. + * + * @returns Owner document. + */ + public get ownerDocument(): IDocument { + return this[PropertySymbol.ownerDocument]; + } + + /** + * Returns parent node. + * + * @returns Parent node. + */ + public get parentNode(): INode | null { + return this[PropertySymbol.parentNode]; + } + + /** + * Returns node type. + * + * @returns Node type. + */ + public get nodeType(): number { + return this[PropertySymbol.nodeType]; + } + /** * Get child nodes. * @@ -153,10 +192,12 @@ export default class Node extends EventTarget implements INode { * @returns Node. */ public get previousSibling(): INode { - if (this.parentNode) { - const index = (this.parentNode)[PropertySymbol.childNodes].indexOf(this); + if (this[PropertySymbol.parentNode]) { + const index = (this[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf( + this + ); if (index > 0) { - return (this.parentNode)[PropertySymbol.childNodes][index - 1]; + return (this[PropertySymbol.parentNode])[PropertySymbol.childNodes][index - 1]; } } return null; @@ -168,10 +209,15 @@ export default class Node extends EventTarget implements INode { * @returns Node. */ public get nextSibling(): INode { - if (this.parentNode) { - const index = (this.parentNode)[PropertySymbol.childNodes].indexOf(this); - if (index > -1 && index + 1 < (this.parentNode)[PropertySymbol.childNodes].length) { - return (this.parentNode)[PropertySymbol.childNodes][index + 1]; + if (this[PropertySymbol.parentNode]) { + const index = (this[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf( + this + ); + if ( + index > -1 && + index + 1 < (this[PropertySymbol.parentNode])[PropertySymbol.childNodes].length + ) { + return (this[PropertySymbol.parentNode])[PropertySymbol.childNodes][index + 1]; } } return null; @@ -206,10 +252,10 @@ export default class Node extends EventTarget implements INode { * * @returns Element. */ - public get parentElement(): IElement { - let parent = this.parentNode; - while (parent && parent.nodeType !== NodeTypeEnum.elementNode) { - parent = parent.parentNode; + public get parentElement(): IElement | null { + let parent = this[PropertySymbol.parentNode]; + while (parent && parent[PropertySymbol.nodeType] !== NodeTypeEnum.elementNode) { + parent = parent[PropertySymbol.parentNode]; } return parent; } @@ -220,11 +266,11 @@ export default class Node extends EventTarget implements INode { * @returns Base URI. */ public get baseURI(): string { - const base = this.ownerDocument.querySelector('base'); + const base = this[PropertySymbol.ownerDocument].querySelector('base'); if (base) { return base.href; } - return this.ownerDocument[PropertySymbol.defaultView].location.href; + return this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location.href; } /** @@ -264,7 +310,7 @@ export default class Node extends EventTarget implements INode { * @returns Node. */ public getRootNode(options?: { composed: boolean }): INode { - if (!this.isConnected) { + if (!this[PropertySymbol.isConnected]) { return this; } @@ -272,7 +318,7 @@ export default class Node extends EventTarget implements INode { return this[PropertySymbol.rootNode]; } - return this.ownerDocument; + return this[PropertySymbol.ownerDocument]; } /** @@ -282,19 +328,22 @@ export default class Node extends EventTarget implements INode { * @returns Cloned node. */ public cloneNode(deep = false): INode { - const clone = NodeFactory.createNode(this.ownerDocument, this.constructor); + const clone = NodeFactory.createNode( + this[PropertySymbol.ownerDocument], + this.constructor + ); // Document has childNodes directly when it is created if (clone[PropertySymbol.childNodes].length) { for (const node of clone[PropertySymbol.childNodes].slice()) { - node.parentNode.removeChild(node); + node[PropertySymbol.parentNode].removeChild(node); } } if (deep) { for (const childNode of this[PropertySymbol.childNodes]) { const childClone = childNode.cloneNode(true); - (childClone.parentNode) = clone; + childClone[PropertySymbol.parentNode] = clone; clone[PropertySymbol.childNodes].push(childClone); } } @@ -400,14 +449,14 @@ export default class Node extends EventTarget implements INode { * @param parentNode Parent node. */ public [PropertySymbol.connectToNode](parentNode: INode = null): void { - const isConnected = !!parentNode && parentNode.isConnected; + const isConnected = !!parentNode && parentNode[PropertySymbol.isConnected]; const formNode = (this)[PropertySymbol.formNode]; const selectNode = (this)[PropertySymbol.selectNode]; const textAreaNode = (this)[PropertySymbol.textAreaNode]; - if (this.nodeType !== NodeTypeEnum.documentFragmentNode) { - (this.parentNode) = parentNode; - (this)[PropertySymbol.rootNode] = + if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) { + this[PropertySymbol.parentNode] = parentNode; + this[PropertySymbol.rootNode] = isConnected && parentNode ? (parentNode)[PropertySymbol.rootNode] : null; if (this['tagName'] !== 'FORM') { @@ -429,12 +478,12 @@ export default class Node extends EventTarget implements INode { } } - if (this.isConnected !== isConnected) { - (this.isConnected) = isConnected; + if (this[PropertySymbol.isConnected] !== isConnected) { + this[PropertySymbol.isConnected] = isConnected; if (!isConnected) { - if (this.ownerDocument[PropertySymbol.activeElement] === this) { - this.ownerDocument[PropertySymbol.activeElement] = null; + if (this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] === this) { + this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] = null; } } @@ -493,18 +542,18 @@ export default class Node extends EventTarget implements INode { /** * 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’s element. */ - if (node1.nodeType === Node.ATTRIBUTE_NODE) { + if (node1[PropertySymbol.nodeType] === NodeTypeEnum.attributeNode) { attr1 = node1; - node1 = (attr1).ownerElement; + node1 = (attr1)[PropertySymbol.ownerElement]; } /** * 5. If node2 is an attribute, then: * 5.1. Set attr2 to node2 and node2 to attr2’s element. */ - if (node2.nodeType === Node.ATTRIBUTE_NODE) { + if (node2[PropertySymbol.nodeType] === NodeTypeEnum.attributeNode) { attr2 = node2; - node2 = (attr2).ownerElement; + node2 = (attr2)[PropertySymbol.ownerElement]; /** * 5.2. If attr1 and node1 are non-null, and node2 is node1, then: @@ -513,7 +562,7 @@ export default class Node extends EventTarget implements INode { /** * 5.2.1. For each attr in node2’s attribute list: */ - for (const attr of Object.values((node2).attributes)) { + for (const attr of Object.values((node2)[PropertySymbol.attributes])) { /** * 5.2.1.1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING. */ @@ -547,7 +596,7 @@ export default class Node extends EventTarget implements INode { } node2Ancestors.push(node2Ancestor); - node2Ancestor = node2Ancestor.parentNode; + node2Ancestor = node2Ancestor[PropertySymbol.parentNode]; } const node1Ancestors: INode[] = []; @@ -562,7 +611,7 @@ export default class Node extends EventTarget implements INode { } node1Ancestors.push(node1Ancestor); - node1Ancestor = node1Ancestor.parentNode; + node1Ancestor = node1Ancestor[PropertySymbol.parentNode]; } const reverseArrayIndex = (array: INode[], reverseIndex: number): INode => { @@ -678,3 +727,27 @@ export default class Node extends EventTarget implements INode { return this === node; } } + +// According to the spec, these properties should be on the prototype. +(Node.prototype.ELEMENT_NODE) = NodeTypeEnum.elementNode; +(Node.prototype.ATTRIBUTE_NODE) = NodeTypeEnum.attributeNode; + +(Node.prototype.TEXT_NODE) = NodeTypeEnum.textNode; +(Node.prototype.CDATA_SECTION_NODE) = NodeTypeEnum.cdataSectionNode; +(Node.prototype.COMMENT_NODE) = NodeTypeEnum.commentNode; +(Node.prototype.DOCUMENT_NODE) = NodeTypeEnum.documentNode; +(Node.prototype.DOCUMENT_TYPE_NODE) = NodeTypeEnum.documentTypeNode; +(Node.prototype.DOCUMENT_FRAGMENT_NODE) = NodeTypeEnum.documentFragmentNode; +(Node.prototype.PROCESSING_INSTRUCTION_NODE) = NodeTypeEnum.processingInstructionNode; +(Node.prototype.DOCUMENT_POSITION_CONTAINED_BY) = + NodeDocumentPositionEnum.containedBy; +(Node.prototype.DOCUMENT_POSITION_CONTAINS) = + NodeDocumentPositionEnum.contains; +(Node.prototype.DOCUMENT_POSITION_DISCONNECTED) = + NodeDocumentPositionEnum.disconnect; +(Node.prototype.DOCUMENT_POSITION_FOLLOWING) = + NodeDocumentPositionEnum.following; +(Node.prototype.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) = + NodeDocumentPositionEnum.implementationSpecific; +(Node.prototype.DOCUMENT_POSITION_PRECEDING) = + NodeDocumentPositionEnum.preceding; diff --git a/packages/happy-dom/src/nodes/node/NodeUtility.ts b/packages/happy-dom/src/nodes/node/NodeUtility.ts index 33f954156..3d64fc511 100644 --- a/packages/happy-dom/src/nodes/node/NodeUtility.ts +++ b/packages/happy-dom/src/nodes/node/NodeUtility.ts @@ -47,7 +47,7 @@ export default class NodeUtility { // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node. // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment - if (node.nodeType === NodeTypeEnum.documentFragmentNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode) { for (const child of (node)[PropertySymbol.childNodes].slice()) { ancestorNode.appendChild(child); } @@ -55,15 +55,17 @@ export default class NodeUtility { } // Remove the node from its previous parent if it has any. - if (node.parentNode) { - const index = (node.parentNode)[PropertySymbol.childNodes].indexOf(node); + if (node[PropertySymbol.parentNode]) { + const index = (node[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf( + node + ); if (index !== -1) { - (node.parentNode)[PropertySymbol.childNodes].splice(index, 1); + (node[PropertySymbol.parentNode])[PropertySymbol.childNodes].splice(index, 1); } } - if (ancestorNode.isConnected) { - (ancestorNode.ownerDocument || this)[PropertySymbol.cacheID]++; + if (ancestorNode[PropertySymbol.isConnected]) { + (ancestorNode[PropertySymbol.ownerDocument] || this)[PropertySymbol.cacheID]++; } (ancestorNode)[PropertySymbol.childNodes].push(node); @@ -105,8 +107,8 @@ export default class NodeUtility { throw new DOMException('Failed to remove node. Node is not child of parent.'); } - if (ancestorNode.isConnected) { - (ancestorNode.ownerDocument || this)[PropertySymbol.cacheID]++; + if (ancestorNode[PropertySymbol.isConnected]) { + (ancestorNode[PropertySymbol.ownerDocument] || this)[PropertySymbol.cacheID]++; } (ancestorNode)[PropertySymbol.childNodes].splice(index, 1); @@ -162,7 +164,7 @@ export default class NodeUtility { // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node. // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment - if (newNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + if (newNode[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode) { for (const child of (newNode)[PropertySymbol.childNodes].slice()) { ancestorNode.insertBefore(child, referenceNode); } @@ -182,14 +184,16 @@ export default class NodeUtility { ); } - if (ancestorNode.isConnected) { - (ancestorNode.ownerDocument || this)[PropertySymbol.cacheID]++; + if (ancestorNode[PropertySymbol.isConnected]) { + (ancestorNode[PropertySymbol.ownerDocument] || this)[PropertySymbol.cacheID]++; } - if (newNode.parentNode) { - const index = (newNode.parentNode)[PropertySymbol.childNodes].indexOf(newNode); + if (newNode[PropertySymbol.parentNode]) { + const index = (newNode[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf( + newNode + ); if (index !== -1) { - (newNode.parentNode)[PropertySymbol.childNodes].splice(index, 1); + (newNode[PropertySymbol.parentNode])[PropertySymbol.childNodes].splice(index, 1); } } @@ -229,7 +233,7 @@ export default class NodeUtility { * @returns "true" if the node is a text node. */ public static isTextNode(node: INode | null): node is IText { - return node?.nodeType === NodeTypeEnum.textNode; + return node?.[PropertySymbol.nodeType] === NodeTypeEnum.textNode; } /** @@ -261,27 +265,30 @@ export default class NodeUtility { return false; } - if (includeShadowRoots && referenceNode.isConnected !== ancestorNode.isConnected) { + if ( + includeShadowRoots && + referenceNode[PropertySymbol.isConnected] !== ancestorNode[PropertySymbol.isConnected] + ) { return false; } if ( includeShadowRoots && - ancestorNode === referenceNode.ownerDocument && - referenceNode.isConnected + ancestorNode === referenceNode[PropertySymbol.ownerDocument] && + referenceNode[PropertySymbol.isConnected] ) { return true; } - let parent: INode = referenceNode.parentNode; + let parent: INode = referenceNode[PropertySymbol.parentNode]; while (parent) { if (ancestorNode === parent) { return true; } - parent = parent.parentNode - ? parent.parentNode + parent = parent[PropertySymbol.parentNode] + ? parent[PropertySymbol.parentNode] : includeShadowRoots && (parent).host ? (parent).host : null; @@ -330,7 +337,7 @@ export default class NodeUtility { * @returns Node length. */ public static getNodeLength(node: INode): number { - switch (node.nodeType) { + switch (node[PropertySymbol.nodeType]) { case NodeTypeEnum.documentTypeNode: return 0; @@ -374,7 +381,7 @@ export default class NodeUtility { return nextSibling; } - current = current.parentNode; + current = current[PropertySymbol.parentNode]; } return null; @@ -388,7 +395,7 @@ export default class NodeUtility { */ public static nextDescendantNode(node: INode): INode { while (node && !node.nextSibling) { - node = node.parentNode; + node = node[PropertySymbol.parentNode]; } if (!node) { @@ -405,13 +412,13 @@ export default class NodeUtility { * @param elementB */ public static attributeListsEqual(elementA: IElement, elementB: IElement): boolean { - for (let i = 0, max = elementA.attributes.length; i < max; i++) { - const attributeA = elementA.attributes[i]; - const attributeB = elementB.attributes.getNamedItemNS( - attributeA.namespaceURI, + for (let i = 0, max = elementA[PropertySymbol.attributes].length; i < max; i++) { + const attributeA = elementA[PropertySymbol.attributes][i]; + const attributeB = elementB[PropertySymbol.attributes].getNamedItemNS( + attributeA[PropertySymbol.namespaceURI], attributeA.localName ); - if (!attributeB || attributeB.value !== attributeA.value) { + if (!attributeB || attributeB[PropertySymbol.value] !== attributeA[PropertySymbol.value]) { return false; } } @@ -426,11 +433,11 @@ export default class NodeUtility { * @param nodeB Node B. */ public static isEqualNode(nodeA: INode, nodeB: INode): boolean { - if (nodeA.nodeType !== nodeB.nodeType) { + if (nodeA[PropertySymbol.nodeType] !== nodeB[PropertySymbol.nodeType]) { return false; } - switch (nodeA.nodeType) { + switch (nodeA[PropertySymbol.nodeType]) { case NodeTypeEnum.documentTypeNode: const documentTypeA = nodeA; const documentTypeB = nodeB; @@ -448,10 +455,10 @@ export default class NodeUtility { const elementB = nodeB; if ( - elementA.namespaceURI !== elementB.namespaceURI || - elementA.prefix !== elementB.prefix || + elementA[PropertySymbol.namespaceURI] !== elementB[PropertySymbol.namespaceURI] || + elementA[PropertySymbol.prefix] !== elementB[PropertySymbol.prefix] || elementA.localName !== elementB.localName || - elementA.attributes.length !== elementB.attributes.length + elementA[PropertySymbol.attributes].length !== elementB[PropertySymbol.attributes].length ) { return false; } @@ -461,9 +468,9 @@ export default class NodeUtility { const attributeB = nodeB; if ( - attributeA.namespaceURI !== attributeB.namespaceURI || + attributeA[PropertySymbol.namespaceURI] !== attributeB[PropertySymbol.namespaceURI] || attributeA.localName !== attributeB.localName || - attributeA.value !== attributeB.value + attributeA[PropertySymbol.value] !== attributeB[PropertySymbol.value] ) { return false; } @@ -492,7 +499,7 @@ export default class NodeUtility { } if ( - nodeA.nodeType === NodeTypeEnum.elementNode && + nodeA[PropertySymbol.nodeType] === NodeTypeEnum.elementNode && !NodeUtility.attributeListsEqual(nodeA, nodeB) ) { return false; diff --git a/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts b/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts index 6aa389fc5..dfae298c6 100644 --- a/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts +++ b/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts @@ -24,7 +24,9 @@ export default class ParentNodeUtility { ): void { for (const node of nodes) { if (typeof node === 'string') { - XMLParser.parse(parentNode.ownerDocument, node, { rootNode: parentNode }); + XMLParser.parse(parentNode[PropertySymbol.ownerDocument], node, { + rootNode: parentNode + }); } else { parentNode.appendChild(node); } @@ -45,7 +47,7 @@ export default class ParentNodeUtility { for (const node of nodes) { if (typeof node === 'string') { const newChildNodes = (( - XMLParser.parse(parentNode.ownerDocument, node) + XMLParser.parse(parentNode[PropertySymbol.ownerDocument], node) ))[PropertySymbol.childNodes].slice(); for (const newChildNode of newChildNodes) { parentNode.insertBefore(newChildNode, firstChild); @@ -113,7 +115,7 @@ export default class ParentNodeUtility { let matches = new HTMLCollection(); for (const child of (parentNode)[PropertySymbol.children]) { - if (includeAll || child.tagName === upperTagName) { + if (includeAll || child[PropertySymbol.tagName] === upperTagName) { matches.push(child); } matches = >( @@ -142,7 +144,10 @@ export default class ParentNodeUtility { let matches = new HTMLCollection(); for (const child of (parentNode)[PropertySymbol.children]) { - if ((includeAll || child.tagName === upperTagName) && child.namespaceURI === namespaceURI) { + if ( + (includeAll || child[PropertySymbol.tagName] === upperTagName) && + child[PropertySymbol.namespaceURI] === namespaceURI + ) { matches.push(child); } matches = >( @@ -168,7 +173,7 @@ export default class ParentNodeUtility { const upperTagName = tagName.toUpperCase(); for (const child of (parentNode)[PropertySymbol.children]) { - if (child.tagName === upperTagName) { + if (child[PropertySymbol.tagName] === upperTagName) { return child; } const match = this.getElementByTagName(child, tagName); diff --git a/packages/happy-dom/src/nodes/processing-instruction/ProcessingInstruction.ts b/packages/happy-dom/src/nodes/processing-instruction/ProcessingInstruction.ts index 9aac25dfd..c7de93cfd 100644 --- a/packages/happy-dom/src/nodes/processing-instruction/ProcessingInstruction.ts +++ b/packages/happy-dom/src/nodes/processing-instruction/ProcessingInstruction.ts @@ -1,6 +1,7 @@ import IProcessingInstruction from './IProcessingInstruction.js'; import CharacterData from '../character-data/CharacterData.js'; import NodeTypeEnum from '../node/NodeTypeEnum.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * Processing instruction node interface. @@ -8,6 +9,15 @@ import NodeTypeEnum from '../node/NodeTypeEnum.js'; * Reference: https://developer.mozilla.org/en-US/docs/Web/API/ProcessingInstruction. */ export default class ProcessingInstruction extends CharacterData implements IProcessingInstruction { - public readonly nodeType = NodeTypeEnum.processingInstructionNode; - public target: string; + public [PropertySymbol.nodeType] = NodeTypeEnum.processingInstructionNode; + public [PropertySymbol.target]: string; + + /** + * Returns target. + * + * @returns Target. + */ + public get target(): string { + return this[PropertySymbol.target]; + } } diff --git a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts index 405d6bf86..1b59cab38 100644 --- a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts +++ b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts @@ -12,13 +12,32 @@ import Event from '../../event/Event.js'; * ShadowRoot. */ export default class ShadowRoot extends DocumentFragment implements IShadowRoot { - public readonly mode = 'open'; - public readonly host: IElement = null; - public adoptedStyleSheets: CSSStyleSheet[] = []; - // Events public onslotchange: (event: Event) => void | null = null; + // Internal properties + public [PropertySymbol.adoptedStyleSheets]: CSSStyleSheet[] = []; + public [PropertySymbol.mode]: 'open' | 'closed' = 'open'; + public [PropertySymbol.host]: IElement | null = null; + + /** + * Returns mode. + * + * @returns Mode. + */ + public get mode(): 'open' | 'closed' { + return this[PropertySymbol.mode]; + } + + /** + * Returns host. + * + * @returns Host. + */ + public get host(): IElement { + return this[PropertySymbol.host]; + } + /** * Returns inner HTML. * @@ -45,7 +64,25 @@ export default class ShadowRoot extends DocumentFragment implements IShadowRoot this.removeChild(child); } - XMLParser.parse(this.ownerDocument, html, { rootNode: this }); + XMLParser.parse(this[PropertySymbol.ownerDocument], html, { rootNode: this }); + } + + /** + * Returns adopted style sheets. + * + * @returns Adopted style sheets. + */ + public get adoptedStyleSheets(): CSSStyleSheet[] { + return this[PropertySymbol.adoptedStyleSheets]; + } + + /** + * Sets adopted style sheets. + * + * @param value Adopted style sheets. + */ + public set adoptedStyleSheets(value: CSSStyleSheet[]) { + this[PropertySymbol.adoptedStyleSheets] = value; } /** @@ -54,8 +91,13 @@ export default class ShadowRoot extends DocumentFragment implements IShadowRoot * @returns Active element. */ public get activeElement(): IHTMLElement | null { - const activeElement: IHTMLElement = this.ownerDocument[PropertySymbol.activeElement]; - if (activeElement && activeElement.isConnected && activeElement.getRootNode() === this) { + const activeElement: IHTMLElement = + this[PropertySymbol.ownerDocument][PropertySymbol.activeElement]; + if ( + activeElement && + activeElement[PropertySymbol.isConnected] && + activeElement.getRootNode() === this + ) { return activeElement; } return null; @@ -79,7 +121,7 @@ export default class ShadowRoot extends DocumentFragment implements IShadowRoot */ public cloneNode(deep = false): IShadowRoot { const clone = super.cloneNode(deep); - (clone.mode) = this.mode; + clone[PropertySymbol.mode] = this.mode; return clone; } } diff --git a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts index fd79b82a2..12d1f33e8 100644 --- a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts +++ b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts @@ -16,8 +16,6 @@ import SVGElementNamedNodeMap from './SVGElementNamedNodeMap.js'; * https://developer.mozilla.org/en-US/docs/Web/API/SVGElement. */ export default class SVGElement extends Element implements ISVGElement { - public override readonly attributes: INamedNodeMap = new SVGElementNamedNodeMap(this); - // Events public onabort: (event: Event) => void | null = null; public onerror: (event: Event) => void | null = null; @@ -26,8 +24,11 @@ export default class SVGElement extends Element implements ISVGElement { public onscroll: (event: Event) => void | null = null; public onunload: (event: Event) => void | null = null; + // Internal properties + public override [PropertySymbol.attributes]: INamedNodeMap = new SVGElementNamedNodeMap(this); + public [PropertySymbol.style]: CSSStyleDeclaration | null = null; + // Private properties - public [PropertySymbol.style]: CSSStyleDeclaration = null; #dataset: Dataset = null; /** @@ -45,13 +46,13 @@ export default class SVGElement extends Element implements ISVGElement { * @returns Element. */ public get ownerSVGElement(): ISVGSVGElement { - let parent = this.parentNode; + let parent = this[PropertySymbol.parentNode]; while (parent) { if (parent['tagName'] === 'SVG') { return parent; } - parent = parent.parentNode; + parent = parent[PropertySymbol.parentNode]; } return null; } diff --git a/packages/happy-dom/src/nodes/svg-element/SVGElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/svg-element/SVGElementNamedNodeMap.ts index 905f464ac..e2fd4fcc2 100644 --- a/packages/happy-dom/src/nodes/svg-element/SVGElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/svg-element/SVGElementNamedNodeMap.ts @@ -17,8 +17,11 @@ export default class SVGElementNamedNodeMap extends ElementNamedNodeMap { public override setNamedItem(item: IAttr): IAttr | null { const replacedItem = super.setNamedItem(item); - if (item.name === 'style' && this[PropertySymbol.ownerElement][PropertySymbol.style]) { - this[PropertySymbol.ownerElement][PropertySymbol.style].cssText = item.value; + if ( + item[PropertySymbol.name] === 'style' && + this[PropertySymbol.ownerElement][PropertySymbol.style] + ) { + this[PropertySymbol.ownerElement][PropertySymbol.style].cssText = item[PropertySymbol.value]; } return replacedItem || null; @@ -32,7 +35,7 @@ export default class SVGElementNamedNodeMap extends ElementNamedNodeMap { if ( removedItem && - removedItem.name === 'style' && + removedItem[PropertySymbol.name] === 'style' && this[PropertySymbol.ownerElement][PropertySymbol.style] ) { this[PropertySymbol.ownerElement][PropertySymbol.style].cssText = ''; diff --git a/packages/happy-dom/src/nodes/text/Text.ts b/packages/happy-dom/src/nodes/text/Text.ts index 975a63bd5..acb5ba0b3 100644 --- a/packages/happy-dom/src/nodes/text/Text.ts +++ b/packages/happy-dom/src/nodes/text/Text.ts @@ -1,4 +1,3 @@ -import Node from '../node/Node.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import CharacterData from '../character-data/CharacterData.js'; import IText from './IText.js'; @@ -6,12 +5,13 @@ import DOMException from '../../exception/DOMException.js'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import HTMLTextAreaElement from '../html-text-area-element/HTMLTextAreaElement.js'; import INode from '../node/INode.js'; +import NodeTypeEnum from '../node/NodeTypeEnum.js'; /** * Text node. */ export default class Text extends CharacterData implements IText { - public readonly nodeType = Node.TEXT_NODE; + public override [PropertySymbol.nodeType] = NodeTypeEnum.textNode; /** * Node name. @@ -59,10 +59,10 @@ export default class Text extends CharacterData implements IText { const count = length - offset; const newData = this.substringData(offset, count); - const newNode = this.ownerDocument.createTextNode(newData); + const newNode = this[PropertySymbol.ownerDocument].createTextNode(newData); - if (this.parentNode !== null) { - this.parentNode.insertBefore(newNode, this.nextSibling); + if (this[PropertySymbol.parentNode] !== null) { + this[PropertySymbol.parentNode].insertBefore(newNode, this.nextSibling); } this.replaceData(offset, count, ''); diff --git a/packages/happy-dom/src/query-selector/QuerySelector.ts b/packages/happy-dom/src/query-selector/QuerySelector.ts index 201fb9ca9..e7ad2df85 100644 --- a/packages/happy-dom/src/query-selector/QuerySelector.ts +++ b/packages/happy-dom/src/query-selector/QuerySelector.ts @@ -48,7 +48,7 @@ export default class QuerySelector { for (const items of groups) { matches = matches.concat( - node.nodeType === NodeTypeEnum.elementNode + node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode ? this.findAll(node, [node], items) : this.findAll(null, (node)[PropertySymbol.children], items) ); @@ -92,7 +92,7 @@ export default class QuerySelector { for (const items of SelectorParser.getSelectorGroups(selector)) { const match = - node.nodeType === NodeTypeEnum.elementNode + node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode ? this.findFirst(node, [node], items) : this.findFirst(null, (node)[PropertySymbol.children], items); diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts index 4d299b0c1..2038090af 100644 --- a/packages/happy-dom/src/query-selector/SelectorItem.ts +++ b/packages/happy-dom/src/query-selector/SelectorItem.ts @@ -65,7 +65,7 @@ export default class SelectorItem { // Tag name match if (this.tagName) { - if (this.tagName !== '*' && this.tagName !== element.tagName) { + if (this.tagName !== '*' && this.tagName !== element[PropertySymbol.tagName]) { return null; } priorityWeight += 1; @@ -112,9 +112,9 @@ export default class SelectorItem { * @returns Result. */ private matchPsuedo(element: IElement): boolean { - const parent = element.parentNode; - const parentChildren = element.parentNode - ? (element.parentNode)[PropertySymbol.children] + const parent = element[PropertySymbol.parentNode]; + const parentChildren = element[PropertySymbol.parentNode] + ? (element[PropertySymbol.parentNode])[PropertySymbol.children] : []; if (!this.pseudos) { @@ -161,7 +161,7 @@ export default class SelectorItem { return parentChildren.length === 1 && parentChildren[0] === element; case 'first-of-type': for (const child of parentChildren) { - if (child.tagName === element.tagName) { + if (child[PropertySymbol.tagName] === element[PropertySymbol.tagName]) { return child === element; } } @@ -169,7 +169,7 @@ export default class SelectorItem { case 'last-of-type': for (let i = parentChildren.length - 1; i >= 0; i--) { const child = parentChildren[i]; - if (child.tagName === element.tagName) { + if (child[PropertySymbol.tagName] === element[PropertySymbol.tagName]) { return child === element; } } @@ -177,7 +177,7 @@ export default class SelectorItem { case 'only-of-type': let isFound = false; for (const child of parentChildren) { - if (child.tagName === element.tagName) { + if (child[PropertySymbol.tagName] === element[PropertySymbol.tagName]) { if (isFound || child !== element) { return false; } @@ -186,11 +186,13 @@ export default class SelectorItem { } return isFound; case 'checked': - return element.tagName === 'INPUT' && (element).checked; + return ( + element[PropertySymbol.tagName] === 'INPUT' && (element).checked + ); case 'empty': return !(element)[PropertySymbol.children].length; case 'root': - return element.tagName === 'HTML'; + return element[PropertySymbol.tagName] === 'HTML'; case 'not': return !psuedo.selectorItem.match(element); case 'nth-child': @@ -199,11 +201,11 @@ export default class SelectorItem { : parentChildren.indexOf(element); return nthChildIndex !== -1 && psuedo.nthFunction(nthChildIndex + 1); case 'nth-of-type': - if (!element.parentNode) { + if (!element[PropertySymbol.parentNode]) { return false; } const nthOfTypeIndex = parentChildren - .filter((child) => child.tagName === element.tagName) + .filter((child) => child[PropertySymbol.tagName] === element[PropertySymbol.tagName]) .indexOf(element); return nthOfTypeIndex !== -1 && psuedo.nthFunction(nthOfTypeIndex + 1); case 'nth-last-child': @@ -216,7 +218,7 @@ export default class SelectorItem { return nthLastChildIndex !== -1 && psuedo.nthFunction(nthLastChildIndex + 1); case 'nth-last-of-type': const nthLastOfTypeIndex = parentChildren - .filter((child) => child.tagName === element.tagName) + .filter((child) => child[PropertySymbol.tagName] === element[PropertySymbol.tagName]) .reverse() .indexOf(element); return nthLastOfTypeIndex !== -1 && psuedo.nthFunction(nthLastOfTypeIndex + 1); @@ -240,7 +242,9 @@ export default class SelectorItem { let priorityWeight = 0; for (const attribute of this.attributes) { - const elementAttribute = (element).attributes.getNamedItem(attribute.name); + const elementAttribute = (element)[PropertySymbol.attributes].getNamedItem( + attribute.name + ); if (!elementAttribute) { return null; @@ -250,9 +254,9 @@ export default class SelectorItem { if ( attribute.value !== null && - (elementAttribute.value === null || - (attribute.regExp && !attribute.regExp.test(elementAttribute.value)) || - (!attribute.regExp && attribute.value !== elementAttribute.value)) + (elementAttribute[PropertySymbol.value] === null || + (attribute.regExp && !attribute.regExp.test(elementAttribute[PropertySymbol.value])) || + (!attribute.regExp && attribute.value !== elementAttribute[PropertySymbol.value])) ) { return null; } diff --git a/packages/happy-dom/src/range/Range.ts b/packages/happy-dom/src/range/Range.ts index 042704bda..3d984de6f 100644 --- a/packages/happy-dom/src/range/Range.ts +++ b/packages/happy-dom/src/range/Range.ts @@ -133,7 +133,7 @@ export default class Range { if (NodeUtility.isInclusiveAncestor(container, this[PropertySymbol.end].node)) { return container; } - container = container.parentNode; + container = container[PropertySymbol.parentNode]; } return null; @@ -229,7 +229,7 @@ export default class Range { * @returns -1,0, or 1. */ public comparePoint(node: INode, offset): number { - if (node.ownerDocument !== this[PropertySymbol.ownerDocument]) { + if (node[PropertySymbol.ownerDocument] !== this[PropertySymbol.ownerDocument]) { throw new DOMException( `The two Ranges are not in the same tree.`, DOMExceptionNameEnum.wrongDocumentError @@ -276,9 +276,10 @@ export default class Range { if ( this[PropertySymbol.start].node === this[PropertySymbol.end].node && - (this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.processingInstructionNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.commentNode) + (this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.commentNode) ) { const clone = (this[PropertySymbol.start].node).cloneNode(false); clone[PropertySymbol.data] = clone.substringData(startOffset, endOffset - startOffset); @@ -288,7 +289,7 @@ export default class Range { let commonAncestor = this[PropertySymbol.start].node; while (!NodeUtility.isInclusiveAncestor(commonAncestor, this[PropertySymbol.end].node)) { - commonAncestor = commonAncestor.parentNode; + commonAncestor = commonAncestor[PropertySymbol.parentNode]; } let firstPartialContainedChild = null; @@ -329,7 +330,7 @@ export default class Range { for (const node of (commonAncestor)[PropertySymbol.childNodes]) { if (RangeUtility.isContained(node, this)) { - if (node.nodeType === NodeTypeEnum.documentTypeNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { throw new DOMException( 'Invalid document type element.', DOMExceptionNameEnum.hierarchyRequestError @@ -341,9 +342,10 @@ export default class Range { if ( firstPartialContainedChild !== null && - (firstPartialContainedChild.nodeType === NodeTypeEnum.textNode || - firstPartialContainedChild.nodeType === NodeTypeEnum.processingInstructionNode || - firstPartialContainedChild.nodeType === NodeTypeEnum.commentNode) + (firstPartialContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + firstPartialContainedChild[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + firstPartialContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.commentNode) ) { const clone = (this[PropertySymbol.start].node).cloneNode(false); clone[PropertySymbol.data] = clone.substringData( @@ -373,9 +375,10 @@ export default class Range { if ( lastPartiallyContainedChild !== null && - (lastPartiallyContainedChild.nodeType === NodeTypeEnum.textNode || - lastPartiallyContainedChild.nodeType === NodeTypeEnum.processingInstructionNode || - lastPartiallyContainedChild.nodeType === NodeTypeEnum.commentNode) + (lastPartiallyContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + lastPartiallyContainedChild[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + lastPartiallyContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.commentNode) ) { const clone = (this[PropertySymbol.end].node).cloneNode(false); clone[PropertySymbol.data] = clone.substringData(0, endOffset); @@ -442,9 +445,10 @@ export default class Range { if ( this[PropertySymbol.start].node === this[PropertySymbol.end].node && - (this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.processingInstructionNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.commentNode) + (this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.commentNode) ) { (this[PropertySymbol.start].node).replaceData( startOffset, @@ -460,7 +464,7 @@ export default class Range { while (currentNode && currentNode !== endNode) { if ( RangeUtility.isContained(currentNode, this) && - !RangeUtility.isContained(currentNode.parentNode, this) + !RangeUtility.isContained(currentNode[PropertySymbol.parentNode], this) ) { nodesToRemove.push(currentNode); } @@ -483,20 +487,26 @@ export default class Range { while ( referenceNode && - !NodeUtility.isInclusiveAncestor(referenceNode.parentNode, this[PropertySymbol.end].node) + !NodeUtility.isInclusiveAncestor( + referenceNode[PropertySymbol.parentNode], + this[PropertySymbol.end].node + ) ) { - referenceNode = referenceNode.parentNode; + referenceNode = referenceNode[PropertySymbol.parentNode]; } - newNode = referenceNode.parentNode; + newNode = referenceNode[PropertySymbol.parentNode]; newOffset = - (referenceNode.parentNode)[PropertySymbol.childNodes].indexOf(referenceNode) + 1; + (referenceNode[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf( + referenceNode + ) + 1; } if ( - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.processingInstructionNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.commentNode + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.commentNode ) { (this[PropertySymbol.start].node).replaceData( this.startOffset, @@ -506,14 +516,15 @@ export default class Range { } for (const node of nodesToRemove) { - const parent = node.parentNode; + const parent = node[PropertySymbol.parentNode]; parent.removeChild(node); } if ( - this[PropertySymbol.end].node.nodeType === NodeTypeEnum.textNode || - this[PropertySymbol.end].node.nodeType === NodeTypeEnum.processingInstructionNode || - this[PropertySymbol.end].node.nodeType === NodeTypeEnum.commentNode + this[PropertySymbol.end].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + this[PropertySymbol.end].node[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + this[PropertySymbol.end].node[PropertySymbol.nodeType] === NodeTypeEnum.commentNode ) { (this[PropertySymbol.end].node).replaceData(0, endOffset, ''); } @@ -550,9 +561,10 @@ export default class Range { if ( this[PropertySymbol.start].node === this[PropertySymbol.end].node && - (this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.processingInstructionNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.commentNode) + (this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.commentNode) ) { const clone = this[PropertySymbol.start].node.cloneNode(false); clone[PropertySymbol.data] = clone.substringData(startOffset, endOffset - startOffset); @@ -570,7 +582,7 @@ export default class Range { let commonAncestor = this[PropertySymbol.start].node; while (!NodeUtility.isInclusiveAncestor(commonAncestor, this[PropertySymbol.end].node)) { - commonAncestor = commonAncestor.parentNode; + commonAncestor = commonAncestor[PropertySymbol.parentNode]; } let firstPartialContainedChild = null; @@ -611,7 +623,7 @@ export default class Range { for (const node of (commonAncestor)[PropertySymbol.childNodes]) { if (RangeUtility.isContained(node, this)) { - if (node.nodeType === NodeTypeEnum.documentTypeNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { throw new DOMException( 'Invalid document type element.', DOMExceptionNameEnum.hierarchyRequestError @@ -636,21 +648,27 @@ export default class Range { while ( referenceNode && - !NodeUtility.isInclusiveAncestor(referenceNode.parentNode, this[PropertySymbol.end].node) + !NodeUtility.isInclusiveAncestor( + referenceNode[PropertySymbol.parentNode], + this[PropertySymbol.end].node + ) ) { - referenceNode = referenceNode.parentNode; + referenceNode = referenceNode[PropertySymbol.parentNode]; } - newNode = referenceNode.parentNode; + newNode = referenceNode[PropertySymbol.parentNode]; newOffset = - (referenceNode.parentNode)[PropertySymbol.childNodes].indexOf(referenceNode) + 1; + (referenceNode[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf( + referenceNode + ) + 1; } if ( firstPartialContainedChild !== null && - (firstPartialContainedChild.nodeType === NodeTypeEnum.textNode || - firstPartialContainedChild.nodeType === NodeTypeEnum.processingInstructionNode || - firstPartialContainedChild.nodeType === NodeTypeEnum.commentNode) + (firstPartialContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + firstPartialContainedChild[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + firstPartialContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.commentNode) ) { const clone = this[PropertySymbol.start].node.cloneNode(false); clone[PropertySymbol.data] = clone.substringData( @@ -685,9 +703,10 @@ export default class Range { if ( lastPartiallyContainedChild !== null && - (lastPartiallyContainedChild.nodeType === NodeTypeEnum.textNode || - lastPartiallyContainedChild.nodeType === NodeTypeEnum.processingInstructionNode || - lastPartiallyContainedChild.nodeType === NodeTypeEnum.commentNode) + (lastPartiallyContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.textNode || + lastPartiallyContainedChild[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + lastPartiallyContainedChild[PropertySymbol.nodeType] === NodeTypeEnum.commentNode) ) { const clone = this[PropertySymbol.end].node.cloneNode(false); clone[PropertySymbol.data] = clone.substringData(0, endOffset); @@ -746,7 +765,7 @@ export default class Range { * @returns "true" if in range. */ public isPointInRange(node: INode, offset = 0): boolean { - if (node.ownerDocument !== this[PropertySymbol.ownerDocument]) { + if (node[PropertySymbol.ownerDocument] !== this[PropertySymbol.ownerDocument]) { return false; } @@ -778,23 +797,26 @@ export default class Range { */ public insertNode(newNode: INode): void { if ( - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.processingInstructionNode || - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.commentNode || - (this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode && - !this[PropertySymbol.start].node.parentNode) || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === + NodeTypeEnum.processingInstructionNode || + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.commentNode || + (this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode && + !this[PropertySymbol.start].node[PropertySymbol.parentNode]) || newNode === this[PropertySymbol.start].node ) { throw new DOMException('Invalid start node.', DOMExceptionNameEnum.hierarchyRequestError); } let referenceNode = - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode ? this[PropertySymbol.start].node : (this[PropertySymbol.start].node)[PropertySymbol.childNodes][this.startOffset] || null; - const parent = !referenceNode ? this[PropertySymbol.start].node : referenceNode.parentNode; + const parent = !referenceNode + ? this[PropertySymbol.start].node + : referenceNode[PropertySymbol.parentNode]; - if (this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode) { + if (this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode) { referenceNode = (this[PropertySymbol.start].node).splitText(this.startOffset); } @@ -802,16 +824,18 @@ export default class Range { referenceNode = referenceNode.nextSibling; } - const nodeParent = newNode.parentNode; + const nodeParent = newNode[PropertySymbol.parentNode]; if (nodeParent) { nodeParent.removeChild(newNode); } let newOffset = !referenceNode ? NodeUtility.getNodeLength(parent) - : (referenceNode.parentNode)[PropertySymbol.childNodes].indexOf(referenceNode); + : (referenceNode[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf( + referenceNode + ); newOffset += - newNode.nodeType === NodeTypeEnum.documentFragmentNode + newNode[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode ? NodeUtility.getNodeLength(newNode) : 1; @@ -831,11 +855,11 @@ export default class Range { * @returns "true" if it intersects. */ public intersectsNode(node: INode): boolean { - if (node.ownerDocument !== this[PropertySymbol.ownerDocument]) { + if (node[PropertySymbol.ownerDocument] !== this[PropertySymbol.ownerDocument]) { return false; } - const parent = node.parentNode; + const parent = node[PropertySymbol.parentNode]; if (!parent) { return true; @@ -862,18 +886,18 @@ export default class Range { * @param node Reference node. */ public selectNode(node: INode): void { - if (!node.parentNode) { + if (!node[PropertySymbol.parentNode]) { throw new DOMException( `The given Node has no parent.`, DOMExceptionNameEnum.invalidNodeTypeError ); } - const index = (node.parentNode)[PropertySymbol.childNodes].indexOf(node); + const index = (node[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf(node); - this[PropertySymbol.start].node = node.parentNode; + this[PropertySymbol.start].node = node[PropertySymbol.parentNode]; this[PropertySymbol.start].offset = index; - this[PropertySymbol.end].node = node.parentNode; + this[PropertySymbol.end].node = node[PropertySymbol.parentNode]; this[PropertySymbol.end].offset = index + 1; } @@ -884,7 +908,7 @@ export default class Range { * @param node Reference node. */ public selectNodeContents(node: INode): void { - if (node.nodeType === NodeTypeEnum.documentTypeNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { throw new DOMException( "DocumentType Node can't be used as boundary point.", DOMExceptionNameEnum.invalidNodeTypeError @@ -910,7 +934,7 @@ export default class Range { const boundaryPoint = { node, offset }; if ( - node.ownerDocument !== this[PropertySymbol.ownerDocument] || + node[PropertySymbol.ownerDocument] !== this[PropertySymbol.ownerDocument] || RangeUtility.compareBoundaryPointsPosition(boundaryPoint, { node: this[PropertySymbol.start].node, offset: this.startOffset @@ -937,7 +961,7 @@ export default class Range { const boundaryPoint = { node, offset }; if ( - node.ownerDocument !== this[PropertySymbol.ownerDocument] || + node[PropertySymbol.ownerDocument] !== this[PropertySymbol.ownerDocument] || RangeUtility.compareBoundaryPointsPosition(boundaryPoint, { node: this[PropertySymbol.end].node, offset: this.endOffset @@ -958,15 +982,15 @@ export default class Range { * @param node Reference node. */ public setEndAfter(node: INode): void { - if (!node.parentNode) { + if (!node[PropertySymbol.parentNode]) { throw new DOMException( 'The given Node has no parent.', DOMExceptionNameEnum.invalidNodeTypeError ); } this.setEnd( - node.parentNode, - (node.parentNode)[PropertySymbol.childNodes].indexOf(node) + 1 + node[PropertySymbol.parentNode], + (node[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf(node) + 1 ); } @@ -977,13 +1001,16 @@ export default class Range { * @param node Reference node. */ public setEndBefore(node: INode): void { - if (!node.parentNode) { + if (!node[PropertySymbol.parentNode]) { throw new DOMException( 'The given Node has no parent.', DOMExceptionNameEnum.invalidNodeTypeError ); } - this.setEnd(node.parentNode, (node.parentNode)[PropertySymbol.childNodes].indexOf(node)); + this.setEnd( + node[PropertySymbol.parentNode], + (node[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf(node) + ); } /** @@ -993,15 +1020,15 @@ export default class Range { * @param node Reference node. */ public setStartAfter(node: INode): void { - if (!node.parentNode) { + if (!node[PropertySymbol.parentNode]) { throw new DOMException( 'The given Node has no parent.', DOMExceptionNameEnum.invalidNodeTypeError ); } this.setStart( - node.parentNode, - (node.parentNode)[PropertySymbol.childNodes].indexOf(node) + 1 + node[PropertySymbol.parentNode], + (node[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf(node) + 1 ); } @@ -1012,15 +1039,15 @@ export default class Range { * @param node Reference node. */ public setStartBefore(node: INode): void { - if (!node.parentNode) { + if (!node[PropertySymbol.parentNode]) { throw new DOMException( 'The given Node has no parent.', DOMExceptionNameEnum.invalidNodeTypeError ); } this.setStart( - node.parentNode, - (node.parentNode)[PropertySymbol.childNodes].indexOf(node) + node[PropertySymbol.parentNode], + (node[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf(node) ); } @@ -1035,7 +1062,7 @@ export default class Range { const endNode = NodeUtility.nextDescendantNode(node); while (node !== endNode) { if ( - node.nodeType !== NodeTypeEnum.textNode && + node[PropertySymbol.nodeType] !== NodeTypeEnum.textNode && RangeUtility.isPartiallyContained(node, this) ) { throw new DOMException( @@ -1048,9 +1075,9 @@ export default class Range { } if ( - newParent.nodeType === NodeTypeEnum.documentNode || - newParent.nodeType === NodeTypeEnum.documentTypeNode || - newParent.nodeType === NodeTypeEnum.documentFragmentNode + newParent[PropertySymbol.nodeType] === NodeTypeEnum.documentNode || + newParent[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode || + newParent[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode ) { throw new DOMException('Invalid element type.', DOMExceptionNameEnum.invalidNodeTypeError); } @@ -1080,12 +1107,12 @@ export default class Range { if ( this[PropertySymbol.start].node === this[PropertySymbol.end].node && - this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode + this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode ) { return (this[PropertySymbol.start].node).data.slice(startOffset, endOffset); } - if (this[PropertySymbol.start].node.nodeType === NodeTypeEnum.textNode) { + if (this[PropertySymbol.start].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode) { string += (this[PropertySymbol.start].node).data.slice(startOffset); } @@ -1094,7 +1121,7 @@ export default class Range { while (currentNode && currentNode !== endNode) { if ( - currentNode.nodeType === NodeTypeEnum.textNode && + currentNode[PropertySymbol.nodeType] === NodeTypeEnum.textNode && RangeUtility.isContained(currentNode, this) ) { string += (currentNode).data; @@ -1103,7 +1130,7 @@ export default class Range { currentNode = NodeUtility.following(currentNode); } - if (this[PropertySymbol.end].node.nodeType === NodeTypeEnum.textNode) { + if (this[PropertySymbol.end].node[PropertySymbol.nodeType] === NodeTypeEnum.textNode) { string += (this[PropertySymbol.end].node).data.slice(0, endOffset); } diff --git a/packages/happy-dom/src/range/RangeUtility.ts b/packages/happy-dom/src/range/RangeUtility.ts index 56b72bc70..81d591b2d 100644 --- a/packages/happy-dom/src/range/RangeUtility.ts +++ b/packages/happy-dom/src/range/RangeUtility.ts @@ -47,11 +47,14 @@ export default class RangeUtility { if (NodeUtility.isInclusiveAncestor(pointA.node, pointB.node)) { let child = pointB.node; - while (child.parentNode !== pointA.node) { - child = child.parentNode; + while (child[PropertySymbol.parentNode] !== pointA.node) { + child = child[PropertySymbol.parentNode]; } - if ((child.parentNode)[PropertySymbol.childNodes].indexOf(child) < pointA.offset) { + if ( + (child[PropertySymbol.parentNode])[PropertySymbol.childNodes].indexOf(child) < + pointA.offset + ) { return 1; } } @@ -66,7 +69,7 @@ export default class RangeUtility { * @param point Boundary point. */ public static validateBoundaryPoint(point: IRangeBoundaryPoint): void { - if (point.node.nodeType === NodeTypeEnum.documentTypeNode) { + if (point.node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { throw new DOMException( `DocumentType Node can't be used as boundary point.`, DOMExceptionNameEnum.invalidNodeTypeError diff --git a/packages/happy-dom/src/selection/Selection.ts b/packages/happy-dom/src/selection/Selection.ts index c15961efe..b142cbdd6 100644 --- a/packages/happy-dom/src/selection/Selection.ts +++ b/packages/happy-dom/src/selection/Selection.ts @@ -235,7 +235,7 @@ export default class Selection { return; } - if (node.nodeType === NodeTypeEnum.documentTypeNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { throw new DOMException( "DocumentType Node can't be used as boundary point.", DOMExceptionNameEnum.invalidNodeTypeError @@ -246,11 +246,11 @@ export default class Selection { throw new DOMException('Invalid range index.', DOMExceptionNameEnum.indexSizeError); } - if (node.ownerDocument !== this.#ownerDocument) { + if (node[PropertySymbol.ownerDocument] !== this.#ownerDocument) { return; } - const newRange = new this.#ownerDocument[PropertySymbol.defaultView].Range(); + const newRange = new this.#ownerDocument[PropertySymbol.ownerWindow].Range(); newRange[PropertySymbol.start].node = node; newRange[PropertySymbol.start].offset = offset; @@ -286,7 +286,7 @@ export default class Selection { } const { node, offset } = this.#range[PropertySymbol.end]; - const newRange = new this.#ownerDocument[PropertySymbol.defaultView].Range(); + const newRange = new this.#ownerDocument[PropertySymbol.ownerWindow].Range(); newRange[PropertySymbol.start].node = node; newRange[PropertySymbol.start].offset = offset; @@ -310,7 +310,7 @@ export default class Selection { } const { node, offset } = this.#range[PropertySymbol.start]; - const newRange = new this.#ownerDocument[PropertySymbol.defaultView].Range(); + const newRange = new this.#ownerDocument[PropertySymbol.ownerWindow].Range(); newRange[PropertySymbol.start].node = node; newRange[PropertySymbol.start].offset = offset; @@ -329,7 +329,7 @@ export default class Selection { * @returns Always returns "true" for now. */ public containsNode(node: INode, allowPartialContainment = false): boolean { - if (!this.#range || node.ownerDocument !== this.#ownerDocument) { + if (!this.#range || node[PropertySymbol.ownerDocument] !== this.#ownerDocument) { return false; } @@ -368,7 +368,7 @@ export default class Selection { * @param offset Offset. */ public extend(node: INode, offset: number): void { - if (node.ownerDocument !== this.#ownerDocument) { + if (node[PropertySymbol.ownerDocument] !== this.#ownerDocument) { return; } @@ -381,13 +381,13 @@ export default class Selection { const anchorNode = this.anchorNode; const anchorOffset = this.anchorOffset; - const newRange = new this.#ownerDocument[PropertySymbol.defaultView].Range(); + const newRange = new this.#ownerDocument[PropertySymbol.ownerWindow].Range(); newRange[PropertySymbol.start].node = node; newRange[PropertySymbol.start].offset = 0; newRange[PropertySymbol.end].node = node; newRange[PropertySymbol.end].offset = 0; - if (node.ownerDocument !== this.#range[PropertySymbol.ownerDocument]) { + if (node[PropertySymbol.ownerDocument] !== this.#range[PropertySymbol.ownerDocument]) { newRange[PropertySymbol.start].offset = offset; newRange[PropertySymbol.end].offset = offset; } else if ( @@ -424,19 +424,19 @@ export default class Selection { * @param node Node. */ public selectAllChildren(node: INode): void { - if (node.nodeType === NodeTypeEnum.documentTypeNode) { + if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) { throw new DOMException( "DocumentType Node can't be used as boundary point.", DOMExceptionNameEnum.invalidNodeTypeError ); } - if (node.ownerDocument !== this.#ownerDocument) { + if (node[PropertySymbol.ownerDocument] !== this.#ownerDocument) { return; } const length = node.childNodes.length; - const newRange = new this.#ownerDocument[PropertySymbol.defaultView].Range(); + const newRange = new this.#ownerDocument[PropertySymbol.ownerWindow].Range(); newRange[PropertySymbol.start].node = node; newRange[PropertySymbol.start].offset = 0; @@ -472,15 +472,15 @@ export default class Selection { } if ( - anchorNode.ownerDocument !== this.#ownerDocument || - focusNode.ownerDocument !== this.#ownerDocument + anchorNode[PropertySymbol.ownerDocument] !== this.#ownerDocument || + focusNode[PropertySymbol.ownerDocument] !== this.#ownerDocument ) { return; } const anchor = { node: anchorNode, offset: anchorOffset }; const focus = { node: focusNode, offset: focusOffset }; - const newRange = new this.#ownerDocument[PropertySymbol.defaultView].Range(); + const newRange = new this.#ownerDocument[PropertySymbol.ownerWindow].Range(); if (RangeUtility.compareBoundaryPointsPosition(anchor, focus) === -1) { newRange[PropertySymbol.start] = anchor; diff --git a/packages/happy-dom/src/tree-walker/TreeWalker.ts b/packages/happy-dom/src/tree-walker/TreeWalker.ts index 0c391db86..5a264a0e5 100644 --- a/packages/happy-dom/src/tree-walker/TreeWalker.ts +++ b/packages/happy-dom/src/tree-walker/TreeWalker.ts @@ -63,8 +63,12 @@ export default class TreeWalker { * @returns Current node. */ public parentNode(): INode { - if (this.currentNode !== this.root && this.currentNode && this.currentNode.parentNode) { - this.currentNode = this.currentNode.parentNode; + if ( + this.currentNode !== this.root && + this.currentNode && + this.currentNode[PropertySymbol.parentNode] + ) { + this.currentNode = this.currentNode[PropertySymbol.parentNode]; if (this.filterNode(this.currentNode) === NodeFilter.FILTER_ACCEPT) { return this.currentNode; @@ -126,8 +130,14 @@ export default class TreeWalker { * @returns Current node. */ public previousSibling(): INode { - if (this.currentNode !== this.root && this.currentNode && this.currentNode.parentNode) { - const siblings = (this.currentNode.parentNode)[PropertySymbol.childNodes]; + if ( + this.currentNode !== this.root && + this.currentNode && + this.currentNode[PropertySymbol.parentNode] + ) { + const siblings = (this.currentNode[PropertySymbol.parentNode])[ + PropertySymbol.childNodes + ]; const index = siblings.indexOf(this.currentNode); if (index > 0) { @@ -150,8 +160,14 @@ export default class TreeWalker { * @returns Current node. */ public nextSibling(): INode { - if (this.currentNode !== this.root && this.currentNode && this.currentNode.parentNode) { - const siblings = (this.currentNode.parentNode)[PropertySymbol.childNodes]; + if ( + this.currentNode !== this.root && + this.currentNode && + this.currentNode[PropertySymbol.parentNode] + ) { + const siblings = (this.currentNode[PropertySymbol.parentNode])[ + PropertySymbol.childNodes + ]; const index = siblings.indexOf(this.currentNode); if (index + 1 < siblings.length) { diff --git a/packages/happy-dom/src/validity-state/ValidityState.ts b/packages/happy-dom/src/validity-state/ValidityState.ts index 8670a1419..c3bb9f5f6 100644 --- a/packages/happy-dom/src/validity-state/ValidityState.ts +++ b/packages/happy-dom/src/validity-state/ValidityState.ts @@ -59,7 +59,7 @@ export default class ValidityState { * @returns "true" if valid. */ public get customError(): boolean { - return this.element.validationMessage.length > 0; + return this.element[PropertySymbol.validationMessage].length > 0; } /** diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index 36d7f66be..dfb697cad 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -656,7 +656,7 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow // Document this.document = new HTMLDocument(); - (this.document.defaultView) = this; + this.document[PropertySymbol.defaultView] = this; // Override owner document this.Audio[PropertySymbol.ownerDocument] = this.document; @@ -665,7 +665,7 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow // Ready state manager this[PropertySymbol.readyStateManager].waitUntilComplete().then(() => { - (this.document.readyState) = DocumentReadyStateEnum.complete; + this.document[PropertySymbol.readyState] = DocumentReadyStateEnum.complete; this.document.dispatchEvent(new Event('readystatechange')); this.document.dispatchEvent(new Event('load', { bubbles: true })); }); diff --git a/packages/happy-dom/src/window/WindowErrorUtility.ts b/packages/happy-dom/src/window/WindowErrorUtility.ts index 564eaba19..ae5fdf68c 100644 --- a/packages/happy-dom/src/window/WindowErrorUtility.ts +++ b/packages/happy-dom/src/window/WindowErrorUtility.ts @@ -57,7 +57,9 @@ export default class WindowErrorUtility { (elementOrWindow).console.error(error); elementOrWindow.dispatchEvent(new ErrorEvent('error', { message: error.message, error })); } else { - (elementOrWindow).ownerDocument[PropertySymbol.defaultView].console.error(error); + (elementOrWindow)[PropertySymbol.ownerDocument][ + PropertySymbol.defaultView + ].console.error(error); (elementOrWindow).dispatchEvent( new ErrorEvent('error', { message: error.message, error }) ); diff --git a/packages/happy-dom/src/xml-http-request/XMLHttpRequest.ts b/packages/happy-dom/src/xml-http-request/XMLHttpRequest.ts index ae721d9de..12066da3c 100644 --- a/packages/happy-dom/src/xml-http-request/XMLHttpRequest.ts +++ b/packages/happy-dom/src/xml-http-request/XMLHttpRequest.ts @@ -297,7 +297,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget { if ( typeof body === 'object' && body !== null && - (body).nodeType === NodeTypeEnum.documentNode + (body)[PropertySymbol.nodeType] === NodeTypeEnum.documentNode ) { body = (body).documentElement.outerHTML; } diff --git a/packages/happy-dom/src/xml-parser/XMLParser.ts b/packages/happy-dom/src/xml-parser/XMLParser.ts index 508b0c6f5..d1e4cca56 100755 --- a/packages/happy-dom/src/xml-parser/XMLParser.ts +++ b/packages/happy-dom/src/xml-parser/XMLParser.ts @@ -115,7 +115,7 @@ export default class XMLParser { if (unnestableTagNameIndex !== -1) { unnestableTagNames.splice(unnestableTagNameIndex, 1); while (currentNode !== root) { - if ((currentNode).tagName === tagName) { + if ((currentNode)[PropertySymbol.tagName] === tagName) { stack.pop(); currentNode = stack[stack.length - 1] || root; break; @@ -130,7 +130,7 @@ export default class XMLParser { const namespaceURI = tagName === 'SVG' ? NamespaceURI.svg - : (currentNode).namespaceURI || NamespaceURI.html; + : (currentNode)[PropertySymbol.namespaceURI] || NamespaceURI.html; const newElement = document.createElementNS(namespaceURI, tagName); currentNode.appendChild(newElement); @@ -141,11 +141,11 @@ export default class XMLParser { } else if (match[2]) { // End tag. - if (match[2].toUpperCase() === (currentNode).tagName) { + if (match[2].toUpperCase() === (currentNode)[PropertySymbol.tagName]) { // Some elements are not allowed to be nested (e.g. "" is not allowed.). // Therefore we need to auto-close the tag, so that it become valid (e.g. ""). const unnestableTagNameIndex = unnestableTagNames.indexOf( - (currentNode).tagName + (currentNode)[PropertySymbol.tagName] ); if (unnestableTagNameIndex !== -1) { unnestableTagNames.splice(unnestableTagNameIndex, 1); @@ -157,7 +157,8 @@ export default class XMLParser { } else if ( match[3] || match[4] || - (match[6] && (currentNode).namespaceURI === NamespaceURI.html) + (match[6] && + (currentNode)[PropertySymbol.namespaceURI] === NamespaceURI.html) ) { // Comment. @@ -227,7 +228,9 @@ export default class XMLParser { attributeMatch[2] || attributeMatch[4] || attributeMatch[7] || ''; const value = rawValue ? Entities.decodeHTMLAttribute(rawValue) : ''; const namespaceURI = - (currentNode).tagName === 'SVG' && name === 'xmlns' ? value : null; + (currentNode)[PropertySymbol.tagName] === 'SVG' && name === 'xmlns' + ? value + : null; (currentNode).setAttributeNS(namespaceURI, name, value); @@ -253,24 +256,27 @@ export default class XMLParser { // Self closing tags are not allowed in the HTML namespace, but the parser should still allow it for void elements. // Self closing tags is supported in the SVG namespace. if ( - VoidElements[(currentNode).tagName] || - (match[7] && (currentNode).namespaceURI === NamespaceURI.svg) + VoidElements[(currentNode)[PropertySymbol.tagName]] || + (match[7] && + (currentNode)[PropertySymbol.namespaceURI] === NamespaceURI.svg) ) { stack.pop(); currentNode = stack[stack.length - 1] || root; readState = MarkupReadStateEnum.startOrEndTag; } else { // Plain text elements such as