Skip to content

Commit

Permalink
fix: [#1445] Adds fix to update CSS rules in HTMLStyleElement sheet w…
Browse files Browse the repository at this point in the history
…hen editing the data of a child Text node
  • Loading branch information
capricorn86 committed May 28, 2024
1 parent 6ada816 commit 3bce547
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 10 deletions.
2 changes: 2 additions & 0 deletions packages/happy-dom/src/PropertySymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,5 @@ export const appendChild = Symbol('appendChild');
export const removeChild = Symbol('removeChild');
export const insertBefore = Symbol('insertBefore');
export const replaceChild = Symbol('replaceChild');
export const styleNode = Symbol('styleNode');
export const updateSheet = Symbol('updateSheet');
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Node from '../node/Node.js';
*/
export default class HTMLStyleElement extends HTMLElement {
private [PropertySymbol.sheet]: CSSStyleSheet | null = null;
public [PropertySymbol.styleNode] = this;

/**
* Returns CSS style sheet.
Expand Down Expand Up @@ -84,9 +85,7 @@ export default class HTMLStyleElement extends HTMLElement {
*/
public override [PropertySymbol.appendChild](node: Node): Node {
const returnValue = super[PropertySymbol.appendChild](node);
if (this[PropertySymbol.sheet]) {
this[PropertySymbol.sheet].replaceSync(this.textContent);
}
this[PropertySymbol.updateSheet]();
return returnValue;
}

Expand All @@ -95,9 +94,7 @@ export default class HTMLStyleElement extends HTMLElement {
*/
public override [PropertySymbol.removeChild](node: Node): Node {
const returnValue = super[PropertySymbol.removeChild](node);
if (this[PropertySymbol.sheet]) {
this[PropertySymbol.sheet].replaceSync(this.textContent);
}
this[PropertySymbol.updateSheet]();
return returnValue;
}

Expand All @@ -106,9 +103,7 @@ export default class HTMLStyleElement extends HTMLElement {
*/
public override [PropertySymbol.insertBefore](newNode: Node, referenceNode: Node | null): Node {
const returnValue = super[PropertySymbol.insertBefore](newNode, referenceNode);
if (this[PropertySymbol.sheet]) {
this[PropertySymbol.sheet].replaceSync(this.textContent);
}
this[PropertySymbol.updateSheet]();
return returnValue;
}

Expand All @@ -125,4 +120,14 @@ export default class HTMLStyleElement extends HTMLElement {
this[PropertySymbol.sheet] = null;
}
}

/**
* Updates the CSSStyleSheet with the text content.
*/
public [PropertySymbol.updateSheet](): void {
if (this[PropertySymbol.sheet]) {
this[PropertySymbol.ownerDocument][PropertySymbol.cacheID]++;
this[PropertySymbol.sheet].replaceSync(this.textContent);
}
}
}
11 changes: 10 additions & 1 deletion packages/happy-dom/src/nodes/node/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default class Node extends EventTarget {
public [PropertySymbol.formNode]: Node = null;
public [PropertySymbol.selectNode]: Node = null;
public [PropertySymbol.textAreaNode]: Node = null;
public [PropertySymbol.styleNode]: Node = null;
public [PropertySymbol.observers]: MutationListener[] = [];
public [PropertySymbol.childNodes]: NodeList<Node> = new NodeList<Node>();

Expand Down Expand Up @@ -516,6 +517,7 @@ export default class Node extends EventTarget {
const formNode = (<Node>this)[PropertySymbol.formNode];
const selectNode = (<Node>this)[PropertySymbol.selectNode];
const textAreaNode = (<Node>this)[PropertySymbol.textAreaNode];
const styleNode = (<Node>this)[PropertySymbol.styleNode];

if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) {
this[PropertySymbol.parentNode] = parentNode;
Expand All @@ -539,6 +541,12 @@ export default class Node extends EventTarget {
? (<Node>parentNode)[PropertySymbol.textAreaNode]
: null;
}

if (this['tagName'] !== 'STYLE') {
(<Node>this)[PropertySymbol.styleNode] = parentNode
? (<Node>parentNode)[PropertySymbol.styleNode]
: null;
}
}

if (this[PropertySymbol.isConnected] !== isConnected) {
Expand Down Expand Up @@ -584,7 +592,8 @@ export default class Node extends EventTarget {
} else if (
formNode !== this[PropertySymbol.formNode] ||
selectNode !== this[PropertySymbol.selectNode] ||
textAreaNode !== this[PropertySymbol.textAreaNode]
textAreaNode !== this[PropertySymbol.textAreaNode] ||
styleNode !== this[PropertySymbol.styleNode]
) {
for (const child of this[PropertySymbol.childNodes]) {
(<Node>child)[PropertySymbol.connectToNode](this);
Expand Down
4 changes: 4 additions & 0 deletions packages/happy-dom/src/nodes/text/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export default class Text extends CharacterData {
if (this[PropertySymbol.textAreaNode]) {
(<HTMLTextAreaElement>this[PropertySymbol.textAreaNode])[PropertySymbol.resetSelection]();
}

if (this[PropertySymbol.styleNode]) {
this[PropertySymbol.styleNode][PropertySymbol.updateSheet]();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,91 @@ describe('HTMLStyleElement', () => {
expect(element.sheet.cssRules[1].cssText).toBe('body { background-color: red; }');
expect(element.sheet.cssRules[2].cssText).toBe('div { background-color: green; }');
});

it('Updates rules when appending a text node.', () => {
document.head.appendChild(element);

expect(element.sheet.cssRules.length).toBe(0);

const textNode = document.createTextNode(
'body { background-color: red }\ndiv { background-color: green }'
);

element.appendChild(textNode);

expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');
});

it('Updates rules when removing a text node.', () => {
document.head.appendChild(element);

const textNode = document.createTextNode(
'body { background-color: red }\ndiv { background-color: green }'
);

element.appendChild(textNode);

expect(element.sheet.cssRules.length).toBe(2);

expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');

element.removeChild(textNode);

expect(element.sheet.cssRules.length).toBe(0);
});

it('Updates rules when inserting a text node.', () => {
document.head.appendChild(element);

const textNode = document.createTextNode(
'body { background-color: red }\ndiv { background-color: green }'
);

element.appendChild(textNode);

expect(element.sheet.cssRules.length).toBe(2);

expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');

const textNode2 = document.createTextNode('html { background-color: blue }');

element.insertBefore(textNode2, textNode);

expect(element.sheet.cssRules.length).toBe(3);

expect(element.sheet.cssRules[0].cssText).toBe('html { background-color: blue; }');
expect(element.sheet.cssRules[1].cssText).toBe('body { background-color: red; }');
expect(element.sheet.cssRules[2].cssText).toBe('div { background-color: green; }');
});

it('Updates rules editing data of a child Text node.', () => {
document.head.appendChild(element);

expect(element.sheet.cssRules.length).toBe(0);

const textNode = document.createTextNode(
'body { background-color: red }\ndiv { background-color: green }'
);

const documentElementComputedStyle = window.getComputedStyle(document.documentElement);

element.appendChild(textNode);

expect(element.sheet.cssRules.length).toBe(2);
expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');

expect(documentElementComputedStyle.backgroundColor).toBe('');

textNode.data = 'html { background-color: blue }';

expect(element.sheet.cssRules.length).toBe(1);
expect(element.sheet.cssRules[0].cssText).toBe('html { background-color: blue; }');

expect(documentElementComputedStyle.backgroundColor).toBe('blue');
});
});
});

0 comments on commit 3bce547

Please sign in to comment.