Skip to content

Commit

Permalink
BREAKING CHANGE: [#1615] Improves XML and HTML parsing (#1617)
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 authored Dec 27, 2024
1 parent d3566f4 commit ad3872d
Show file tree
Hide file tree
Showing 92 changed files with 6,945 additions and 2,011 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/happy-dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
"test:debug": "vitest run --inspect-brk --no-file-parallelism"
},
"dependencies": {
"entities": "^4.5.0",
"webidl-conversions": "^7.0.0",
"whatwg-mimetype": "^3.0.0"
},
Expand Down
5 changes: 4 additions & 1 deletion packages/happy-dom/src/PropertySymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const listeners = Symbol('listeners');
export const namedItems = Symbol('namedItems');
export const nextActiveElement = Symbol('nextActiveElement');
export const observeMutations = Symbol('observeMutations');
export const observedAttributes = Symbol('observedAttributes');
export const mutationListeners = Symbol('mutationListeners');
export const ownerDocument = Symbol('ownerDocument');
export const ownerElement = Symbol('ownerElement');
Expand Down Expand Up @@ -375,3 +374,7 @@ export const getLength = Symbol('getLength');
export const currentScale = Symbol('currentScale');
export const rotate = Symbol('rotate');
export const bindMethods = Symbol('bindMethods');
export const xmlProcessingInstruction = Symbol('xmlProcessingInstruction');
export const root = Symbol('root');
export const filterNode = Symbol('filterNode');
export const customElementReactionStack = Symbol('customElementReactionStack');
70 changes: 53 additions & 17 deletions packages/happy-dom/src/config/HTMLElementConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ import HTMLElementConfigContentModelEnum from './HTMLElementConfigContentModelEn
* @see https://html.spec.whatwg.org/multipage/indices.html
*/
export default <
{ [key: string]: { className: string; contentModel: HTMLElementConfigContentModelEnum } }
{
[key: string]: {
className: string;
contentModel: HTMLElementConfigContentModelEnum;
forbiddenDescendants?: string[];
permittedDescendants?: string[];
permittedParents?: string[];
addPermittedParent?: string;
moveForbiddenDescendant?: { exclude: string[] };
};
}
>{
a: {
className: 'HTMLAnchorElement',
Expand Down Expand Up @@ -116,7 +126,7 @@ export default <
},
caption: {
className: 'HTMLTableCaptionElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.textOrComments
},
cite: {
className: 'HTMLElement',
Expand All @@ -128,11 +138,13 @@ export default <
},
col: {
className: 'HTMLTableColElement',
contentModel: HTMLElementConfigContentModelEnum.noDescendants
contentModel: HTMLElementConfigContentModelEnum.noDescendants,
permittedParents: ['colgroup']
},
colgroup: {
className: 'HTMLTableColElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.permittedDescendants,
permittedDescendants: ['col']
},
data: {
className: 'HTMLDataElement',
Expand All @@ -144,7 +156,8 @@ export default <
},
dd: {
className: 'HTMLElement',
contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants
contentModel: HTMLElementConfigContentModelEnum.noForbiddenFirstLevelDescendants,
forbiddenDescendants: ['dt', 'dd']
},
del: {
className: 'HTMLModElement',
Expand Down Expand Up @@ -172,7 +185,8 @@ export default <
},
dt: {
className: 'HTMLElement',
contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants
contentModel: HTMLElementConfigContentModelEnum.noForbiddenFirstLevelDescendants,
forbiddenDescendants: ['dt', 'dd']
},
em: {
className: 'HTMLElement',
Expand Down Expand Up @@ -304,11 +318,12 @@ export default <
},
optgroup: {
className: 'HTMLOptGroupElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants
},
option: {
className: 'HTMLOptionElement',
contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants
contentModel: HTMLElementConfigContentModelEnum.noForbiddenFirstLevelDescendants,
forbiddenDescendants: ['option', 'optgroup']
},
output: {
className: 'HTMLOutputElement',
Expand Down Expand Up @@ -344,11 +359,13 @@ export default <
},
rp: {
className: 'HTMLElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.noForbiddenFirstLevelDescendants,
forbiddenDescendants: ['rp', 'rt']
},
rt: {
className: 'HTMLElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.noForbiddenFirstLevelDescendants,
forbiddenDescendants: ['rp', 'rt']
},
rtc: {
className: 'HTMLElement',
Expand Down Expand Up @@ -404,27 +421,42 @@ export default <
},
table: {
className: 'HTMLTableElement',
contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants
contentModel: HTMLElementConfigContentModelEnum.permittedDescendants,
permittedDescendants: ['caption', 'colgroup', 'thead', 'tfoot', 'tbody'],
moveForbiddenDescendant: { exclude: [] }
},
tbody: {
className: 'HTMLTableSectionElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.permittedDescendants,
permittedDescendants: ['tr'],
permittedParents: ['table'],
moveForbiddenDescendant: { exclude: ['caption', 'colgroup', 'thead', 'tfoot', 'tbody'] }
},
td: {
className: 'HTMLTableCellElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.noForbiddenFirstLevelDescendants,
forbiddenDescendants: ['td', 'th', 'tr', 'tbody', 'tfoot', 'thead'],
permittedParents: ['tr']
},
tfoot: {
className: 'HTMLTableSectionElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.permittedDescendants,
permittedDescendants: ['tr'],
permittedParents: ['table'],
moveForbiddenDescendant: { exclude: ['caption', 'colgroup', 'thead', 'tfoot', 'tbody'] }
},
th: {
className: 'HTMLTableCellElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.noForbiddenFirstLevelDescendants,
forbiddenDescendants: ['td', 'th', 'tr', 'tbody', 'tfoot', 'thead'],
permittedParents: ['tr']
},
thead: {
className: 'HTMLTableSectionElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.permittedDescendants,
permittedDescendants: ['tr'],
permittedParents: ['table'],
moveForbiddenDescendant: { exclude: ['caption', 'colgroup', 'thead', 'tfoot', 'tbody'] }
},
time: {
className: 'HTMLTimeElement',
Expand All @@ -436,7 +468,11 @@ export default <
},
tr: {
className: 'HTMLTableRowElement',
contentModel: HTMLElementConfigContentModelEnum.anyDescendants
contentModel: HTMLElementConfigContentModelEnum.permittedDescendants,
permittedDescendants: ['td', 'th'],
permittedParents: ['tbody', 'tfoot', 'thead'],
addPermittedParent: 'tbody',
moveForbiddenDescendant: { exclude: ['caption', 'colgroup', 'thead', 'tfoot', 'tbody', 'tr'] }
},
track: {
className: 'HTMLTrackElement',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ enum HTMLElementConfigContentModelEnum {
rawText = 'rawText',
noSelfDescendants = 'noSelfDescendants',
noFirstLevelSelfDescendants = 'noFirstLevelSelfDescendants',
noForbiddenFirstLevelDescendants = 'noForbiddenFirstLevelDescendants',
noDescendants = 'noDescendants',
permittedDescendants = 'permittedDescendants',
textOrComments = 'textOrComments',
anyDescendants = 'anyDescendants'
}

Expand Down
2 changes: 2 additions & 0 deletions packages/happy-dom/src/config/NamespaceURI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ export default {
html: 'http://www.w3.org/1999/xhtml',
svg: 'http://www.w3.org/2000/svg',
mathML: 'http://www.w3.org/1998/Math/MathML',
xml: 'http://www.w3.org/XML/1998/namespace',
xlink: 'http://www.w3.org/1999/xlink',
xmlns: 'http://www.w3.org/2000/xmlns/'
};
Original file line number Diff line number Diff line change
Expand Up @@ -4963,10 +4963,7 @@ export default class CSSStyleDeclaration {
return new CSSStyleDeclarationComputedStyle(element).getComputedStyle();
}

const attributeValue =
element[PropertySymbol.attributes][PropertySymbol.namedItems].get('style')?.[
PropertySymbol.value
];
const attributeValue = element.getAttribute('style') || '';

if (cache.attributeValue !== attributeValue) {
cache.propertyManager = new CSSStyleDeclarationPropertyManager({ cssText: attributeValue });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,10 @@ export default class CSSStyleDeclarationComputedStyle {
elementCSSText += cssText.cssText;
}

const elementStyleAttribute = (<Element>parentElement.element)[PropertySymbol.attributes][
PropertySymbol.namedItems
].get('style');
const elementStyleAttribute = (<Element>parentElement.element).getAttribute('style');

if (elementStyleAttribute) {
elementCSSText += elementStyleAttribute[PropertySymbol.value];
elementCSSText += elementStyleAttribute;
}

const rulesAndProperties = CSSStyleDeclarationCSSParser.parse(elementCSSText);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import HTMLElement from '../nodes/html-element/HTMLElement.js';
import BrowserWindow from '../window/BrowserWindow.js';
import * as PropertySymbol from '../PropertySymbol.js';
import WindowBrowserContext from '../window/WindowBrowserContext.js';

/**
* Custom element reaction stack.
*
* @see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack
*/
export default class CustomElementReactionStack {
private window: BrowserWindow;

/**
* Constructor.
*
* @param window Window.
*/
constructor(window: BrowserWindow) {
this.window = window;
}

/**
* Enqueues a custom element reaction.
*
* @see https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-a-custom-element-callback-reaction
* @see https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-an-element-on-the-appropriate-element-queue
* @param element Element.
* @param callbackName Callback name.
* @param [args] Arguments.
*/
public enqueueReaction(element: HTMLElement, callbackName: string, args?: any[]): void {
// If a polyfill is used, [PropertySymbol.registry] may be undefined
const definition = this.window.customElements[PropertySymbol.registry]?.get(element.localName);

if (!definition) {
return;
}

// According to the spec, we should use a queue for each element and then invoke the reactions in the order they were enqueued asynchronously.
// However, the browser seem to always invoke the reactions synchronously.
// TODO: Can we find an example where the reactions are invoked asynchronously? In that case we should use a queue for those cases.

switch (callbackName) {
case 'connectedCallback':
if (definition.livecycleCallbacks.connectedCallback) {
const returnValue = definition.livecycleCallbacks.connectedCallback.call(element);

/**
* It is common to import dependencies in the connectedCallback() method of web components.
* As Happy DOM doesn't have support for dynamic imports yet, this is a temporary solution to wait for imports in connectedCallback().
*
* @see https://github.com/capricorn86/happy-dom/issues/1442
*/
if (returnValue instanceof Promise) {
const asyncTaskManager = new WindowBrowserContext(this.window).getAsyncTaskManager();
if (asyncTaskManager) {
const taskID = asyncTaskManager.startTask();
returnValue
.then(() => asyncTaskManager.endTask(taskID))
.catch(() => asyncTaskManager.endTask(taskID));
}
}
}
break;
case 'disconnectedCallback':
if (definition.livecycleCallbacks.disconnectedCallback) {
definition.livecycleCallbacks.disconnectedCallback.call(element);
}
break;
case 'attributeChangedCallback':
if (
definition.livecycleCallbacks.attributeChangedCallback &&
definition.observedAttributes.has(args[0])
) {
definition.livecycleCallbacks.attributeChangedCallback.apply(element, args);
}
break;
}
}
}
Loading

0 comments on commit ad3872d

Please sign in to comment.