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