diff --git a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts index 1758d36..903122e 100644 --- a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts +++ b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts @@ -1,5 +1,8 @@ import { SvelteMap } from 'svelte/reactivity'; -import { getEffectiveConcurrency } from './utils/getEffectiveConcurrency/getEffectiveConcurrency'; +import { + getEffectiveConcurrency, + yieldToMainThread, +} from './utils'; /** Loading state of a font. Failed loads may be retried up to MAX_RETRIES. */ export type FontStatus = 'loading' | 'loaded' | 'error'; @@ -143,37 +146,7 @@ export class AppliedFontsManager { } } - /** Yields to main thread during CPU-intensive parsing. Uses scheduler.yield() (Chrome/Edge) or MessageChannel fallback. */ - async #yieldToMain(): Promise { - // @ts-expect-error - scheduler not in TypeScript lib yet - if (typeof scheduler !== 'undefined' && 'yield' in scheduler) { - // @ts-expect-error - scheduler.yield not in TypeScript lib yet - await scheduler.yield(); - } else { - await new Promise(resolve => { - const ch = new MessageChannel(); - ch.port1.onmessage = () => resolve(); - ch.port2.postMessage(null); - }); - } - } - /** Returns optimal concurrent fetches based on Network Information API: 1 for 2G, 2 for 3G, 4 for 4G/default. */ - #getEffectiveConcurrency(): number { - const nav = navigator as any; - const conn = nav.connection; - if (!conn) return 4; - - switch (conn.effectiveType) { - case 'slow-2g': - case '2g': - return 1; - case '3g': - return 2; - default: - return 4; - } - } /** Returns true if data-saver mode is enabled (defers non-critical weights). */ #shouldDeferNonCritical(): boolean { @@ -261,7 +234,7 @@ export class AppliedFontsManager { : (performance.now() - lastYield > YIELD_INTERVAL); if (shouldYield) { - await this.#yieldToMain(); + await yieldToMainThread(); lastYield = performance.now(); } } diff --git a/src/entities/Font/model/store/appliedFontsStore/utils/index.ts b/src/entities/Font/model/store/appliedFontsStore/utils/index.ts new file mode 100644 index 0000000..c16a74b --- /dev/null +++ b/src/entities/Font/model/store/appliedFontsStore/utils/index.ts @@ -0,0 +1,2 @@ +export { getEffectiveConcurrency } from './getEffectiveConcurrency/getEffectiveConcurrency'; +export { yieldToMainThread } from './yieldToMainThread/yieldToMainThread'; diff --git a/src/entities/Font/model/store/appliedFontsStore/utils/yieldToMainThread/yieldToMainThread.test.ts b/src/entities/Font/model/store/appliedFontsStore/utils/yieldToMainThread/yieldToMainThread.test.ts new file mode 100644 index 0000000..16f9c51 --- /dev/null +++ b/src/entities/Font/model/store/appliedFontsStore/utils/yieldToMainThread/yieldToMainThread.test.ts @@ -0,0 +1,17 @@ +import { yieldToMainThread } from './yieldToMainThread'; + +describe('yieldToMainThread', () => { + it('uses scheduler.yield when available', async () => { + const mockYield = vi.fn().mockResolvedValue(undefined); + vi.stubGlobal('scheduler', { yield: mockYield }); + + await yieldToMainThread(); + + expect(mockYield).toHaveBeenCalledOnce(); + vi.unstubAllGlobals(); + }); + it('falls back to MessageChannel when scheduler is unavailable', async () => { + // scheduler is not defined in jsdom by default + await expect(yieldToMainThread()).resolves.toBeUndefined(); + }); +}); diff --git a/src/entities/Font/model/store/appliedFontsStore/utils/yieldToMainThread/yieldToMainThread.ts b/src/entities/Font/model/store/appliedFontsStore/utils/yieldToMainThread/yieldToMainThread.ts new file mode 100644 index 0000000..4fa26a3 --- /dev/null +++ b/src/entities/Font/model/store/appliedFontsStore/utils/yieldToMainThread/yieldToMainThread.ts @@ -0,0 +1,16 @@ +/** + * Yields to main thread during CPU-intensive parsing. Uses scheduler.yield() where available or MessageChannel fallback. + */ +export async function yieldToMainThread(): Promise { + // @ts-expect-error - scheduler not in TypeScript lib yet + if (typeof scheduler !== 'undefined' && 'yield' in scheduler) { + // @ts-expect-error - scheduler.yield not in TypeScript lib yet + await scheduler.yield(); + } else { + await new Promise(resolve => { + const ch = new MessageChannel(); + ch.port1.onmessage = () => resolve(); + ch.port2.postMessage(null); + }); + } +}