Wei-Shaw/sub2api · warning
profile.avatar.compressFailed
Error message
profile.avatar.compressFailed
What it means
In frontend/src/components/user/profile/ProfileAvatarCard.vue:168, compressAvatarFile() calls canvas.getContext('2d') and throws the localized 'profile.avatar.compressFailed' if it returns null. getContext('2d') returns null when the canvas element cannot provide a 2D context — most commonly because a different context type was already obtained on the same canvas, the browser has exhausted canvas memory/GPU contexts, or the document environment doesn't support 2D rendering.
Source
Thrown at frontend/src/components/user/profile/ProfileAvatarCard.vue:168
function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality: number): Promise<Blob> {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (!blob) {
reject(new Error(t('profile.avatar.compressFailed')))
return
}
resolve(blob)
}, type, quality)
})
}
async function compressAvatarFile(file: File): Promise<File> {
const sourceDataURL = await readFileAsDataURL(file)
const image = await loadImage(sourceDataURL)
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) {
throw new Error(t('profile.avatar.compressFailed'))
}
for (const scale of avatarScaleSteps) {
const width = Math.max(1, Math.round(image.naturalWidth * scale))
const height = Math.max(1, Math.round(image.naturalHeight * scale))
canvas.width = width
canvas.height = height
ctx.clearRect(0, 0, width, height)
ctx.drawImage(image, 0, 0, width, height)
for (const quality of avatarQualitySteps) {
const blob = await canvasToBlob(canvas, 'image/webp', quality)
if (blob.size <= targetAvatarUploadBytes) {
const fileName = file.name.replace(/\.[^.]+$/, '') || 'avatar'
return new File([blob], `${fileName}.webp`, { type: 'image/webp' })
}
}
}View on GitHub (pinned to 073e92d171)
Solutions
- Create a fresh canvas per compression call (the code does this — audit for any reuse with webgl) and release old ones by zeroing width/height.
- In tests, install the 'canvas' npm package so jsdom returns a real 2D context.
- Catch the error and offer the original file (or reject large files) instead of dead-ending the upload UI.
- If it fails in production, prompt a page reload — GPU/context exhaustion usually recovers after refresh.
Example fix
// before
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) {
throw new Error(t('profile.avatar.compressFailed'))
}
// after
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d', { willReadFrequently: true })
if (!ctx) {
if (file.size <= hardAvatarLimitBytes) return file // last resort: accept original
throw new Error(t('profile.avatar.compressFailed'))
} Defensive patterns
Strategy: fallback
Validate before calling
const probeCtx = document.createElement('canvas').getContext('2d');
export const canvas2dAvailable = !!probeCtx;
if (!canvas2dAvailable) disableAvatarCompression(); Type guard
function has2dContext(ctx: CanvasRenderingContext2D | null): ctx is CanvasRenderingContext2D {
return ctx !== null;
} Try / catch
try { file = await compressAvatarFile(file); }
catch (e) {
if (e.message === t('profile.avatar.compressFailed') && file.size <= hardLimit) return file; // degrade to original
throw e;
} Prevention
- Never reuse a canvas that already has a webgl context
- Release canvases after use (set width=height=0) in long-lived SPAs
- Polyfill canvas in jsdom tests via the 'canvas' npm package
- Prompt reload if context creation fails — GPU exhaustion usually recovers
When it happens
Trigger: Calling getContext('2d') after getContext('webgl') on the same canvas; too many live canvases exhausting the browser's context limit (common in long sessions or pages creating many canvases); Chrome's GPU process crash disabling accelerated 2D contexts; running in a DOM-less test environment with an incompletely polyfilled canvas.
Common situations: Memory leaks in SPAs accumulating canvases (avatar re-uploads in a long-lived profile page); jsdom tests without the node-canvas polyfill; browsers after a GPU driver reset where context creation fails until reload.
Related errors
- profile.avatar.compressTooLarge
- profile.avatar.invalidType
- profile.avatar.gifTooLarge
- Passkeys are not supported by this browser
- Passkey sign-in was cancelled
AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15).
Data as JSON: /api/errors/466919a0cba92b81.
Report an issue: GitHub.