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