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, WebGL2RenderingContext, WebGLRenderingContext,
20};
21
22/// Non rooted variant of [`crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas`]
23#[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    /// Returns a traced version suitable for use as member of other DOM objects.
40    #[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    /// Returns a rooted version suitable for use on the stack.
57    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    // Resets the backing bitmap (to transparent or opaque black) without the
81    // context state reset.
82    // Used by OffscreenCanvas.transferToImageBitmap.
83    fn reset_bitmap(&self);
84
85    /// Returns none if area of canvas is zero.
86    ///
87    /// In case of other errors it returns cleared snapshot
88    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            // FIXME(34628): Offscreen canvases should be considered offscreen if a placeholder is set.
112            // <https://www.w3.org/TR/webgpu/#abstract-opdef-updating-the-rendering-of-a-webgpu-canvas>
113            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/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::RenderingContext`]
166#[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/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::OffscreenRenderingContext`]
318#[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    // #[cfg(feature = "webgpu")]
326    // WebGPU(Dom<GPUCanvasContext>),
327    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}