{"record":{"id":"c95d4c1a14a6fb75","repo":"GraphiteEditor/Graphite","slug":"failed-to-cast-context-to-canvasrenderingcontext2d-c95d4c","errorCode":null,"errorMessage":"Failed to cast context to CanvasRenderingContext2d","messagePattern":"Failed to cast context to CanvasRenderingContext2d","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"node-graph/libraries/canvas-utils/src/wasm.rs","lineNumber":187,"sourceCode":"\n\t\t// Convert key and value to JsValue\n\t\tlet js_key = JsValue::from_str(canvas_id.to_string().as_str());\n\t\tlet js_value = JsValue::from(canvas.clone());\n\n\t\tlet canvases = Object::from(canvases.unwrap());\n\n\t\t// Use Reflect API to set property\n\t\tReflect::set(&canvases, &js_key, &js_value).unwrap();\n\n\t\tSelf { canvas_id, canvas }\n\t}\n\tfn context(&self) -> CanvasRenderingContext2d {\n\t\tself.canvas\n\t\t\t.get_context(\"2d\")\n\t\t\t.expect(\"Failed to get 2D context from canvas\")\n\t\t\t.unwrap()\n\t\t\t.dyn_into::<CanvasRenderingContext2d>()\n\t\t\t.expect(\"Failed to cast context to CanvasRenderingContext2d\")\n\t}\n\tfn set_resolution(&self, resolution: glam::UVec2) {\n\t\tself.canvas.set_width(resolution.x);\n\t\tself.canvas.set_height(resolution.y);\n\t}\n}\n\nimpl Drop for CanvasImpl {\n\tfn drop(&mut self) {\n\t\tlet canvas_id = self.canvas_id;\n\t\tlet window = window().expect(\"should have a window in this context\");\n\t\tlet window_obj = Object::from(window);\n\n\t\tlet image_canvases_key = JsValue::from_str(CANVASES_OBJECT_KEY);\n\n\t\tif let Ok(canvases) = Reflect::get(&window_obj, &image_canvases_key) {\n\t\t\tlet canvases = Object::from(canvases);\n\t\t\tlet js_key = JsValue::from_str(canvas_id.to_string().as_str());","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/node-graph/libraries/canvas-utils/src/wasm.rs#L169-L205","documentation":"The final step of CanvasImpl::context casts the value returned by getContext(\"2d\") with dyn_into::<CanvasRenderingContext2d>(). The call succeeded, but the object is not an instance of the standard 2D context class — offscreen 2D contexts and polyfills have a different prototype chain, so the cast is rejected.","triggerScenarios":"An OffscreenCanvasRenderingContext2D returned after transferControlToOffscreen; jsdom or polyfill contexts that are not true instances; prototype tampering on the context class.","commonSituations":"Node/jsdom test suites exercising wasm canvas code; moving raster work into workers; older browsers with non-standard context objects.","solutions":["Do not transferControlToOffscreen canvases used by this code path; bind to the offscreen context type instead","Check has_type::<CanvasRenderingContext2d>() before casting and degrade gracefully","Test canvas code in real browsers rather than DOM emulations"],"exampleFix":"// before\n.dyn_into::<CanvasRenderingContext2d>()\n\t.expect(\"Failed to cast context to CanvasRenderingContext2d\")\n\n// after\nlet Ok(context) = value.dyn_into::<CanvasRenderingContext2d>() else {\n\terror!(\"got {:?} instead of a 2D rendering context\", value.js_typeof());\n\treturn;\n};","handlingStrategy":"type-guard","validationCode":"// ensure the value is the standard context class before casting\nvalue.has_type::<CanvasRenderingContext2d>()","typeGuard":"fn as_2d_context(value: &JsValue) -> Option<CanvasRenderingContext2d> {\n\tvalue.dyn_ref::<CanvasRenderingContext2d>().cloned()\n}","tryCatchPattern":null,"preventionTips":["Never call transferControlToOffscreen on canvases used for 2D blitting","Test canvas interop in real browsers, not DOM emulations","Prefer dyn_ref over dyn_into anywhere a host could supply an unexpected context class"],"tags":["wasm-bindgen","canvas","dyn-into","offscreen-canvas"],"backgroundTag":"canvas-context-type-mismatch","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}