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, allow(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(_) => {
186                unreachable!("Should never set an `ImageKey` on a ImageBitmapRenderingContext")
187            },
188            RenderingContext::WebGL(context) => context.set_image_key(image_key),
189            RenderingContext::WebGL2(context) => context.set_image_key(image_key),
190            #[cfg(feature = "webgpu")]
191            RenderingContext::WebGPU(context) => context.set_image_key(image_key),
192        }
193    }
194}
195
196impl CanvasContext for RenderingContext {
197    type ID = ();
198
199    fn context_id(&self) -> Self::ID {}
200
201    fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
202        match self {
203            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas.context()?.canvas(),
204            RenderingContext::Context2d(context) => context.canvas(),
205            RenderingContext::BitmapRenderer(context) => context.canvas(),
206            RenderingContext::WebGL(context) => context.canvas(),
207            RenderingContext::WebGL2(context) => context.canvas(),
208            #[cfg(feature = "webgpu")]
209            RenderingContext::WebGPU(context) => context.canvas(),
210        }
211    }
212
213    fn resize(&self) {
214        match self {
215            RenderingContext::Placeholder(offscreen_canvas) => {
216                if let Some(context) = offscreen_canvas.context() {
217                    context.resize()
218                }
219            },
220            RenderingContext::Context2d(context) => context.resize(),
221            RenderingContext::BitmapRenderer(context) => context.resize(),
222            RenderingContext::WebGL(context) => context.resize(),
223            RenderingContext::WebGL2(context) => context.resize(),
224            #[cfg(feature = "webgpu")]
225            RenderingContext::WebGPU(context) => context.resize(),
226        }
227    }
228
229    fn reset_bitmap(&self) {
230        match self {
231            RenderingContext::Placeholder(offscreen_canvas) => {
232                if let Some(context) = offscreen_canvas.context() {
233                    context.reset_bitmap()
234                }
235            },
236            RenderingContext::Context2d(context) => context.reset_bitmap(),
237            RenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
238            RenderingContext::WebGL(context) => context.reset_bitmap(),
239            RenderingContext::WebGL2(context) => context.reset_bitmap(),
240            #[cfg(feature = "webgpu")]
241            RenderingContext::WebGPU(context) => context.reset_bitmap(),
242        }
243    }
244
245    fn get_image_data(&self) -> Option<Snapshot> {
246        match self {
247            RenderingContext::Placeholder(offscreen_canvas) => {
248                offscreen_canvas.context()?.get_image_data()
249            },
250            RenderingContext::Context2d(context) => context.get_image_data(),
251            RenderingContext::BitmapRenderer(context) => context.get_image_data(),
252            RenderingContext::WebGL(context) => context.get_image_data(),
253            RenderingContext::WebGL2(context) => context.get_image_data(),
254            #[cfg(feature = "webgpu")]
255            RenderingContext::WebGPU(context) => context.get_image_data(),
256        }
257    }
258
259    fn origin_is_clean(&self) -> bool {
260        match self {
261            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
262                .context()
263                .is_none_or(|context| context.origin_is_clean()),
264            RenderingContext::Context2d(context) => context.origin_is_clean(),
265            RenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
266            RenderingContext::WebGL(context) => context.origin_is_clean(),
267            RenderingContext::WebGL2(context) => context.origin_is_clean(),
268            #[cfg(feature = "webgpu")]
269            RenderingContext::WebGPU(context) => context.origin_is_clean(),
270        }
271    }
272
273    fn size(&self) -> Size2D<u32> {
274        match self {
275            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
276                .context()
277                .map(|context| context.size())
278                .unwrap_or_default(),
279            RenderingContext::Context2d(context) => context.size(),
280            RenderingContext::BitmapRenderer(context) => context.size(),
281            RenderingContext::WebGL(context) => context.size(),
282            RenderingContext::WebGL2(context) => context.size(),
283            #[cfg(feature = "webgpu")]
284            RenderingContext::WebGPU(context) => context.size(),
285        }
286    }
287
288    fn mark_as_dirty(&self) {
289        match self {
290            RenderingContext::Placeholder(offscreen_canvas) => {
291                if let Some(context) = offscreen_canvas.context() {
292                    context.mark_as_dirty()
293                }
294            },
295            RenderingContext::Context2d(context) => context.mark_as_dirty(),
296            RenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
297            RenderingContext::WebGL(context) => context.mark_as_dirty(),
298            RenderingContext::WebGL2(context) => context.mark_as_dirty(),
299            #[cfg(feature = "webgpu")]
300            RenderingContext::WebGPU(context) => context.mark_as_dirty(),
301        }
302    }
303
304    fn onscreen(&self) -> bool {
305        match self {
306            RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas
307                .context()
308                .is_some_and(|context| context.onscreen()),
309            RenderingContext::Context2d(context) => context.onscreen(),
310            RenderingContext::BitmapRenderer(context) => context.onscreen(),
311            RenderingContext::WebGL(context) => context.onscreen(),
312            RenderingContext::WebGL2(context) => context.onscreen(),
313            #[cfg(feature = "webgpu")]
314            RenderingContext::WebGPU(context) => context.onscreen(),
315        }
316    }
317}
318
319/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::OffscreenRenderingContext`]
320#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
321#[derive(Clone, JSTraceable, MallocSizeOf)]
322pub(crate) enum OffscreenRenderingContext {
323    Context2d(Dom<OffscreenCanvasRenderingContext2D>),
324    BitmapRenderer(Dom<ImageBitmapRenderingContext>),
325    // WebGL(Dom<WebGLRenderingContext>),
326    // WebGL2(Dom<WebGL2RenderingContext>),
327    // #[cfg(feature = "webgpu")]
328    // WebGPU(Dom<GPUCanvasContext>),
329    Detached,
330}
331
332impl CanvasContext for OffscreenRenderingContext {
333    type ID = ();
334
335    fn context_id(&self) -> Self::ID {}
336
337    fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
338        match self {
339            OffscreenRenderingContext::Context2d(context) => context.canvas(),
340            OffscreenRenderingContext::BitmapRenderer(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::Detached => {},
350        }
351    }
352
353    fn reset_bitmap(&self) {
354        match self {
355            OffscreenRenderingContext::Context2d(context) => context.reset_bitmap(),
356            OffscreenRenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
357            OffscreenRenderingContext::Detached => {},
358        }
359    }
360
361    fn get_image_data(&self) -> Option<Snapshot> {
362        match self {
363            OffscreenRenderingContext::Context2d(context) => context.get_image_data(),
364            OffscreenRenderingContext::BitmapRenderer(context) => context.get_image_data(),
365            OffscreenRenderingContext::Detached => None,
366        }
367    }
368
369    fn origin_is_clean(&self) -> bool {
370        match self {
371            OffscreenRenderingContext::Context2d(context) => context.origin_is_clean(),
372            OffscreenRenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
373            OffscreenRenderingContext::Detached => true,
374        }
375    }
376
377    fn size(&self) -> Size2D<u32> {
378        match self {
379            OffscreenRenderingContext::Context2d(context) => context.size(),
380            OffscreenRenderingContext::BitmapRenderer(context) => context.size(),
381            OffscreenRenderingContext::Detached => Size2D::default(),
382        }
383    }
384
385    fn mark_as_dirty(&self) {
386        match self {
387            OffscreenRenderingContext::Context2d(context) => context.mark_as_dirty(),
388            OffscreenRenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
389            OffscreenRenderingContext::Detached => {},
390        }
391    }
392
393    fn onscreen(&self) -> bool {
394        match self {
395            OffscreenRenderingContext::Context2d(context) => context.onscreen(),
396            OffscreenRenderingContext::BitmapRenderer(context) => context.onscreen(),
397            OffscreenRenderingContext::Detached => false,
398        }
399    }
400}