All files / hooks useWebGLContextLossHandler.ts

91.11% Statements 41/45
77.77% Branches 14/18
100% Functions 8/8
91.11% Lines 41/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132                                                                                        7x     97x     97x 97x     97x 97x 97x     97x 68x 68x 62x 62x     6x 3x     3x 2x     3x     6x 1x 1x       6x 6x     6x 6x 6x               7x 2x 2x 2x 2x   2x       1x   1x             7x 2x 2x 2x 2x   2x       1x   1x      
/**
 * useWebGLContextLossHandler - React hook for handling WebGL context loss
 * 
 * This hook sets up event listeners for WebGL context loss and restoration,
 * which can occur due to GPU issues, memory pressure, or browser tab switching.
 * 
 * @example
 * ```tsx
 * const Canvas = () => {
 *   useWebGLContextLossHandler();
 *   return <Canvas>...</Canvas>;
 * };
 * ```
 */
 
import { useEffect, useRef } from 'react';
import type React from 'react';
 
export interface WebGLContextLossOptions {
  /**
   * Callback when context is lost
   */
  readonly onContextLost?: () => void;
  
  /**
   * Callback when context is restored
   */
  readonly onContextRestored?: () => void;
  
  /**
   * Whether to attempt automatic restoration (default: true)
   */
  readonly autoRestore?: boolean;
  
  /**
   * Optional canvas ref to attach to a specific canvas element
   * If not provided, will query for the first canvas in the document
   */
  readonly canvasRef?: React.RefObject<HTMLCanvasElement>;
}
 
/**
 * Hook to handle WebGL context loss and restoration
 */
export const useWebGLContextLossHandler = (
  options: WebGLContextLossOptions = {}
): void => {
  const { onContextLost, onContextRestored, autoRestore = true, canvasRef } = options;
 
  // Use refs to store the latest callbacks to avoid re-registering event listeners
  const onContextLostRef = useRef(onContextLost);
  const onContextRestoredRef = useRef(onContextRestored);
 
  // Update refs when callbacks change
  useEffect(() => {
    onContextLostRef.current = onContextLost;
    onContextRestoredRef.current = onContextRestored;
  });
 
  useEffect(() => {
    const canvas = canvasRef?.current ?? document.querySelector('canvas');
    if (!canvas) {
      console.warn('useWebGLContextLossHandler: No canvas element found');
      return;
    }
 
    const handleContextLost = (event: Event) => {
      console.warn('WebGL context lost - attempting to restore');
      
      // Prevent default behavior to allow restoration
      if (autoRestore) {
        event.preventDefault();
      }
      
      onContextLostRef.current?.();
    };
 
    const handleContextRestored = () => {
      console.log('WebGL context restored successfully');
      onContextRestoredRef.current?.();
    };
 
    // Add event listeners for context loss/restoration
    canvas.addEventListener('webglcontextlost', handleContextLost, false);
    canvas.addEventListener('webglcontextrestored', handleContextRestored, false);
 
    // Cleanup
    return () => {
      canvas.removeEventListener('webglcontextlost', handleContextLost);
      canvas.removeEventListener('webglcontextrestored', handleContextRestored);
    };
  }, [autoRestore, canvasRef]);
};
 
/**
 * Check if WebGL is available in the current browser
 */
export const isWebGLAvailable = (): boolean => {
  try {
    const canvas = document.createElement('canvas');
    const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
    const available = gl !== null;
    // Help GC by cleaning up WebGL context
    Iif (gl && 'getExtension' in gl) {
      const loseContext = gl.getExtension('WEBGL_lose_context');
      loseContext?.loseContext();
    }
    return available;
  } catch (e) {
    return false;
  }
};
 
/**
 * Check if WebGL2 is available in the current browser
 */
export const isWebGL2Available = (): boolean => {
  try {
    const canvas = document.createElement('canvas');
    const gl = canvas.getContext('webgl2');
    const available = gl !== null;
    // Help GC by cleaning up WebGL context
    Iif (gl && 'getExtension' in gl) {
      const loseContext = gl.getExtension('WEBGL_lose_context');
      loseContext?.loseContext();
    }
    return available;
  } catch (e) {
    return false;
  }
};