mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: Address multiple memory leaks in CodeBlock component (#4244)
* fix: address multiple memory leaks in CodeBlock component (CodeBlock_247, CodeBlock_459, CodeBlock_694) * fix: Address review feedback for CodeBlock memory leak fixes - Consolidate isMountedRef management into syntax highlighting useEffect - Unify timeout cleanup patterns for consistent maintainability - Maintain all existing memory leak protections - Improve code organization and readability Addresses review comments from daniel-lxs in PR #4244 --------- Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
parent
976dcfdcef
commit
64901c867c
1 changed files with 70 additions and 18 deletions
|
|
@ -232,6 +232,10 @@ const CodeBlock = memo(
|
|||
const copyButtonWrapperRef = useRef<HTMLDivElement>(null)
|
||||
const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard()
|
||||
const { t } = useAppTranslation()
|
||||
const isMountedRef = useRef(true)
|
||||
const buttonPositionTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const collapseTimeout1Ref = useRef<NodeJS.Timeout | null>(null)
|
||||
const collapseTimeout2Ref = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
// Update current language when prop changes, but only if user hasn't
|
||||
// made a selection.
|
||||
|
|
@ -243,17 +247,23 @@ const CodeBlock = memo(
|
|||
}
|
||||
}, [language, currentLanguage])
|
||||
|
||||
// Syntax highlighting with cached Shiki instance.
|
||||
// Syntax highlighting with cached Shiki instance and mounted state management
|
||||
useEffect(() => {
|
||||
// Set mounted state at the beginning of this effect
|
||||
isMountedRef.current = true
|
||||
|
||||
const fallback = `<pre style="padding: 0; margin: 0;"><code class="hljs language-${currentLanguage || "txt"}">${source || ""}</code></pre>`
|
||||
|
||||
const highlight = async () => {
|
||||
// Show plain text if language needs to be loaded.
|
||||
if (currentLanguage && !isLanguageLoaded(currentLanguage)) {
|
||||
setHighlightedCode(fallback)
|
||||
if (isMountedRef.current) {
|
||||
setHighlightedCode(fallback)
|
||||
}
|
||||
}
|
||||
|
||||
const highlighter = await getHighlighter(currentLanguage)
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
const html = await highlighter.codeToHtml(source || "", {
|
||||
lang: currentLanguage || "txt",
|
||||
|
|
@ -277,14 +287,36 @@ const CodeBlock = memo(
|
|||
},
|
||||
] as ShikiTransformer[],
|
||||
})
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
setHighlightedCode(html)
|
||||
if (isMountedRef.current) {
|
||||
setHighlightedCode(html)
|
||||
}
|
||||
}
|
||||
|
||||
highlight().catch((e) => {
|
||||
console.error("[CodeBlock] Syntax highlighting error:", e, "\nStack trace:", e.stack)
|
||||
setHighlightedCode(fallback)
|
||||
if (isMountedRef.current) {
|
||||
setHighlightedCode(fallback)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup function - manage mounted state and clear all timeouts
|
||||
return () => {
|
||||
isMountedRef.current = false
|
||||
if (buttonPositionTimeoutRef.current) {
|
||||
clearTimeout(buttonPositionTimeoutRef.current)
|
||||
buttonPositionTimeoutRef.current = null
|
||||
}
|
||||
if (collapseTimeout1Ref.current) {
|
||||
clearTimeout(collapseTimeout1Ref.current)
|
||||
collapseTimeout1Ref.current = null
|
||||
}
|
||||
if (collapseTimeout2Ref.current) {
|
||||
clearTimeout(collapseTimeout2Ref.current)
|
||||
collapseTimeout2Ref.current = null
|
||||
}
|
||||
}
|
||||
}, [source, currentLanguage, collapsedHeight])
|
||||
|
||||
// Check if content height exceeds collapsed height whenever content changes
|
||||
|
|
@ -455,8 +487,15 @@ const CodeBlock = memo(
|
|||
// Update button position and scroll when highlightedCode changes
|
||||
useEffect(() => {
|
||||
if (highlightedCode) {
|
||||
// Clear any existing timeout before setting a new one
|
||||
if (buttonPositionTimeoutRef.current) {
|
||||
clearTimeout(buttonPositionTimeoutRef.current)
|
||||
}
|
||||
// Update button position
|
||||
setTimeout(updateCodeBlockButtonPosition, 0)
|
||||
buttonPositionTimeoutRef.current = setTimeout(() => {
|
||||
updateCodeBlockButtonPosition()
|
||||
buttonPositionTimeoutRef.current = null // Optional: Clear ref after execution
|
||||
}, 0)
|
||||
|
||||
// Scroll to bottom if needed (immediately after Shiki updates)
|
||||
if (shouldScrollAfterHighlightRef.current) {
|
||||
|
|
@ -479,6 +518,12 @@ const CodeBlock = memo(
|
|||
shouldScrollAfterHighlightRef.current = false
|
||||
}
|
||||
}
|
||||
// Cleanup function for this effect
|
||||
return () => {
|
||||
if (buttonPositionTimeoutRef.current) {
|
||||
clearTimeout(buttonPositionTimeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [highlightedCode, updateCodeBlockButtonPosition])
|
||||
|
||||
// Advanced inertial scroll chaining
|
||||
|
|
@ -682,23 +727,30 @@ const CodeBlock = memo(
|
|||
{showCollapseButton && (
|
||||
<CodeBlockButton
|
||||
onClick={() => {
|
||||
// Get the current code block element and scrollable container
|
||||
const codeBlock = codeBlockRef.current
|
||||
const scrollContainer = document.querySelector('[data-virtuoso-scroller="true"]')
|
||||
if (!codeBlock || !scrollContainer) return
|
||||
|
||||
// Get the current code block element
|
||||
const codeBlock = codeBlockRef.current // Capture ref early
|
||||
// Toggle window shade state
|
||||
setWindowShade(!windowShade)
|
||||
|
||||
// After UI updates, ensure code block is visible and update button position
|
||||
setTimeout(
|
||||
() => {
|
||||
codeBlock.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
||||
// Clear any previous timeouts
|
||||
if (collapseTimeout1Ref.current) clearTimeout(collapseTimeout1Ref.current)
|
||||
if (collapseTimeout2Ref.current) clearTimeout(collapseTimeout2Ref.current)
|
||||
|
||||
// Wait for scroll to complete before updating button position
|
||||
setTimeout(() => {
|
||||
updateCodeBlockButtonPosition()
|
||||
}, 50)
|
||||
// After UI updates, ensure code block is visible and update button position
|
||||
collapseTimeout1Ref.current = setTimeout(
|
||||
() => {
|
||||
if (codeBlock) {
|
||||
// Check if codeBlock element still exists
|
||||
codeBlock.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
||||
|
||||
// Wait for scroll to complete before updating button position
|
||||
collapseTimeout2Ref.current = setTimeout(() => {
|
||||
// updateCodeBlockButtonPosition itself should also check for refs if needed
|
||||
updateCodeBlockButtonPosition()
|
||||
collapseTimeout2Ref.current = null
|
||||
}, 50)
|
||||
}
|
||||
collapseTimeout1Ref.current = null
|
||||
},
|
||||
WINDOW_SHADE_SETTINGS.transitionDelayS * 1000 + 50,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue