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::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 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 let document = window.Document();
179 let root = DocumentFragment::new(cx, &document);
180 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 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 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 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 new_child_element.set_attribute(
249 cx,
250 &local_name!("title"),
251 title.clone().into(),
252 );
253 },
254 _ => {},
255 }
256 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 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 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 Ok(VTTCue::new(
304 cx,
305 window,
306 proto,
307 *start_time,
309 *end_time,
310 text,
315 Default::default(),
317 DirectionSetting::_empty,
319 true,
321 LineAndPositionSetting::Auto,
323 LineAlignSetting::Start,
325 LineAndPositionSetting::Auto,
327 PositionAlignSetting::Auto,
329 100.,
331 AlignSetting::Center,
333 None,
334 ))
335 }
336
337 fn GetRegion(&self) -> Option<DomRoot<VTTRegion>> {
339 self.region.get()
340 }
341
342 fn SetRegion(&self, value: Option<&VTTRegion>) {
344 self.region.set(value);
345 }
346
347 fn Vertical(&self) -> DirectionSetting {
349 self.vertical.get()
350 }
351
352 fn SetVertical(&self, value: DirectionSetting) {
354 self.vertical.set(value);
355 }
356
357 fn SnapToLines(&self) -> bool {
359 self.snap_to_lines.get()
360 }
361
362 fn SetSnapToLines(&self, value: bool) {
364 self.snap_to_lines.set(value)
365 }
366
367 fn Line(&self) -> VTTCueBinding::LineAndPositionSetting {
369 VTTCueBinding::LineAndPositionSetting::from(self.line.borrow().clone())
370 }
371
372 fn SetLine(&self, value: VTTCueBinding::LineAndPositionSetting) {
374 *self.line.borrow_mut() = value.into();
375 }
376
377 fn LineAlign(&self) -> LineAlignSetting {
379 self.line_align.get()
380 }
381
382 fn SetLineAlign(&self, value: LineAlignSetting) {
384 self.line_align.set(value);
385 }
386
387 fn Position(&self) -> VTTCueBinding::LineAndPositionSetting {
389 VTTCueBinding::LineAndPositionSetting::from(self.position.borrow().clone())
390 }
391
392 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 fn PositionAlign(&self) -> PositionAlignSetting {
406 self.position_align.get()
407 }
408
409 fn SetPositionAlign(&self, value: PositionAlignSetting) {
411 self.position_align.set(value);
412 }
413
414 fn Size(&self) -> Finite<f64> {
416 Finite::wrap(self.size.get())
417 }
418
419 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 fn Align(&self) -> AlignSetting {
431 self.align.get()
432 }
433
434 fn SetAlign(&self, value: AlignSetting) {
436 self.align.set(value);
437 }
438
439 fn Text(&self) -> DOMString {
441 self.text.borrow().clone().root()
442 }
443
444 fn SetText(&self, value: DOMString) {
446 *self.text.borrow_mut() = value.traced();
447 }
448
449 fn GetCueAsHTML(&self, cx: &mut JSContext) -> DomRoot<DocumentFragment> {
451 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}