Skip to main content

script/dom/html/embedded_content/
htmltrackelement.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;
6
7use bytes::Bytes;
8use content_security_policy::Destination;
9use dom_struct::dom_struct;
10use html5ever::{LocalName, Prefix, local_name};
11use js::context::{JSContext, NoGC};
12use js::rust::HandleObject;
13use net_traits::fetch::fetch_controller::FetchController;
14use net_traits::request::RequestId;
15use net_traits::{FetchMetadata, NetworkError, ResourceFetchTiming};
16use script_bindings::cell::DomRefCell;
17use servo_url::ServoUrl;
18use servo_webvtt::cue::settings::WebVttCue;
19use servo_webvtt::{IncrementalWebVTTParser, WebVttParserSink};
20
21use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods;
22use crate::dom::bindings::codegen::Bindings::HTMLTrackElementBinding::{
23    HTMLTrackElementConstants, HTMLTrackElementMethods,
24};
25use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
26use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackMethods, TextTrackMode};
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::{Dom, DomRoot, UnrootedDom};
31use crate::dom::bindings::str::{DOMString, USVString};
32use crate::dom::csp::Violation;
33use crate::dom::document::Document;
34use crate::dom::element::Element;
35use crate::dom::element::storage::AttrRef;
36use crate::dom::eventtarget::EventTarget;
37use crate::dom::globalscope::GlobalScope;
38use crate::dom::html::htmlelement::HTMLElement;
39use crate::dom::html::htmlmediaelement::HTMLMediaElement;
40use crate::dom::node::node::NodeTraits;
41use crate::dom::node::{BindContext, MoveContext, Node, UnbindContext};
42use crate::dom::performanceresourcetiming::InitiatorType;
43use crate::dom::security::csp::GlobalCspReporting;
44use crate::dom::texttrack::TextTrack;
45use crate::dom::virtualmethods::VirtualMethods;
46use crate::dom::webvtt::vttcue::VTTCue;
47use crate::dom::{AttributeMutation, cors_setting_for_element};
48use crate::event_loop::script_thread::ScriptThread;
49use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
50use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
51use crate::realms::enter_auto_realm;
52use crate::runtime::job_queue::MicrotaskRunnable;
53
54#[derive(Clone, Copy, Default, JSTraceable, MallocSizeOf, PartialEq)]
55#[repr(u16)]
56/// <https://html.spec.whatwg.org/multipage/#text-track-readiness-state>
57pub(crate) enum TextTrackReadinessState {
58    /// <https://html.spec.whatwg.org/multipage/#text-track-not-loaded>
59    #[default]
60    None = HTMLTrackElementConstants::NONE,
61    /// <https://html.spec.whatwg.org/multipage/#text-track-loading>
62    Loading = HTMLTrackElementConstants::LOADING,
63    /// <https://html.spec.whatwg.org/multipage/#text-track-loaded>
64    Loaded = HTMLTrackElementConstants::LOADED,
65    /// <https://html.spec.whatwg.org/multipage/#text-track-failed-to-load>
66    FailedToLoad = HTMLTrackElementConstants::ERROR,
67}
68
69#[dom_struct]
70pub(crate) struct HTMLTrackElement {
71    htmlelement: HTMLElement,
72    /// <https://html.spec.whatwg.org/multipage/#text-track-readiness-state>
73    readiness_state: Cell<TextTrackReadinessState>,
74    /// <https://html.spec.whatwg.org/multipage/#text-track>
75    track: Dom<TextTrack>,
76    /// <https://html.spec.whatwg.org/multipage/#track-url>
77    #[no_trace]
78    track_url: DomRefCell<Option<ServoUrl>>,
79    /// The track_url used for the last load that was successful.
80    #[no_trace]
81    last_successful_load: DomRefCell<Option<ServoUrl>>,
82    /// Used as part of
83    /// <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
84    /// whether the algorithm is running or not.
85    is_running_processing_model_algorithm: Cell<bool>,
86    /// Used as part of
87    /// <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
88    /// whether the algorithm has at least once progressed past step 5.
89    has_started_tracking_processing_model: Cell<bool>,
90    /// Used to cancel previous fetch request if a new one has been scheduled.
91    #[no_trace]
92    controller: DomRefCell<Option<FetchController>>,
93}
94
95impl HTMLTrackElement {
96    fn new_inherited(
97        local_name: LocalName,
98        prefix: Option<Prefix>,
99        document: &Document,
100        track: &TextTrack,
101    ) -> HTMLTrackElement {
102        HTMLTrackElement {
103            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
104            readiness_state: Default::default(),
105            track: Dom::from_ref(track),
106            track_url: Default::default(),
107            last_successful_load: Default::default(),
108            is_running_processing_model_algorithm: Default::default(),
109            has_started_tracking_processing_model: Default::default(),
110            controller: Default::default(),
111        }
112    }
113
114    pub(crate) fn new(
115        cx: &mut JSContext,
116        local_name: LocalName,
117        prefix: Option<Prefix>,
118        document: &Document,
119        proto: Option<HandleObject>,
120    ) -> DomRoot<HTMLTrackElement> {
121        // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
122        // > When a track element is created, it must be associated
123        // > with a new text track (with its value set as defined below).
124        let track = TextTrack::new(
125            cx,
126            document.window(),
127            Default::default(),
128            Default::default(),
129            Default::default(),
130            Default::default(),
131            Default::default(),
132            None,
133        );
134        let track_element = Node::reflect_node_with_proto(
135            cx,
136            Box::new(HTMLTrackElement::new_inherited(
137                local_name, prefix, document, &track,
138            )),
139            document,
140            proto,
141        );
142        track.set_associated_track(&track_element);
143        track_element
144    }
145
146    /// <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
147    pub(crate) fn start_the_track_processing_model(&self, cx: &mut JSContext) {
148        // Step 1. If another occurrence of this algorithm is already running
149        // for this text track and its track element, return,
150        // letting that other algorithm take care of this element.
151        if self.is_running_processing_model_algorithm.get() {
152            return;
153        }
154        // Step 2. If the text track's text track mode is not set to one of hidden or showing, then return.
155        if !matches!(
156            self.track.Mode(),
157            TextTrackMode::Hidden | TextTrackMode::Showing
158        ) {
159            return;
160        }
161        // Step 3. If the text track's track element does not have a media element as a parent, return.
162        if self
163            .upcast::<Node>()
164            .GetParentElement()
165            .is_none_or(|parent| !parent.is::<HTMLMediaElement>())
166        {
167            return;
168        };
169        // Step 4. Run the remainder of these steps in parallel, allowing whatever caused these steps to run to continue.
170        self.has_started_tracking_processing_model.set(true);
171        // Step 5. Top: Await a stable state.
172        let task = TrackElementMicrotask::ProcessingModel {
173            element: Dom::from_ref(self),
174        };
175        self.is_running_processing_model_algorithm.set(true);
176
177        ScriptThread::await_stable_state(cx, Box::new(task));
178    }
179
180    fn check_if_track_parent_element_changed(&self, cx: &mut JSContext) {
181        if let Some(parent) = self
182            .upcast::<Node>()
183            .GetParentNode()
184            .and_then(DomRoot::downcast::<HTMLMediaElement>)
185        {
186            // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
187            // > When a track element's parent element changes and the new parent is a media element,
188            // > then the user agent must add the track element's corresponding text track to
189            // > the media element's list of text tracks, and then queue a media element task
190            // > given the media element to fire an event named addtrack at the media element's
191            // > textTracks attribute's TextTrackList object, using TrackEvent,
192            // > with the track attribute initialized to the text track's TextTrack object.
193            parent.TextTracks(cx).add(cx, &self.track);
194
195            // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks:start-the-track-processing-model
196            // > The track element's parent element changes and the new parent is a media element.
197            self.start_the_track_processing_model(cx);
198        }
199    }
200
201    pub(crate) fn track<'a>(&self, no_gc: &'a NoGC) -> UnrootedDom<'a, TextTrack> {
202        self.track.as_unrooted(no_gc)
203    }
204
205    fn is_hidden_or_showing(&self) -> bool {
206        matches!(
207            self.track.Mode(),
208            TextTrackMode::Hidden | TextTrackMode::Showing
209        )
210    }
211
212    /// Step 10.4 of <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
213    fn cancel_ongoing_request(&self) {
214        if let Some(mut controller) = self.controller.take() {
215            // > If, while fetching is ongoing, either:
216            //     > the track URL changes so that it is no longer equal to URL,
217            //       > while the text track mode is set to hidden or showing; or
218            //     > the text track mode changes to hidden or showing,
219            //       > while the track URL is not equal to URL,
220            // > ...then the user agent must abort fetching,
221            // > discarding any pending tasks generated by that algorithm
222            // > (and in particular, not adding any cues to the text track
223            // > list of cues after the moment the URL changed),
224            // > and then queue an element task on the DOM manipulation task source
225            // > given the track element that first changes the text track readiness state
226            // > to failed to load and then fires an event named error at the track element.
227            controller.abort();
228            self.fire_error_event_during_fetching();
229        }
230    }
231
232    fn fire_error_event_during_fetching(&self) {
233        let this = Trusted::new(self);
234        self.global()
235            .task_manager()
236            .dom_manipulation_task_source()
237            .queue(task!(failed_to_load: move |cx| {
238                let track = this.root();
239                track.readiness_state.set(TextTrackReadinessState::FailedToLoad);
240                track.upcast::<EventTarget>().fire_event(cx, atom!("error"));
241            }));
242    }
243}
244
245impl HTMLTrackElementMethods<crate::DomTypeHolder> for HTMLTrackElement {
246    /// <https://html.spec.whatwg.org/multipage/#dom-track-kind>
247    fn Kind(&self) -> DOMString {
248        let element = self.upcast::<Element>();
249        // Get the value of "kind" and transform all uppercase
250        // chars into lowercase.
251        let kind = element
252            .get_string_attribute(&local_name!("kind"))
253            .to_lowercase();
254        match &*kind {
255            "subtitles" | "captions" | "descriptions" | "chapters" | "metadata" => {
256                // The value of "kind" is valid. Return the lowercase version
257                // of it.
258                DOMString::from(kind)
259            },
260            _ if kind.is_empty() => {
261                // The default value should be "subtitles". If "kind" has not
262                // been set, the real value for "kind" is "subtitles"
263                DOMString::from_static("subtitles")
264            },
265            _ => {
266                // If "kind" has been set but it is not one of the valid
267                // values, return the default invalid value of "metadata"
268                DOMString::from_static("metadata")
269            },
270        }
271    }
272
273    // https://html.spec.whatwg.org/multipage/#dom-track-kind
274    // Do no transformations on the value of "kind" when setting it.
275    // All transformations should be done in the get method.
276    make_setter!(SetKind, "kind");
277
278    // https://html.spec.whatwg.org/multipage/#dom-track-src
279    make_url_getter!(Src, "src");
280    // https://html.spec.whatwg.org/multipage/#dom-track-src
281    make_url_setter!(SetSrc, "src");
282
283    // https://html.spec.whatwg.org/multipage/#dom-track-srclang
284    make_getter!(Srclang, "srclang");
285    // https://html.spec.whatwg.org/multipage/#dom-track-srclang
286    make_setter!(SetSrclang, "srclang");
287
288    // https://html.spec.whatwg.org/multipage/#dom-track-label
289    make_getter!(Label, "label");
290    // https://html.spec.whatwg.org/multipage/#dom-track-label
291    make_setter!(SetLabel, "label");
292
293    // https://html.spec.whatwg.org/multipage/#dom-track-default
294    make_bool_getter!(Default, "default");
295    // https://html.spec.whatwg.org/multipage/#dom-track-default
296    make_bool_setter!(SetDefault, "default");
297
298    /// <https://html.spec.whatwg.org/multipage/#dom-track-readystate>
299    fn ReadyState(&self) -> u16 {
300        self.readiness_state.get() as u16
301    }
302
303    /// <https://html.spec.whatwg.org/multipage/#dom-track-track>
304    fn Track(&self) -> DomRoot<TextTrack> {
305        DomRoot::from_ref(&*self.track)
306    }
307}
308
309impl VirtualMethods for HTMLTrackElement {
310    fn super_type(&self) -> Option<&dyn VirtualMethods> {
311        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
312    }
313
314    fn attribute_mutated(
315        &self,
316        cx: &mut JSContext,
317        attr: AttrRef<'_>,
318        mutation: AttributeMutation,
319    ) {
320        self.super_type()
321            .unwrap()
322            .attribute_mutated(cx, attr, mutation);
323        match *attr.local_name() {
324            local_name!("src") => {
325                // https://html.spec.whatwg.org/multipage/#attr-track-src
326                // > When the element's src attribute is set, run these steps:
327                if matches!(mutation, AttributeMutation::Set(_)) {
328                    // Step 2. Let value be the element's src attribute value.
329                    let value = &**attr.value();
330                    // Step 1. Let trackURL be failure.
331                    // Step 3. If value is not the empty string,
332                    // then set trackURL to the result of encoding-parsing-and-serializing
333                    // a URL given value, relative to the element's node document.
334                    // Step 4. Set the element's track URL to trackURL if it is not failure;
335                    // otherwise to the empty string.
336                    *self.track_url.borrow_mut() = if !value.is_empty() {
337                        self.owner_document().encoding_parse_a_url(value).ok()
338                    } else {
339                        None
340                    };
341                } else {
342                    *self.track_url.borrow_mut() = None;
343                }
344
345                if mutation.old_value(attr) !=
346                    mutation.new_value(attr).map(|value| value.to_string())
347                {
348                    // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
349                    // > Whenever a track element has its src attribute set, changed, or removed,
350                    // > the user agent must immediately empty the element's text track's text track list of cues.
351                    // > (This also causes the algorithm above to stop adding cues from the resource
352                    // > being obtained using the previously given URL, if any.)
353                    self.track.empty_cue_list();
354
355                    if self.is_hidden_or_showing() {
356                        // Step 10.4 of https://html.spec.whatwg.org/multipage/#start-the-track-processing-model
357                        //
358                        // > If, while fetching is ongoing, either:
359                        //     > the track URL changes so that it is no longer equal to URL,
360                        //       > while the text track mode is set to hidden or showing; or
361                        //     > the text track mode changes to hidden or showing,
362                        //       > while the track URL is not equal to URL,
363                        // > ...then the user agent must abort fetching,
364                        // > discarding any pending tasks generated by that algorithm
365                        // > (and in particular, not adding any cues to the text track
366                        // > list of cues after the moment the URL changed),
367                        // > and then queue an element task on the DOM manipulation task source
368                        // > given the track element that first changes the text track readiness state
369                        // > to failed to load and then fires an event named error at the track element.
370                        self.cancel_ongoing_request();
371
372                        // https://html.spec.whatwg.org/multipage/#start-the-track-processing-model
373                        // Step 11. Wait until the text track readiness state is no longer set to loading.
374                        // Step 12. Wait until the track URL is no longer equal to URL,
375                        // at the same time as the text track mode is set to hidden or showing.
376                        //
377                        // These conditions have been checked above by the if-statements
378                        if self.has_started_tracking_processing_model.get() &&
379                            !self.is_running_processing_model_algorithm.get()
380                        {
381                            // Step 13. Jump to the step labeled top.
382                            let task = TrackElementMicrotask::ProcessingModel {
383                                element: Dom::from_ref(self),
384                            };
385                            self.is_running_processing_model_algorithm.set(true);
386
387                            // Step 5. Top: Await a stable state. The synchronous section consists
388                            // of the following steps. (The steps in the synchronous section are marked with ⌛.)
389                            ScriptThread::await_stable_state(cx, Box::new(task));
390                        }
391                    }
392                }
393            },
394            local_name!("kind") |
395            local_name!("label") |
396            local_name!("srclang") |
397            local_name!("id") => {
398                // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
399                // > As the kind, label, srclang, and id attributes are set, changed,
400                // > or removed, the text track must update accordingly, as per the definitions above.
401                self.track.update_attributes_from_track_element(self);
402            },
403            _ => {},
404        }
405    }
406
407    fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
408        if let Some(super_type) = self.super_type() {
409            super_type.moving_steps(cx, context);
410        }
411
412        if context.is_subtree_root &&
413            let Some(parent) = context.old_ancestor.downcast::<HTMLMediaElement>()
414        {
415            // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
416            // > When a track element's parent element changes and the old parent was a media element,
417            // > then the user agent must remove the track element's corresponding text track from
418            // > the media element's list of text tracks, and then queue a media element task
419            // > given the media element to fire an event named removetrack at the media element's
420            // > textTracks attribute's TextTrackList object, using TrackEvent,
421            // > with the track attribute initialized to the text track's TextTrack object.
422            parent.TextTracks(cx).remove(&self.track);
423        }
424
425        self.check_if_track_parent_element_changed(cx);
426    }
427
428    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
429        if let Some(super_type) = self.super_type() {
430            super_type.bind_to_tree(cx, context);
431        }
432
433        self.check_if_track_parent_element_changed(cx);
434    }
435
436    fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
437        if let Some(s) = self.super_type() {
438            s.unbind_from_tree(cx, context);
439        }
440
441        if let Some(parent) = context.parent.downcast::<HTMLMediaElement>() {
442            // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
443            // > When a track element's parent element changes and the old parent was a media element,
444            // > then the user agent must remove the track element's corresponding text track from
445            // > the media element's list of text tracks, and then queue a media element task
446            // > given the media element to fire an event named removetrack at the media element's
447            // > textTracks attribute's TextTrackList object, using TrackEvent,
448            // > with the track attribute initialized to the text track's TextTrack object.
449            parent.TextTracks(cx).remove(&self.track);
450        }
451    }
452}
453
454#[derive(JSTraceable, MallocSizeOf)]
455pub(crate) enum TrackElementMicrotask {
456    ProcessingModel { element: Dom<HTMLTrackElement> },
457}
458
459impl MicrotaskRunnable for TrackElementMicrotask {
460    fn handler(&self, cx: &mut JSContext) {
461        let _realm = match self {
462            TrackElementMicrotask::ProcessingModel { element, .. } => {
463                enter_auto_realm(cx, &**element)
464            },
465        };
466        match self {
467            // https://html.spec.whatwg.org/multipage/#start-the-track-processing-model
468            TrackElementMicrotask::ProcessingModel { element } => {
469                // Not specced, but required for browser compatibility:
470                // https://github.com/whatwg/html/issues/12796
471                if element.readiness_state.get() == TextTrackReadinessState::Loaded &&
472                    *element.track_url.borrow() == *element.last_successful_load.borrow()
473                {
474                    element.is_running_processing_model_algorithm.set(false);
475                    return;
476                }
477
478                let media_parent = element
479                    .upcast::<Node>()
480                    .GetParentNode()
481                    .and_then(DomRoot::downcast::<HTMLMediaElement>);
482
483                // The synchronous section consists of the following steps.
484                // (The steps in the synchronous section are marked with ⌛.)
485                // Step 6. ⌛ Set the text track readiness state to loading.
486                element
487                    .readiness_state
488                    .set(TextTrackReadinessState::Loading);
489                // Step 7. ⌛ Let URL be the track URL of the track element.
490                let url = element.track_url.borrow().clone();
491                // Step 8. ⌛ If the track element's parent is a media element,
492                // then let corsAttributeState be the state of the parent media element's
493                // crossorigin content attribute. Otherwise, let corsAttributeState be No CORS.
494                let cors_attribute_state =
495                    media_parent.and_then(|parent| cors_setting_for_element(parent.upcast()));
496                // Step 9. End the synchronous section, continuing the remaining steps in parallel.
497                // TODO
498                // Step 10. If URL is not the empty string:
499                if let Some(url) = url {
500                    // Step 10.1. Let request be the result of creating a potential-CORS request given URL,
501                    // "track", and corsAttributeState, and with the same-origin fallback flag set.
502                    let global = element.global();
503                    let document = element.owner_document();
504                    let request = create_a_potential_cors_request(
505                        Some(document.webview_id()),
506                        url.clone(),
507                        Destination::Track,
508                        cors_attribute_state,
509                        Some(true),
510                        global.get_referrer(),
511                    )
512                    // Step 10.2. Set request's client to the track element's node document's relevant
513                    // settings object.
514                    .with_global_scope(&global);
515                    // Step 10.3. Set request's initiator type to "track".
516                    //
517                    // Set in listener
518
519                    // Step 10.4. Fetch request.
520                    let listener = HTMLTrackElementFetchListener {
521                        element: Trusted::new(element),
522                        url,
523                        payload: vec![],
524                    };
525                    element.cancel_ongoing_request();
526                    *element.controller.borrow_mut() = Some(FetchController::new(
527                        request.id,
528                        false,
529                        global.core_resource_thread(),
530                    ));
531                    document.fetch_background(request, listener);
532                } else {
533                    // > If fetching fails for any reason (network error, the server returns an error code, CORS fails, etc.),
534                    // > or if URL is the empty string, then queue an element task on the DOM manipulation task source
535                    // > given the media element to first change the text track readiness state to failed to load
536                    // > and then fire an event named error at the track element.
537                    //
538                    // This is the "URL is the empty string" case
539                    element.is_running_processing_model_algorithm.set(false);
540                    *element.controller.borrow_mut() = None;
541                    element.fire_error_event_during_fetching();
542                }
543                // Step 11. Wait until the text track readiness state is no longer set to loading.
544                // TODO
545                // Step 12. Wait until the track URL is no longer equal to URL,
546                // at the same time as the text track mode is set to hidden or showing.
547                // TODO
548                // Step 13. Jump to the step labeled top.
549                // TODO
550            },
551        }
552    }
553}
554
555struct TextTrackCueSink {
556    track_element: Trusted<HTMLTrackElement>,
557}
558
559impl WebVttParserSink<JSContext> for TextTrackCueSink {
560    fn consume_cue(&self, cx: &mut JSContext, cue: WebVttCue) {
561        let element = self.track_element.root();
562        let global = element.global();
563        let text_track = &element.track;
564
565        let cue = VTTCue::create_from_vtt(cx, cue, global.as_window(), Some(text_track));
566        text_track.text_track_cue_list(cx).add(cx, cue.upcast());
567    }
568}
569
570struct HTMLTrackElementFetchListener {
571    /// The element that initiated the request.
572    element: Trusted<HTMLTrackElement>,
573    /// URL for the resource.
574    url: ServoUrl,
575    /// The payload received
576    payload: Vec<u8>,
577}
578
579impl HTMLTrackElementFetchListener {
580    fn has_track_url_changed_since_fetch(&self) -> bool {
581        let element = self.element.root();
582        element
583            .track_url
584            .borrow()
585            .as_ref()
586            .is_none_or(|url| url != &self.url)
587    }
588}
589
590impl FetchResponseListener for HTMLTrackElementFetchListener {
591    fn process_request_body(&mut self, _: &GlobalScope, _: RequestId) {}
592
593    fn process_response(
594        &mut self,
595        _: &mut JSContext,
596        _: &GlobalScope,
597        _: RequestId,
598        _: Result<FetchMetadata, NetworkError>,
599    ) {
600    }
601
602    /// Step 10.4 of <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
603    fn process_response_chunk(
604        &mut self,
605        _: &mut JSContext,
606        _: &GlobalScope,
607        _: RequestId,
608        payload: Bytes,
609    ) {
610        // > If, while fetching is ongoing, either:
611        //     > the track URL changes so that it is no longer equal to URL,
612        //       > while the text track mode is set to hidden or showing; or
613        //     > the text track mode changes to hidden or showing,
614        //       > while the track URL is not equal to URL,
615        // > ...then the user agent must abort fetching,
616        // > discarding any pending tasks generated by that algorithm
617        // > (and in particular, not adding any cues to the text track
618        // > list of cues after the moment the URL changed),
619        // > and then queue an element task on the DOM manipulation task source
620        // > given the track element that first changes the text track readiness state
621        // > to failed to load and then fires an event named error at the track element.
622        //
623        // Note that we don't abort here, since we do that when we schedule the new fetch.
624        // We only bail out here to avoid adding new cues to the list of cues
625        let element = self.element.root();
626        if self.has_track_url_changed_since_fetch() && element.is_hidden_or_showing() {
627            return;
628        }
629
630        self.payload.extend_from_slice(&payload);
631    }
632
633    /// Step 10.4 of <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
634    fn process_response_eof(
635        self,
636        cx: &mut JSContext,
637        global: &GlobalScope,
638        _: RequestId,
639        status: Result<(), NetworkError>,
640        timing: ResourceFetchTiming,
641    ) {
642        let track = self.element.clone();
643        let element = self.element.root();
644        if status.is_err() || self.has_track_url_changed_since_fetch() {
645            // > If fetching fails for any reason (network error, the server returns an error code, CORS fails, etc.),
646            // > or if URL is the empty string, then queue an element task on the DOM manipulation task source
647            // > given the media element to first change the text track readiness state to failed to load
648            // > and then fire an event named error at the track element.
649            element.fire_error_event_during_fetching();
650        } else {
651            // > The tasks queued by the fetching algorithm on the networking task source to
652            // > process the data as it is being fetched must determine the type of the resource.
653            // > If the type of the resource is not a supported text track format, the load will fail,
654            // > as described below. Otherwise, the resource's data must be passed to the appropriate parser
655            // > (e.g., the WebVTT parser) as it is received, with the text track list of cues
656            // > being used for that parser's output. [WEBVTT]
657            let result = str::from_utf8(&self.payload)
658                .map_err(|str_error| debug!("WebVTT file contains non-utf8 data: {str_error}"))
659                .and_then(|payload| {
660                    let sink = TextTrackCueSink {
661                        track_element: track.clone(),
662                    };
663                    IncrementalWebVTTParser::new(sink)
664                        .parse_sync(cx, payload)
665                        .map_err(|parser_error| {
666                            debug!("Failed to parse WEBVTT file: {parser_error}")
667                        })
668                });
669            if result.is_ok() {
670                // > If fetching does not fail, and the file was successfully processed,
671                // > then the final task that is queued by the networking task source,
672                // > after it has finished parsing the data, must change the text track readiness state to loaded,
673                // > and fire an event named load at the track element.
674                let url = self.url.clone();
675                element
676                    .global()
677                    .task_manager()
678                    .networking_task_source()
679                    .queue(task!(successfully_loaded: move |cx| {
680                        let track = track.root();
681                        *track.last_successful_load.borrow_mut() = Some(url);
682                        track.readiness_state.set(TextTrackReadinessState::Loaded);
683                        track.upcast::<EventTarget>().fire_event(cx, atom!("load"));
684                    }));
685            } else {
686                // > If fetching does not fail, but the type of the resource is not a supported text track format,
687                // > or the file was not successfully processed (e.g., the format in question is an XML format
688                // > and the file contained a well-formedness error that XML requires be detected
689                // > and reported to the application), then the task that is queued on the networking task source
690                // > in which the aforementioned problem is found must change the text track readiness state
691                // > to failed to load and fire an event named error at the track element.
692                element.fire_error_event_during_fetching();
693            }
694        }
695        *element.controller.borrow_mut() = None;
696        element.is_running_processing_model_algorithm.set(false);
697        network_listener::submit_timing(cx, global, &self, &status, &timing);
698    }
699
700    fn process_csp_violations(
701        &mut self,
702        cx: &mut JSContext,
703        global: &GlobalScope,
704        _: RequestId,
705        violations: Vec<Violation>,
706    ) {
707        global.report_csp_violations(cx, violations, None, None);
708    }
709
710    fn should_invoke(&self) -> bool {
711        true
712    }
713}
714
715impl ResourceTimingListener for HTMLTrackElementFetchListener {
716    /// Step 10.3. of <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
717    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
718        (InitiatorType::Track, self.url.clone())
719    }
720}