mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 04:07:44 +02:00
[eric] canvas: webview attaches go one per frame, so opening a loaded dashboard stops locking the app for 4.7s
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
||||
import { requestWebviewAttachSlot } from './webviewAttachQueue';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { subscribeLiveDrag } from '../hooks/interaction/liveDragChannel';
|
||||
import Box from '@mui/material/Box';
|
||||
@@ -416,6 +417,15 @@ const BrowserCard: React.FC<Props> = ({
|
||||
}, [activeUrl, activeTabId]);
|
||||
|
||||
const webviewMap = useRef<Map<string, WebviewElement>>(new Map());
|
||||
|
||||
// Electron attaches a guest with a SYNCHRONOUS renderer IPC, so a dashboard that mounts N cards
|
||||
// puts N blocking round-trips in one frame (measured: 4755ms over 40 long tasks at 18 cards).
|
||||
// Waiting for a slot spreads them one per frame; nothing unmounts, so sessions are untouched.
|
||||
const [attachSlotReady, setAttachSlotReady] = useState(false);
|
||||
useEffect(() => {
|
||||
if (attachSlotReady) return undefined;
|
||||
return requestWebviewAttachSlot(() => setAttachSlotReady(true));
|
||||
}, [attachSlotReady]);
|
||||
const initializedTabs = useRef(new Set<string>());
|
||||
const tabBarRef = useRef<HTMLDivElement>(null);
|
||||
// Some pages (Zillow's map) rewrite their own URL many times a second, across did-navigate-in-page AND did-stop-loading; throttle the persisted URL mirror so each tick can't fan out to a full dashboard save + webview suspend re-eval. Leading edge keeps a real navigation's URL immediate.
|
||||
@@ -1636,7 +1646,7 @@ const BrowserCard: React.FC<Props> = ({
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{tabs.map((tab) => (
|
||||
{(attachSlotReady ? tabs : []).map((tab) => (
|
||||
<webview
|
||||
key={tab.id}
|
||||
ref={(el: any) => {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Serialises <webview> attachment to one per frame.
|
||||
*
|
||||
* Electron attaches a guest view with a SYNCHRONOUS renderer IPC (GUEST_VIEW_MANAGER_CALL), so N
|
||||
* cards mounting together put N blocking round-trips in one frame. Measured on a real dashboard:
|
||||
* opening one with 18 cards / 8 webviews blocked the main thread for 4755ms across 40 long tasks,
|
||||
* while an idle canvas blocked for 0ms (ENG-193). Nothing here makes the attach cheaper; it just
|
||||
* stops them landing on the same frame, so the UI keeps painting between them.
|
||||
*/
|
||||
|
||||
type Slot = () => void;
|
||||
|
||||
let pending: Slot[] = [];
|
||||
let pumping = false;
|
||||
|
||||
function pump(): void {
|
||||
const next = pending.shift();
|
||||
if (!next) {
|
||||
pumping = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
next();
|
||||
} catch {
|
||||
/* a card that blew up on attach must not stall every card behind it */
|
||||
}
|
||||
requestAnimationFrame(() => pump());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for the next attach slot. `onReady` fires on this frame if the queue is empty, otherwise one
|
||||
* frame per card ahead of it. Returns a cancel function for unmount before the slot arrives.
|
||||
*/
|
||||
export function requestWebviewAttachSlot(onReady: Slot): () => void {
|
||||
pending.push(onReady);
|
||||
if (!pumping) {
|
||||
pumping = true;
|
||||
// Start on the next frame so a burst of cards mounting in one commit all queue up first.
|
||||
requestAnimationFrame(() => pump());
|
||||
}
|
||||
return () => {
|
||||
pending = pending.filter((s) => s !== onReady);
|
||||
};
|
||||
}
|
||||
|
||||
/** Cards waiting behind the queue right now; exposed so a test can prove the burst is serialised. */
|
||||
export function pendingAttachCount(): number {
|
||||
return pending.length;
|
||||
}
|
||||
Reference in New Issue
Block a user