script/dom/webvtt/rules_for_rendering.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 js::context::JSContext;
6use script_bindings::inheritance::Castable;
7use script_bindings::root::DomRoot;
8use script_bindings::str::DOMString;
9
10use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
11use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackMethods, TextTrackMode};
12use crate::dom::bindings::codegen::Bindings::VTTCueBinding::VTTCueMethods;
13use crate::dom::html::htmlmediaelement::HTMLMediaElement;
14use crate::dom::html::htmlvideoelement::HTMLVideoElement;
15use crate::dom::node::Node;
16use crate::dom::node::node::NodeTraits;
17use crate::dom::texttrack::TextTrack;
18use crate::dom::webvtt::vttcue::VTTCue;
19
20#[derive(PartialEq)]
21pub(crate) enum ShouldResetRenderingControls {
22 Yes,
23 No,
24}
25
26/// <https://html.spec.whatwg.org/multipage/#rules-for-updating-the-text-track-rendering>
27#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
28pub(crate) enum RulesForUpdatingTheTextTrackRendering {
29 /// <https://w3c.github.io/webvtt/#rules-for-updating-the-display-of-webvtt-text-tracks>
30 WebVTT,
31}
32
33impl RulesForUpdatingTheTextTrackRendering {
34 pub(crate) fn run(
35 &self,
36 cx: &mut JSContext,
37 media_element: &HTMLMediaElement,
38 text_tracks: Vec<DomRoot<TextTrack>>,
39 _language: Option<DOMString>,
40 reset: ShouldResetRenderingControls,
41 ) {
42 debug_assert!(*self == RulesForUpdatingTheTextTrackRendering::WebVTT);
43 // https://w3c.github.io/webvtt/#rules-for-updating-the-display-of-webvtt-text-tracks
44 // Step 2. Let video be the media element or other playback mechanism.
45 if !media_element.is::<HTMLVideoElement>() {
46 // Step 1. If the media element is an audio element,
47 // or is another playback mechanism with no rendering area,
48 // abort these steps.
49 return;
50 };
51
52 // Step 3. Let output be an empty list of absolutely positioned CSS block boxes.
53 // Step 4. If the user agent is exposing a user interface for video,
54 // add to output one or more completely transparent positioned CSS block boxes
55 // that cover the same region as the user interface.
56 //
57 // All of these are handled by modifying the DOM, which in turn will invalidate
58 // the layout of the video element
59 let cue_container = media_element.cue_container(cx);
60 let cue_container = cue_container.upcast::<Node>();
61
62 // Step 5. If the last time these rules were run,
63 // the user agent was not exposing a user interface for video,
64 // but now it is, optionally let reset be true.
65 // Otherwise, let reset be false.
66 //
67 // Passed in as argument
68
69 // Step 6. Let tracks be the subset of video’s list of text tracks
70 // that have as their rules for updating the text track rendering
71 // these rules for updating the display of WebVTT text tracks,
72 // and whose text track mode is showing.
73 let tracks: Vec<DomRoot<TextTrack>> = text_tracks
74 .into_iter()
75 .filter(|text_track| {
76 let is_showing = text_track.Mode() == TextTrackMode::Showing;
77 debug_assert!(
78 !is_showing ||
79 text_track
80 .rules_for_updating_the_text_track_rendering()
81 .is_some_and(
82 |rules| rules == RulesForUpdatingTheTextTrackRendering::WebVTT
83 ) ||
84 text_track.cues(cx.no_gc()).is_empty()
85 );
86 is_showing
87 })
88 .collect();
89
90 // Step 7. Let cues be an empty list of text track cues.
91 // Step 8. For each track track in tracks,
92 // append to cues all the cues from track’s list of cues
93 // that have their text track cue active flag set.
94 //
95 // Note we handle the active flag in the subsequent loop,
96 // to avoid iterating multiple times through the list
97 let cues: Vec<_> = tracks
98 .iter()
99 .flat_map(|track| track.cues(cx.no_gc()))
100 .map(|cue| cue.as_rooted())
101 .collect();
102
103 let document = media_element.owner_document();
104
105 for cue in cues {
106 // Step 13. If reset is false, then, for each WebVTT cue cue in cues:
107 // if cue’s text track cue display state has a set of CSS boxes, then:
108 // > If cue’s WebVTT cue region is not null,
109 // > add those boxes to that region’s box and remove cue from cues.
110 // > Otherwise, add those boxes to output and remove cue from cues.
111 //
112 // This is written as a CSS spec, but it essentially means we should only
113 // reset a cue if either the controls are added/removed (in which case
114 // the size of the viewport has changed) or if the cue is no longer active.
115 if let Some(current_display_state) = cue.display_state() {
116 if reset == ShouldResetRenderingControls::No && cue.is_active() {
117 continue;
118 }
119
120 current_display_state.remove_self(cx);
121 cue.set_display_state(None);
122 }
123
124 // Step 14. For each WebVTT cue cue in cues that has not yet
125 // had corresponding CSS boxes added to output,
126 // in text track cue order, run the following substeps:
127 if !cue.is_active() {
128 continue;
129 }
130
131 // Step 14.1. Let nodes be the list of WebVTT Node Objects obtained
132 // by applying the WebVTT cue text parsing rules,
133 // with the fallback language language if provided, to the cue’s cue text.
134 let nodes = cue
135 .downcast::<VTTCue>()
136 .expect("Can only have WebVTT cues")
137 .GetCueAsHTML(cx);
138 // Step 14.2. If cue’s WebVTT cue region is null, run the following substeps:
139 // TODO
140 // Step 14.2.1. Apply WebVTT cue settings to obtain CSS boxes boxes from nodes.
141 //
142 // We model boxes as a node that we add the document fragment to. Note that since
143 // `nodes` is a document fragment, `appendChild` would have added all its children
144 // rather than the fragment itself. That's why we use this wrapper `boxes` to keep
145 // track of cues so that we can later remove it.
146 let boxes = document.create_element(cx, "span");
147 let _ = boxes.upcast::<Node>().AppendChild(cx, nodes.upcast());
148 // Step 14.2.2. Let cue’s text track cue display state have the CSS boxes in boxes.
149 cue.set_display_state(Some(boxes.upcast()));
150 // Step 14.2.3. Add the CSS boxes in boxes to output.
151 let _ = cue_container.AppendChild(cx, boxes.upcast());
152 }
153 }
154}