Skip to main content

script/dom/webvtt/
vttcue.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;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use html5ever::local_name;
10use js::context::JSContext;
11use js::rust::HandleObject;
12use script_bindings::cell::DomRefCell;
13use script_bindings::domstring::TracedDOMString;
14use script_bindings::reflector::reflect_dom_object_with_proto;
15use servo_webvtt::cue::settings::{
16    WebVttCue, WebVttCueSize, WebVttLineAlignment, WebVttLineAndPositionSetting,
17    WebVttPositionAlignment, WebVttSnapToLines, WebVttTextAlignment, WebVttWritingDirection,
18};
19use servo_webvtt::cue::text::{
20    WebVTTNodeObject, WebVTTNodeObjectIterable, WebVTTNodeObjectIteratorDirection,
21    WebVTTNodeObjectKind, webvtt_cue_text_parsing_rules,
22};
23
24use crate::conversions::Convert;
25use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
26use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
27use crate::dom::bindings::codegen::Bindings::VTTCueBinding::{
28    self, AlignSetting, AutoKeyword, DirectionSetting, LineAlignSetting, PositionAlignSetting,
29    VTTCueMethods,
30};
31use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
32use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
33use crate::dom::bindings::inheritance::Castable;
34use crate::dom::bindings::num::Finite;
35use crate::dom::bindings::reflector::DomGlobal;
36use crate::dom::bindings::root::{DomRoot, MutNullableDom};
37use crate::dom::bindings::str::DOMString;
38use crate::dom::documentfragment::DocumentFragment;
39use crate::dom::node::Node;
40use crate::dom::texttrack::TextTrack;
41use crate::dom::texttrackcue::TextTrackCue;
42use crate::dom::vttregion::VTTRegion;
43use crate::dom::webvtt::rules_for_rendering::RulesForUpdatingTheTextTrackRendering;
44use crate::dom::window::Window;
45
46#[dom_struct]
47pub(crate) struct VTTCue {
48    texttrackcue: TextTrackCue,
49    region: MutNullableDom<VTTRegion>,
50    vertical: Cell<DirectionSetting>,
51    snap_to_lines: Cell<bool>,
52    line: DomRefCell<LineAndPositionSetting>,
53    line_align: Cell<LineAlignSetting>,
54    position: DomRefCell<LineAndPositionSetting>,
55    position_align: Cell<PositionAlignSetting>,
56    size: Cell<f64>,
57    align: Cell<AlignSetting>,
58    text: DomRefCell<TracedDOMString>,
59}
60
61impl VTTCue {
62    #[expect(clippy::too_many_arguments)]
63    fn new_inherited(
64        start_time: f64,
65        end_time: f64,
66        text: DOMString,
67        id: DOMString,
68        vertical: DirectionSetting,
69        snap_to_lines: bool,
70        line: LineAndPositionSetting,
71        line_align: LineAlignSetting,
72        position: LineAndPositionSetting,
73        position_align: PositionAlignSetting,
74        size: f64,
75        align: AlignSetting,
76        track: Option<&TextTrack>,
77    ) -> Self {
78        VTTCue {
79            texttrackcue: TextTrackCue::new_inherited(
80                id,
81                start_time,
82                end_time,
83                track,
84                RulesForUpdatingTheTextTrackRendering::WebVTT,
85            ),
86            text: DomRefCell::new(text.traced()),
87            region: MutNullableDom::new(None),
88            vertical: Cell::new(vertical),
89            snap_to_lines: Cell::new(snap_to_lines),
90            line: DomRefCell::new(line),
91            line_align: Cell::new(line_align),
92            position: DomRefCell::new(position),
93            position_align: Cell::new(position_align),
94            size: Cell::new(size),
95            align: Cell::new(align),
96        }
97    }
98
99    #[expect(clippy::too_many_arguments)]
100    fn new(
101        cx: &mut JSContext,
102        window: &Window,
103        proto: Option<HandleObject>,
104        start_time: f64,
105        end_time: f64,
106        text: DOMString,
107        id: DOMString,
108        vertical: DirectionSetting,
109        snap_to_lines: bool,
110        line: LineAndPositionSetting,
111        line_align: LineAlignSetting,
112        position: LineAndPositionSetting,
113        position_align: PositionAlignSetting,
114        size: f64,
115        align: AlignSetting,
116        track: Option<&TextTrack>,
117    ) -> DomRoot<Self> {
118        reflect_dom_object_with_proto(
119            cx,
120            Box::new(Self::new_inherited(
121                start_time,
122                end_time,
123                text,
124                id,
125                vertical,
126                snap_to_lines,
127                line,
128                line_align,
129                position,
130                position_align,
131                size,
132                align,
133                track,
134            )),
135            window,
136            proto,
137        )
138    }
139
140    pub(crate) fn create_from_vtt(
141        cx: &mut JSContext,
142        vtt_cue: WebVttCue,
143        window: &Window,
144        track: Option<&TextTrack>,
145    ) -> DomRoot<VTTCue> {
146        Self::new(
147            cx,
148            window,
149            None,
150            vtt_cue.start_time,
151            vtt_cue.end_time,
152            vtt_cue.text.into(),
153            vtt_cue.identifier.into(),
154            vtt_cue.writing_direction.convert(),
155            vtt_cue.snap_to_lines.convert(),
156            vtt_cue.line.convert(),
157            vtt_cue.line_alignment.convert(),
158            vtt_cue.position.convert(),
159            vtt_cue.position_alignment.convert(),
160            vtt_cue.size.convert(),
161            vtt_cue.text_alignment.convert(),
162            track,
163        )
164    }
165
166    /// <https://w3c.github.io/webvtt/#dom-construction-rules>
167    fn webvtt_cue_text_dom_construction_rules(
168        &self,
169        cx: &mut JSContext,
170        root_object: Rc<WebVTTNodeObject>,
171    ) -> DomRoot<DocumentFragment> {
172        assert!(root_object.kind == WebVTTNodeObjectKind::List);
173
174        let global = self.global();
175        let window = global.as_window();
176        // > The ownerDocument attribute of all nodes in the DOM
177        // > tree must be set to the given document owner.
178        let document = window.Document();
179        let root = DocumentFragment::new(cx, &document);
180        // > To convert a list of WebVTT Node Objects to a DOM tree for Document owner,
181        // > user agents must create a tree of DOM nodes that is isomorphous
182        // > to the tree of WebVTT Node Objects,
183        // > with the following mapping of WebVTT Node Objects to DOM nodes:
184        let mut current = DomRoot::upcast::<Node>(root.clone());
185        for node_traversal in root_object.into_iter() {
186            match node_traversal {
187                WebVTTNodeObjectIteratorDirection::NewChild(new_child) => {
188                    let new_tag_name = match &new_child.kind {
189                        WebVTTNodeObjectKind::Italic => "i",
190                        WebVTTNodeObjectKind::Bold => "b",
191                        WebVTTNodeObjectKind::Underline => "u",
192                        WebVTTNodeObjectKind::Ruby => "ruby",
193                        WebVTTNodeObjectKind::RubyText => "rt",
194                        WebVTTNodeObjectKind::Class |
195                        WebVTTNodeObjectKind::Voice(_) |
196                        WebVTTNodeObjectKind::Language => "span",
197                        WebVTTNodeObjectKind::Text(text) => {
198                            // > Text node whose data is the value of the WebVTT Text Object.
199                            let text = document.CreateTextNode(cx, DOMString::from(text.as_ref()));
200                            current
201                                .AppendChild(cx, text.upcast())
202                                .expect("Must always be able to append");
203                            continue;
204                        },
205                        WebVTTNodeObjectKind::Timestamp(timestamp) => {
206                            // > ProcessingInstruction node whose target is "timestamp"
207                            // > and whose data is a WebVTT timestamp representing the
208                            // > value of the WebVTT Timestamp Object,
209                            // > with all optional components included,
210                            // > with one leading zero if the hours component is less than ten,
211                            // > and with no leading zeros otherwise.
212                            let new_child = document
213                                .CreateProcessingInstruction(
214                                    cx,
215                                    DOMString::from_static("timestamp"),
216                                    format!("{timestamp}").into(),
217                                )
218                                .expect("Must always be able to create processing instruction");
219                            current
220                                .AppendChild(cx, new_child.upcast())
221                                .expect("Must always be able to append");
222                            current = DomRoot::upcast(new_child);
223                            continue;
224                        },
225                        WebVTTNodeObjectKind::List => {
226                            let new_child = document.CreateDocumentFragment(cx);
227                            current
228                                .AppendChild(cx, new_child.upcast())
229                                .expect("Must always be able to append");
230                            current = DomRoot::upcast(new_child);
231                            continue;
232                        },
233                    };
234                    let new_child_element = document.create_element(cx, new_tag_name);
235                    match &new_child.kind {
236                        WebVTTNodeObjectKind::Language => {
237                            // > HTML span element with a lang attribute set to
238                            // > the WebVTT Language Object’s applicable language.
239                            new_child_element.set_attribute(
240                                cx,
241                                &local_name!("lang"),
242                                new_child.applicable_language.clone().into(),
243                            );
244                        },
245                        WebVTTNodeObjectKind::Voice(title) => {
246                            // > HTML span element with a title attribute set to
247                            // > the WebVTT Voice Object’s value.
248                            new_child_element.set_attribute(
249                                cx,
250                                &local_name!("title"),
251                                title.clone().into(),
252                            );
253                        },
254                        _ => {},
255                    }
256                    // > HTML elements created as part of the mapping described above must
257                    // > have their namespaceURI set to the HTML namespace,
258                    // > use the appropriate IDL interface as defined in the HTML specification,
259                    // > and, if the corresponding WebVTT Internal Node Object has any applicable classes,
260                    // > must have a class attribute set to the string obtained by concatenating all those classes,
261                    // > each separated from the next by a single U+0020 SPACE character.
262                    if !new_child.applicable_classes.is_empty() {
263                        new_child_element.set_attribute(
264                            cx,
265                            &local_name!("class"),
266                            new_child.applicable_classes.join("\u{0020}").into(),
267                        );
268                    }
269                    current
270                        .AppendChild(cx, new_child_element.upcast())
271                        .expect("Must always be able to append");
272                    current = DomRoot::upcast(new_child_element);
273                },
274                WebVTTNodeObjectIteratorDirection::BackToParent => {
275                    current = current.GetParentNode().expect("Must always have a parent");
276                },
277            }
278        }
279        root
280    }
281}
282
283impl VTTCueMethods<crate::DomTypeHolder> for VTTCue {
284    /// <https://w3c.github.io/webvtt/#dom-vttcue-vttcue>
285    fn Constructor(
286        cx: &mut JSContext,
287        window: &Window,
288        proto: Option<HandleObject>,
289        start_time: Finite<f64>,
290        end_time: Finite<f64>,
291        text: DOMString,
292    ) -> Fallible<DomRoot<Self>> {
293        // Step 3. If the value of the endTime argument is negative Infinity or a Not-a-Number (NaN) value,
294        // then throw a TypeError exception.
295        // Otherwise, let cue’s text track cue end time be the value of the endTime argument.
296        if (end_time.is_infinite() && end_time.is_sign_negative()) || end_time.is_nan() {
297            return Err(Error::Type(
298                c"End time is negative Infinity or Not-a-Number".to_owned(),
299            ));
300        }
301        // Step 1. Create a new WebVTT cue. Let cue be that WebVTT cue.
302        // Step 16. Return the VTTCue object representing cue.
303        Ok(VTTCue::new(
304            cx,
305            window,
306            proto,
307            // Step 2. Let cue’s text track cue start time be the value of the startTime argument.
308            *start_time,
309            *end_time,
310            // Step 4. Let cue’s cue text be the value of the text argument,
311            // and let the rules for extracting the chapter title be the
312            // WebVTT rules for extracting the chapter title.
313            // TODO: Extract title
314            text,
315            // Step 5. Let cue’s text track cue identifier be the empty string.
316            Default::default(),
317            // Step 8. Let cue’s WebVTT cue writing direction be horizontal.
318            DirectionSetting::_empty,
319            // Step 9. Let cue’s WebVTT cue snap-to-lines flag be true.
320            true,
321            // Step 10. Let cue’s WebVTT cue line be auto.
322            LineAndPositionSetting::Auto,
323            // Step 11. Let cue’s WebVTT cue line alignment be start alignment.
324            LineAlignSetting::Start,
325            // Step 12. Let cue’s WebVTT cue position be auto.
326            LineAndPositionSetting::Auto,
327            // Step 13. Let cue’s WebVTT cue position alignment be auto.
328            PositionAlignSetting::Auto,
329            // Step 14. Let cue’s WebVTT cue size be 100.
330            100.,
331            // Step 15. Let cue’s WebVTT cue text alignment be center alignment.
332            AlignSetting::Center,
333            None,
334        ))
335    }
336
337    /// <https://w3c.github.io/webvtt/#dom-vttcue-region>
338    fn GetRegion(&self) -> Option<DomRoot<VTTRegion>> {
339        self.region.get()
340    }
341
342    /// <https://w3c.github.io/webvtt/#dom-vttcue-region>
343    fn SetRegion(&self, value: Option<&VTTRegion>) {
344        self.region.set(value);
345    }
346
347    /// <https://w3c.github.io/webvtt/#dom-vttcue-vertical>
348    fn Vertical(&self) -> DirectionSetting {
349        self.vertical.get()
350    }
351
352    /// <https://w3c.github.io/webvtt/#dom-vttcue-vertical>
353    fn SetVertical(&self, value: DirectionSetting) {
354        self.vertical.set(value);
355    }
356
357    /// <https://w3c.github.io/webvtt/#dom-vttcue-snaptolines>
358    fn SnapToLines(&self) -> bool {
359        self.snap_to_lines.get()
360    }
361
362    /// <https://w3c.github.io/webvtt/#dom-vttcue-snaptolines>
363    fn SetSnapToLines(&self, value: bool) {
364        self.snap_to_lines.set(value)
365    }
366
367    /// <https://w3c.github.io/webvtt/#dom-vttcue-line>
368    fn Line(&self) -> VTTCueBinding::LineAndPositionSetting {
369        VTTCueBinding::LineAndPositionSetting::from(self.line.borrow().clone())
370    }
371
372    /// <https://w3c.github.io/webvtt/#dom-vttcue-line>
373    fn SetLine(&self, value: VTTCueBinding::LineAndPositionSetting) {
374        *self.line.borrow_mut() = value.into();
375    }
376
377    /// <https://w3c.github.io/webvtt/#dom-vttcue-linealign>
378    fn LineAlign(&self) -> LineAlignSetting {
379        self.line_align.get()
380    }
381
382    /// <https://w3c.github.io/webvtt/#dom-vttcue-linealign>
383    fn SetLineAlign(&self, value: LineAlignSetting) {
384        self.line_align.set(value);
385    }
386
387    /// <https://w3c.github.io/webvtt/#dom-vttcue-position>
388    fn Position(&self) -> VTTCueBinding::LineAndPositionSetting {
389        VTTCueBinding::LineAndPositionSetting::from(self.position.borrow().clone())
390    }
391
392    /// <https://w3c.github.io/webvtt/#dom-vttcue-position>
393    fn SetPosition(&self, value: VTTCueBinding::LineAndPositionSetting) -> ErrorResult {
394        if let VTTCueBinding::LineAndPositionSetting::Double(x) = value &&
395            (*x < 0_f64 || *x > 100_f64)
396        {
397            return Err(Error::IndexSize(None));
398        }
399
400        *self.position.borrow_mut() = value.into();
401        Ok(())
402    }
403
404    /// <https://w3c.github.io/webvtt/#dom-vttcue-positionalign>
405    fn PositionAlign(&self) -> PositionAlignSetting {
406        self.position_align.get()
407    }
408
409    /// <https://w3c.github.io/webvtt/#dom-vttcue-positionalign>
410    fn SetPositionAlign(&self, value: PositionAlignSetting) {
411        self.position_align.set(value);
412    }
413
414    /// <https://w3c.github.io/webvtt/#dom-vttcue-size>
415    fn Size(&self) -> Finite<f64> {
416        Finite::wrap(self.size.get())
417    }
418
419    /// <https://w3c.github.io/webvtt/#dom-vttcue-size>
420    fn SetSize(&self, value: Finite<f64>) -> ErrorResult {
421        if *value < 0_f64 || *value > 100_f64 {
422            return Err(Error::IndexSize(None));
423        }
424
425        self.size.set(*value);
426        Ok(())
427    }
428
429    /// <https://w3c.github.io/webvtt/#dom-vttcue-align>
430    fn Align(&self) -> AlignSetting {
431        self.align.get()
432    }
433
434    /// <https://w3c.github.io/webvtt/#dom-vttcue-align>
435    fn SetAlign(&self, value: AlignSetting) {
436        self.align.set(value);
437    }
438
439    /// <https://w3c.github.io/webvtt/#dom-vttcue-text>
440    fn Text(&self) -> DOMString {
441        self.text.borrow().clone().root()
442    }
443
444    /// <https://w3c.github.io/webvtt/#dom-vttcue-text>
445    fn SetText(&self, value: DOMString) {
446        *self.text.borrow_mut() = value.traced();
447    }
448
449    /// <https://w3c.github.io/webvtt/#dom-vttcue-getcueashtml>
450    fn GetCueAsHTML(&self, cx: &mut JSContext) -> DomRoot<DocumentFragment> {
451        // > The getCueAsHTML() method must convert the cue text to a DocumentFragment
452        // > for the responsible document specified by the entry settings object
453        // > by applying the WebVTT cue text DOM construction rules to the result
454        // > of applying the WebVTT cue text parsing rules to the cue text.
455        self.webvtt_cue_text_dom_construction_rules(
456            cx,
457            webvtt_cue_text_parsing_rules(&self.text.borrow().str(), None),
458        )
459    }
460}
461
462#[derive(Clone, JSTraceable, MallocSizeOf)]
463enum LineAndPositionSetting {
464    Double(f64),
465    Auto,
466}
467
468impl From<VTTCueBinding::LineAndPositionSetting> for LineAndPositionSetting {
469    fn from(value: VTTCueBinding::LineAndPositionSetting) -> Self {
470        match value {
471            VTTCueBinding::LineAndPositionSetting::Double(x) => LineAndPositionSetting::Double(*x),
472            VTTCueBinding::LineAndPositionSetting::AutoKeyword(_) => LineAndPositionSetting::Auto,
473        }
474    }
475}
476
477impl From<LineAndPositionSetting> for VTTCueBinding::LineAndPositionSetting {
478    fn from(value: LineAndPositionSetting) -> Self {
479        match value {
480            LineAndPositionSetting::Double(x) => {
481                VTTCueBinding::LineAndPositionSetting::Double(Finite::wrap(x))
482            },
483            LineAndPositionSetting::Auto => {
484                VTTCueBinding::LineAndPositionSetting::AutoKeyword(AutoKeyword::Auto)
485            },
486        }
487    }
488}
489
490impl Convert<LineAndPositionSetting> for WebVttLineAndPositionSetting {
491    fn convert(self) -> LineAndPositionSetting {
492        match self {
493            WebVttLineAndPositionSetting::Double(x) => LineAndPositionSetting::Double(x),
494            WebVttLineAndPositionSetting::Auto => LineAndPositionSetting::Auto,
495        }
496    }
497}
498
499impl Convert<DirectionSetting> for WebVttWritingDirection {
500    fn convert(self) -> DirectionSetting {
501        match self {
502            WebVttWritingDirection::Horizontal => DirectionSetting::_empty,
503            WebVttWritingDirection::VerticalGrowingLeft => DirectionSetting::Rl,
504            WebVttWritingDirection::VerticalGrowingRight => DirectionSetting::Lr,
505        }
506    }
507}
508
509impl Convert<AlignSetting> for WebVttTextAlignment {
510    fn convert(self) -> AlignSetting {
511        match self {
512            WebVttTextAlignment::Start => AlignSetting::Start,
513            WebVttTextAlignment::Center => AlignSetting::Center,
514            WebVttTextAlignment::End => AlignSetting::End,
515            WebVttTextAlignment::Left => AlignSetting::Left,
516            WebVttTextAlignment::Right => AlignSetting::Right,
517        }
518    }
519}
520
521impl Convert<LineAlignSetting> for WebVttLineAlignment {
522    fn convert(self) -> LineAlignSetting {
523        match self {
524            WebVttLineAlignment::Start => LineAlignSetting::Start,
525            WebVttLineAlignment::Center => LineAlignSetting::Center,
526            WebVttLineAlignment::End => LineAlignSetting::End,
527        }
528    }
529}
530
531impl Convert<PositionAlignSetting> for WebVttPositionAlignment {
532    fn convert(self) -> PositionAlignSetting {
533        match self {
534            WebVttPositionAlignment::LineLeft => PositionAlignSetting::Line_left,
535            WebVttPositionAlignment::Center => PositionAlignSetting::Center,
536            WebVttPositionAlignment::LineRight => PositionAlignSetting::Line_right,
537            WebVttPositionAlignment::Auto => PositionAlignSetting::Auto,
538        }
539    }
540}
541
542impl Convert<bool> for WebVttSnapToLines {
543    fn convert(self) -> bool {
544        match self {
545            WebVttSnapToLines::Yes => true,
546            WebVttSnapToLines::No => false,
547        }
548    }
549}
550
551impl Convert<f64> for WebVttCueSize {
552    fn convert(self) -> f64 {
553        self.0
554    }
555}