Skip to main content

script/dom/canvas/
canvas_context.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Common interfaces for Canvas Contexts
6
7use 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,
20};
21#[cfg(feature = "webgl")]
22use crate::dom::types::{WebGL2RenderingContext, WebGLRenderingContext};
23
24/// Non rooted variant of [`crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas`]
25#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
26#[derive(Clone, JSTraceable, MallocSizeOf)]
27pub(crate) enum HTMLCanvasElementOrOffscreenCanvas {
28    HTMLCanvasElement(Dom<HTMLCanvasElement>),
29    OffscreenCanvas(Dom<OffscreenCanvas>),
30}
31
32impl HTMLCanvasElementOrOffscreenCanvas {
33    pub(crate) fn mark_as_dirty(&self) {
34        if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) = self {
35            canvas.owner_document().mark_canvas_as_dirty(canvas);
36        }
37    }
38}
39
40impl From<&RootedHTMLCanvasElementOrOffscreenCanvas> for HTMLCanvasElementOrOffscreenCanvas {
41    /// Returns a traced version suitable for use as member of other DOM objects.
42    fn from(
43        value: &RootedHTMLCanvasElementOrOffscreenCanvas,
44    ) -> HTMLCanvasElementOrOffscreenCanvas {
45        match value {
46            RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
47                HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas.as_traced())
48            },
49            RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => {
50                HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas.as_traced())
51            },
52        }
53    }
54}
55
56impl From<&HTMLCanvasElementOrOffscreenCanvas> for RootedHTMLCanvasElementOrOffscreenCanvas {
57    /// Returns a rooted version suitable for use on the stack.
58    fn from(
59        value: &HTMLCanvasElementOrOffscreenCanvas,
60    ) -> RootedHTMLCanvasElementOrOffscreenCanvas {
61        match value {
62            HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
63                RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas.as_rooted())
64            },
65            HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => {
66                RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas.as_rooted())
67            },
68        }
69    }
70}
71
72pub(crate) trait CanvasContext {
73    type ID;
74
75    fn context_id(&self) -> Self::ID;
76
77    fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas>;
78
79    fn resize(&self);
80
81    // Resets the backing bitmap (to transparent or opaque black) without the
82    // context state reset.
83    // Used by OffscreenCanvas.transferToImageBitmap.
84    fn reset_bitmap(&self);
85
86    /// Returns none if area of canvas is zero.
87    ///
88    /// In case of other errors it returns cleared snapshot
89    fn get_image_data(&self) -> Option<Snapshot>;
90
91    fn origin_is_clean(&self) -> bool {
92        true
93    }
94
95    fn size(&self) -> Size2D<u32> {
96        self.canvas()
97            .map(|canvas| canvas.size())
98            .unwrap_or_default()
99    }
100
101    fn mark_as_dirty(&self);
102
103    fn onscreen(&self) -> bool {
104        let Some(canvas) = self.canvas() else {
105            return false;
106        };
107
108        match canvas {
109            RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
110                canvas.upcast::<Node>().is_connected()
111            },
112            // FIXME(34628): Offscreen canvases should be considered offscreen if a placeholder is set.
113            // <https://www.w3.org/TR/webgpu/#abstract-opdef-updating-the-rendering-of-a-webgpu-canvas>
114            RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(_) => false,
115        }
116    }
117}
118
119pub(crate) trait CanvasHelpers {
120    fn size(&self) -> Size2D<u32>;
121    fn canvas(&self) -> Option<DomRoot<HTMLCanvasElement>>;
122}
123
124impl CanvasHelpers for HTMLCanvasElementOrOffscreenCanvas {
125    fn size(&self) -> Size2D<u32> {
126        match self {
127            HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
128                canvas.get_size().cast()
129            },
130            HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(),
131        }
132    }
133
134    fn canvas(&self) -> Option<DomRoot<HTMLCanvasElement>> {
135        match self {
136            HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
137                Some(canvas.as_rooted())
138            },
139            HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.placeholder(),
140        }
141    }
142}
143
144impl CanvasHelpers for RootedHTMLCanvasElementOrOffscreenCanvas {
145    fn size(&self) -> Size2D<u32> {
146        match self {
147            RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
148                canvas.get_size().cast()
149            },
150            RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(),
151        }
152    }
153
154    fn canvas(&self) -> Option<DomRoot<HTMLCanvasElement>> {
155        match self {
156            RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
157                Some(canvas.clone())
158            },
159            RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => {
160                canvas.placeholder()
161            },
162        }
163    }
164}
165
166/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::RenderingContext`]
167#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
168#[derive(Clone, JSTraceable, MallocSizeOf)]
169pub(crate) enum RenderingContext {
170    Placeholder(Dom<OffscreenCanvas>),
171    Context2d(Dom<CanvasRenderingContext2D>),
172    BitmapRenderer(Dom<ImageBitmapRenderingContext>),
173    #[cfg(feature = "webgl")]
174    WebGL(Dom<WebGLRenderingContext>),
175    #[cfg(feature = "webgl")]
176    WebGL2(Dom<WebGL2RenderingContext>),
177    #[cfg(feature = "webgpu")]
178    WebGPU(Dom<GPUCanvasContext>),
179}
180
181impl RenderingContext {
182    pub(crate) fn set_image_key(&self, image_key: ImageKey) {
183        match self {
184            RenderingContext::Placeholder(_) => {
185                unreachable!("Should never set an `ImageKey` on a Placeholder")
186            },
187            RenderingContext::Context2d(context) => context.set_image_key(image_key),
188            RenderingContext::BitmapRenderer(context) => context.set_image_key(image_key),
189            #[cfg(feature = "webgl")]
190            RenderingContext::WebGL(context) => context.set_image_key(image_key),
191            #[cfg(feature = "webgl")]
192            RenderingContext::WebGL2(context) => context.set_image_key(image_key),
193            #[cfg(feature = "webgpu")]
194            RenderingContext::WebGPU(context) => context.set_image_key(image_key),
195        }
196    }
197}
198
199impl CanvasContext for RenderingContext {
200    type ID = ();
201
202    fn context_id(&self) -> Self::ID {}
203
204    fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
205        match self {
206            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas.context()?.canvas(),
207            RenderingContext::Context2d(context) => context.canvas(),
208            RenderingContext::BitmapRenderer(context) => context.canvas(),
209            #[cfg(feature = "webgl")]
210            RenderingContext::WebGL(context) => context.canvas(),
211            #[cfg(feature = "webgl")]
212            RenderingContext::WebGL2(context) => context.canvas(),
213            #[cfg(feature = "webgpu")]
214            RenderingContext::WebGPU(context) => context.canvas(),
215        }
216    }
217
218    fn resize(&self) {
219        match self {
220            RenderingContext::Placeholder(offscreen_canvas) => {
221                if let Some(context) = offscreen_canvas.context() {
222                    context.resize()
223                }
224            },
225            RenderingContext::Context2d(context) => context.resize(),
226            RenderingContext::BitmapRenderer(context) => context.resize(),
227            #[cfg(feature = "webgl")]
228            RenderingContext::WebGL(context) => context.resize(),
229            #[cfg(feature = "webgl")]
230            RenderingContext::WebGL2(context) => context.resize(),
231            #[cfg(feature = "webgpu")]
232            RenderingContext::WebGPU(context) => context.resize(),
233        }
234    }
235
236    fn reset_bitmap(&self) {
237        match self {
238            RenderingContext::Placeholder(offscreen_canvas) => {
239                if let Some(context) = offscreen_canvas.context() {
240                    context.reset_bitmap()
241                }
242            },
243            RenderingContext::Context2d(context) => context.reset_bitmap(),
244            RenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
245            #[cfg(feature = "webgl")]
246            RenderingContext::WebGL(context) => context.reset_bitmap(),
247            #[cfg(feature = "webgl")]
248            RenderingContext::WebGL2(context) => context.reset_bitmap(),
249            #[cfg(feature = "webgpu")]
250            RenderingContext::WebGPU(context) => context.reset_bitmap(),
251        }
252    }
253
254    fn get_image_data(&self) -> Option<Snapshot> {
255        match self {
256            RenderingContext::Placeholder(offscreen_canvas) => {
257                offscreen_canvas.context()?.get_image_data()
258            },
259            RenderingContext::Context2d(context) => context.get_image_data(),
260            RenderingContext::BitmapRenderer(context) => context.get_image_data(),
261            #[cfg(feature = "webgl")]
262            RenderingContext::WebGL(context) => context.get_image_data(),
263            #[cfg(feature = "webgl")]
264            RenderingContext::WebGL2(context) => context.get_image_data(),
265            #[cfg(feature = "webgpu")]
266            RenderingContext::WebGPU(context) => context.get_image_data(),
267        }
268    }
269
270    fn origin_is_clean(&self) -> bool {
271        match self {
272            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
273                .context()
274                .is_none_or(|context| context.origin_is_clean()),
275            RenderingContext::Context2d(context) => context.origin_is_clean(),
276            RenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
277            #[cfg(feature = "webgl")]
278            RenderingContext::WebGL(context) => context.origin_is_clean(),
279            #[cfg(feature = "webgl")]
280            RenderingContext::WebGL2(context) => context.origin_is_clean(),
281            #[cfg(feature = "webgpu")]
282            RenderingContext::WebGPU(context) => context.origin_is_clean(),
283        }
284    }
285
286    fn size(&self) -> Size2D<u32> {
287        match self {
288            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
289                .context()
290                .map(|context| context.size())
291                .unwrap_or_default(),
292            RenderingContext::Context2d(context) => context.size(),
293            RenderingContext::BitmapRenderer(context) => context.size(),
294            #[cfg(feature = "webgl")]
295            RenderingContext::WebGL(context) => context.size(),
296            #[cfg(feature = "webgl")]
297            RenderingContext::WebGL2(context) => context.size(),
298            #[cfg(feature = "webgpu")]
299            RenderingContext::WebGPU(context) => context.size(),
300        }
301    }
302
303    fn mark_as_dirty(&self) {
304        match self {
305            RenderingContext::Placeholder(offscreen_canvas) => {
306                if let Some(context) = offscreen_canvas.context() {
307                    context.mark_as_dirty()
308                }
309            },
310            RenderingContext::Context2d(context) => context.mark_as_dirty(),
311            RenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
312            #[cfg(feature = "webgl")]
313            RenderingContext::WebGL(context) => context.mark_as_dirty(),
314            #[cfg(feature = "webgl")]
315            RenderingContext::WebGL2(context) => context.mark_as_dirty(),
316            #[cfg(feature = "webgpu")]
317            RenderingContext::WebGPU(context) => context.mark_as_dirty(),
318        }
319    }
320
321    fn onscreen(&self) -> bool {
322        match self {
323            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
324                .context()
325                .is_some_and(|context| context.onscreen()),
326            RenderingContext::Context2d(context) => context.onscreen(),
327            RenderingContext::BitmapRenderer(context) => context.onscreen(),
328            #[cfg(feature = "webgl")]
329            RenderingContext::WebGL(context) => context.onscreen(),
330            #[cfg(feature = "webgl")]
331            RenderingContext::WebGL2(context) => context.onscreen(),
332            #[cfg(feature = "webgpu")]
333            RenderingContext::WebGPU(context) => context.onscreen(),
334        }
335    }
336}
337
338/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::OffscreenRenderingContext`]
339#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
340#[derive(Clone, JSTraceable, MallocSizeOf)]
341pub(crate) enum OffscreenRenderingContext {
342    Context2d(Dom<OffscreenCanvasRenderingContext2D>),
343    BitmapRenderer(Dom<ImageBitmapRenderingContext>),
344    #[cfg(feature = "webgl")]
345    WebGL(Dom<WebGLRenderingContext>),
346    #[cfg(feature = "webgl")]
347    WebGL2(Dom<WebGL2RenderingContext>),
348    // #[cfg(feature = "webgpu")]
349    // WebGPU(Dom<GPUCanvasContext>),
350    Detached,
351}
352
353impl CanvasContext for OffscreenRenderingContext {
354    type ID = ();
355
356    fn context_id(&self) -> Self::ID {}
357
358    fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
359        match self {
360            OffscreenRenderingContext::Context2d(context) => context.canvas(),
361            OffscreenRenderingContext::BitmapRenderer(context) => context.canvas(),
362            #[cfg(feature = "webgl")]
363            OffscreenRenderingContext::WebGL(context) => context.canvas(),
364            #[cfg(feature = "webgl")]
365            OffscreenRenderingContext::WebGL2(context) => context.canvas(),
366            OffscreenRenderingContext::Detached => None,
367        }
368    }
369
370    fn resize(&self) {
371        match self {
372            OffscreenRenderingContext::Context2d(context) => context.resize(),
373            OffscreenRenderingContext::BitmapRenderer(context) => context.resize(),
374            #[cfg(feature = "webgl")]
375            OffscreenRenderingContext::WebGL(context) => context.resize(),
376            #[cfg(feature = "webgl")]
377            OffscreenRenderingContext::WebGL2(context) => context.resize(),
378            OffscreenRenderingContext::Detached => {},
379        }
380    }
381
382    fn reset_bitmap(&self) {
383        match self {
384            OffscreenRenderingContext::Context2d(context) => context.reset_bitmap(),
385            OffscreenRenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
386            #[cfg(feature = "webgl")]
387            OffscreenRenderingContext::WebGL(context) => context.reset_bitmap(),
388            #[cfg(feature = "webgl")]
389            OffscreenRenderingContext::WebGL2(context) => context.reset_bitmap(),
390            OffscreenRenderingContext::Detached => {},
391        }
392    }
393
394    fn get_image_data(&self) -> Option<Snapshot> {
395        match self {
396            OffscreenRenderingContext::Context2d(context) => context.get_image_data(),
397            OffscreenRenderingContext::BitmapRenderer(context) => context.get_image_data(),
398            #[cfg(feature = "webgl")]
399            OffscreenRenderingContext::WebGL(context) => context.get_image_data(),
400            #[cfg(feature = "webgl")]
401            OffscreenRenderingContext::WebGL2(context) => context.get_image_data(),
402            OffscreenRenderingContext::Detached => None,
403        }
404    }
405
406    fn origin_is_clean(&self) -> bool {
407        match self {
408            OffscreenRenderingContext::Context2d(context) => context.origin_is_clean(),
409            OffscreenRenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
410            #[cfg(feature = "webgl")]
411            OffscreenRenderingContext::WebGL(context) => context.origin_is_clean(),
412            #[cfg(feature = "webgl")]
413            OffscreenRenderingContext::WebGL2(context) => context.origin_is_clean(),
414            OffscreenRenderingContext::Detached => true,
415        }
416    }
417
418    fn size(&self) -> Size2D<u32> {
419        match self {
420            OffscreenRenderingContext::Context2d(context) => context.size(),
421            OffscreenRenderingContext::BitmapRenderer(context) => context.size(),
422            #[cfg(feature = "webgl")]
423            OffscreenRenderingContext::WebGL(context) => context.size(),
424            #[cfg(feature = "webgl")]
425            OffscreenRenderingContext::WebGL2(context) => context.size(),
426            OffscreenRenderingContext::Detached => Size2D::default(),
427        }
428    }
429
430    fn mark_as_dirty(&self) {
431        match self {
432            OffscreenRenderingContext::Context2d(context) => context.mark_as_dirty(),
433            OffscreenRenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
434            #[cfg(feature = "webgl")]
435            OffscreenRenderingContext::WebGL(context) => context.mark_as_dirty(),
436            #[cfg(feature = "webgl")]
437            OffscreenRenderingContext::WebGL2(context) => context.mark_as_dirty(),
438            OffscreenRenderingContext::Detached => {},
439        }
440    }
441
442    fn onscreen(&self) -> bool {
443        match self {
444            OffscreenRenderingContext::Context2d(context) => context.onscreen(),
445            OffscreenRenderingContext::BitmapRenderer(context) => context.onscreen(),
446            #[cfg(feature = "webgl")]
447            OffscreenRenderingContext::WebGL(context) => context.onscreen(),
448            #[cfg(feature = "webgl")]
449            OffscreenRenderingContext::WebGL2(context) => context.onscreen(),
450            OffscreenRenderingContext::Detached => false,
451        }
452    }
453}