Skip to content

Commit

Permalink
chore: [#1615] Continues on implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 committed Nov 29, 2024
1 parent 62dc2f2 commit 88cd748
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 192 deletions.
75 changes: 36 additions & 39 deletions packages/happy-dom/src/html-serializer/HTMLSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import HTMLElementConfigContentModelEnum from '../config/HTMLElementConfigConten
* Serializes a node into HTML.
*/
export default class HTMLSerializer {
public [PropertySymbol.options]: {
private options: {
serializableShadowRoots: boolean;
shadowRoots: ShadowRoot[] | null;
allShadowRoots: boolean;
Expand All @@ -25,15 +25,41 @@ export default class HTMLSerializer {
allShadowRoots: false
};

/**
* Constructor.
*
* @param [options] Options.
* @param [options.serializableShadowRoots] If shadow roots should be serialized.
* @param [options.shadowRoots] Shadow roots to serialize.
* @param [options.allShadowRoots] If all shadow roots should be serialized.
*/
constructor(options?: {
serializableShadowRoots?: boolean;
shadowRoots?: ShadowRoot[] | null;
allShadowRoots?: boolean;
}) {
if (options) {
if (options.serializableShadowRoots) {
this.options.serializableShadowRoots = options.serializableShadowRoots;
}

if (options.shadowRoots) {
this.options.shadowRoots = options.shadowRoots;
}

if (options.allShadowRoots) {
this.options.allShadowRoots = options.allShadowRoots;
}
}
}

/**
* Renders an element as HTML.
*
* @param root Root element.
* @returns Result.
*/
public serializeToString(root: Node): string {
const options = this[PropertySymbol.options];

switch (root[PropertySymbol.nodeType]) {
case NodeTypeEnum.elementNode:
const element = <Element>root;
Expand All @@ -49,9 +75,10 @@ export default class HTMLSerializer {
// TODO: Should we include closed shadow roots? We are currently only including open shadow roots.
if (
element.shadowRoot &&
(options.allShadowRoots ||
(options.serializableShadowRoots && element.shadowRoot[PropertySymbol.serializable]) ||
options.shadowRoots?.includes(element.shadowRoot))
(this.options.allShadowRoots ||
(this.options.serializableShadowRoots &&
element.shadowRoot[PropertySymbol.serializable]) ||
this.options.shadowRoots?.includes(element.shadowRoot))
) {
innerHTML += `<template shadowrootmode="${element.shadowRoot[PropertySymbol.mode]}"${
element.shadowRoot[PropertySymbol.serializable] ? ' shadowrootserializable=""' : ''
Expand All @@ -73,14 +100,6 @@ export default class HTMLSerializer {
innerHTML += this.serializeToString(node);
}

// if (
// !innerHTML &&
// (root[PropertySymbol.namespaceURI] === NamespaceURI.xmlns ||
// root[PropertySymbol.namespaceURI] === NamespaceURI.svg)
// ) {
// return `<${localName}${this.getAttributes(element)}/>`;
// }

return `<${localName}${this.getAttributes(element)}>${innerHTML}</${localName}>`;
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.DOCUMENT_NODE:
Expand All @@ -92,7 +111,6 @@ export default class HTMLSerializer {
case NodeTypeEnum.commentNode:
return `<!--${root.textContent}-->`;
case NodeTypeEnum.processingInstructionNode:
// TODO: Add support for processing instructions.
return `<!--?${(<ProcessingInstruction>root).target} ${root.textContent}?-->`;
case NodeTypeEnum.textNode:
const parentElement = root.parentElement;
Expand Down Expand Up @@ -123,36 +141,15 @@ export default class HTMLSerializer {
private getAttributes(element: Element): string {
let attributeString = '';

if (
!(<Element>element)[PropertySymbol.attributes].getNamedItem('is') &&
(<Element>element)[PropertySymbol.isValue]
) {
attributeString += ' is="' + (<Element>element)[PropertySymbol.isValue] + '"';
}

const namedItems = (<Element>element)[PropertySymbol.attributes][PropertySymbol.namedItems];

// if (
// element[PropertySymbol.namespaceURI] === NamespaceURI.svg ||
// element[PropertySymbol.namespaceURI] === NamespaceURI.xmlns
// ) {
// const xmlns = namedItems.get('xmlns');

// // The "xmlns" attribute should always be the first attribute if it exists.
// if (xmlns && xmlns[PropertySymbol.value]) {
// attributeString += ' xmlns="' + xmlns[PropertySymbol.value] + '"';
// }
// }
if (!namedItems.has('is') && element[PropertySymbol.isValue]) {
attributeString += ' is="' + element[PropertySymbol.isValue] + '"';
}

for (const attribute of namedItems.values()) {
// if (
// attribute[PropertySymbol.name] !== 'xmlns' ||
// (element[PropertySymbol.namespaceURI] !== NamespaceURI.svg &&
// element[PropertySymbol.namespaceURI] !== NamespaceURI.xmlns)
// ) {
const escapedValue = Entities.escapeAttribute(attribute[PropertySymbol.value]);
attributeString += ' ' + attribute[PropertySymbol.name] + '="' + escapedValue + '"';
// }
}

return attributeString;
Expand Down
40 changes: 15 additions & 25 deletions packages/happy-dom/src/nodes/element/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import DOMRect from '../../dom/DOMRect.js';
import DOMTokenList from '../../dom/DOMTokenList.js';
import QuerySelector from '../../query-selector/QuerySelector.js';
import XMLParser from '../../xml-parser/XMLParser.js';
import XMLSerializer from '../../xml-serializer/XMLSerializer.js';
import ChildNodeUtility from '../child-node/ChildNodeUtility.js';
import ParentNodeUtility from '../parent-node/ParentNodeUtility.js';
import NonDocumentChildNodeUtility from '../child-node/NonDocumentChildNodeUtility.js';
Expand All @@ -32,7 +31,7 @@ import NodeList from '../node/NodeList.js';
import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration.js';
import NamedNodeMapProxyFactory from './NamedNodeMapProxyFactory.js';
import NodeFactory from '../NodeFactory.js';
import XMLParserModeEnum from '../../xml-parser/XMLParserModeEnum.js';
import HTMLSerializer from '../../html-serializer/HTMLSerializer.js';

type InsertAdjacentPosition = 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend';

Expand Down Expand Up @@ -416,7 +415,7 @@ export default class Element
* @returns HTML.
*/
public get outerHTML(): string {
return new XMLSerializer().serializeToString(this);
return new HTMLSerializer().serializeToString(this);
}

/**
Expand Down Expand Up @@ -495,19 +494,17 @@ export default class Element
* @returns HTML.
*/
public getInnerHTML(options?: { includeShadowRoots?: boolean }): string {
const xmlSerializer = new XMLSerializer();
const serializer = new HTMLSerializer({
allShadowRoots: !!options?.includeShadowRoots
});

if (options?.includeShadowRoots) {
xmlSerializer[PropertySymbol.options].allShadowRoots = true;
}

let xml = '';
let html = '';

for (const node of this[PropertySymbol.nodeArray]) {
xml += xmlSerializer.serializeToString(node);
html += serializer.serializeToString(node);
}

return xml;
return html;
}

/**
Expand All @@ -522,25 +519,18 @@ export default class Element
serializableShadowRoots?: boolean;
shadowRoots?: ShadowRoot[];
}): string {
const xmlSerializer = new XMLSerializer();

if (options) {
if (options.serializableShadowRoots) {
xmlSerializer[PropertySymbol.options].serializableShadowRoots =
options.serializableShadowRoots;
}
if (options.shadowRoots) {
xmlSerializer[PropertySymbol.options].shadowRoots = options.shadowRoots;
}
}
const serializer = new HTMLSerializer({
serializableShadowRoots: !!options?.serializableShadowRoots,
shadowRoots: options?.shadowRoots ?? null
});

let xml = '';
let html = '';

for (const node of this[PropertySymbol.nodeArray]) {
xml += xmlSerializer.serializeToString(node);
html += serializer.serializeToString(node);
}

return xml;
return html;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import HTMLElement from '../html-element/HTMLElement.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import DocumentFragment from '../document-fragment/DocumentFragment.js';
import Node from '../node/Node.js';
import XMLSerializer from '../../xml-serializer/XMLSerializer.js';
import XMLParser from '../../xml-parser/XMLParser.js';
import ShadowRoot from '../shadow-root/ShadowRoot.js';
import XMLParserModeEnum from '../../xml-parser/XMLParserModeEnum.js';
import HTMLSerializer from '../../html-serializer/HTMLSerializer.js';

/**
* HTML Template Element.
Expand Down Expand Up @@ -70,18 +69,18 @@ export default class HTMLTemplateElement extends HTMLElement {
* @override
*/
public override getInnerHTML(_options?: { includeShadowRoots?: boolean }): string {
const xmlSerializer = new XMLSerializer();
const serializer = new HTMLSerializer();

// Options should be ignored as shadow roots should not be serialized for HTMLTemplateElement.

const content = <DocumentFragment>this[PropertySymbol.content];
let xml = '';
let html = '';

for (const node of content[PropertySymbol.nodeArray]) {
xml += xmlSerializer.serializeToString(node);
html += serializer.serializeToString(node);
}

return xml;
return html;
}

/**
Expand All @@ -91,18 +90,18 @@ export default class HTMLTemplateElement extends HTMLElement {
serializableShadowRoots?: boolean;
shadowRoots?: ShadowRoot[];
}): string {
const xmlSerializer = new XMLSerializer();
const serializer = new HTMLSerializer();

// Options should be ignored as shadow roots should not be serialized for HTMLTemplateElement.

const content = <DocumentFragment>this[PropertySymbol.content];
let xml = '';
let html = '';

for (const node of content[PropertySymbol.nodeArray]) {
xml += xmlSerializer.serializeToString(node);
html += serializer.serializeToString(node);
}

return xml;
return html;
}

/**
Expand Down
11 changes: 5 additions & 6 deletions packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import DocumentFragment from '../document-fragment/DocumentFragment.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import XMLParser from '../../xml-parser/XMLParser.js';
import XMLSerializer from '../../xml-serializer/XMLSerializer.js';
import Element from '../element/Element.js';
import CSSStyleSheet from '../../css/CSSStyleSheet.js';
import HTMLElement from '../../nodes/html-element/HTMLElement.js';
import Event from '../../event/Event.js';
import SVGElement from '../svg-element/SVGElement.js';
import Document from '../document/Document.js';
import XMLParserModeEnum from '../../xml-parser/XMLParserModeEnum.js';
import HTMLSerializer from '../../html-serializer/HTMLSerializer.js';

/**
* ShadowRoot.
Expand Down Expand Up @@ -120,12 +119,12 @@ export default class ShadowRoot extends DocumentFragment {
* @returns HTML.
*/
public get innerHTML(): string {
const xmlSerializer = new XMLSerializer();
let xml = '';
const serializer = new HTMLSerializer();
let html = '';
for (const node of this[PropertySymbol.nodeArray]) {
xml += xmlSerializer.serializeToString(node);
html += serializer.serializeToString(node);
}
return xml;
return html;
}

/**
Expand Down
Loading

0 comments on commit 88cd748

Please sign in to comment.