82 lines
3.5 KiB
TypeScript
82 lines
3.5 KiB
TypeScript
/**
|
|
* Warn before a browser-engine model load that risks crashing the tab on
|
|
* mobile (Decisions.md D-027).
|
|
*
|
|
* Root cause: transformers.js buffers each ONNX weight file fully into a
|
|
* JS-heap `Uint8Array`, then onnxruntime-web mallocs a second full-size copy
|
|
* inside WASM linear memory to parse it at session-creation time — a
|
|
* per-file ~2x peak (`models.ts`'s `estimatePeakLoadBytes`; the
|
|
* sequential-session-load patch in `patches/` at least keeps the encoder and
|
|
* decoder from both being resident at once). Desktop browsers shrug this
|
|
* off; mobile Safari/Chrome enforce much tighter per-tab memory budgets and
|
|
* respond to going over them with an outright, uncatchable tab kill —
|
|
* observed as Whisper Medium (~460 MB download, ~555 MB estimated peak)
|
|
* crashing the browser entirely, cascading on Android into the OS's
|
|
* low-memory killer reaping other processes (including the launcher).
|
|
*
|
|
* Pure and UA-driven rather than feature-detected, same rationale as
|
|
* D-026's Firefox check: there is no memory-pressure API to query in
|
|
* advance, so a benchmark probe would cost more than it saves.
|
|
*/
|
|
|
|
export interface MobileMemoryHint {
|
|
/** `navigator.userAgent`, or undefined outside a browser (tests, SSR). */
|
|
userAgent: string | undefined;
|
|
/**
|
|
* `navigator.maxTouchPoints`. iPadOS 13+ Safari reports a desktop-Safari
|
|
* "Macintosh" user agent, so touch support is the only signal left to
|
|
* tell an iPad apart from a real Mac.
|
|
*/
|
|
maxTouchPoints: number | undefined;
|
|
/**
|
|
* `navigator.deviceMemory` (GiB, quantized: 0.25 .. 8) — Chromium only,
|
|
* and capped at 8 regardless of actual RAM. Undefined on Safari/Firefox,
|
|
* which means undefined on every iOS browser (all WebKit under the hood).
|
|
*/
|
|
deviceMemoryGB: number | undefined;
|
|
}
|
|
|
|
const MOBILE_UA_RE = /Android|iPhone|iPod|Mobile/i;
|
|
|
|
/**
|
|
* True for phones/tablets: Android/iPhone/iPod/generic-Mobile UAs, plus a
|
|
* touch-capable "Macintosh" UA (iPadOS masquerading as desktop Safari).
|
|
*/
|
|
export function isLikelyMobile(
|
|
hint: Pick<MobileMemoryHint, "userAgent" | "maxTouchPoints">,
|
|
): boolean {
|
|
if (!hint.userAgent) return false;
|
|
if (MOBILE_UA_RE.test(hint.userAgent)) return true;
|
|
return /Macintosh/.test(hint.userAgent) && (hint.maxTouchPoints ?? 0) > 1;
|
|
}
|
|
|
|
/**
|
|
* Fraction of reported device RAM a single tab can safely peak at before
|
|
* mobile OSes intervene. Conservative on purpose — `deviceMemory` already
|
|
* caps at 8 regardless of actual RAM, and the tab competes with the rest of
|
|
* the OS, the browser process itself, and other open tabs.
|
|
*/
|
|
const SAFE_TAB_FRACTION_OF_DEVICE_MEMORY = 0.5;
|
|
|
|
/**
|
|
* Fallback risk threshold when `deviceMemory` isn't reported (notably all of
|
|
* iOS/iPadOS, and Firefox). Set between Small's (~330-350 MB) and Medium's
|
|
* (~555 MB) estimated peaks, so it flags Medium/Large without flagging the
|
|
* models this codebase has no crash evidence for (D-022's precedent: scope
|
|
* fixes to where the evidence is, not every model preemptively).
|
|
*/
|
|
const MOBILE_FALLBACK_RISK_THRESHOLD_BYTES = 400_000_000;
|
|
|
|
/**
|
|
* True when a browser-engine load of `peakBytes` is likely to crash this
|
|
* device's tab. Desktop/laptop browsers are out of scope — the crash reports
|
|
* this guards against are mobile-only.
|
|
*/
|
|
export function shouldWarnBeforeLoad(hint: MobileMemoryHint, peakBytes: number): boolean {
|
|
if (!isLikelyMobile(hint)) return false;
|
|
if (hint.deviceMemoryGB !== undefined) {
|
|
return peakBytes > hint.deviceMemoryGB * 1e9 * SAFE_TAB_FRACTION_OF_DEVICE_MEMORY;
|
|
}
|
|
return peakBytes > MOBILE_FALLBACK_RISK_THRESHOLD_BYTES;
|
|
}
|