Skip to main content

kozan_core/html/
html_video_element.rs

1//! `HTMLVideoElement` — a video playback element.
2//!
3//! Chrome equivalent: `HTMLVideoElement`.
4//! Both a `MediaElement` AND a `ReplacedElement` — it has intrinsic
5//! dimensions from the video resolution.
6//!
7//! # Chrome hierarchy
8//!
9//! ```text
10//! HTMLElement → HTMLMediaElement → HTMLVideoElement
11//! LayoutObject → LayoutBox → LayoutReplaced → LayoutImage → LayoutMedia → LayoutVideo
12//! ```
13
14use super::media_element::MediaElement;
15use super::replaced::{IntrinsicSizing, ReplacedElement};
16use crate::Handle;
17use kozan_macros::{Element, Props};
18
19/// A video playback element (`<video>`).
20///
21/// Chrome equivalent: `HTMLVideoElement`.
22/// Implements both `MediaElement` (playback) and `ReplacedElement` (intrinsic size).
23#[derive(Copy, Clone, Element)]
24#[element(tag = "video", data = VideoData)]
25pub struct HtmlVideoElement(Handle);
26
27/// Element-specific data for `<video>`.
28#[derive(Default, Clone, Props)]
29#[props(element = HtmlVideoElement)]
30#[non_exhaustive]
31pub struct VideoData {
32    /// The video's natural width in pixels.
33    #[prop]
34    pub video_width: f32,
35    /// The video's natural height in pixels.
36    #[prop]
37    pub video_height: f32,
38    /// The poster image URL (shown before playback starts).
39    #[prop]
40    pub poster: String,
41}
42
43impl MediaElement for HtmlVideoElement {}
44
45impl ReplacedElement for HtmlVideoElement {
46    fn intrinsic_sizing(&self) -> IntrinsicSizing {
47        let w = self.video_width();
48        let h = self.video_height();
49
50        if w > 0.0 && h > 0.0 {
51            IntrinsicSizing::from_size(w, h)
52        } else {
53            // Video metadata not loaded yet — default 300x150 per spec.
54            IntrinsicSizing::from_size(300.0, 150.0)
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::dom::document::Document;
63
64    #[test]
65    fn video_default_intrinsic_size() {
66        let doc = Document::new();
67        let video = doc.create::<HtmlVideoElement>();
68
69        // Default: 300x150 per HTML spec.
70        let sizing = video.intrinsic_sizing();
71        assert_eq!(sizing.width, Some(300.0));
72        assert_eq!(sizing.height, Some(150.0));
73    }
74
75    #[test]
76    fn video_with_dimensions() {
77        let doc = Document::new();
78        let video = doc.create::<HtmlVideoElement>();
79
80        video.set_video_width(1920.0);
81        video.set_video_height(1080.0);
82
83        let sizing = video.intrinsic_sizing();
84        assert_eq!(sizing.width, Some(1920.0));
85        assert_eq!(sizing.height, Some(1080.0));
86        assert!((sizing.aspect_ratio.unwrap() - 1.777).abs() < 0.01);
87    }
88
89    #[test]
90    fn video_poster() {
91        let doc = Document::new();
92        let video = doc.create::<HtmlVideoElement>();
93
94        video.set_poster("thumbnail.jpg");
95        assert_eq!(video.poster(), "thumbnail.jpg");
96    }
97}