From 51f31021b985cccc1bd8ff125b7ff220f0b96991 Mon Sep 17 00:00:00 2001 From: hope Date: Wed, 4 Mar 2026 18:04:13 +0800 Subject: [PATCH] fix(web): use try/finally to guarantee textarea cleanup This addresses Greptile feedback about DOM leak when execCommand throws. The textarea element is now always removed in the finally block. Ref: #34092 --- ui/src/ui/chat/copy-as-markdown.ts | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/ui/src/ui/chat/copy-as-markdown.ts b/ui/src/ui/chat/copy-as-markdown.ts index d402bdeccb7..4065f5afda9 100644 --- a/ui/src/ui/chat/copy-as-markdown.ts +++ b/ui/src/ui/chat/copy-as-markdown.ts @@ -23,22 +23,27 @@ async function copyTextToClipboard(text: string): Promise { return true; } catch { // Fallback for non-secure contexts (HTTP on Windows/localhost) - try { - // Use textarea element fallback for insecure contexts - const textarea = document.createElement("textarea"); - textarea.value = text; - textarea.style.position = "fixed"; - textarea.style.left = "-9999px"; - textarea.style.top = "-9999px"; - document.body.appendChild(textarea); - textarea.focus(); - textarea.select(); - const success = document.execCommand("copy"); - document.body.removeChild(textarea); - return success; - } catch { - return false; - } + return copyViaExecCommand(text); + } +} + +function copyViaExecCommand(text: string): boolean { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.left = "-9999px"; + textarea.style.top = "-9999px"; + document.body.appendChild(textarea); + + try { + textarea.focus(); + textarea.select(); + return document.execCommand("copy"); + } catch { + return false; + } finally { + // Always clean up the textarea element to prevent DOM leak + document.body.removeChild(textarea); } }