From a93f015795299d1f815567b1b39022e165788a40 Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Wed, 17 Aug 2022 08:01:23 +0200 Subject: [PATCH] [FIX] app: allow mounting owl apps in iframe Previously, attempting to mount an app in an iframe would crash, saying that the target is not a valid DOM element, this is because instanceof checks do not work cross-frame as global objects do not have the same identity in frames as with the main window. This commit fixes that by making sure the target is an instance of HTMLElement of the corresponding window, and checks that the corresponding document body contains it. --- src/runtime/utils.ts | 16 +++++++++++----- tests/app/__snapshots__/app.test.ts.snap | 13 +++++++++++++ tests/app/app.test.ts | 18 ++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts index 9c5c067d..2bc5c8c0 100644 --- a/src/runtime/utils.ts +++ b/src/runtime/utils.ts @@ -28,12 +28,18 @@ export function batched(callback: Callback): Callback { } export function validateTarget(target: HTMLElement) { - if (!(target instanceof HTMLElement)) { - throw new OwlError("Cannot mount component: the target is not a valid DOM element"); - } - if (!document.body.contains(target)) { - throw new OwlError("Cannot mount a component on a detached dom node"); + // 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 (target instanceof HTMLElement) { + if (!document.body.contains(target)) { + throw new OwlError("Cannot mount a component on a detached dom node"); + } + return; + } } + throw new OwlError("Cannot mount component: the target is not a valid DOM element"); } export class EventBus extends EventTarget { diff --git a/tests/app/__snapshots__/app.test.ts.snap b/tests/app/__snapshots__/app.test.ts.snap index 010c4172..bbb6fabe 100644 --- a/tests/app/__snapshots__/app.test.ts.snap +++ b/tests/app/__snapshots__/app.test.ts.snap @@ -29,6 +29,19 @@ exports[`app can configure an app with props 1`] = ` }" `; +exports[`app can mount app in an iframe 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + exports[`app destroy remove the widget from the DOM 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/app/app.test.ts b/tests/app/app.test.ts index 0111d2c5..37699a70 100644 --- a/tests/app/app.test.ts +++ b/tests/app/app.test.ts @@ -76,4 +76,22 @@ describe("app", () => { "Component 'Root' does not have a static props description" ); }); + + test("can mount app in an iframe", async () => { + class SomeComponent extends Component { + static template = xml`
`; + } + + const iframe = document.createElement("iframe"); + fixture.appendChild(iframe); + const app = new App(SomeComponent); + const iframeDoc = iframe.contentDocument!; + const comp = await app.mount(iframeDoc.body); + const div = iframeDoc.querySelector(".my-div"); + expect(div).not.toBe(null); + expect(iframeDoc.contains(div)).toBe(true); + app.destroy(); + expect(iframeDoc.contains(div)).toBe(false); + expect(status(comp)).toBe("destroyed"); + }); });