Skip to content

Commit e6f8b13

Browse files
authored
fix: [#1581] Fixes bug where Node.getRootNode() returned null when it was within a ShadowRoot that previously been disconnected from the Document (#1582)
1 parent 38ab960 commit e6f8b13

File tree

4 files changed

+52
-8
lines changed

4 files changed

+52
-8
lines changed

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,10 @@ export default class Node extends EventTarget {
10251025
*/
10261026
public [PropertySymbol.disconnectedFromDocument](): void {
10271027
this[PropertySymbol.isConnected] = false;
1028-
this[PropertySymbol.rootNode] = null;
1028+
1029+
if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) {
1030+
this[PropertySymbol.rootNode] = null;
1031+
}
10291032

10301033
if (this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] === <unknown>this) {
10311034
this[PropertySymbol.ownerDocument][PropertySymbol.clearCache]();

packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts

+17-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import CSSStyleSheet from '../../css/CSSStyleSheet.js';
77
import HTMLElement from '../../nodes/html-element/HTMLElement.js';
88
import Event from '../../event/Event.js';
99
import SVGElement from '../svg-element/SVGElement.js';
10+
import Document from '../document/Document.js';
1011

1112
/**
1213
* ShadowRoot.
@@ -165,16 +166,25 @@ export default class ShadowRoot extends DocumentFragment {
165166
* @returns Active element.
166167
*/
167168
public get activeElement(): HTMLElement | SVGElement | null {
168-
const activeElement: HTMLElement | SVGElement =
169+
let activeElement: HTMLElement | SVGElement =
169170
this[PropertySymbol.ownerDocument][PropertySymbol.activeElement];
170-
if (
171-
activeElement &&
172-
activeElement[PropertySymbol.isConnected] &&
173-
activeElement.getRootNode() === this
174-
) {
171+
172+
let rootNode: ShadowRoot | Document = <ShadowRoot | Document>activeElement?.getRootNode();
173+
174+
if (!rootNode || rootNode === this[PropertySymbol.ownerDocument]) {
175+
return null;
176+
}
177+
178+
if (rootNode === this) {
175179
return activeElement;
176180
}
177-
return null;
181+
182+
while (rootNode && rootNode !== this) {
183+
activeElement = <HTMLElement | SVGElement>(<ShadowRoot>rootNode).host;
184+
rootNode = <ShadowRoot | Document>activeElement.getRootNode();
185+
}
186+
187+
return activeElement;
178188
}
179189

180190
/**

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

+8
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,14 @@ describe('Node', () => {
366366
const rootNode = (<ShadowRoot>customElement.shadowRoot).querySelector('span')?.getRootNode();
367367

368368
expect(rootNode === customElement.shadowRoot).toBe(true);
369+
370+
document.body.removeChild(customElement);
371+
372+
document.body.appendChild(customElement);
373+
374+
const rootNode2 = (<ShadowRoot>customElement.shadowRoot).querySelector('span')?.getRootNode();
375+
376+
expect(rootNode2 === customElement.shadowRoot).toBe(true);
369377
});
370378

371379
it('Returns Document when used on a node inside a ShadowRoot and the option "composed" is set to "true".', () => {

packages/happy-dom/test/nodes/shadow-root/ShadowRoot.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,29 @@ describe('ShadowRoot', () => {
218218

219219
expect(shadowRoot.activeElement === null).toBe(true);
220220
});
221+
222+
it('Returns the first custom element when the active element is not a child of the ShadowRoot, but is a child of a custom element within it.', () => {
223+
const customElement = document.createElement('custom-element');
224+
const shadowRoot = <ShadowRoot>customElement.shadowRoot;
225+
const div = <HTMLElement>document.createElement('div');
226+
const customElementA = document.createElement('custom-element-a');
227+
const shadowRootA = <ShadowRoot>customElementA.shadowRoot;
228+
229+
document.body.appendChild(customElement);
230+
231+
shadowRoot.appendChild(customElementA);
232+
shadowRootA.appendChild(div);
233+
234+
expect(shadowRoot.activeElement === null).toBe(true);
235+
236+
div.focus();
237+
238+
expect(shadowRoot.activeElement).toBe(customElementA);
239+
240+
customElementA.remove();
241+
242+
expect(shadowRoot.activeElement === null).toBe(true);
243+
});
221244
});
222245

223246
describe('getAnimations()', () => {

0 commit comments

Comments
 (0)