From 87dfa25903bf9a37c222ff25ba140f3eb24ba07f Mon Sep 17 00:00:00 2001 From: stravo1 Date: Thu, 2 Jul 2026 02:47:24 +0530 Subject: [PATCH 1/9] feat: render canvas in iframe to support DOM isolation --- frontend/components.d.ts | 1 + frontend/cypress/e2e/canvas_iframe.cy.js | 25 ++ frontend/src/components/BlockEditor.vue | 20 +- .../src/components/BorderRadiusHandler.vue | 14 +- frontend/src/components/BoxResizer.vue | 7 +- frontend/src/components/BuilderBlock.vue | 57 ++-- frontend/src/components/BuilderCanvas.vue | 261 +++++++++++++++--- .../src/components/BuilderCanvasFrame.vue | 155 +++++++++++ frontend/src/components/MarginHandler.vue | 9 +- frontend/src/components/PaddingHandler.vue | 3 +- frontend/src/components/TextBlock.vue | 8 +- frontend/src/stores/builderStore.ts | 1 + frontend/src/stores/canvasStore.ts | 15 +- frontend/src/types/Builder/BuilderCanvas.ts | 2 + frontend/src/utils/canvasFrameDom.ts | 80 ++++++ frontend/src/utils/fontManager.ts | 43 ++- frontend/src/utils/guidesTracker.ts | 37 ++- frontend/src/utils/helpers.ts | 6 +- frontend/src/utils/scriptSandbox.ts | 63 +++-- frontend/src/utils/trackTarget.ts | 49 ++-- frontend/src/utils/useBlockEventHandlers.ts | 137 ++++----- frontend/src/utils/useBuilderEvents.ts | 1 + frontend/src/utils/useCanvasDropZone.ts | 10 +- frontend/src/utils/useCanvasEvents.ts | 183 ++++++------ .../src/utils/useCanvasMarqueeSelection.ts | 69 +++-- 25 files changed, 916 insertions(+), 340 deletions(-) create mode 100644 frontend/cypress/e2e/canvas_iframe.cy.js create mode 100644 frontend/src/components/BuilderCanvasFrame.vue create mode 100644 frontend/src/utils/canvasFrameDom.ts diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 197005627..c2bea1fa2 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -39,6 +39,7 @@ declare module 'vue' { BuilderBlock: typeof import('./src/components/BuilderBlock.vue')['default'] BuilderBlockTemplates: typeof import('./src/components/BuilderBlockTemplates.vue')['default'] BuilderCanvas: typeof import('./src/components/BuilderCanvas.vue')['default'] + BuilderCanvasFrame: typeof import('./src/components/BuilderCanvasFrame.vue')['default'] BuilderCommandPalette: typeof import('./src/components/BuilderCommandPalette.vue')['default'] BuilderLeftPanel: typeof import('./src/components/BuilderLeftPanel.vue')['default'] BuilderRightPanel: typeof import('./src/components/BuilderRightPanel.vue')['default'] diff --git a/frontend/cypress/e2e/canvas_iframe.cy.js b/frontend/cypress/e2e/canvas_iframe.cy.js new file mode 100644 index 000000000..525806ecd --- /dev/null +++ b/frontend/cypress/e2e/canvas_iframe.cy.js @@ -0,0 +1,25 @@ +describe("Iframe canvas", () => { + before(() => { + cy.login(); + }); + + it("renders the page tree inside a replaceable breakpoint frame", () => { + cy.visit("builder/page/new"); + + cy.get('iframe[data-canvas-breakpoint="desktop"]') + .should("be.visible") + .then(($frame) => { + const firstFrame = $frame[0]; + const frameDocument = firstFrame.contentDocument; + + expect(frameDocument?.body.querySelector(".canvas[data-breakpoint='desktop']")).to.exist; + expect(frameDocument?.body.querySelector(".__builder_component__")).to.exist; + expect(firstFrame.ownerDocument.body.querySelector(".__builder_component__")).not.to.exist; + + cy.get('[data-testid="run-canvas-scripts"]').click(); + cy.get('iframe[data-canvas-breakpoint="desktop"]').should(($replacement) => { + expect($replacement[0]).not.to.equal(firstFrame); + }); + }); + }); +}); diff --git a/frontend/src/components/BlockEditor.vue b/frontend/src/components/BlockEditor.vue index af93e4483..d04d827fb 100644 --- a/frontend/src/components/BlockEditor.vue +++ b/frontend/src/components/BlockEditor.vue @@ -37,6 +37,7 @@ import type Block from "@/block"; import useBuilderStore from "@/stores/builderStore"; import useCanvasStore from "@/stores/canvasStore"; import blockController from "@/utils/blockController"; +import { elementFromEditorPoint } from "@/utils/canvasFrameDom"; import { addPxToNumber } from "@/utils/helpers"; import { Ref, computed, inject, nextTick, onMounted, ref, watch, watchEffect } from "vue"; import setGuides from "../utils/guidesTracker"; @@ -217,14 +218,27 @@ const handleClick = (ev: MouseEvent) => { const editorWrapper = editor.value; editorWrapper.classList.add("pointer-events-none"); - let element = document.elementFromPoint(ev.x, ev.y) as HTMLElement; + let element = elementFromEditorPoint(ev.x, ev.y) as HTMLElement; + if (!element) return; if (element.classList.contains("editor")) { element.classList.remove("pointer-events-auto"); element.classList.add("pointer-events-none"); - element = document.elementFromPoint(ev.x, ev.y) as HTMLElement; + element = elementFromEditorPoint(ev.x, ev.y) as HTMLElement; } if (element.classList.contains("__builder_component__")) { - element.dispatchEvent(new MouseEvent("click", ev)); + const EventConstructor = element.ownerDocument.defaultView?.MouseEvent || MouseEvent; + element.dispatchEvent( + new EventConstructor("click", { + bubbles: true, + cancelable: true, + clientX: ev.clientX, + clientY: ev.clientY, + ctrlKey: ev.ctrlKey, + metaKey: ev.metaKey, + shiftKey: ev.shiftKey, + altKey: ev.altKey, + }), + ); } }; diff --git a/frontend/src/components/BorderRadiusHandler.vue b/frontend/src/components/BorderRadiusHandler.vue index adab3b15c..ae95c0a4f 100644 --- a/frontend/src/components/BorderRadiusHandler.vue +++ b/frontend/src/components/BorderRadiusHandler.vue @@ -18,8 +18,8 @@ diff --git a/frontend/src/components/MarginHandler.vue b/frontend/src/components/MarginHandler.vue index c58440660..9574c52ad 100644 --- a/frontend/src/components/MarginHandler.vue +++ b/frontend/src/components/MarginHandler.vue @@ -115,6 +115,7 @@ diff --git a/frontend/src/components/PaddingHandler.vue b/frontend/src/components/PaddingHandler.vue index 5549ebcda..18ae5383f 100644 --- a/frontend/src/components/PaddingHandler.vue +++ b/frontend/src/components/PaddingHandler.vue @@ -111,12 +111,7 @@