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