Skip to content

Commit db97904

Browse files
authored
Merge pull request #1446 from capricorn86/1445-cssstylesheet-not-updated-when-editing-the-data-of-a-child-text-node
fix: [#1445] Adds fix to update CSS rules in HTMLStyleElement sheet w…
2 parents 6ada816 + 3bce547 commit db97904

File tree

5 files changed

+116
-10
lines changed

5 files changed

+116
-10
lines changed

packages/happy-dom/src/PropertySymbol.ts

+2
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,5 @@ export const appendChild = Symbol('appendChild');
164164
export const removeChild = Symbol('removeChild');
165165
export const insertBefore = Symbol('insertBefore');
166166
export const replaceChild = Symbol('replaceChild');
167+
export const styleNode = Symbol('styleNode');
168+
export const updateSheet = Symbol('updateSheet');

packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Node from '../node/Node.js';
1111
*/
1212
export default class HTMLStyleElement extends HTMLElement {
1313
private [PropertySymbol.sheet]: CSSStyleSheet | null = null;
14+
public [PropertySymbol.styleNode] = this;
1415

1516
/**
1617
* Returns CSS style sheet.
@@ -84,9 +85,7 @@ export default class HTMLStyleElement extends HTMLElement {
8485
*/
8586
public override [PropertySymbol.appendChild](node: Node): Node {
8687
const returnValue = super[PropertySymbol.appendChild](node);
87-
if (this[PropertySymbol.sheet]) {
88-
this[PropertySymbol.sheet].replaceSync(this.textContent);
89-
}
88+
this[PropertySymbol.updateSheet]();
9089
return returnValue;
9190
}
9291

@@ -95,9 +94,7 @@ export default class HTMLStyleElement extends HTMLElement {
9594
*/
9695
public override [PropertySymbol.removeChild](node: Node): Node {
9796
const returnValue = super[PropertySymbol.removeChild](node);
98-
if (this[PropertySymbol.sheet]) {
99-
this[PropertySymbol.sheet].replaceSync(this.textContent);
100-
}
97+
this[PropertySymbol.updateSheet]();
10198
return returnValue;
10299
}
103100

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

@@ -125,4 +120,14 @@ export default class HTMLStyleElement extends HTMLElement {
125120
this[PropertySymbol.sheet] = null;
126121
}
127122
}
123+
124+
/**
125+
* Updates the CSSStyleSheet with the text content.
126+
*/
127+
public [PropertySymbol.updateSheet](): void {
128+
if (this[PropertySymbol.sheet]) {
129+
this[PropertySymbol.ownerDocument][PropertySymbol.cacheID]++;
130+
this[PropertySymbol.sheet].replaceSync(this.textContent);
131+
}
132+
}
128133
}

packages/happy-dom/src/nodes/node/Node.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export default class Node extends EventTarget {
6161
public [PropertySymbol.formNode]: Node = null;
6262
public [PropertySymbol.selectNode]: Node = null;
6363
public [PropertySymbol.textAreaNode]: Node = null;
64+
public [PropertySymbol.styleNode]: Node = null;
6465
public [PropertySymbol.observers]: MutationListener[] = [];
6566
public [PropertySymbol.childNodes]: NodeList<Node> = new NodeList<Node>();
6667

@@ -516,6 +517,7 @@ export default class Node extends EventTarget {
516517
const formNode = (<Node>this)[PropertySymbol.formNode];
517518
const selectNode = (<Node>this)[PropertySymbol.selectNode];
518519
const textAreaNode = (<Node>this)[PropertySymbol.textAreaNode];
520+
const styleNode = (<Node>this)[PropertySymbol.styleNode];
519521

520522
if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) {
521523
this[PropertySymbol.parentNode] = parentNode;
@@ -539,6 +541,12 @@ export default class Node extends EventTarget {
539541
? (<Node>parentNode)[PropertySymbol.textAreaNode]
540542
: null;
541543
}
544+
545+
if (this['tagName'] !== 'STYLE') {
546+
(<Node>this)[PropertySymbol.styleNode] = parentNode
547+
? (<Node>parentNode)[PropertySymbol.styleNode]
548+
: null;
549+
}
542550
}
543551

544552
if (this[PropertySymbol.isConnected] !== isConnected) {
@@ -584,7 +592,8 @@ export default class Node extends EventTarget {
584592
} else if (
585593
formNode !== this[PropertySymbol.formNode] ||
586594
selectNode !== this[PropertySymbol.selectNode] ||
587-
textAreaNode !== this[PropertySymbol.textAreaNode]
595+
textAreaNode !== this[PropertySymbol.textAreaNode] ||
596+
styleNode !== this[PropertySymbol.styleNode]
588597
) {
589598
for (const child of this[PropertySymbol.childNodes]) {
590599
(<Node>child)[PropertySymbol.connectToNode](this);

packages/happy-dom/src/nodes/text/Text.ts

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export default class Text extends CharacterData {
3838
if (this[PropertySymbol.textAreaNode]) {
3939
(<HTMLTextAreaElement>this[PropertySymbol.textAreaNode])[PropertySymbol.resetSelection]();
4040
}
41+
42+
if (this[PropertySymbol.styleNode]) {
43+
this[PropertySymbol.styleNode][PropertySymbol.updateSheet]();
44+
}
4145
}
4246

4347
/**

packages/happy-dom/test/nodes/html-style-element/HTMLStyleElement.test.ts

+86
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,91 @@ describe('HTMLStyleElement', () => {
7575
expect(element.sheet.cssRules[1].cssText).toBe('body { background-color: red; }');
7676
expect(element.sheet.cssRules[2].cssText).toBe('div { background-color: green; }');
7777
});
78+
79+
it('Updates rules when appending a text node.', () => {
80+
document.head.appendChild(element);
81+
82+
expect(element.sheet.cssRules.length).toBe(0);
83+
84+
const textNode = document.createTextNode(
85+
'body { background-color: red }\ndiv { background-color: green }'
86+
);
87+
88+
element.appendChild(textNode);
89+
90+
expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
91+
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');
92+
});
93+
94+
it('Updates rules when removing a text node.', () => {
95+
document.head.appendChild(element);
96+
97+
const textNode = document.createTextNode(
98+
'body { background-color: red }\ndiv { background-color: green }'
99+
);
100+
101+
element.appendChild(textNode);
102+
103+
expect(element.sheet.cssRules.length).toBe(2);
104+
105+
expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
106+
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');
107+
108+
element.removeChild(textNode);
109+
110+
expect(element.sheet.cssRules.length).toBe(0);
111+
});
112+
113+
it('Updates rules when inserting a text node.', () => {
114+
document.head.appendChild(element);
115+
116+
const textNode = document.createTextNode(
117+
'body { background-color: red }\ndiv { background-color: green }'
118+
);
119+
120+
element.appendChild(textNode);
121+
122+
expect(element.sheet.cssRules.length).toBe(2);
123+
124+
expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
125+
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');
126+
127+
const textNode2 = document.createTextNode('html { background-color: blue }');
128+
129+
element.insertBefore(textNode2, textNode);
130+
131+
expect(element.sheet.cssRules.length).toBe(3);
132+
133+
expect(element.sheet.cssRules[0].cssText).toBe('html { background-color: blue; }');
134+
expect(element.sheet.cssRules[1].cssText).toBe('body { background-color: red; }');
135+
expect(element.sheet.cssRules[2].cssText).toBe('div { background-color: green; }');
136+
});
137+
138+
it('Updates rules editing data of a child Text node.', () => {
139+
document.head.appendChild(element);
140+
141+
expect(element.sheet.cssRules.length).toBe(0);
142+
143+
const textNode = document.createTextNode(
144+
'body { background-color: red }\ndiv { background-color: green }'
145+
);
146+
147+
const documentElementComputedStyle = window.getComputedStyle(document.documentElement);
148+
149+
element.appendChild(textNode);
150+
151+
expect(element.sheet.cssRules.length).toBe(2);
152+
expect(element.sheet.cssRules[0].cssText).toBe('body { background-color: red; }');
153+
expect(element.sheet.cssRules[1].cssText).toBe('div { background-color: green; }');
154+
155+
expect(documentElementComputedStyle.backgroundColor).toBe('');
156+
157+
textNode.data = 'html { background-color: blue }';
158+
159+
expect(element.sheet.cssRules.length).toBe(1);
160+
expect(element.sheet.cssRules[0].cssText).toBe('html { background-color: blue; }');
161+
162+
expect(documentElementComputedStyle.backgroundColor).toBe('blue');
163+
});
78164
});
79165
});

0 commit comments

Comments
 (0)