Context Loss

Detecting and Handling contextlost Events

A context is lost when the GPU resets (Windows restarts a driver that stalls for two seconds), the driver is updated, the GPU process crashes, a phone reclaims a background tab's memory, or a page opens too many contexts. Every GPU object is gone, and the canvas fires webglcontextlost. The WEBGL_lose_context extension triggers the same path on demand, so you can test your handler:

What a lost context does to the calls that follow itJavaScript
const gl = document.createElement('canvas').getContext('webgl2'), buffer = gl.createBuffer();
gl.canvas.addEventListener('webglcontextlost', (event) => {
  event.preventDefault();                    // "I will restore": required for contextrestored
  console.log('webglcontextlost fired');
});
gl.getExtension('WEBGL_lose_context').loseContext();      // simulate a GPU reset
console.log(gl.isContextLost(), gl.getError(), gl.getError(),
  gl.getParameter(gl.MAX_TEXTURE_SIZE), gl.isBuffer(buffer),
  gl.createBuffer().constructor.name);
await new Promise((resolve) => setTimeout(resolve));      // the event comes in a later task
Output
true 37442 0 null false WebGLBuffer
webglcontextlost fired

Nothing throws: getError() reports CONTEXT_LOST_WEBGL (37442) once, queries return null, draws are ignored and createBuffer() returns a dead object. In the handler, which runs in a later task, cancel the animation loop and stop issuing GL calls. The loss you can cause by accident: Chrome 154 1 kept 16 contexts per page, and the 17th logged "Too many active WebGL contexts. Oldest context will be lost." and lost the first.

Two animated canvases; the right one loses its context through WEBGL_lose_context and its handler stops the loop and says soHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  .row { display: flex; gap: 10px; max-width: 600px; }
  .cell { flex: 1; position: relative; }
  canvas { display: block; width: 100%; background: #fff; border: 1px solid #ddd; box-sizing: border-box; }
  .lost { position: absolute; inset: 0; display: none; align-items: center; justify-content: center;
          color: #b5452f; font: bold 14px Georgia; background: repeating-linear-gradient(45deg, #fbeee9 0 10px, #fff 10px 20px); }
  pre { max-width: 600px; margin: 6px 0 0; font: 11px monospace; }
</style>
<div class="row">
  <div class="cell"><canvas id="a" width="290" height="200"></canvas></div>
  <div class="cell"><canvas id="b" width="290" height="200"></canvas><div class="lost" id="lost">webglcontextlost</div></div>
</div>
<pre id="log"></pre>
<script>
const log = (text) => { document.getElementById('log').textContent += text + '\n'; };
function spinner(canvas) {
  const gl = canvas.getContext('webgl2');
  const program = gl.createProgram();
  for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
uniform float uAngle;
void main() { vec2 p = vec2(gl_VertexID & 1, gl_VertexID >> 1) * 2.0 - 1.0;
  float c = cos(uAngle), s = sin(uAngle); p = mat2(c, s, -s, c) * (p * vec2(0.3, 0.45));
  gl_Position = vec4(p.x * 0.69, p.y, 0.0, 1.0); }`], [gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float; out vec4 c; void main() { c = vec4(0.12, 0.37, 0.55, 1.0); }`]]) {
    const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); gl.attachShader(program, s);
  }
  gl.linkProgram(program); gl.useProgram(program);
  gl.bindVertexArray(gl.createVertexArray());
  const state = { gl, request: 0, running: true };
  const frame = (ms) => {
    gl.clearColor(0.97, 0.96, 0.93, 1); gl.clear(gl.COLOR_BUFFER_BIT);
    gl.uniform1f(gl.getUniformLocation(program, 'uAngle'), ms * 0.001);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    if (state.running) state.request = requestAnimationFrame(frame);
  };
  state.request = requestAnimationFrame(frame);
  return state;
}
spinner(document.getElementById('a'));                         // keeps running
const b = spinner(document.getElementById('b')), gl = b.gl, buffer = gl.createBuffer();
gl.canvas.addEventListener('webglcontextlost', (event) => {
  event.preventDefault();                                        // "I will restore": needed for contextrestored
  b.running = false; cancelAnimationFrame(b.request);            // stop issuing GL calls
  document.getElementById('lost').style.display = 'flex';
  log('webglcontextlost fired (in a later task)');
});
setTimeout(() => {
  gl.getExtension('WEBGL_lose_context').loseContext();          // simulate a GPU reset
  // Nothing throws: one CONTEXT_LOST_WEBGL error, null queries, ignored draws, dead objects.
  log(`isContextLost ${gl.isContextLost()} | getError ${gl.getError()} then ${gl.getError()} (CONTEXT_LOST_WEBGL = 37442)`);
  log(`MAX_TEXTURE_SIZE ${gl.getParameter(gl.MAX_TEXTURE_SIZE)} | isBuffer(old) ${gl.isBuffer(buffer)} | createBuffer() -> ${gl.createBuffer().constructor.name}`);
}, 600);
</script>