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