Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48f0e2f64c |
@@ -21,6 +21,7 @@ export default function TerminalView({ sessionId, active }: Props) {
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const termRef = useRef<Terminal | null>(null);
|
const termRef = useRef<Terminal | null>(null);
|
||||||
const fitRef = useRef<FitAddon | null>(null);
|
const fitRef = useRef<FitAddon | null>(null);
|
||||||
|
const webglRef = useRef<WebglAddon | null>(null);
|
||||||
const { sendInput, resize, onOutput, onExit } = useTerminal();
|
const { sendInput, resize, onOutput, onExit } = useTerminal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -68,13 +69,8 @@ export default function TerminalView({ sessionId, active }: Props) {
|
|||||||
|
|
||||||
term.open(containerRef.current);
|
term.open(containerRef.current);
|
||||||
|
|
||||||
// Try WebGL renderer, fall back silently
|
// WebGL addon is loaded/disposed dynamically in the active effect
|
||||||
try {
|
// to avoid exhausting the browser's limited WebGL context pool.
|
||||||
const webglAddon = new WebglAddon();
|
|
||||||
term.loadAddon(webglAddon);
|
|
||||||
} catch {
|
|
||||||
// WebGL not available, canvas renderer is fine
|
|
||||||
}
|
|
||||||
|
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
termRef.current = term;
|
termRef.current = term;
|
||||||
@@ -145,12 +141,16 @@ export default function TerminalView({ sessionId, active }: Props) {
|
|||||||
return unlisten;
|
return unlisten;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle resize (throttled via requestAnimationFrame to avoid excessive calls)
|
// Handle resize (throttled via requestAnimationFrame to avoid excessive calls).
|
||||||
|
// Skip resize work for hidden terminals — containerRef will have 0 dimensions.
|
||||||
let resizeRafId: number | null = null;
|
let resizeRafId: number | null = null;
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
if (resizeRafId !== null) return;
|
if (resizeRafId !== null) return;
|
||||||
|
const el = containerRef.current;
|
||||||
|
if (!el || el.offsetWidth === 0 || el.offsetHeight === 0) return;
|
||||||
resizeRafId = requestAnimationFrame(() => {
|
resizeRafId = requestAnimationFrame(() => {
|
||||||
resizeRafId = null;
|
resizeRafId = null;
|
||||||
|
if (!containerRef.current || containerRef.current.offsetWidth === 0) return;
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
resize(sessionId, term.cols, term.rows);
|
resize(sessionId, term.cols, term.rows);
|
||||||
});
|
});
|
||||||
@@ -165,15 +165,42 @@ export default function TerminalView({ sessionId, active }: Props) {
|
|||||||
exitPromise.then((fn) => fn?.());
|
exitPromise.then((fn) => fn?.());
|
||||||
if (resizeRafId !== null) cancelAnimationFrame(resizeRafId);
|
if (resizeRafId !== null) cancelAnimationFrame(resizeRafId);
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
|
try { webglRef.current?.dispose(); } catch { /* may already be disposed */ }
|
||||||
|
webglRef.current = null;
|
||||||
term.dispose();
|
term.dispose();
|
||||||
};
|
};
|
||||||
}, [sessionId]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [sessionId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
// Re-fit when tab becomes active
|
// Manage WebGL lifecycle and re-fit when tab becomes active.
|
||||||
|
// Only the active terminal holds a WebGL context to avoid exhausting
|
||||||
|
// the browser's limited pool (~8-16 contexts).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (active && fitRef.current && termRef.current) {
|
const term = termRef.current;
|
||||||
fitRef.current.fit();
|
if (!term) return;
|
||||||
termRef.current.focus();
|
|
||||||
|
if (active) {
|
||||||
|
// Attach WebGL renderer
|
||||||
|
if (!webglRef.current) {
|
||||||
|
try {
|
||||||
|
const addon = new WebglAddon();
|
||||||
|
addon.onContextLoss(() => {
|
||||||
|
try { addon.dispose(); } catch { /* ignore */ }
|
||||||
|
webglRef.current = null;
|
||||||
|
});
|
||||||
|
term.loadAddon(addon);
|
||||||
|
webglRef.current = addon;
|
||||||
|
} catch {
|
||||||
|
// WebGL not available, canvas renderer is fine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fitRef.current?.fit();
|
||||||
|
term.focus();
|
||||||
|
} else {
|
||||||
|
// Release WebGL context for inactive terminals
|
||||||
|
if (webglRef.current) {
|
||||||
|
try { webglRef.current.dispose(); } catch { /* ignore */ }
|
||||||
|
webglRef.current = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [active]);
|
}, [active]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user