mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 13:16:16 -08:00
* fix(electron): show the main window once its document has loaded; enforce J1 IPC and mutation counters Re-lands #1788, which merged into #1782's branch after #1782 had already reached master, so none of it is on master. The hidden main window was shown on ready-to-show only. On Linux under X11, when the startup scripts run before the window's first frame, the next frame comes about a second later: nothing is on screen and the splash's requestAnimationFrame waits, so J1's first card came ~940 ms after load instead of ~480 ms in most runner launches (18 bridge calls / 1,031-1,033 DOM mutations instead of 15 / 558). The window is now shown at ready-to-show or the main frame's did-finish-load, whichever comes first, with the splash colour as its background so showing before the first paint does not flash. The journey gate keeps the app's did-finish-load listeners away from its about:blank detour, as it already does for ready-to-show. Three dispatched runs on this branch (37192092882, 37192097790, 37192103151) read 15 calls and 558 mutations in all 18 iterations, stable: true. Both become baselines (slack 0), and the Performance journeys job checks them with check-journey-ratchet.mjs --only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(perf): record the evidence PR of the J1 runtime baselines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: 4gray <fourgray@proton.me>
149 lines
5.2 KiB
TypeScript
149 lines
5.2 KiB
TypeScript
import { join } from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { expect, test } from './electron-test-fixtures';
|
|
import {
|
|
launchUnautomatedElectronApp,
|
|
type UnautomatedElectronApp,
|
|
} from './electron-unautomated-launch';
|
|
|
|
// Linux CI runs Electron under xvfb without a window manager, so a minimize
|
|
// request never takes effect there (see window-controls.e2e.ts).
|
|
const isWindowManagerlessCi =
|
|
process.platform === 'linux' && !!process.env['CI'];
|
|
|
|
const videoFixtureUrl = pathToFileURL(
|
|
join(__dirname, '../../web-e2e/src/fixtures/playback/episode.webm')
|
|
).href;
|
|
|
|
const visibility = (app: UnautomatedElectronApp) =>
|
|
app.evaluateInPage<string>('document.visibilityState');
|
|
|
|
const setWindowState = (
|
|
app: UnautomatedElectronApp,
|
|
action: 'hide' | 'show' | 'minimize' | 'restore'
|
|
) =>
|
|
app.evaluateInMain(
|
|
`electron.BrowserWindow.getAllWindows()[0].${action}();`
|
|
);
|
|
|
|
/**
|
|
* The app creates its window with `show: false` and shows it at
|
|
* `ready-to-show` or `did-finish-load`, whichever comes first; a `hide()`
|
|
* sent earlier would be undone by that `show()`.
|
|
*/
|
|
async function waitUntilShown(app: UnautomatedElectronApp): Promise<void> {
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
app.evaluateInMain<boolean>(
|
|
'return electron.BrowserWindow.getAllWindows()[0]?.isVisible() ?? false;'
|
|
),
|
|
{ timeout: 30_000 }
|
|
)
|
|
.toBe(true);
|
|
await expect.poll(() => visibility(app)).toBe('visible');
|
|
}
|
|
|
|
/**
|
|
* The renderer must see a hidden or minimized window as hidden: idle timers
|
|
* pause on `visibilitychange`, and the playback keep-awake gate releases the
|
|
* display for a minimized window. A main window created with
|
|
* `backgroundThrottling: false` keeps reporting `visible` in both cases.
|
|
*
|
|
* Launched without Playwright: its focus emulation pins every page it
|
|
* attaches to as visible (see `electron-unautomated-launch.ts`).
|
|
*/
|
|
test.describe('Main window visibility', () => {
|
|
test('@electron reports a hidden window as hidden and a shown one as visible', async ({
|
|
dataDir,
|
|
}) => {
|
|
const app = await launchUnautomatedElectronApp(dataDir);
|
|
try {
|
|
await waitUntilShown(app);
|
|
|
|
await setWindowState(app, 'hide');
|
|
await expect
|
|
.poll(() => visibility(app), { timeout: 10_000 })
|
|
.toBe('hidden');
|
|
|
|
await setWindowState(app, 'show');
|
|
await expect
|
|
.poll(() => visibility(app), { timeout: 10_000 })
|
|
.toBe('visible');
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
|
|
test('@electron reports a minimized window as hidden until it is restored', async ({
|
|
dataDir,
|
|
}) => {
|
|
test.skip(
|
|
isWindowManagerlessCi,
|
|
'xvfb on Linux CI has no window manager'
|
|
);
|
|
const app = await launchUnautomatedElectronApp(dataDir);
|
|
try {
|
|
await waitUntilShown(app);
|
|
|
|
await setWindowState(app, 'minimize');
|
|
await expect
|
|
.poll(() => visibility(app), { timeout: 10_000 })
|
|
.toBe('hidden');
|
|
|
|
await setWindowState(app, 'restore');
|
|
await expect
|
|
.poll(() => visibility(app), { timeout: 10_000 })
|
|
.toBe('visible');
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
|
|
test('@electron releases the playback display lock while the window is hidden', async ({
|
|
dataDir,
|
|
}) => {
|
|
const app = await launchUnautomatedElectronApp(dataDir);
|
|
try {
|
|
await waitUntilShown(app);
|
|
// Record the display-sleep blockers the keep-awake service holds.
|
|
await app.evaluateInMain(`
|
|
const blocker = electron.powerSaveBlocker;
|
|
const active = (globalThis.__e2eDisplayBlockers = new Set());
|
|
const start = blocker.start.bind(blocker);
|
|
const stop = blocker.stop.bind(blocker);
|
|
blocker.start = (type) => {
|
|
const id = start(type);
|
|
if (type === 'prevent-display-sleep') active.add(id);
|
|
return id;
|
|
};
|
|
blocker.stop = (id) => {
|
|
active.delete(id);
|
|
return stop(id);
|
|
};
|
|
`);
|
|
const heldBlockers = () =>
|
|
app.evaluateInMain<number>(
|
|
'return globalThis.__e2eDisplayBlockers.size;'
|
|
);
|
|
await app.evaluateInPage(`(() => {
|
|
const video = document.createElement('video');
|
|
video.muted = true;
|
|
video.loop = true;
|
|
video.src = ${JSON.stringify(videoFixtureUrl)};
|
|
document.body.append(video);
|
|
return video.play().then(() => true);
|
|
})()`);
|
|
await expect.poll(heldBlockers, { timeout: 10_000 }).toBe(1);
|
|
|
|
await setWindowState(app, 'hide');
|
|
await expect.poll(heldBlockers, { timeout: 10_000 }).toBe(0);
|
|
|
|
await setWindowState(app, 'show');
|
|
await expect.poll(heldBlockers, { timeout: 10_000 }).toBe(1);
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
});
|