Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "wok-client",
"productName": "WOK Client",
"version": "1.1.0-rc.17",
"version": "1.1.0-rc.18",
"private": true,
"description": "The fastest Krunker client. Ever.",
"main": "./bundle/main.mjs",
Expand Down
3 changes: 3 additions & 0 deletions src/intro-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface IntroGameWindowHandoff {
handleReadyToShow(): void;
revealBehindIntro(): void;
revealForUse(): void;
/** True while the intro owns the game window's first reveal (between beginIntro and end). */
isRevealOwned(): boolean;
}
export function createIntroGameWindowHandoff(gameWindow: IntroGameWindow, fullscreenMode: string): IntroGameWindowHandoff {
let introActive = false;
Expand All @@ -60,6 +62,7 @@ export function createIntroGameWindowHandoff(gameWindow: IntroGameWindow, fullsc
endIntro: () => {
introActive = false;
},
isRevealOwned: () => introActive,
handleReadyToShow: () => {
if (!introActive) reveal(true);
},
Expand Down
12 changes: 12 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2436,6 +2436,18 @@ app.on('ready', async () => {
logPerfMark('loadurl-called');
const gameNavigation = mainWindow.loadURL('https://krunker.io');

/*
* Show the hidden game window as soon as the navigation is under way, with or without the
* intro. A hidden renderer receives no compositor frames, so Krunker's load can stall before
* ready-to-show on affected machines and the launch deadlocks; showing the window (which the
* intro path always does at its opaque reveal) keeps the load moving, and the black splash
* background in the preload covers the unstyled page until readiness.
*/
if (!introHandoff.isRevealOwned() && !mainWindow.isDestroyed() && !mainWindow.isVisible()) {
mainWindow.showInactive();
logPerfMark('window-revealed-without-intro');
}

// Start the intro only once the game navigation is under way, so the identity animation can
// never delay the first byte of Krunker's load. Calibration launches skip it: that flow has
// already put windows in front of the user and should reach the game as directly as possible.
Expand Down
8 changes: 4 additions & 4 deletions src/menu-declutter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,8 @@ export function collectMenuDeclutterTargets(
}
}

const streamPromotions = environment
.queryAll('.stream-card.promo-card')
const streamPromotions = [...environment
.queryAll('.stream-card.promo-card')]
.filter(isKrunkerStreamPromotionCard);
for (const promotion of streamPromotions) targets.add(promotion);

Expand All @@ -368,7 +368,7 @@ export function collectMenuDeclutterTargets(
for (const promotion of streamPromotions) {
const section = promotion.closest('.featured-section');
if (!section) continue;
const cards = section.querySelectorAll('.stream-card');
const cards = [...section.querySelectorAll('.stream-card')];
if (cards.length > 0 && cards.every(isKrunkerStreamPromotionCard)) targets.add(section);
}

Expand Down Expand Up @@ -453,7 +453,7 @@ function collectStreamLayoutTargets(
): void {
const smallOverlays = new Set(environment.queryAll('.streams-overlay.small'));
for (const grid of environment.queryAll('.streams-grid')) {
const cards = grid.querySelectorAll('.stream-card');
const cards = [...grid.querySelectorAll('.stream-card')];
const promotions = cards.filter(card => hiddenTargets.has(card) || isKrunkerStreamPromotionCard(card));
if (promotions.length === 0) continue;

Expand Down
11 changes: 8 additions & 3 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,11 +608,16 @@ function applyRawMouseInputPreference(_userPrefs: UserPrefs): void {
*/
function whenDOMReady(callback: () => void) {
let ran = false;
// Declared before run() so every path that reaches run() sees initialized bindings. run()
// fires synchronously from the document.body branch below on every reload and on every
// did-finish-load fallback, where the body already exists.
let observer: MutationObserver | undefined;
let poll: number | undefined;
const run = () => {
if (ran) return;
ran = true;
observer?.disconnect();
window.clearInterval(poll);
if (poll !== undefined) window.clearInterval(poll);
document.removeEventListener('DOMContentLoaded', run);
callback();
};
Expand All @@ -624,13 +629,13 @@ function whenDOMReady(callback: () => void) {

document.addEventListener('DOMContentLoaded', run, { once: true });
// A parser that never emits the event still builds the body, so watch for the element itself.
const observer = typeof MutationObserver === 'function'
observer = typeof MutationObserver === 'function'
? new MutationObserver(() => { if (document.body) run(); })
: undefined;
if (document.documentElement) observer?.observe(document.documentElement, { childList: true, subtree: true });
// Last resort for a document that produces neither: the steps are all body-dependent, so a
// cheap poll is preferable to features that silently never start.
const poll = window.setInterval(() => { if (document.body) run(); }, 250);
poll = window.setInterval(() => { if (document.body) run(); }, 250);
}

/** Resolve an element by id as soon as the parser creates it, giving up at DOMContentLoaded. */
Expand Down