{"record":{"id":"ce42f24e2d2a2ea0","repo":"bevyengine/bevy","slug":"window-canvas-can-only-be-called-in-main-thread","errorCode":null,"errorMessage":"window.canvas() can only be called in main thread.","messagePattern":"window\\.canvas\\(\\) can only be called in main thread\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/bevy_winit/src/system.rs","lineNumber":106,"sourceCode":"                commands.entity(entity).insert((\n                    CachedWindow(window.clone()),\n                    CachedCursorOptions(cursor_options.clone()),\n                    WinitWindowPressedKeys::default(),\n                ));\n\n                if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) {\n                    commands.entity(entity).insert(handle_wrapper.clone());\n                    if let Some(handle_holder) = handle_holder {\n                        *handle_holder.0.lock().unwrap() = Some(handle_wrapper);\n                    }\n                }\n\n                #[cfg(target_arch = \"wasm32\")]\n                {\n                    if window.fit_canvas_to_parent {\n                        let canvas = winit_window\n                            .canvas()\n                            .expect(\"window.canvas() can only be called in main thread.\");\n                        let style = canvas.style();\n                        style.set_property(\"width\", \"100%\").unwrap();\n                        style.set_property(\"height\", \"100%\").unwrap();\n                    }\n                }\n\n                #[cfg(target_os = \"ios\")]\n                {\n                    winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture);\n                    winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture);\n                    winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture);\n                    if let Some((min, max)) = window.recognize_pan_gesture {\n                        winit_window.recognize_pan_gesture(true, min, max);\n                    } else {\n                        winit_window.recognize_pan_gesture(false, 0, 0);\n                    }\n                }\n","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/bevyengine/bevy/blob/396ca727080776bd313bb892423b7d94e03b81b4/crates/bevy_winit/src/system.rs#L88-L124","documentation":"On wasm32, when a Window has fit_canvas_to_parent: true, the window-creation system in bevy_winit/src/system.rs calls winit_window.canvas() to restyle the HTML canvas. winit only permits that call from the browser's main thread; from any other thread it returns Err, which the .expect(\"window.canvas() can only be called in main thread.\") converts into a panic during window creation.","triggerScenarios":"Creating or respawn a Window with fit_canvas_to_parent = true from a web worker, a spawned std::thread, or JS-driven code not on the main thread; running the Bevy app loop off the main thread on the web target.","commonSituations":"Web deployments that move heavy work to workers and accidentally also drive window creation there; interop layers where JS triggers window creation from a callback running on a non-main thread; embedding Bevy in an existing JS app framework that manages threads.","solutions":["Keep the entire Bevy app — especially window creation — on the browser main thread; offload computation to tasks/workers instead of moving the app loop.","Spawn windows reactively from systems via Commands so creation happens inside the main-thread schedule.","Set fit_canvas_to_parent: false if canvas resizing must happen off the main thread (then apply CSS sizing yourself from JS).","On wasm, drive the app with the standard wasm-bindgen main-thread entry point rather than a custom thread."],"exampleFix":"// before: app run from a spawned thread on wasm\nstd::thread::spawn(|| App::new().add_plugins(DefaultPlugins).run());\n\n// after: keep the app on the main thread; push heavy work onto task pools\nfn main() { App::new().add_plugins(DefaultPlugins).add_systems(Update, heavy_work_async).run(); }","handlingStrategy":"validation","validationCode":"// capture the main thread at startup and assert before creating windows on wasm\nstatic MAIN_THREAD: std::sync::OnceLock<std::thread::ThreadId> = std::sync::OnceLock::new();\n\nfn init_main_thread() { MAIN_THREAD.set(std::thread::current().id()).ok(); }\n\nfn on_main_thread() -> bool {\n    MAIN_THREAD.get().is_some_and(|id| *id == std::thread::current().id())\n}\n\n// before spawning a fit_canvas_to_parent window:\nassert!(on_main_thread(), \"window creation must happen on the main thread\");","typeGuard":null,"tryCatchPattern":"// Panic occurs inside the window-creation system; prevention is structural:\n// keep the app loop on the browser main thread and use task pools for heavy work.","preventionTips":["On wasm, never run the Bevy app from a worker or spawned thread.","Create windows via Commands from systems so creation stays on the main-thread schedule.","If you must style the canvas off-thread, do it from JS and set fit_canvas_to_parent: false."],"tags":["rust","bevy","wasm","winit","windowing","threading","panic"],"backgroundTag":"wasm-main-thread-violation","analyzedSha":"396ca727080776bd313bb892423b7d94e03b81b4","analyzedAt":"2026-08-20T16:12:39.808Z","contentChangedAt":"2026-08-20T16:12:39.808Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}