1use 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;
12use js::error::throw_type_error;
13use js::rust::{HandleObject, HandleValue};
14use layout_api::HTMLCanvasData;
15use pixels::{EncodedImageType, Snapshot};
16use rustc_hash::FxHashMap;
17use script_bindings::cell::{DomRefCell, Ref};
18#[cfg(feature = "webgl")]
19use script_bindings::reflector::DomObject;
20use script_bindings::weakref::WeakRef;
21use servo_base::Epoch;
22#[cfg(feature = "webgl")]
23use servo_canvas_traits::webgl::{GLContextAttributes, WebGLVersion};
24use servo_constellation_traits::BlobImpl;
25#[cfg(feature = "webgpu")]
26use servo_constellation_traits::ScriptToConstellationMessage;
27use servo_media::streams::MediaStreamType;
28use servo_media::streams::registry::MediaStreamId;
29use style::attr::AttrValue;
30use webrender_api::ImageKey;
31
32use crate::canvas_context::{CanvasContext, RenderingContext};
33#[cfg(feature = "webgl")]
34use crate::conversions::Convert;
35use crate::dom::bindings::callback::{ExceptionHandling, RootedCallback, TracedCallback};
36use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DSettings;
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;
44use crate::dom::bindings::conversions::ConversionResult;
45use crate::dom::bindings::error::{Error, Fallible};
46use crate::dom::bindings::inheritance::Castable;
47use crate::dom::bindings::num::Finite;
48use crate::dom::bindings::refcounted::Trusted;
49use crate::dom::bindings::reflector::DomGlobal;
50use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom};
51use crate::dom::bindings::str::{DOMString, USVString};
52use crate::dom::blob::Blob;
53use crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D;
54use crate::dom::document::Document;
55use crate::dom::element::attributes::storage::AttrRef;
56use crate::dom::element::{AttributeMutation, Element};
57#[cfg(not(feature = "webgpu"))]
58use crate::dom::gpucanvascontext::GPUCanvasContext;
59use crate::dom::html::htmlelement::HTMLElement;
60use crate::dom::imagebitmaprenderingcontext::ImageBitmapRenderingContext;
61use crate::dom::mediastream::MediaStream;
62use crate::dom::mediastreamtrack::MediaStreamTrack;
63use crate::dom::node::virtualmethods::VirtualMethods;
64use crate::dom::node::{Node, NodeDamage, NodeTraits};
65use crate::dom::offscreencanvas::OffscreenCanvas;
66use crate::dom::values::UNSIGNED_LONG_MAX;
67#[cfg(feature = "webgl")]
68use crate::dom::webgl::webgl2renderingcontext::WebGL2RenderingContext;
69#[cfg(feature = "webgl")]
70use crate::dom::webgl::webglrenderingcontext::WebGLRenderingContext;
71#[cfg(feature = "webgpu")]
72use crate::dom::webgpu::gpucanvascontext::GPUCanvasContext;
73
74const DEFAULT_WIDTH: u32 = 300;
75const DEFAULT_HEIGHT: u32 = 150;
76
77#[dom_struct]
79pub(crate) struct HTMLCanvasElement {
80 htmlelement: HTMLElement,
81
82 context_mode: DomRefCell<Option<RenderingContext>>,
84
85 callback_id: Cell<u32>,
87
88 blob_callbacks: RefCell<FxHashMap<u32, TracedCallback<BlobCallback>>>,
90
91 #[no_trace]
95 image_key: Cell<Option<ImageKey>>,
96}
97
98impl HTMLCanvasElement {
99 fn new_inherited(
100 local_name: LocalName,
101 prefix: Option<Prefix>,
102 document: &Document,
103 ) -> HTMLCanvasElement {
104 HTMLCanvasElement {
105 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
106 context_mode: DomRefCell::new(None),
107 callback_id: Cell::new(0),
108 blob_callbacks: RefCell::new(FxHashMap::default()),
109 image_key: Default::default(),
110 }
111 }
112
113 pub(crate) fn new(
114 cx: &mut js::context::JSContext,
115 local_name: LocalName,
116 prefix: Option<Prefix>,
117 document: &Document,
118 proto: Option<HandleObject>,
119 ) -> DomRoot<HTMLCanvasElement> {
120 Node::reflect_weak_referenceable_node_with_proto(
121 cx,
122 Rc::new(HTMLCanvasElement::new_inherited(
123 local_name, prefix, document,
124 )),
125 document,
126 proto,
127 )
128 }
129
130 fn recreate_contexts_after_resize(&self) {
131 if let Some(ref context) = *self.context_mode.borrow() {
132 context.resize()
133 }
134 }
135
136 pub(crate) fn get_size(&self) -> Size2D<u32> {
137 Size2D::new(self.Width(), self.Height())
138 }
139
140 pub(crate) fn origin_is_clean(&self) -> bool {
141 match *self.context_mode.borrow() {
142 Some(ref context) => context.origin_is_clean(),
143 _ => true,
144 }
145 }
146
147 pub(crate) fn mark_as_dirty(&self) {
148 if let Some(ref context) = *self.context_mode.borrow() {
149 context.mark_as_dirty()
150 }
151 }
152
153 pub(crate) fn set_natural_width(&self, cx: &mut js::context::JSContext, value: u32) {
154 let value = if value > UNSIGNED_LONG_MAX {
155 DEFAULT_WIDTH
156 } else {
157 value
158 };
159 self.upcast::<Element>()
160 .set_attribute(cx, &html5ever::local_name!("width"), value.into());
161 }
162
163 pub(crate) fn set_natural_height(&self, cx: &mut js::context::JSContext, value: u32) {
164 let value = if value > UNSIGNED_LONG_MAX {
165 DEFAULT_HEIGHT
166 } else {
167 value
168 };
169 self.upcast::<Element>()
170 .set_attribute(cx, &html5ever::local_name!("height"), value.into());
171 }
172}
173
174impl LayoutDom<'_, HTMLCanvasElement> {
175 pub(crate) fn data(self) -> HTMLCanvasData {
176 let width_attr = self
177 .upcast::<Element>()
178 .get_attr_for_layout(&ns!(), &local_name!("width"));
179 let height_attr = self
180 .upcast::<Element>()
181 .get_attr_for_layout(&ns!(), &local_name!("height"));
182 HTMLCanvasData {
183 image_key: self.unsafe_get().image_key.get(),
184 width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()),
185 height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()),
186 }
187 }
188}
189
190impl HTMLCanvasElement {
191 pub(crate) fn context(&self) -> Option<Ref<'_, RenderingContext>> {
192 Ref::filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref()).ok()
193 }
194
195 fn set_rendering_context(
196 &self,
197 no_gc: &NoGC,
198 make_rendering_context: impl FnOnce() -> RenderingContext,
199 ) {
200 self.upcast::<Node>()
201 .dirty(no_gc, NodeDamage::ContentOrHeritage);
202 self.context_mode
203 .borrow_mut()
204 .replace(make_rendering_context());
205
206 let Some(rendering_context) = &*self.context_mode.borrow() else {
207 return;
208 };
209
210 let get_image_key = || self.owner_window().image_cache().get_image_key();
211 let image_key = match rendering_context {
212 RenderingContext::Placeholder(..) => None,
213 RenderingContext::Context2d(..) => get_image_key(),
214 RenderingContext::BitmapRenderer(..) => get_image_key(),
215 #[cfg(feature = "webgl")]
216 RenderingContext::WebGL(..) => get_image_key(),
217 #[cfg(feature = "webgl")]
218 RenderingContext::WebGL2(..) => get_image_key(),
219 #[cfg(feature = "webgpu")]
220 RenderingContext::WebGPU(..) => get_image_key(),
221 };
222 self.image_key.set(image_key);
223 if let Some(image_key) = image_key {
224 rendering_context.set_image_key(image_key);
225 }
226 }
227
228 fn get_or_init_2d_context(
230 &self,
231 cx: &mut js::context::JSContext,
232 options: HandleValue,
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
244 let settings = match CanvasRenderingContext2DSettings::new(cx, options) {
247 Ok(ConversionResult::Success(settings)) => settings,
248 Ok(ConversionResult::Failure(error)) => {
249 throw_type_error(cx, &error);
250 return None;
251 },
252 Err(()) => return None,
253 };
254 let context =
255 CanvasRenderingContext2D::new(cx, window.as_global_scope(), self, size, &settings)?;
256 self.set_rendering_context(cx.no_gc(), || {
257 RenderingContext::Context2d(Dom::from_ref(&*context))
258 });
259 Some(context)
260 }
261
262 fn get_or_init_bitmaprenderer_context(
264 &self,
265 cx: &mut js::context::JSContext,
266 ) -> Option<DomRoot<ImageBitmapRenderingContext>> {
267 if let Some(ctx) = self.context() {
270 return match *ctx {
271 RenderingContext::BitmapRenderer(ref ctx) => Some(DomRoot::from_ref(ctx)),
272 _ => None,
273 };
274 }
275
276 let canvas =
280 RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
281
282 let context = ImageBitmapRenderingContext::new(cx, &self.owner_global(), &canvas);
284 self.set_rendering_context(cx.no_gc(), || {
285 RenderingContext::BitmapRenderer(Dom::from_ref(&*context))
286 });
287
288 Some(context)
290 }
291
292 #[cfg(feature = "webgl")]
293 fn get_or_init_webgl_context(
294 &self,
295 cx: &mut js::context::JSContext,
296 options: HandleValue,
297 ) -> Option<DomRoot<WebGLRenderingContext>> {
298 if let Some(ctx) = self.context() {
299 return match *ctx {
300 RenderingContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)),
301 _ => None,
302 };
303 }
304 let window = self.owner_window();
305 let canvas =
306 RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
307 let size = self.get_size();
308 let attrs = Self::get_gl_attributes(cx, options)?;
309 let context =
310 WebGLRenderingContext::new(cx, &window, &canvas, WebGLVersion::WebGL1, size, attrs)?;
311 self.set_rendering_context(cx.no_gc(), || {
312 RenderingContext::WebGL(Dom::from_ref(&*context))
313 });
314 Some(context)
315 }
316
317 #[cfg(feature = "webgl")]
318 fn get_or_init_webgl2_context(
319 &self,
320 cx: &mut js::context::JSContext,
321 options: HandleValue,
322 ) -> Option<DomRoot<WebGL2RenderingContext>> {
323 if !WebGL2RenderingContext::is_webgl2_enabled(cx, self.global().reflector().get_jsobject())
324 {
325 return None;
326 }
327 if let Some(ctx) = self.context() {
328 return match *ctx {
329 RenderingContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)),
330 _ => None,
331 };
332 }
333 let window = self.owner_window();
334 let canvas =
335 RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
336 let size = self.get_size();
337 let attrs = Self::get_gl_attributes(cx, options)?;
338 let context = WebGL2RenderingContext::new(cx, &window, &canvas, size, attrs)?;
339 self.set_rendering_context(cx.no_gc(), || {
340 RenderingContext::WebGL2(Dom::from_ref(&*context))
341 });
342 Some(context)
343 }
344
345 #[cfg(not(feature = "webgpu"))]
346 fn get_or_init_webgpu_context(&self) -> Option<DomRoot<GPUCanvasContext>> {
347 None
348 }
349
350 #[cfg(feature = "webgpu")]
351 fn get_or_init_webgpu_context(
352 &self,
353 cx: &mut js::context::JSContext,
354 ) -> Option<DomRoot<GPUCanvasContext>> {
355 use servo_base::generic_channel;
356
357 if let Some(ctx) = self.context() {
358 return match *ctx {
359 RenderingContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)),
360 _ => None,
361 };
362 }
363 let (sender, receiver) = generic_channel::channel().unwrap();
364 let global_scope = self.owner_global();
365 let _ = global_scope
366 .script_to_constellation_chan()
367 .send(ScriptToConstellationMessage::GetWebGPUChan(sender));
368 receiver
369 .recv()
370 .expect("Failed to get WebGPU channel")
371 .map(|channel| {
372 let context = GPUCanvasContext::new(cx, &global_scope, self, channel);
373 self.set_rendering_context(cx.no_gc(), || {
374 RenderingContext::WebGPU(Dom::from_ref(&*context))
375 });
376 context
377 })
378 }
379
380 #[cfg(feature = "webgl")]
381 fn get_gl_attributes(
382 cx: &mut js::context::JSContext,
383 options: HandleValue,
384 ) -> Option<GLContextAttributes> {
385 match WebGLContextAttributes::new(cx, options) {
386 Ok(ConversionResult::Success(attrs)) => Some(attrs.convert()),
387 Ok(ConversionResult::Failure(error)) => {
388 throw_type_error(cx, &error);
389 None
390 },
391 _ => {
392 debug!("Unexpected error on conversion of WebGLContextAttributes");
393 None
394 },
395 }
396 }
397
398 pub(crate) fn is_valid(&self) -> bool {
399 self.Height() != 0 && self.Width() != 0
400 }
401
402 pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
403 match self.context_mode.borrow().as_ref() {
404 Some(context) => context.get_image_data(),
405 None => {
406 let size = self.get_size();
407 if size.is_empty() ||
408 pixels::compute_rgba8_byte_length_if_within_limit(
409 size.width as usize,
410 size.height as usize,
411 )
412 .is_none()
413 {
414 None
415 } else {
416 Some(Snapshot::cleared(size.cast()))
417 }
418 },
419 }
420 }
421
422 fn maybe_quality(quality: HandleValue) -> Option<f64> {
423 if quality.is_number() {
424 Some(quality.to_number())
425 } else {
426 None
427 }
428 }
429
430 pub(crate) fn update_rendering(&self, epoch: Epoch) -> Option<ImageKey> {
431 let context = self.context()?;
432 let image_key = self.image_key.get()?;
433 let pending = match &*context {
434 RenderingContext::Placeholder(..) => false,
435 RenderingContext::Context2d(context) => context.update_rendering(epoch),
436 RenderingContext::BitmapRenderer(context) => context.update_rendering(epoch),
437 #[cfg(feature = "webgl")]
438 RenderingContext::WebGL(context) => context.update_rendering(epoch),
439 #[cfg(feature = "webgl")]
440 RenderingContext::WebGL2(context) => context.base_context().update_rendering(epoch),
441 #[cfg(feature = "webgpu")]
442 RenderingContext::WebGPU(context) => context.update_rendering(epoch),
443 };
444
445 if pending {
446 return Some(image_key);
447 }
448 None
449 }
450}
451
452impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
453 make_uint_getter!(Width, "width", DEFAULT_WIDTH);
455
456 fn SetWidth(&self, cx: &mut js::context::JSContext, value: u32) -> Fallible<()> {
458 if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
462 return Err(Error::InvalidState(Some(
463 "Canvas element's context mode is set to placeholder: Cannot set width".into(),
464 )));
465 }
466
467 let value = if value > UNSIGNED_LONG_MAX {
468 DEFAULT_WIDTH
469 } else {
470 value
471 };
472 self.upcast::<Element>()
473 .set_attribute(cx, &html5ever::local_name!("width"), value.into());
474 Ok(())
475 }
476
477 make_uint_getter!(Height, "height", DEFAULT_HEIGHT);
479
480 fn SetHeight(&self, cx: &mut js::context::JSContext, value: u32) -> Fallible<()> {
482 if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
486 return Err(Error::InvalidState(Some(
487 "Canvas element's context mode is set to placeholder: Cannot set height".into(),
488 )));
489 }
490
491 let value = if value > UNSIGNED_LONG_MAX {
492 DEFAULT_HEIGHT
493 } else {
494 value
495 };
496 self.upcast::<Element>()
497 .set_attribute(cx, &html5ever::local_name!("height"), value.into());
498 Ok(())
499 }
500
501 fn GetContext(
503 &self,
504 cx: &mut js::context::JSContext,
505 id: DOMString,
506 options: HandleValue,
507 ) -> Fallible<Option<RootedRenderingContext>> {
508 let options = if options.get().is_object() {
510 options
511 } else {
512 HandleValue::null()
513 };
514
515 if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
517 return Err(Error::InvalidState(Some(
518 "Canvas element's context mode is set to placeholder: Cannot get context".into(),
519 )));
520 }
521
522 Ok(match &*id.str() {
523 "2d" => self
524 .get_or_init_2d_context(cx, options)
525 .map(RootedRenderingContext::CanvasRenderingContext2D),
526 "bitmaprenderer" => self
527 .get_or_init_bitmaprenderer_context(cx)
528 .map(RootedRenderingContext::ImageBitmapRenderingContext),
529 #[cfg(feature = "webgl")]
530 "webgl" | "experimental-webgl" => self
531 .get_or_init_webgl_context(cx, options)
532 .map(RootedRenderingContext::WebGLRenderingContext),
533 #[cfg(feature = "webgl")]
534 "webgl2" | "experimental-webgl2" => self
535 .get_or_init_webgl2_context(cx, options)
536 .map(RootedRenderingContext::WebGL2RenderingContext),
537 #[cfg(feature = "webgpu")]
538 "webgpu" => self
539 .get_or_init_webgpu_context(cx)
540 .map(RootedRenderingContext::GPUCanvasContext),
541 _ => None,
542 })
543 }
544
545 fn ToDataURL(&self, mime_type: DOMString, quality: HandleValue) -> Fallible<USVString> {
547 if !self.origin_is_clean() {
550 return Err(Error::Security(Some("Canvas bitmap is not clean".into())));
551 }
552
553 if self.Width() == 0 || self.Height() == 0 {
558 return Ok(USVString("data:,".into()));
559 }
560
561 let Some(mut snapshot) = self.get_image_data() else {
564 return Ok(USVString("data:,".into()));
565 };
566
567 let image_type = EncodedImageType::from(&mime_type.str() as &str);
568
569 let mut url = format!("data:{};base64,", image_type.as_mime_type());
570
571 let mut encoder = base64::write::EncoderStringWriter::from_consumer(
572 &mut url,
573 &base64::engine::general_purpose::STANDARD,
574 );
575
576 if snapshot
577 .encode_for_mime_type(&image_type, Self::maybe_quality(quality), &mut encoder)
578 .is_err()
579 {
580 return Ok(USVString("data:,".into()));
582 }
583
584 encoder.into_inner();
586 Ok(USVString(url))
587 }
588
589 fn ToBlob(
591 &self,
592 callback: RootedCallback<BlobCallback>,
593 mime_type: DOMString,
594 quality: HandleValue,
595 ) -> Fallible<()> {
596 if !self.origin_is_clean() {
600 return Err(Error::Security(Some("Canvas bitmap is not clean".into())));
601 }
602
603 let result = if self.Width() == 0 || self.Height() == 0 {
608 None
609 } else {
610 self.get_image_data()
611 };
612
613 let this = Trusted::new(self);
614 let callback_id = self.callback_id.get().wrapping_add(1);
615 self.callback_id.set(callback_id);
616
617 self.blob_callbacks
618 .borrow_mut()
619 .insert(callback_id, callback.to_traced());
620 let quality = Self::maybe_quality(quality);
621 let image_type = EncodedImageType::from(&mime_type.str() as &str);
622
623 self.global()
624 .task_manager()
625 .canvas_blob_task_source()
626 .queue(task!(to_blob: move |cx| {
627 let this = this.root();
628 let Some(callback) = &this.blob_callbacks.borrow_mut().remove(&callback_id) else {
629 return error!("Expected blob callback, but found none!");
630 };
631
632 let Some(mut snapshot) = result else {
633 let _ = callback.Call__(cx, None, ExceptionHandling::Report);
634 return;
635 };
636
637 let mut encoded: Vec<u8> = vec![];
642 let blob_impl;
643 let blob;
644 let result = match snapshot.encode_for_mime_type(&image_type, quality, &mut encoded) {
645 Ok(..) => {
646 blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type().to_owned());
650 blob = Blob::new(cx, &this.global(), blob_impl);
651 Some(&*blob)
652 }
653 Err(..) => None,
654 };
655
656 let _ = callback.Call__(cx, result, ExceptionHandling::Report);
658 }));
659
660 Ok(())
661 }
662
663 fn TransferControlToOffscreen(
665 &self,
666 cx: &mut js::context::JSContext,
667 ) -> Fallible<DomRoot<OffscreenCanvas>> {
668 if self.context_mode.borrow().is_some() {
669 return Err(Error::InvalidState(Some("Canvas element's context mode must not be set when transferring control to an offscreen canvas".into())));
672 };
673
674 let offscreen_canvas = OffscreenCanvas::new(
680 cx,
681 &self.global(),
682 None,
683 self.Width().into(),
684 self.Height().into(),
685 Some(WeakRef::new(self)),
686 );
687
688 self.set_rendering_context(cx.no_gc(), || {
690 RenderingContext::Placeholder(offscreen_canvas.as_traced())
691 });
692
693 Ok(offscreen_canvas)
695 }
696
697 fn CaptureStream(
699 &self,
700 cx: &mut js::context::JSContext,
701 _frame_request_rate: Option<Finite<f64>>,
702 ) -> DomRoot<MediaStream> {
703 let global = self.global();
704 let stream = MediaStream::new(cx, &global);
705 let track =
706 MediaStreamTrack::new(cx, &global, MediaStreamId::new(), MediaStreamType::Video);
707 stream.AddTrack(&track);
708 stream
709 }
710}
711
712impl VirtualMethods for HTMLCanvasElement {
713 fn super_type(&self) -> Option<&dyn VirtualMethods> {
714 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
715 }
716
717 fn attribute_mutated(
718 &self,
719 cx: &mut js::context::JSContext,
720 attr: AttrRef<'_>,
721 mutation: AttributeMutation,
722 ) {
723 self.super_type()
724 .unwrap()
725 .attribute_mutated(cx, attr, mutation);
726 match attr.local_name() {
727 &local_name!("width") | &local_name!("height") => {
728 self.recreate_contexts_after_resize();
729 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
730 },
731 _ => {},
732 };
733 }
734
735 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
736 match attr.local_name() {
737 &local_name!("width") | &local_name!("height") => true,
738 _ => self
739 .super_type()
740 .unwrap()
741 .attribute_affects_presentational_hints(attr),
742 }
743 }
744
745 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
746 match *name {
747 local_name!("width") => AttrValue::from_u32(value.into(), DEFAULT_WIDTH),
748 local_name!("height") => AttrValue::from_u32(value.into(), DEFAULT_HEIGHT),
749 _ => self
750 .super_type()
751 .unwrap()
752 .parse_plain_attribute(name, value),
753 }
754 }
755}
756
757#[cfg(feature = "webgl")]
758impl Convert<GLContextAttributes> for WebGLContextAttributes {
759 fn convert(self) -> GLContextAttributes {
760 GLContextAttributes {
761 alpha: self.alpha,
762 depth: self.depth,
763 stencil: self.stencil,
764 antialias: self.antialias,
765 premultiplied_alpha: self.premultipliedAlpha,
766 preserve_drawing_buffer: self.preserveDrawingBuffer,
767 }
768 }
769}