fix: regression of issue: ShadowHost can't be a string (issue 941) (#1092)
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
|||||||
isSerializedIframe,
|
isSerializedIframe,
|
||||||
isSerializedStylesheet,
|
isSerializedStylesheet,
|
||||||
inDom,
|
inDom,
|
||||||
|
getShadowHost,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
type DoubleLinkedListNode = {
|
type DoubleLinkedListNode = {
|
||||||
@@ -268,18 +269,11 @@ export default class MutationBuffer {
|
|||||||
return nextId;
|
return nextId;
|
||||||
};
|
};
|
||||||
const pushAdd = (n: Node) => {
|
const pushAdd = (n: Node) => {
|
||||||
let shadowHost: Element | null = null;
|
|
||||||
if (
|
|
||||||
n.getRootNode?.()?.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
|
|
||||||
(n.getRootNode() as ShadowRoot).host
|
|
||||||
)
|
|
||||||
shadowHost = (n.getRootNode() as ShadowRoot).host;
|
|
||||||
|
|
||||||
if (!n.parentNode || !inDom(n)) {
|
if (!n.parentNode || !inDom(n)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parentId = isShadowRoot(n.parentNode)
|
const parentId = isShadowRoot(n.parentNode)
|
||||||
? this.mirror.getId(shadowHost)
|
? this.mirror.getId(getShadowHost(n))
|
||||||
: this.mirror.getId(n.parentNode);
|
: this.mirror.getId(n.parentNode);
|
||||||
const nextId = getNextId(n);
|
const nextId = getNextId(n);
|
||||||
if (parentId === -1 || nextId === -1) {
|
if (parentId === -1 || nextId === -1) {
|
||||||
|
|||||||
@@ -519,16 +519,29 @@ export class StyleSheetMirror {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRootShadowHost(n: Node): Node | null {
|
/**
|
||||||
const shadowHost = (n.getRootNode() as ShadowRoot).host;
|
* Get the direct shadow host of a node in shadow dom. Returns null if it is not in a shadow dom.
|
||||||
// If n is in a nested shadow dom.
|
*/
|
||||||
let rootShadowHost = shadowHost;
|
export function getShadowHost(n: Node): Element | null {
|
||||||
|
let shadowHost: Element | null = null;
|
||||||
while (
|
if (
|
||||||
rootShadowHost?.getRootNode?.()?.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
|
n.getRootNode?.()?.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
|
||||||
(rootShadowHost.getRootNode() as ShadowRoot).host
|
(n.getRootNode() as ShadowRoot).host
|
||||||
)
|
)
|
||||||
rootShadowHost = (rootShadowHost.getRootNode() as ShadowRoot).host;
|
shadowHost = (n.getRootNode() as ShadowRoot).host;
|
||||||
|
return shadowHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the root shadow host of a node in nested shadow doms. Returns the node itself if it is not in a shadow dom.
|
||||||
|
*/
|
||||||
|
export function getRootShadowHost(n: Node): Node {
|
||||||
|
let rootShadowHost: Node = n;
|
||||||
|
|
||||||
|
let shadowHost: Element | null;
|
||||||
|
// If n is in a nested shadow dom.
|
||||||
|
while ((shadowHost = getShadowHost(rootShadowHost)))
|
||||||
|
rootShadowHost = shadowHost;
|
||||||
|
|
||||||
return rootShadowHost;
|
return rootShadowHost;
|
||||||
}
|
}
|
||||||
@@ -537,7 +550,7 @@ export function shadowHostInDom(n: Node): boolean {
|
|||||||
const doc = n.ownerDocument;
|
const doc = n.ownerDocument;
|
||||||
if (!doc) return false;
|
if (!doc) return false;
|
||||||
const shadowHost = getRootShadowHost(n);
|
const shadowHost = getRootShadowHost(n);
|
||||||
return Boolean(shadowHost && doc.contains(shadowHost));
|
return doc.contains(shadowHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function inDom(n: Node): boolean {
|
export function inDom(n: Node): boolean {
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
import { StyleSheetMirror } from '../src/utils';
|
import {
|
||||||
|
getRootShadowHost,
|
||||||
|
StyleSheetMirror,
|
||||||
|
inDom,
|
||||||
|
shadowHostInDom,
|
||||||
|
getShadowHost,
|
||||||
|
} from '../src/utils';
|
||||||
|
|
||||||
describe('Utilities for other modules', () => {
|
describe('Utilities for other modules', () => {
|
||||||
describe('StyleSheetMirror', () => {
|
describe('StyleSheetMirror', () => {
|
||||||
@@ -75,4 +81,66 @@ describe('Utilities for other modules', () => {
|
|||||||
expect(mirror.add(new CSSStyleSheet())).toBe(1);
|
expect(mirror.add(new CSSStyleSheet())).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('inDom()', () => {
|
||||||
|
it('should get correct result given nested shadow doms', () => {
|
||||||
|
const shadowHost = document.createElement('div');
|
||||||
|
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
|
||||||
|
const shadowHost2 = document.createElement('div');
|
||||||
|
const shadowRoot2 = shadowHost2.attachShadow({ mode: 'open' });
|
||||||
|
const div = document.createElement('div');
|
||||||
|
shadowRoot.appendChild(shadowHost2);
|
||||||
|
shadowRoot2.appendChild(div);
|
||||||
|
// Not in Dom yet.
|
||||||
|
expect(getShadowHost(div)).toBe(shadowHost2);
|
||||||
|
expect(getRootShadowHost(div)).toBe(shadowHost);
|
||||||
|
expect(shadowHostInDom(div)).toBeFalsy();
|
||||||
|
expect(inDom(div)).toBeFalsy();
|
||||||
|
|
||||||
|
// Added to the Dom.
|
||||||
|
document.body.appendChild(shadowHost);
|
||||||
|
expect(getShadowHost(div)).toBe(shadowHost2);
|
||||||
|
expect(getRootShadowHost(div)).toBe(shadowHost);
|
||||||
|
expect(shadowHostInDom(div)).toBeTruthy();
|
||||||
|
expect(inDom(div)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get correct result given a normal node', () => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
// Not in Dom yet.
|
||||||
|
expect(getShadowHost(div)).toBeNull();
|
||||||
|
expect(getRootShadowHost(div)).toBe(div);
|
||||||
|
expect(shadowHostInDom(div)).toBeFalsy();
|
||||||
|
expect(inDom(div)).toBeFalsy();
|
||||||
|
|
||||||
|
// Added to the Dom.
|
||||||
|
document.body.appendChild(div);
|
||||||
|
expect(getShadowHost(div)).toBeNull();
|
||||||
|
expect(getRootShadowHost(div)).toBe(div);
|
||||||
|
expect(shadowHostInDom(div)).toBeTruthy();
|
||||||
|
expect(inDom(div)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the textNode of a detached HTMLAnchorElement, getRootNode() will return the anchor element itself and its host property is a string.
|
||||||
|
* This corner case may cause an error in getRootShadowHost().
|
||||||
|
*/
|
||||||
|
it('should get correct result given the textNode of a detached HTMLAnchorElement', () => {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = 'example.com';
|
||||||
|
a.textContent = 'something';
|
||||||
|
// Not in Dom yet.
|
||||||
|
expect(getShadowHost(a.childNodes[0])).toBeNull();
|
||||||
|
expect(getRootShadowHost(a.childNodes[0])).toBe(a.childNodes[0]);
|
||||||
|
expect(shadowHostInDom(a.childNodes[0])).toBeFalsy();
|
||||||
|
expect(inDom(a.childNodes[0])).toBeFalsy();
|
||||||
|
|
||||||
|
// Added to the Dom.
|
||||||
|
document.body.appendChild(a);
|
||||||
|
expect(getShadowHost(a.childNodes[0])).toBeNull();
|
||||||
|
expect(getRootShadowHost(a.childNodes[0])).toBe(a.childNodes[0]);
|
||||||
|
expect(shadowHostInDom(a.childNodes[0])).toBeTruthy();
|
||||||
|
expect(inDom(a.childNodes[0])).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user