Skip to content

deno desktop (Linux, WebKitGTK/Wayland): windows created after a close() sometimes ignore setSize() #36276

Description

@jeffreyschultz

Deno: 2.9.3 (stable, x86_64-linux) · Backend: webview (WebKitGTK) · Session: Wayland, with the usual EGL/ZINK: failed to choose pdev spam on stderr

We hit something odd last week. In one session, every window we created after closing a BrowserWindow just ignored setSize() and stayed at the default 800×600:

PROBE a1: [300,200] a2: [310,210]      (before the close, works fine)
PROBE b1 (delayed set): [800,600]      (after the close, stuck)
PROBE c1 (immediate set): [800,600]    (still stuck)

It was consistent too — 3 out of 3 runs across two unrelated scripts, plus our conformance battery's proof window hit the same thing.

Then we came back the next day to write it up properly, and... it stopped. Same machine, same Deno build, same scripts. We ran it 8+ times, then a 45-cycle close/create stress test for good measure — zero occurrences. Every window sizes correctly now. We poked at every variable we could think of (anchor window vs none, shown vs unshown, delayed vs immediate setSize, one close vs several) and can't make it happen again.

So: no reliable repro, sorry. But 3/3 across two different scripts in that one session suggests something — WM state, EGL state, runtime state — can flip this into a deterministic mode, so we're reporting it in case the signature rings a bell for whoever owns window geometry. If there are flags or logs you'd like us to run with, happy to. Our own test battery now asserts exact sizes on read-back, so if it ever happens again we'll catch it with state intact.

Probe script (the one that showed it — currently passes, of course):

// deno desktop setSize-after-close probe (Deno 2.9.3, webview backend)
// Run: deno desktop race3.ts   →   then run the produced app binary.
const bw = (Deno as any).BrowserWindow;

// First construction adopts the runtime's initial app window
// (the webview backend auto-navigates it to the local HTTP server).
const anchor = new bw({ title: "anchor", width: 200, height: 150 });
anchor.show();
await new Promise(r => setTimeout(r, 400));

const a1 = new bw({ title: "a1", width: 300, height: 200 });
a1.setSize(300, 200);
const a2 = new bw({ title: "a2", width: 310, height: 210 });
a2.setSize(310, 210);
await new Promise(r => setTimeout(r, 300));
console.log("PROBE a1:", JSON.stringify(a1.getSize()),
            "a2:", JSON.stringify(a2.getSize()));

a1.close();
a2.close();
await new Promise(r => setTimeout(r, 200));

// Post-close: delayed setSize
const b1 = new bw({ title: "b1", width: 320, height: 220 });
await new Promise(r => setTimeout(r, 300));
b1.setSize(320, 220);
await new Promise(r => setTimeout(r, 300));
console.log("PROBE b1 (delayed set):", JSON.stringify(b1.getSize()));
b1.close();
await new Promise(r => setTimeout(r, 200));

// Post-close: immediate setSize
const c1 = new bw({ title: "c1", width: 330, height: 230 });
c1.setSize(330, 230);
await new Promise(r => setTimeout(r, 300));
console.log("PROBE c1 (immediate set):", JSON.stringify(c1.getSize()));
c1.close();
await new Promise(r => setTimeout(r, 200));

console.log("PROBE DONE");
anchor.close();
Deno.exit(0);

Stress variant (the hammer — exits 1 and prints every stuck cycle):

// deno desktop setSize-after-close stress (Deno 2.9.3, webview backend)
// Hammers close/create cycles and logs any window that ignores setSize.
// Run: deno desktop stress.ts   →   then run the produced app binary.
const bw = (Deno as any).BrowserWindow;

const CYCLES = 50;
const WANT: [number, number] = [512, 384];

let stuck = 0;
const failures: string[] = [];

const anchor = new bw({ title: "anchor", width: 200, height: 150 });
anchor.show();
await new Promise(r => setTimeout(r, 400));

for (let i = 0; i < CYCLES; i++) {
  const w = new bw({ title: `cycle-${i}`, width: 800, height: 600 });
  await new Promise(r => setTimeout(r, 150));
  w.setSize(WANT[0], WANT[1]);
  await new Promise(r => setTimeout(r, 150));
  const [width, height] = w.getSize();
  if (width !== WANT[0] || height !== WANT[1]) {
    stuck++;
    failures.push(`cycle ${i}: got [${width},${height}]`);
    console.log(`STUCK cycle ${i}: got [${width},${height}]`);
  }
  w.close();
  await new Promise(r => setTimeout(r, 100));
}

console.log(`STRESS SUMMARY stuck=${stuck}/${CYCLES}`);
if (failures.length) console.log("cycles:", failures.join(", "));
anchor.close();
Deno.exit(stuck ? 1 : 0);

Bump CYCLES to 500 for an overnight soak if that's useful. Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions