1use 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 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 let document = window.Document();
178 let root = DocumentFragment::new(cx, &document);
179 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 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 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 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 new_child_element.set_attribute(
248 cx,
249 &local_name!("title"),
250 title.clone().into(),
251 );
252 },
253 _ => {},
254 }
255 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 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 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 Ok(VTTCue::new(
303 cx,
304 window,
305 proto,
306 *start_time,
308 *end_time,
309 text,
314 Default::default(),
316 DirectionSetting::_empty,
318 true,
320 LineAndPositionSetting::Auto,
322 LineAlignSetting::Start,
324 LineAndPositionSetting::Auto,
326 PositionAlignSetting::Auto,
328 100.,
330 AlignSetting::Center,
332 None,
333 ))
334 }
335
336 fn GetRegion(&self) -> Option<DomRoot<VTTRegion>> {
338 self.region.get()
339 }
340
341 fn SetRegion(&self, value: Option<&VTTRegion>) {
343 self.region.set(value);
344 }
345
346 fn Vertical(&self) -> DirectionSetting {
348 self.vertical.get()
349 }
350
351 fn SetVertical(&self, value: DirectionSetting) {
353 self.vertical.set(value);
354 }
355
356 fn SnapToLines(&self) -> bool {
358 self.snap_to_lines.get()
359 }
360
361 fn SetSnapToLines(&self, value: bool) {
363 self.snap_to_lines.set(value)
364 }
365
366 fn Line(&self) -> VTTCueBinding::LineAndPositionSetting {
368 VTTCueBinding::LineAndPositionSetting::from(self.line.borrow().clone())
369 }
370
371 fn SetLine(&self, value: VTTCueBinding::LineAndPositionSetting) {
373 *self.line.borrow_mut() = value.into();
374 }
375
376 fn LineAlign(&self) -> LineAlignSetting {
378 self.line_align.get()
379 }
380
381 fn SetLineAlign(&self, value: LineAlignSetting) {
383 self.line_align.set(value);
384 }
385
386 fn Position(&self) -> VTTCueBinding::LineAndPositionSetting {
388 VTTCueBinding::LineAndPositionSetting::from(self.position.borrow().clone())
389 }
390
391 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 fn PositionAlign(&self) -> PositionAlignSetting {
405 self.position_align.get()
406 }
407
408 fn SetPositionAlign(&self, value: PositionAlignSetting) {
410 self.position_align.set(value);
411 }
412
413 fn Size(&self) -> Finite<f64> {
415 Finite::wrap(self.size.get())
416 }
417
418 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 fn Align(&self) -> AlignSetting {
430 self.align.get()
431 }
432
433 fn SetAlign(&self, value: AlignSetting) {
435 self.align.set(value);
436 }
437
438 fn Text(&self) -> DOMString {
440 self.text.borrow().clone()
441 }
442
443 fn SetText(&self, value: DOMString) {
445 *self.text.borrow_mut() = value;
446 }
447
448 fn GetCueAsHTML(&self, cx: &mut JSContext) -> DomRoot<DocumentFragment> {
450 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}