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