Description
wgpu 30.0.0's BrowserWebGpu error decoder handles GPUValidationError and GPUOutOfMemoryError, but not the WebGPU-specified GPUInternalError. If GPUDevice.popErrorScope() resolves with that third error subclass, wgpu::Error::from_js reaches panic!("Unexpected error") even though wgpu already exposes Error::Internal.
The vendored webgpu_sys subset also omits the generated GpuInternalError binding, so the decoder cannot add the corresponding typed branch without first vendoring/exporting it.
This is separate from the nullable clean-scope binding regression reported at wasm-bindgen/wasm-bindgen#5234.
Repro steps
The fixture is self-contained and MIT-licensed. No product code or other private code is required; the surrounding project repository is private, so the exact method and results are included here.
The deterministic BrowserWebGpu integration test:
- requests a real adapter/device and enters the production wgpu bootstrap/error-scope path;
- saves and wraps the real
GPUDevice.prototype.popErrorScope;
- awaits the original browser promise;
- substitutes
new GPUInternalError("alpine synthetic internal error") for the internal-scope result; and
- verifies that the patched Rust decoder produces
wgpu::Error::Internal, preserves the message, and does not contain Unexpected error or unreachable.
This synthetic substitution exercises the real browser-native error class, real Promise boundary, and production Rust decoder. It intentionally proves only the returned-value type/mapping boundary; it does not claim to induce or reproduce an organic browser/driver internal fault.
The stock v30.0.0 source has no GpuInternalError branch and therefore deterministically falls through to the panic. The synthetic browser assertion was run against the patched build; the current stock build also encounters the independent clean-null regression before this injected internal result.
The standalone fixture's exact three-row clean-scope control matrix is:
| row |
wgpu source |
wasm-bindgen / wasm-bindgen-futures / js-sys + web-sys |
wasm-bindgen CLI |
observed result |
stock-old |
crates.io 30.0.0 |
0.2.122 / 0.4.72 / 0.3.99 |
0.2.122 |
exactly clean |
stock-current |
crates.io 30.0.0 |
0.2.126 / 0.4.76 / 0.3.103 |
0.2.126 |
panic hook payload exactly Unexpected error plus browser WebAssembly.RuntimeError: unreachable; the exported async Promise remains pending |
patched-current |
locally patched 30.0.0 |
0.2.126 / 0.4.76 / 0.3.103 |
0.2.126 |
exactly clean |
The matrix is included to establish the exact runtime and explain why the patched-current row is used to reach the separate synthetic internal-error decoder test. It is not evidence that a browser organically produced GPUInternalError.
Expected vs observed behavior
Expected: a GPUInternalError result is returned as wgpu::Error::Internal and its browser message is preserved.
Observed in stock v30 decoding: the object matches neither of the two handled subclasses and reaches panic!("Unexpected error"). With the local three-part patch, the synthetic result maps to an internal GPU error containing the exact synthetic message and produces no panic, unreachable trap, or unhandled rejection.
Relevant source/spec locations:
Suggested fix:
- vendor and export
gen_GpuInternalError.rs in the pinned BrowserWebGpu binding subset;
- add a
dyn_ref::<GpuInternalError>() branch mapping its message to Error::Internal; and
- add a browser regression test covering all three WebGPU error subclasses.
Related but not duplicate: #2170 tracks broad historical spec compliance; #4866/#5160 add the input ErrorFilter::Internal; #2299 introduced error scopes; #9090 refreshed the vendored binding subset without this class; and #9683 is an organic producer-side MSL error rather than a Rust returned-value decoder bug.
Temporary workaround: pinning wasm-bindgen/CLI 0.2.122, wasm-bindgen-futures 0.4.72, and js-sys/web-sys 0.3.99 avoids the sibling clean-null regression, but it does not add GPUInternalError mapping. The internal-error path still needs a local wgpu patch or upstream fix.
Extra materials
No screenshots, validation logs, GPU captures, or product assets are required. The failure is at the JavaScript-class/Rust-decoder boundary.
Platform
- Rust 1.96.0, target
wasm32-unknown-unknown
- wgpu 30.0.0,
BrowserWebGpu backend
- wasm-bindgen 0.2.126
- wasm-bindgen-futures 0.4.76
- js-sys/web-sys 0.3.103
- wasm-bindgen CLI 0.2.126
- Node 24.18.0
- Playwright 1.58.2
- Chromium 145.0.7632.6, Playwright browser revision 1208
Description
wgpu 30.0.0's BrowserWebGpu error decoder handles
GPUValidationErrorandGPUOutOfMemoryError, but not the WebGPU-specifiedGPUInternalError. IfGPUDevice.popErrorScope()resolves with that third error subclass,wgpu::Error::from_jsreachespanic!("Unexpected error")even though wgpu already exposesError::Internal.The vendored
webgpu_syssubset also omits the generatedGpuInternalErrorbinding, so the decoder cannot add the corresponding typed branch without first vendoring/exporting it.This is separate from the nullable clean-scope binding regression reported at wasm-bindgen/wasm-bindgen#5234.
Repro steps
The fixture is self-contained and MIT-licensed. No product code or other private code is required; the surrounding project repository is private, so the exact method and results are included here.
The deterministic BrowserWebGpu integration test:
GPUDevice.prototype.popErrorScope;new GPUInternalError("alpine synthetic internal error")for the internal-scope result; andwgpu::Error::Internal, preserves the message, and does not containUnexpected errororunreachable.This synthetic substitution exercises the real browser-native error class, real Promise boundary, and production Rust decoder. It intentionally proves only the returned-value type/mapping boundary; it does not claim to induce or reproduce an organic browser/driver internal fault.
The stock v30.0.0 source has no
GpuInternalErrorbranch and therefore deterministically falls through to the panic. The synthetic browser assertion was run against the patched build; the current stock build also encounters the independent clean-nullregression before this injected internal result.The standalone fixture's exact three-row clean-scope control matrix is:
stock-oldcleanstock-currentUnexpected errorplus browserWebAssembly.RuntimeError: unreachable; the exported async Promise remains pendingpatched-currentcleanThe matrix is included to establish the exact runtime and explain why the patched-current row is used to reach the separate synthetic internal-error decoder test. It is not evidence that a browser organically produced
GPUInternalError.Expected vs observed behavior
Expected: a
GPUInternalErrorresult is returned aswgpu::Error::Internaland its browser message is preserved.Observed in stock v30 decoding: the object matches neither of the two handled subclasses and reaches
panic!("Unexpected error"). With the local three-part patch, the synthetic result maps to an internal GPU error containing the exact synthetic message and produces no panic, unreachable trap, or unhandled rejection.Relevant source/spec locations:
GPUInternalError: https://www.w3.org/TR/webgpu/#gpuinternalerrorwgpu::Error::Internal: https://github.com/gfx-rs/wgpu/blob/v30.0.0/wgpu/src/api/device.rs#L896-L924GpuInternalErrorbinding: https://github.com/wasm-bindgen/wasm-bindgen/blob/0.2.126/crates/web-sys/src/features/gen_GpuInternalError.rs#L5-L34Suggested fix:
gen_GpuInternalError.rsin the pinned BrowserWebGpu binding subset;dyn_ref::<GpuInternalError>()branch mapping its message toError::Internal; andRelated but not duplicate: #2170 tracks broad historical spec compliance; #4866/#5160 add the input
ErrorFilter::Internal; #2299 introduced error scopes; #9090 refreshed the vendored binding subset without this class; and #9683 is an organic producer-side MSL error rather than a Rust returned-value decoder bug.Temporary workaround: pinning wasm-bindgen/CLI 0.2.122, wasm-bindgen-futures 0.4.72, and js-sys/web-sys 0.3.99 avoids the sibling clean-
nullregression, but it does not addGPUInternalErrormapping. The internal-error path still needs a local wgpu patch or upstream fix.Extra materials
No screenshots, validation logs, GPU captures, or product assets are required. The failure is at the JavaScript-class/Rust-decoder boundary.
Platform
wasm32-unknown-unknownBrowserWebGpubackend