Quick answer: Listen for webglcontextlost with preventDefault(), then rebuild every texture, buffer, and program on webglcontextrestored. Plan for this from day one; retrofitting is expensive.

A player switches to a different tab for two minutes. Returns to your game. The canvas is black, the engine throws “INVALID_OPERATION” errors, and the game is unrecoverable without a page reload. The WebGL context was lost while in the background and your engine doesn’t know how to restore it.

The WebGL Context Loss Lifecycle

Two events fire on the canvas:

Step 1: Subscribe to Events

canvas.addEventListener("webglcontextlost", (e) => {
  e.preventDefault();   // opt into restoration
  pauseGame();
  contextLost = true;
}, false);

canvas.addEventListener("webglcontextrestored", () => {
  rebuildAllResources();
  contextLost = false;
  resumeGame();
}, false);

Without preventDefault(), the browser refuses to attempt restoration and you must reload the page. With it, the browser may restore in seconds to minutes (or immediately on tab focus).

Step 2: Keep CPU-Side Copies

For every GPU resource, retain enough CPU data to recreate it:

class Texture {
  constructor(image) {
    this.image = image;   // keep CPU reference
    this.create();
  }
  create() {
    this.handle = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, this.handle);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.image);
    // ... filtering, mipmaps, etc.
  }
  recreate() { this.create(); }
}

On webglcontextrestored, call recreate() on every Texture in your asset table. Same for buffers, programs.

Step 3: Resource Registry

Maintain a registry of every GPU object so you can iterate and recreate:

const registry = {
  textures: new Set(),
  buffers: new Set(),
  programs: new Set(),
};

function rebuildAllResources() {
  for (const t of registry.textures) t.recreate();
  for (const b of registry.buffers) b.recreate();
  for (const p of registry.programs) p.recreate();
}

Have each resource class register itself in its constructor and unregister in dispose(). Order matters: programs depend on shaders; framebuffers depend on textures. Recreate in dependency order.

Step 4: Test with a Manual Context Loss

const ext = gl.getExtension("WEBGL_lose_context");
// Trigger context loss for testing
ext.loseContext();
setTimeout(() => ext.restoreContext(), 2000);

The WEBGL_lose_context extension lets you simulate loss and restore in code. Wire it to a debug button so you can verify recovery in dev without waiting for an actual context loss.

Reducing Loss Frequency

Verifying

Trigger manual context loss with the extension. Verify the game pauses, recovers, and resumes within 1 second. Run a 30-minute soak test in a backgrounded tab; ensure that if the browser reclaims context, your game can recover without reload.

“On WebGL, the context is borrowed. Plan to lose it, plan to recover it — or your game breaks every tab switch.”

Wire up the WEBGL_lose_context test button on every WebGL project. Catch missing rebuilds during development, not in production.