From 20110168f261a49a2ee00ad72bdece24b8eb629c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sat, 4 Apr 2026 09:52:45 +0300 Subject: [PATCH] refactor: extract #processFont and #scheduleProcessing from touch and #processQueue --- .../appliedFontsStore.svelte.ts | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts index 8b86f43..33becd1 100644 --- a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts +++ b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts @@ -131,26 +131,31 @@ export class AppliedFontsManager { hasNewItems = true; } - // Schedule queue processing if we have new items and no existing timer if (hasNewItems && !this.#timeoutId) { - // Prefer requestIdleCallback for better performance (waits for browser idle) - if (typeof requestIdleCallback !== 'undefined') { - this.#timeoutId = requestIdleCallback( - () => this.#processQueue(), - { timeout: 150 }, - ) as unknown as ReturnType; - this.#pendingType = 'idle'; - } else { - // Fallback to setTimeout with ~60fps timing - this.#timeoutId = setTimeout(() => this.#processQueue(), 16); - this.#pendingType = 'timeout'; - } + this.#scheduleProcessing(); } } catch (error) { console.error(error); } } + /** + * Schedules `#processQueue()` via `requestIdleCallback` (150ms timeout) when available, + * falling back to `setTimeout(16ms)` for ~60fps timing. + */ + #scheduleProcessing(): void { + if (typeof requestIdleCallback !== 'undefined') { + this.#timeoutId = requestIdleCallback( + () => this.#processQueue(), + { timeout: 150 }, + ) as unknown as ReturnType; + this.#pendingType = 'idle'; + } else { + this.#timeoutId = setTimeout(() => this.#processQueue(), 16); + this.#pendingType = 'timeout'; + } + } + /** Returns true if data-saver mode is enabled (defers non-critical weights). */ #shouldDeferNonCritical(): boolean { return (navigator as any).connection?.saveData === true; @@ -229,19 +234,7 @@ export class AppliedFontsManager { continue; } - try { - // Parse buffer into FontFace and register with document - const font = await loadFont(config, buffer); - this.#loadedFonts.set(key, font); - this.#urlByKey.set(key, config.url); - this.statuses.set(key, 'loaded'); - } catch (e) { - if (e instanceof FontParseError) { - console.error(`Font parse failed: ${config.name}`, e); - this.statuses.set(key, 'error'); - this.#queue.incrementRetry(key); - } - } + await this.#processFont(key, config, buffer); // Yield to main thread if needed (prevents UI blocking) // Chromium: use isInputPending() for optimal responsiveness @@ -257,6 +250,25 @@ export class AppliedFontsManager { } } + /** + * Parses a fetched buffer into a {@link FontFace}, registers it with `document.fonts`, + * and updates reactive status. On failure, sets status to `'error'` and increments the retry count. + */ + async #processFont(key: string, config: FontLoadRequestConfig, buffer: ArrayBuffer): Promise { + try { + const font = await loadFont(config, buffer); + this.#loadedFonts.set(key, font); + this.#urlByKey.set(key, config.url); + this.statuses.set(key, 'loaded'); + } catch (e) { + if (e instanceof FontParseError) { + console.error(`Font parse failed: ${config.name}`, e); + this.statuses.set(key, 'error'); + this.#queue.incrementRetry(key); + } + } + } + /** Removes fonts unused within TTL (LRU-style cleanup). Runs every PURGE_INTERVAL. Pinned fonts are never evicted. */ #purgeUnused() { const now = Date.now();