From 2b830c3b1de188af95e253c9a1ca3073b5bf8e3d Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 28 May 2025 13:15:05 +0300 Subject: [PATCH 1/2] feat: support download attribute in preview Ref https://github.com/webstudio-is/webstudio/issues/5197 We prevented all link clicks. Though seems like download is safe to proceed. --- apps/builder/app/canvas/interceptor.ts | 75 +++++++++++++++----------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/apps/builder/app/canvas/interceptor.ts b/apps/builder/app/canvas/interceptor.ts index 3421c63d8584..c26867dc4c90 100644 --- a/apps/builder/app/canvas/interceptor.ts +++ b/apps/builder/app/canvas/interceptor.ts @@ -76,45 +76,58 @@ const switchPageAndUpdateSystem = (href: string, formData?: FormData) => { export const subscribeInterceptedEvents = () => { const handleClick = (event: MouseEvent) => { - if (event.target instanceof Element && !$isPreviewMode.get()) { - // Prevent forwarding the click event on an input element when the associated label has a "for" attribute - // and prevent checkbox or radio inputs changing when clicked - if (event.target.closest("label[for]") || event.target.closest("input")) { - event.preventDefault(); + if (!(event.target instanceof Element)) { + return; + } + const isPreviewMode = $isPreviewMode.get(); + + // Prevent forwarding the click event on an input element when the associated label has a "for" attribute + // and prevent checkbox or radio inputs changing when clicked + if (event.target.closest("label[for]") || event.target.closest("input")) { + if (isPreviewMode) { + return; } + event.preventDefault(); } - if ( - event.target instanceof HTMLElement || - event.target instanceof SVGElement - ) { - const a = event.target.closest("a"); - if (a) { - event.preventDefault(); - if ($isPreviewMode.get()) { - // use attribute instead of a.href to get raw unresolved value - const href = a.getAttribute("href") ?? ""; - if (isAbsoluteUrl(href)) { - window.open(href, "_blank"); - } else { - switchPageAndUpdateSystem(href); - } + const a = event.target.closest("a"); + if (a) { + if (isPreviewMode) { + if (a.hasAttribute("download")) { + return; + } + // use attribute instead of a.href to get raw unresolved value + const href = a.getAttribute("href") ?? ""; + if (isAbsoluteUrl(href)) { + window.open(href, "_blank"); + } else { + switchPageAndUpdateSystem(href); } - } - // prevent invoking submit with buttons in canvas mode - // because form with prevented submit still invokes validation - if (event.target.closest("button") && $isPreviewMode.get() === false) { event.preventDefault(); + return; + } + event.preventDefault(); + } + // prevent invoking submit with buttons in canvas mode + // because form with prevented submit still invokes validation + if (event.target.closest("button")) { + if (isPreviewMode) { + return; } + event.preventDefault(); } }; const handlePointerDown = (event: PointerEvent) => { - if (false === event.target instanceof HTMLElement) { + if (!(event.target instanceof Element)) { return; } + const isPreviewMode = $isPreviewMode.get(); - if (event.target.closest("select") && $isPreviewMode.get() === false) { + if (event.target.closest("select")) { + if (isPreviewMode) { + return; + } event.preventDefault(); } }; @@ -140,16 +153,14 @@ export const subscribeInterceptedEvents = () => { }; const handleKeydown = (event: KeyboardEvent) => { + if (!(event.target instanceof Element)) { + return; + } if ($isPreviewMode.get()) { return; } // prevent typing in inputs only in canvas mode - if ( - event.target instanceof HTMLInputElement || - event.target instanceof HTMLTextAreaElement - ) { - event.preventDefault(); - } + event.preventDefault(); }; // Note: Event handlers behave unexpectedly when used inside a dialog component. From 22316755eebe4e3d957e6de82256ddb3f7b5369b Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 28 May 2025 13:19:34 +0300 Subject: [PATCH 2/2] Allow downloading only relative paths --- apps/builder/app/canvas/interceptor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/builder/app/canvas/interceptor.ts b/apps/builder/app/canvas/interceptor.ts index c26867dc4c90..c0139e08e5b1 100644 --- a/apps/builder/app/canvas/interceptor.ts +++ b/apps/builder/app/canvas/interceptor.ts @@ -93,13 +93,13 @@ export const subscribeInterceptedEvents = () => { const a = event.target.closest("a"); if (a) { if (isPreviewMode) { - if (a.hasAttribute("download")) { - return; - } // use attribute instead of a.href to get raw unresolved value const href = a.getAttribute("href") ?? ""; if (isAbsoluteUrl(href)) { window.open(href, "_blank"); + // relative paths can be safely downloaded + } else if (a.hasAttribute("download")) { + return; } else { switchPageAndUpdateSystem(href); }