Skip to main content

script/dom/html/embedded_content/
htmlcanvaselement.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
5use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use euclid::default::Size2D;
10use html5ever::{LocalName, Prefix, local_name, ns};
11use js::context::NoGC;
12#[cfg(feature = "webgl")]
13use js::error::throw_type_error_safe;
14use js::rust::{HandleObject, HandleValue};
15use layout_api::HTMLCanvasData;
16use pixels::{EncodedImageType, Snapshot};
17use rustc_hash::FxHashMap;
18use script_bindings::cell::{DomRefCell, Ref};
19#[cfg(feature = "webgl")]
20use script_bindings::reflector::DomObject;
21use script_bindings::weakref::WeakRef;
22use servo_base::Epoch;
23#[cfg(feature = "webgl")]
24use servo_canvas_traits::webgl::{GLContextAttributes, WebGLVersion};
25use servo_constellation_traits::BlobImpl;
26#[cfg(feature = "webgpu")]
27use servo_constellation_traits::ScriptToConstellationMessage;
28use servo_media::streams::MediaStreamType;
29use servo_media::streams::registry::MediaStreamId;
30use style::attr::AttrValue;
31use webrender_api::ImageKey;
32
33use crate::canvas_context::{CanvasContext, RenderingContext};
34#[cfg(feature = "webgl")]
35use crate::conversions::Convert;
36use crate::dom::bindings::callback::ExceptionHandling;
37use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{
38    BlobCallback, HTMLCanvasElementMethods, RenderingContext as RootedRenderingContext,
39};
40use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods;
41#[cfg(feature = "webgl")]
42use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
43use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
44#[cfg(feature = "webgl")]
45use crate::dom::bindings::conversions::ConversionResult;
46use crate::dom::bindings::error::{Error, Fallible};
47use crate::dom::bindings::inheritance::Castable;
48use crate::dom::bindings::num::Finite;
49use crate::dom::bindings::refcounted::Trusted;
50use crate::dom::bindings::reflector::DomGlobal;
51use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom};
52use crate::dom::bindings::str::{DOMString, USVString};
53use crate::dom::blob::Blob;
54use crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D;
55use crate::dom::document::Document;
56use crate::dom::element::attributes::storage::AttrRef;
57use crate::dom::element::{AttributeMutation, Element};
58#[cfg(not(feature = "webgpu"))]
59use crate::dom::gpucanvascontext::GPUCanvasContext;
60use crate::dom::html::htmlelement::HTMLElement;
61use crate::dom::imagebitmaprenderingcontext::ImageBitmapRenderingContext;
62use crate::dom::mediastream::MediaStream;
63use crate::dom::mediastreamtrack::MediaStreamTrack;
64use crate::dom::node::virtualmethods::VirtualMethods;
65use crate::dom::node::{Node, NodeDamage, NodeTraits};
66use crate::dom::offscreencanvas::OffscreenCanvas;
67use crate::dom::values::UNSIGNED_LONG_MAX;
68#[cfg(feature = "webgl")]
69use crate::dom::webgl::webgl2renderingcontext::WebGL2RenderingContext;
70#[cfg(feature = "webgl")]
71use crate::dom::webgl::webglrenderingcontext::WebGLRenderingContext;
72#[cfg(feature = "webgpu")]
73use crate::dom::webgpu::gpucanvascontext::GPUCanvasContext;
74
75const DEFAULT_WIDTH: u32 = 300;
76const DEFAULT_HEIGHT: u32 = 150;
77
78/// <https://html.spec.whatwg.org/multipage/#htmlcanvaselement>
79#[dom_struct]
80pub(crate) struct HTMLCanvasElement {
81    htmlelement: HTMLElement,
82
83    /// <https://html.spec.whatwg.org/multipage/#concept-canvas-context-mode>
84    context_mode: DomRefCell<Option<RenderingContext>>,
85
86    /// This id along with [`Self::blob_callbacks`] are used to keep track of ongoing toBlob() calls.
87    callback_id: Cell<u32>,
88
89    /// This hashmap along with [`Self::callback_id`] are used to keep track of ongoing toBlob() calls.
90    #[conditional_malloc_size_of]
91    blob_callbacks: RefCell<FxHashMap<u32, Rc<BlobCallback>>>,
92
93    /// The [`ImageKey`] used to render this [`HTMLCanvasElement`] to the WebRender scene, if it
94    /// has a `RenderingContext`, otherwise `None`. Note that this key is owned by the `RenderingContext`
95    /// itself which will take care of cleaning it up.
96    #[no_trace]
97    image_key: Cell<Option<ImageKey>>,
98}
99
100impl HTMLCanvasElement {
101    fn new_inherited(
102        local_name: LocalName,
103        prefix: Option<Prefix>,
104        document: &Document,
105    ) -> HTMLCanvasElement {
106        HTMLCanvasElement {
107            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
108            context_mode: DomRefCell::new(None),
109            callback_id: Cell::new(0),
110            blob_callbacks: RefCell::new(FxHashMap::default()),
111            image_key: Default::default(),
112        }
113    }
114
115    pub(crate) fn new(
116        cx: &mut js::context::JSContext,
117        local_name: LocalName,
118        prefix: Option<Prefix>,
119        document: &Document,
120        proto: Option<HandleObject>,
121    ) -> DomRoot<HTMLCanvasElement> {
122        Node::reflect_weak_referenceable_node_with_proto(
123            cx,
124            Rc::new(HTMLCanvasElement::new_inherited(
125                local_name, prefix, document,
126            )),
127            document,
128            proto,
129        )
130    }
131
132    fn recreate_contexts_after_resize(&self) {
133        if let Some(ref context) = *self.context_mode.borrow() {
134            context.resize()
135        }
136    }
137
138    pub(crate) fn get_size(&self) -> Size2D<u32> {
139        Size2D::new(self.Width(), self.Height())
140    }
141
142    pub(crate) fn origin_is_clean(&self) -> bool {
143        match *self.context_mode.borrow() {
144            Some(ref context) => context.origin_is_clean(),
145            _ => true,
146        }
147    }
148
149    pub(crate) fn mark_as_dirty(&self) {
150        if let Some(ref context) = *self.context_mode.borrow() {
151            context.mark_as_dirty()
152        }
153    }
154
155    pub(crate) fn set_natural_width(&self, cx: &mut js::context::JSContext, value: u32) {
156        let value = if value > UNSIGNED_LONG_MAX {
157            DEFAULT_WIDTH
158        } else {
159            value
160        };
161        self.upcast::<Element>()
162            .set_attribute(cx, &html5ever::local_name!("width"), value.into());
163    }
164
165    pub(crate) fn set_natural_height(&self, cx: &mut js::context::JSContext, value: u32) {
166        let value = if value > UNSIGNED_LONG_MAX {
167            DEFAULT_HEIGHT
168        } else {
169            value
170        };
171        self.upcast::<Element>()
172            .set_attribute(cx, &html5ever::local_name!("height"), value.into());
173    }
174}
175
176impl LayoutDom<'_, HTMLCanvasElement> {
177    pub(crate) fn data(self) -> HTMLCanvasData {
178        let width_attr = self
179            .upcast::<Element>()
180            .get_attr_for_layout(&ns!(), &local_name!("width"));
181        let height_attr = self
182            .upcast::<Element>()
183            .get_attr_for_layout(&ns!(), &local_name!("height"));
184        HTMLCanvasData {
185            image_key: self.unsafe_get().image_key.get(),
186            width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()),
187            height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()),
188        }
189    }
190}
191
192impl HTMLCanvasElement {
193    pub(crate) fn context(&self) -> Option<Ref<'_, RenderingContext>> {
194        Ref::filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref()).ok()
195    }
196
197    fn set_rendering_context(
198        &self,
199        no_gc: &NoGC,
200        make_rendering_context: impl FnOnce() -> RenderingContext,
201    ) {
202        self.upcast::<Node>()
203            .dirty(no_gc, NodeDamage::ContentOrHeritage);
204        self.context_mode
205            .borrow_mut()
206            .replace(make_rendering_context());
207
208        let Some(rendering_context) = &*self.context_mode.borrow() else {
209            return;
210        };
211
212        let get_image_key = || self.owner_window().image_cache().get_image_key();
213        let image_key = match rendering_context {
214            RenderingContext::Placeholder(..) => None,
215            RenderingContext::Context2d(..) => get_image_key(),
216            RenderingContext::BitmapRenderer(..) => get_image_key(),
217            #[cfg(feature = "webgl")]
218            RenderingContext::WebGL(..) => get_image_key(),
219            #[cfg(feature = "webgl")]
220            RenderingContext::WebGL2(..) => get_image_key(),
221            #[cfg(feature = "webgpu")]
222            RenderingContext::WebGPU(..) => get_image_key(),
223        };
224        self.image_key.set(image_key);
225        if let Some(image_key) = image_key {
226            rendering_context.set_image_key(image_key);
227        }
228    }
229
230    fn get_or_init_2d_context(
231        &self,
232        cx: &mut js::context::JSContext,
233    ) -> Option<DomRoot<CanvasRenderingContext2D>> {
234        if let Some(ctx) = self.context() {
235            return match *ctx {
236                RenderingContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)),
237                _ => None,
238            };
239        }
240
241        let window = self.owner_window();
242        let size = self.get_size();
243        let context = CanvasRenderingContext2D::new(cx, window.as_global_scope(), self, size)?;
244        self.set_rendering_context(cx.no_gc(), || {
245            RenderingContext::Context2d(Dom::from_ref(&*context))
246        });
247        Some(context)
248    }
249
250    /// <https://html.spec.whatwg.org/multipage/#canvas-context-bitmaprenderer>
251    fn get_or_init_bitmaprenderer_context(
252        &self,
253        cx: &mut js::context::JSContext,
254    ) -> Option<DomRoot<ImageBitmapRenderingContext>> {
255        // Return the same object as was returned the last time the method was
256        // invoked with this same first argument.
257        if let Some(ctx) = self.context() {
258            return match *ctx {
259                RenderingContext::BitmapRenderer(ref ctx) => Some(DomRoot::from_ref(ctx)),
260                _ => None,
261            };
262        }
263
264        // Step 1. Let context be the result of running the
265        // ImageBitmapRenderingContext creation algorithm given this and
266        // options.
267        let canvas =
268            RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
269
270        // Step 2. Set this's context mode to bitmaprenderer.
271        let context = ImageBitmapRenderingContext::new(cx, &self.owner_global(), &canvas);
272        self.set_rendering_context(cx.no_gc(), || {
273            RenderingContext::BitmapRenderer(Dom::from_ref(&*context))
274        });
275
276        // Step 3. Return context.
277        Some(context)
278    }
279
280    #[cfg(feature = "webgl")]
281    fn get_or_init_webgl_context(
282        &self,
283        cx: &mut js::context::JSContext,
284        options: HandleValue,
285    ) -> Option<DomRoot<WebGLRenderingContext>> {
286        if let Some(ctx) = self.context() {
287            return match *ctx {
288                RenderingContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)),
289                _ => None,
290            };
291        }
292        let window = self.owner_window();
293        let canvas =
294            RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
295        let size = self.get_size();
296        let attrs = Self::get_gl_attributes(cx, options)?;
297        let context =
298            WebGLRenderingContext::new(cx, &window, &canvas, WebGLVersion::WebGL1, size, attrs)?;
299        self.set_rendering_context(cx.no_gc(), || {
300            RenderingContext::WebGL(Dom::from_ref(&*context))
301        });
302        Some(context)
303    }
304
305    #[cfg(feature = "webgl")]
306    fn get_or_init_webgl2_context(
307        &self,
308        cx: &mut js::context::JSContext,
309        options: HandleValue,
310    ) -> Option<DomRoot<WebGL2RenderingContext>> {
311        if !WebGL2RenderingContext::is_webgl2_enabled(cx, self.global().reflector().get_jsobject())
312        {
313            return None;
314        }
315        if let Some(ctx) = self.context() {
316            return match *ctx {
317                RenderingContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)),
318                _ => None,
319            };
320        }
321        let window = self.owner_window();
322        let canvas =
323            RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
324        let size = self.get_size();
325        let attrs = Self::get_gl_attributes(cx, options)?;
326        let context = WebGL2RenderingContext::new(cx, &window, &canvas, size, attrs)?;
327        self.set_rendering_context(cx.no_gc(), || {
328            RenderingContext::WebGL2(Dom::from_ref(&*context))
329        });
330        Some(context)
331    }
332
333    #[cfg(not(feature = "webgpu"))]
334    fn get_or_init_webgpu_context(&self) -> Option<DomRoot<GPUCanvasContext>> {
335        None
336    }
337
338    #[cfg(feature = "webgpu")]
339    fn get_or_init_webgpu_context(
340        &self,
341        cx: &mut js::context::JSContext,
342    ) -> Option<DomRoot<GPUCanvasContext>> {
343        use servo_base::generic_channel;
344
345        if let Some(ctx) = self.context() {
346            return match *ctx {
347                RenderingContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)),
348                _ => None,
349            };
350        }
351        let (sender, receiver) = generic_channel::channel().unwrap();
352        let global_scope = self.owner_global();
353        let _ = global_scope
354            .script_to_constellation_chan()
355            .send(ScriptToConstellationMessage::GetWebGPUChan(sender));
356        receiver
357            .recv()
358            .expect("Failed to get WebGPU channel")
359            .map(|channel| {
360                let context = GPUCanvasContext::new(cx, &global_scope, self, channel);
361                self.set_rendering_context(cx.no_gc(), || {
362                    RenderingContext::WebGPU(Dom::from_ref(&*context))
363                });
364                context
365            })
366    }
367
368    #[cfg(feature = "webgl")]
369    fn get_gl_attributes(
370        cx: &mut js::context::JSContext,
371        options: HandleValue,
372    ) -> Option<GLContextAttributes> {
373        match WebGLContextAttributes::new(cx, options) {
374            Ok(ConversionResult::Success(attrs)) => Some(attrs.convert()),
375            Ok(ConversionResult::Failure(error)) => {
376                throw_type_error_safe(cx, &error);
377                None
378            },
379            _ => {
380                debug!("Unexpected error on conversion of WebGLContextAttributes");
381                None
382            },
383        }
384    }
385
386    pub(crate) fn is_valid(&self) -> bool {
387        self.Height() != 0 && self.Width() != 0
388    }
389
390    pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
391        match self.context_mode.borrow().as_ref() {
392            Some(context) => context.get_image_data(),
393            None => {
394                let size = self.get_size();
395                if size.is_empty() ||
396                    pixels::compute_rgba8_byte_length_if_within_limit(
397                        size.width as usize,
398                        size.height as usize,
399                    )
400                    .is_none()
401                {
402                    None
403                } else {
404                    Some(Snapshot::cleared(size.cast()))
405                }
406            },
407        }
408    }
409
410    fn maybe_quality(quality: HandleValue) -> Option<f64> {
411        if quality.is_number() {
412            Some(quality.to_number())
413        } else {
414            None
415        }
416    }
417
418    pub(crate) fn update_rendering(&self, epoch: Epoch) -> Option<ImageKey> {
419        let context = self.context()?;
420        let image_key = self.image_key.get()?;
421        let pending = match &*context {
422            RenderingContext::Placeholder(..) => false,
423            RenderingContext::Context2d(context) => context.update_rendering(epoch),
424            RenderingContext::BitmapRenderer(context) => context.update_rendering(epoch),
425            #[cfg(feature = "webgl")]
426            RenderingContext::WebGL(context) => context.update_rendering(epoch),
427            #[cfg(feature = "webgl")]
428            RenderingContext::WebGL2(context) => context.base_context().update_rendering(epoch),
429            #[cfg(feature = "webgpu")]
430            RenderingContext::WebGPU(context) => context.update_rendering(epoch),
431        };
432
433        if pending {
434            return Some(image_key);
435        }
436        None
437    }
438}
439
440impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
441    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
442    make_uint_getter!(Width, "width", DEFAULT_WIDTH);
443
444    /// <https://html.spec.whatwg.org/multipage/#dom-canvas-width>
445    fn SetWidth(&self, cx: &mut js::context::JSContext, value: u32) -> Fallible<()> {
446        // > When setting the value of the width or height attribute, if the context mode of the canvas element
447        // > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the
448        // > attribute's value unchanged.
449        if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
450            return Err(Error::InvalidState(Some(
451                "Canvas element's context mode is set to placeholder: Cannot set width".into(),
452            )));
453        }
454
455        let value = if value > UNSIGNED_LONG_MAX {
456            DEFAULT_WIDTH
457        } else {
458            value
459        };
460        self.upcast::<Element>()
461            .set_attribute(cx, &html5ever::local_name!("width"), value.into());
462        Ok(())
463    }
464
465    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
466    make_uint_getter!(Height, "height", DEFAULT_HEIGHT);
467
468    /// <https://html.spec.whatwg.org/multipage/#dom-canvas-height>
469    fn SetHeight(&self, cx: &mut js::context::JSContext, value: u32) -> Fallible<()> {
470        // > When setting the value of the width or height attribute, if the context mode of the canvas element
471        // > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the
472        // > attribute's value unchanged.
473        if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
474            return Err(Error::InvalidState(Some(
475                "Canvas element's context mode is set to placeholder: Cannot set height".into(),
476            )));
477        }
478
479        let value = if value > UNSIGNED_LONG_MAX {
480            DEFAULT_HEIGHT
481        } else {
482            value
483        };
484        self.upcast::<Element>()
485            .set_attribute(cx, &html5ever::local_name!("height"), value.into());
486        Ok(())
487    }
488
489    /// <https://html.spec.whatwg.org/multipage/#dom-canvas-getcontext>
490    fn GetContext(
491        &self,
492        cx: &mut js::context::JSContext,
493        id: DOMString,
494        options: HandleValue,
495    ) -> Fallible<Option<RootedRenderingContext>> {
496        // Always throw an InvalidState exception when the canvas is in Placeholder mode (See table in the spec).
497        if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
498            return Err(Error::InvalidState(Some(
499                "Canvas element's context mode is set to placeholder: Cannot get context".into(),
500            )));
501        }
502
503        Ok(match &*id.str() {
504            "2d" => self
505                .get_or_init_2d_context(cx)
506                .map(RootedRenderingContext::CanvasRenderingContext2D),
507            "bitmaprenderer" => self
508                .get_or_init_bitmaprenderer_context(cx)
509                .map(RootedRenderingContext::ImageBitmapRenderingContext),
510            #[cfg(feature = "webgl")]
511            "webgl" | "experimental-webgl" => self
512                .get_or_init_webgl_context(cx, options)
513                .map(RootedRenderingContext::WebGLRenderingContext),
514            #[cfg(feature = "webgl")]
515            "webgl2" | "experimental-webgl2" => self
516                .get_or_init_webgl2_context(cx, options)
517                .map(RootedRenderingContext::WebGL2RenderingContext),
518            #[cfg(feature = "webgpu")]
519            "webgpu" => self
520                .get_or_init_webgpu_context(cx)
521                .map(RootedRenderingContext::GPUCanvasContext),
522            _ => None,
523        })
524    }
525
526    /// <https://html.spec.whatwg.org/multipage/#dom-canvas-todataurl>
527    fn ToDataURL(&self, mime_type: DOMString, quality: HandleValue) -> Fallible<USVString> {
528        // Step 1: If this canvas element's bitmap's origin-clean flag is set to false,
529        // then throw a "SecurityError" DOMException.
530        if !self.origin_is_clean() {
531            return Err(Error::Security(Some("Canvas bitmap is not clean".into())));
532        }
533
534        // Step 2: If this canvas element's bitmap has no pixels (i.e. either its
535        // horizontal dimension or its vertical dimension is zero), then return the string
536        // "data:,". (This is the shortest data: URL; it represents the empty string in a
537        // text/plain resource.)
538        if self.Width() == 0 || self.Height() == 0 {
539            return Ok(USVString("data:,".into()));
540        }
541
542        // Step 3: Let file be a serialization of this canvas element's bitmap as a file,
543        // passing type and quality if given.
544        let Some(mut snapshot) = self.get_image_data() else {
545            return Ok(USVString("data:,".into()));
546        };
547
548        let image_type = EncodedImageType::from(&mime_type.str() as &str);
549
550        let mut url = format!("data:{};base64,", image_type.as_mime_type());
551
552        let mut encoder = base64::write::EncoderStringWriter::from_consumer(
553            &mut url,
554            &base64::engine::general_purpose::STANDARD,
555        );
556
557        if snapshot
558            .encode_for_mime_type(&image_type, Self::maybe_quality(quality), &mut encoder)
559            .is_err()
560        {
561            // Step 4. If file is null, then return "data:,".
562            return Ok(USVString("data:,".into()));
563        }
564
565        // Step 5. Return a data: URL representing file. [RFC2397]
566        encoder.into_inner();
567        Ok(USVString(url))
568    }
569
570    /// <https://html.spec.whatwg.org/multipage/#dom-canvas-toblob>
571    fn ToBlob(
572        &self,
573        callback: Rc<BlobCallback>,
574        mime_type: DOMString,
575        quality: HandleValue,
576    ) -> Fallible<()> {
577        // Step 1.
578        // If this canvas element's bitmap's origin-clean flag is set to false, then throw a
579        // "SecurityError" DOMException.
580        if !self.origin_is_clean() {
581            return Err(Error::Security(Some("Canvas bitmap is not clean".into())));
582        }
583
584        // Step 2. Let result be null.
585        // Step 3. If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension
586        // nor its vertical dimension is zero),
587        // then set result to a copy of this canvas element's bitmap.
588        let result = if self.Width() == 0 || self.Height() == 0 {
589            None
590        } else {
591            self.get_image_data()
592        };
593
594        let this = Trusted::new(self);
595        let callback_id = self.callback_id.get().wrapping_add(1);
596        self.callback_id.set(callback_id);
597
598        self.blob_callbacks
599            .borrow_mut()
600            .insert(callback_id, callback);
601        let quality = Self::maybe_quality(quality);
602        let image_type = EncodedImageType::from(&mime_type.str() as &str);
603
604        self.global()
605            .task_manager()
606            .canvas_blob_task_source()
607            .queue(task!(to_blob: move |cx| {
608                let this = this.root();
609                let Some(callback) = &this.blob_callbacks.borrow_mut().remove(&callback_id) else {
610                    return error!("Expected blob callback, but found none!");
611                };
612
613                let Some(mut snapshot) = result else {
614                    let _ = callback.Call__(cx, None, ExceptionHandling::Report);
615                    return;
616                };
617
618                // Step 4.1: If result is non-null, then set result to a serialization of
619                // result as a file with type and quality if given.
620                // Step 4.2: Queue an element task on the canvas blob serialization task
621                // source given the canvas element to run these steps:
622                let mut encoded: Vec<u8> = vec![];
623                let blob_impl;
624                let blob;
625                let result = match snapshot.encode_for_mime_type(&image_type, quality, &mut encoded) {
626                   Ok(..) => {
627                       // Step 4.2.1: If result is non-null, then set result to a new Blob
628                       // object, created in the relevant realm of this canvas element,
629                       // representing result. [FILEAPI]
630                       blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type().to_owned());
631                       blob = Blob::new(cx, &this.global(), blob_impl);
632                       Some(&*blob)
633                   }
634                   Err(..) => None,
635                };
636
637                // Step 4.2.2: Invoke callback with « result » and "report".
638                let _ = callback.Call__(cx, result, ExceptionHandling::Report);
639            }));
640
641        Ok(())
642    }
643
644    /// <https://html.spec.whatwg.org/multipage/#dom-canvas-transfercontroltooffscreen>
645    fn TransferControlToOffscreen(
646        &self,
647        cx: &mut js::context::JSContext,
648    ) -> Fallible<DomRoot<OffscreenCanvas>> {
649        if self.context_mode.borrow().is_some() {
650            // Step 1.
651            // If this canvas element's context mode is not set to none, throw an "InvalidStateError" DOMException.
652            return Err(Error::InvalidState(Some("Canvas element's context mode must not be set when transferring control to an offscreen canvas".into())));
653        };
654
655        // Step 2.
656        // Let offscreenCanvas be a new OffscreenCanvas object with its width and height equal to the values of
657        // the width and height content attributes of this canvas element.
658        // Step 3.
659        // Set the placeholder canvas element of offscreenCanvas to a weak reference to this canvas element.
660        let offscreen_canvas = OffscreenCanvas::new(
661            cx,
662            &self.global(),
663            None,
664            self.Width().into(),
665            self.Height().into(),
666            Some(WeakRef::new(self)),
667        );
668
669        // Step 4. Set this canvas element's context mode to placeholder.
670        self.set_rendering_context(cx.no_gc(), || {
671            RenderingContext::Placeholder(offscreen_canvas.as_traced())
672        });
673
674        // Step 5. Return offscreenCanvas.
675        Ok(offscreen_canvas)
676    }
677
678    /// <https://w3c.github.io/mediacapture-fromelement/#dom-htmlcanvaselement-capturestream>
679    fn CaptureStream(
680        &self,
681        cx: &mut js::context::JSContext,
682        _frame_request_rate: Option<Finite<f64>>,
683    ) -> DomRoot<MediaStream> {
684        let global = self.global();
685        let stream = MediaStream::new(cx, &global);
686        let track =
687            MediaStreamTrack::new(cx, &global, MediaStreamId::new(), MediaStreamType::Video);
688        stream.AddTrack(&track);
689        stream
690    }
691}
692
693impl VirtualMethods for HTMLCanvasElement {
694    fn super_type(&self) -> Option<&dyn VirtualMethods> {
695        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
696    }
697
698    fn attribute_mutated(
699        &self,
700        cx: &mut js::context::JSContext,
701        attr: AttrRef<'_>,
702        mutation: AttributeMutation,
703    ) {
704        self.super_type()
705            .unwrap()
706            .attribute_mutated(cx, attr, mutation);
707        match attr.local_name() {
708            &local_name!("width") | &local_name!("height") => {
709                self.recreate_contexts_after_resize();
710                self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
711            },
712            _ => {},
713        };
714    }
715
716    fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
717        match attr.local_name() {
718            &local_name!("width") | &local_name!("height") => true,
719            _ => self
720                .super_type()
721                .unwrap()
722                .attribute_affects_presentational_hints(attr),
723        }
724    }
725
726    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
727        match *name {
728            local_name!("width") => AttrValue::from_u32(value.into(), DEFAULT_WIDTH),
729            local_name!("height") => AttrValue::from_u32(value.into(), DEFAULT_HEIGHT),
730            _ => self
731                .super_type()
732                .unwrap()
733                .parse_plain_attribute(name, value),
734        }
735    }
736}
737
738#[cfg(feature = "webgl")]
739impl Convert<GLContextAttributes> for WebGLContextAttributes {
740    fn convert(self) -> GLContextAttributes {
741        GLContextAttributes {
742            alpha: self.alpha,
743            depth: self.depth,
744            stencil: self.stencil,
745            antialias: self.antialias,
746            premultiplied_alpha: self.premultipliedAlpha,
747            preserve_drawing_buffer: self.preserveDrawingBuffer,
748        }
749    }
750}