authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-10-09 22:51:37-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-09 22:51:37-03:00
logeddc8fb817dbdb6c9ce34a30a2781c02ae8be9e0
tree89838a6157a5363d3b6e99587afbc9e986dadf24
parent033866c7aa55c53593ca31538f7d8ac99fdfe0fd
signature Signed by PGP key B5690EEEBB952194

fix(web): server browser page resizes while the panel is dragged (#17660)


1 files changed, 26 insertions(+), 13 deletions(-)

apps/web/src/browser/ServerBrowserSurface.tsx+26-13
......@@ -52,7 +52,7 @@ export interface ServerBrowserHandle {
5252 readonly canvas: () => HTMLCanvasElement | null;
5353}
5454
55const RESIZE_DEBOUNCE_MS = 150;
55const RESIZE_THROTTLE_MS = 150;
5656const ACCESS_RETRY_MS = 10_000;
5757// A recent probe answer near a new tap stands in for that tap's own answer,
5858// which on a slow link arrives after the tap ends.
......@@ -334,15 +334,11 @@ export function ServerBrowserSurface(props: {
334334 const element = canvasRef.current?.parentElement;
335335 if (!element) return;
336336 let timer: ReturnType<typeof setTimeout> | null = null;
337 const measure = () => {
338 timer = null;
339 const rect = element.getBoundingClientRect();
340 if (rect.width < 1 || rect.height < 1) return;
341 const size = { width: Math.round(rect.width), height: Math.round(rect.height) };
342 const previous = sizeRef.current;
343 if (previous?.width === size.width && previous.height === size.height) return;
344 sizeRef.current = size;
345 if (followSize) clientRef.current?.send({ type: "resize", ...size });
337 let capTimer: ReturnType<typeof setTimeout> | null = null;
338 const growCap = () => {
339 capTimer = null;
340 const size = sizeRef.current;
341 if (!size) return;
346342 const ratio = window.devicePixelRatio || 1;
347343 const width = Math.round(size.width * ratio);
348344 const height = Math.round(size.height * ratio);
......@@ -355,14 +351,31 @@ export function ServerBrowserSurface(props: {
355351 },
356352 );
357353 };
354 const measure = () => {
355 timer = null;
356 const rect = element.getBoundingClientRect();
357 if (rect.width < 1 || rect.height < 1) return;
358 const size = { width: Math.round(rect.width), height: Math.round(rect.height) };
359 const previous = sizeRef.current;
360 if (previous?.width === size.width && previous.height === size.height) return;
361 sizeRef.current = size;
362 if (followSize) clientRef.current?.send({ type: "resize", ...size });
363 // Outgrowing the cap reconnects the stream, so grow it once the size settles
364 // rather than on every throttled resize of a drag.
365 if (capTimer !== null) clearTimeout(capTimer);
366 capTimer = setTimeout(growCap, previous === null ? 0 : RESIZE_THROTTLE_MS * 2);
367 };
358368 const stopObserving = observeResize(element, () => {
359 if (timer !== null) clearTimeout(timer);
360 // The first size connects right away; later ones settle before resizing the page.
361 timer = setTimeout(measure, sizeRef.current === null ? 0 : RESIZE_DEBOUNCE_MS);
369 // A pending measure reads the latest size when it fires, so a drag resizes
370 // the page at most every RESIZE_THROTTLE_MS and still ends on the final size.
371 if (timer !== null) return;
372 // The first size connects right away.
373 timer = setTimeout(measure, sizeRef.current === null ? 0 : RESIZE_THROTTLE_MS);
362374 });
363375 return () => {
364376 stopObserving();
365377 if (timer !== null) clearTimeout(timer);
378 if (capTimer !== null) clearTimeout(capTimer);
366379 };
367380 }, [followSize]);
368381