1use base::Epoch;
8use euclid::default::Size2D;
9use layout_api::HTMLCanvasData;
10use pixels::Snapshot;
11use script_bindings::root::{Dom, DomRoot};
12use webrender_api::ImageKey;
13
14use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
15use crate::dom::bindings::inheritance::Castable;
16use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
17use crate::dom::node::{Node, NodeDamage};
18#[cfg(feature = "webgpu")]
19use crate::dom::types::GPUCanvasContext;
20use crate::dom::types::{
21 CanvasRenderingContext2D, ImageBitmapRenderingContext, OffscreenCanvas,
22 OffscreenCanvasRenderingContext2D, WebGL2RenderingContext, WebGLRenderingContext,
23};
24
25pub(crate) trait LayoutCanvasRenderingContextHelpers {
26 fn canvas_data_source(self) -> Option<ImageKey>;
28}
29
30pub(crate) trait LayoutHTMLCanvasElementHelpers {
31 fn data(self) -> HTMLCanvasData;
32}
33
34pub(crate) trait CanvasContext {
35 type ID;
36
37 fn context_id(&self) -> Self::ID;
38
39 fn canvas(&self) -> Option<HTMLCanvasElementOrOffscreenCanvas>;
40
41 fn resize(&self);
42
43 fn reset_bitmap(&self);
47
48 fn get_image_data(&self) -> Option<Snapshot>;
52
53 fn origin_is_clean(&self) -> bool {
54 true
55 }
56
57 fn size(&self) -> Size2D<u32> {
58 self.canvas()
59 .map(|canvas| canvas.size())
60 .unwrap_or_default()
61 }
62
63 fn mark_as_dirty(&self) {
64 if let Some(HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas)) = &self.canvas()
65 {
66 canvas.upcast::<Node>().dirty(NodeDamage::Other);
67 }
68 }
69
70 fn image_key(&self) -> Option<ImageKey>;
72
73 fn update_rendering(&self, _canvas_epoch: Epoch) -> bool {
76 false
77 }
78
79 fn onscreen(&self) -> bool {
80 let Some(canvas) = self.canvas() else {
81 return false;
82 };
83
84 match canvas {
85 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => {
86 canvas.upcast::<Node>().is_connected()
87 },
88 HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(_) => false,
91 }
92 }
93}
94
95pub(crate) trait CanvasHelpers {
96 fn size(&self) -> Size2D<u32>;
97 fn canvas(&self) -> Option<DomRoot<HTMLCanvasElement>>;
98}
99
100impl CanvasHelpers for HTMLCanvasElementOrOffscreenCanvas {
101 fn size(&self) -> Size2D<u32> {
102 match self {
103 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
104 canvas.get_size().cast()
105 },
106 HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(),
107 }
108 }
109
110 fn canvas(&self) -> Option<DomRoot<HTMLCanvasElement>> {
111 match self {
112 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => Some(canvas.clone()),
113 HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.placeholder(),
114 }
115 }
116}
117
118#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
120#[derive(Clone, JSTraceable, MallocSizeOf)]
121pub(crate) enum RenderingContext {
122 Placeholder(Dom<OffscreenCanvas>),
123 Context2d(Dom<CanvasRenderingContext2D>),
124 BitmapRenderer(Dom<ImageBitmapRenderingContext>),
125 WebGL(Dom<WebGLRenderingContext>),
126 WebGL2(Dom<WebGL2RenderingContext>),
127 #[cfg(feature = "webgpu")]
128 WebGPU(Dom<GPUCanvasContext>),
129}
130
131impl CanvasContext for RenderingContext {
132 type ID = ();
133
134 fn context_id(&self) -> Self::ID {}
135
136 fn canvas(&self) -> Option<HTMLCanvasElementOrOffscreenCanvas> {
137 match self {
138 RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas.context()?.canvas(),
139 RenderingContext::Context2d(context) => context.canvas(),
140 RenderingContext::BitmapRenderer(context) => context.canvas(),
141 RenderingContext::WebGL(context) => context.canvas(),
142 RenderingContext::WebGL2(context) => context.canvas(),
143 #[cfg(feature = "webgpu")]
144 RenderingContext::WebGPU(context) => context.canvas(),
145 }
146 }
147
148 fn resize(&self) {
149 match self {
150 RenderingContext::Placeholder(offscreen_canvas) => {
151 if let Some(context) = offscreen_canvas.context() {
152 context.resize()
153 }
154 },
155 RenderingContext::Context2d(context) => context.resize(),
156 RenderingContext::BitmapRenderer(context) => context.resize(),
157 RenderingContext::WebGL(context) => context.resize(),
158 RenderingContext::WebGL2(context) => context.resize(),
159 #[cfg(feature = "webgpu")]
160 RenderingContext::WebGPU(context) => context.resize(),
161 }
162 }
163
164 fn reset_bitmap(&self) {
165 match self {
166 RenderingContext::Placeholder(offscreen_canvas) => {
167 if let Some(context) = offscreen_canvas.context() {
168 context.reset_bitmap()
169 }
170 },
171 RenderingContext::Context2d(context) => context.reset_bitmap(),
172 RenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
173 RenderingContext::WebGL(context) => context.reset_bitmap(),
174 RenderingContext::WebGL2(context) => context.reset_bitmap(),
175 #[cfg(feature = "webgpu")]
176 RenderingContext::WebGPU(context) => context.reset_bitmap(),
177 }
178 }
179
180 fn get_image_data(&self) -> Option<Snapshot> {
181 match self {
182 RenderingContext::Placeholder(offscreen_canvas) => {
183 offscreen_canvas.context()?.get_image_data()
184 },
185 RenderingContext::Context2d(context) => context.get_image_data(),
186 RenderingContext::BitmapRenderer(context) => context.get_image_data(),
187 RenderingContext::WebGL(context) => context.get_image_data(),
188 RenderingContext::WebGL2(context) => context.get_image_data(),
189 #[cfg(feature = "webgpu")]
190 RenderingContext::WebGPU(context) => context.get_image_data(),
191 }
192 }
193
194 fn origin_is_clean(&self) -> bool {
195 match self {
196 RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
197 .context()
198 .is_none_or(|context| context.origin_is_clean()),
199 RenderingContext::Context2d(context) => context.origin_is_clean(),
200 RenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
201 RenderingContext::WebGL(context) => context.origin_is_clean(),
202 RenderingContext::WebGL2(context) => context.origin_is_clean(),
203 #[cfg(feature = "webgpu")]
204 RenderingContext::WebGPU(context) => context.origin_is_clean(),
205 }
206 }
207
208 fn size(&self) -> Size2D<u32> {
209 match self {
210 RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
211 .context()
212 .map(|context| context.size())
213 .unwrap_or_default(),
214 RenderingContext::Context2d(context) => context.size(),
215 RenderingContext::BitmapRenderer(context) => context.size(),
216 RenderingContext::WebGL(context) => context.size(),
217 RenderingContext::WebGL2(context) => context.size(),
218 #[cfg(feature = "webgpu")]
219 RenderingContext::WebGPU(context) => context.size(),
220 }
221 }
222
223 fn mark_as_dirty(&self) {
224 match self {
225 RenderingContext::Placeholder(offscreen_canvas) => {
226 if let Some(context) = offscreen_canvas.context() {
227 context.mark_as_dirty()
228 }
229 },
230 RenderingContext::Context2d(context) => context.mark_as_dirty(),
231 RenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
232 RenderingContext::WebGL(context) => context.mark_as_dirty(),
233 RenderingContext::WebGL2(context) => context.mark_as_dirty(),
234 #[cfg(feature = "webgpu")]
235 RenderingContext::WebGPU(context) => context.mark_as_dirty(),
236 }
237 }
238
239 fn image_key(&self) -> Option<ImageKey> {
240 match self {
241 RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
242 .context()
243 .and_then(|context| context.image_key()),
244 RenderingContext::Context2d(context) => context.image_key(),
245 RenderingContext::BitmapRenderer(context) => context.image_key(),
246 RenderingContext::WebGL(context) => context.image_key(),
247 RenderingContext::WebGL2(context) => context.image_key(),
248 #[cfg(feature = "webgpu")]
249 RenderingContext::WebGPU(context) => context.image_key(),
250 }
251 }
252
253 fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
254 match self {
255 RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
256 .context()
257 .is_some_and(|context| context.update_rendering(canvas_epoch)),
258 RenderingContext::Context2d(context) => context.update_rendering(canvas_epoch),
259 RenderingContext::BitmapRenderer(context) => context.update_rendering(canvas_epoch),
260 RenderingContext::WebGL(context) => context.update_rendering(canvas_epoch),
261 RenderingContext::WebGL2(context) => context.update_rendering(canvas_epoch),
262 #[cfg(feature = "webgpu")]
263 RenderingContext::WebGPU(context) => context.update_rendering(canvas_epoch),
264 }
265 }
266
267 fn onscreen(&self) -> bool {
268 match self {
269 RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
270 .context()
271 .is_some_and(|context| context.onscreen()),
272 RenderingContext::Context2d(context) => context.onscreen(),
273 RenderingContext::BitmapRenderer(context) => context.onscreen(),
274 RenderingContext::WebGL(context) => context.onscreen(),
275 RenderingContext::WebGL2(context) => context.onscreen(),
276 #[cfg(feature = "webgpu")]
277 RenderingContext::WebGPU(context) => context.onscreen(),
278 }
279 }
280}
281
282#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
284#[derive(Clone, JSTraceable, MallocSizeOf)]
285pub(crate) enum OffscreenRenderingContext {
286 Context2d(Dom<OffscreenCanvasRenderingContext2D>),
287 BitmapRenderer(Dom<ImageBitmapRenderingContext>),
288 Detached,
293}
294
295impl CanvasContext for OffscreenRenderingContext {
296 type ID = ();
297
298 fn context_id(&self) -> Self::ID {}
299
300 fn canvas(&self) -> Option<HTMLCanvasElementOrOffscreenCanvas> {
301 match self {
302 OffscreenRenderingContext::Context2d(context) => context.canvas(),
303 OffscreenRenderingContext::BitmapRenderer(context) => context.canvas(),
304 OffscreenRenderingContext::Detached => None,
305 }
306 }
307
308 fn resize(&self) {
309 match self {
310 OffscreenRenderingContext::Context2d(context) => context.resize(),
311 OffscreenRenderingContext::BitmapRenderer(context) => context.resize(),
312 OffscreenRenderingContext::Detached => {},
313 }
314 }
315
316 fn reset_bitmap(&self) {
317 match self {
318 OffscreenRenderingContext::Context2d(context) => context.reset_bitmap(),
319 OffscreenRenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
320 OffscreenRenderingContext::Detached => {},
321 }
322 }
323
324 fn get_image_data(&self) -> Option<Snapshot> {
325 match self {
326 OffscreenRenderingContext::Context2d(context) => context.get_image_data(),
327 OffscreenRenderingContext::BitmapRenderer(context) => context.get_image_data(),
328 OffscreenRenderingContext::Detached => None,
329 }
330 }
331
332 fn origin_is_clean(&self) -> bool {
333 match self {
334 OffscreenRenderingContext::Context2d(context) => context.origin_is_clean(),
335 OffscreenRenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
336 OffscreenRenderingContext::Detached => true,
337 }
338 }
339
340 fn size(&self) -> Size2D<u32> {
341 match self {
342 OffscreenRenderingContext::Context2d(context) => context.size(),
343 OffscreenRenderingContext::BitmapRenderer(context) => context.size(),
344 OffscreenRenderingContext::Detached => Size2D::default(),
345 }
346 }
347
348 fn mark_as_dirty(&self) {
349 match self {
350 OffscreenRenderingContext::Context2d(context) => context.mark_as_dirty(),
351 OffscreenRenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
352 OffscreenRenderingContext::Detached => {},
353 }
354 }
355
356 fn image_key(&self) -> Option<ImageKey> {
357 None
358 }
359
360 fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
361 match self {
362 OffscreenRenderingContext::Context2d(context) => context.update_rendering(canvas_epoch),
363 OffscreenRenderingContext::BitmapRenderer(context) => {
364 context.update_rendering(canvas_epoch)
365 },
366 OffscreenRenderingContext::Detached => false,
367 }
368 }
369
370 fn onscreen(&self) -> bool {
371 match self {
372 OffscreenRenderingContext::Context2d(context) => context.onscreen(),
373 OffscreenRenderingContext::BitmapRenderer(context) => context.onscreen(),
374 OffscreenRenderingContext::Detached => false,
375 }
376 }
377}