webdriverio
Version:
Next-gen browser and mobile automation test framework for Node.js
83 lines (82 loc) • 2.98 kB
JavaScript
// src/scripts/customElement.ts
function customElementWrapper() {
const reportedHosts = /* @__PURE__ */ new WeakSet();
const sendShadowRoot = (host, hasShadowRoot) => {
let parentNode = host;
while (parentNode.parentNode) {
parentNode = parentNode.parentNode;
}
if (hasShadowRoot && host.isConnected) {
reportedHosts.add(host);
}
console.debug("[WDIO]", "newShadowRoot", host, parentNode, parentNode === document, document.documentElement);
};
const reportShadowRoot = (host, hasShadowRoot) => {
if (host.isConnected) {
const containingHosts = [];
for (let root = host.getRootNode(); root instanceof ShadowRoot; root = root.host.getRootNode()) {
containingHosts.unshift(root.host);
}
for (const containingHost of containingHosts) {
if (containingHost.shadowRoot && !reportedHosts.has(containingHost)) {
sendShadowRoot(containingHost, true);
}
}
}
sendShadowRoot(host, hasShadowRoot);
};
const reportShadowRootsIn = (root) => {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
const shadowRoot = node.shadowRoot;
if (!shadowRoot) {
continue;
}
if (!reportedHosts.has(node)) {
reportShadowRoot(node, true);
}
reportShadowRootsIn(shadowRoot);
}
};
const origFn = customElements.define.bind(customElements);
customElements.define = function(name, Constructor, options) {
const origConnectedCallback = Constructor.prototype.connectedCallback;
Constructor.prototype.connectedCallback = function() {
reportShadowRoot(this, Boolean(this.shadowRoot));
return origConnectedCallback?.call(this);
};
const origDisconnectedCallback = Constructor.prototype.disconnectedCallback;
Constructor.prototype.disconnectedCallback = function() {
console.debug("[WDIO]", "removeShadowRoot", this);
return origDisconnectedCallback?.call(this);
};
return origFn(name, Constructor, options);
};
const origAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(init) {
const shadowRoot = origAttachShadow.call(this, init);
reportShadowRoot(this, true);
return shadowRoot;
};
for (const proto of [Element.prototype, ShadowRoot.prototype]) {
const origSetHTMLUnsafe = proto.setHTMLUnsafe;
if (typeof origSetHTMLUnsafe !== "function") {
continue;
}
proto.setHTMLUnsafe = function(...args) {
const result = origSetHTMLUnsafe.apply(this, args);
if (this.isConnected) {
reportShadowRootsIn(this);
}
return result;
};
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => reportShadowRootsIn(document), { once: true });
} else {
reportShadowRootsIn(document);
}
}
export {
customElementWrapper as default
};