[FIX] utils: Correct validation of mount target in shadow DOM/iframe

Previously, `validateTarget` only checked if the target element or its
host (if it was a ShadowRoot) was directly contained in the document body.
This failed in cases where the target element was nested inside a shadow
DOM, which itself was attached to the document.

This commit introduces a new helper `isAttachedToDocument` that
traverses through parent nodes and shadow roots to ensure that the
target is ultimately attached to the given document.
Additionally, it now throws a clear error if `document.defaultView` is
missing, indicating that the target document is detached or invalid.

This ensures proper validation of mount targets, including complex
scenarios with shadow roots and iframes.
This commit is contained in:
Achraf (abz)
2025-03-25 15:25:23 +01:00
committed by Géry Debongnie
parent fd3c194525
commit 9d378b0e7b
3 changed files with 171 additions and 2 deletions
+32 -2
View File
@@ -35,13 +35,43 @@ export function inOwnerDocument(el?: HTMLElement) {
return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host);
}
/**
* Determine whether the given element is contained in a specific root documnet:
* either directly or with a shadow root in between or in an iframe.
*/
function isAttachedToDocument(
element: HTMLElement | ShadowRoot,
documentElement: Document
): boolean {
let current: Node = element;
const shadowRoot = documentElement.defaultView!.ShadowRoot;
while (current) {
if (current === documentElement) {
return true;
}
if (current.parentNode) {
current = current.parentNode;
} else if (current instanceof shadowRoot && current.host) {
current = current.host;
} else {
return false;
}
}
return false;
}
export function validateTarget(target: HTMLElement | ShadowRoot) {
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes
const document = target && target.ownerDocument;
if (document) {
const HTMLElement = document.defaultView!.HTMLElement;
if (!document.defaultView) {
throw new OwlError(
"Cannot mount a component: the target document is not attached to a window (defaultView is missing)"
);
}
const HTMLElement = document.defaultView.HTMLElement;
if (target instanceof HTMLElement || target instanceof ShadowRoot) {
if (!document.body.contains(target instanceof HTMLElement ? target : target.host)) {
if (!isAttachedToDocument(target, document)) {
throw new OwlError("Cannot mount a component on a detached dom node");
}
return;