Skip to main content

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}