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