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::request::RequestId;
14use net_traits::{FetchMetadata, NetworkError, ResourceFetchTiming};
15use script_bindings::cell::DomRefCell;
16use servo_url::ServoUrl;
17use servo_webvtt::cue::settings::WebVttCue;
18use servo_webvtt::{IncrementalWebVTTParser, WebVttParserSink};
19
20use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods;
21use crate::dom::bindings::codegen::Bindings::HTMLTrackElementBinding::{
22 HTMLTrackElementConstants, HTMLTrackElementMethods,
23};
24use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
25use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackMethods, TextTrackMode};
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::refcounted::Trusted;
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::{Dom, DomRoot, UnrootedDom};
30use crate::dom::bindings::str::{DOMString, USVString};
31use crate::dom::csp::Violation;
32use crate::dom::document::Document;
33use crate::dom::element::Element;
34use crate::dom::element::storage::AttrRef;
35use crate::dom::eventtarget::EventTarget;
36use crate::dom::globalscope::GlobalScope;
37use crate::dom::html::htmlelement::HTMLElement;
38use crate::dom::html::htmlmediaelement::HTMLMediaElement;
39use crate::dom::node::node::NodeTraits;
40use crate::dom::node::{BindContext, MoveContext, Node, UnbindContext};
41use crate::dom::performanceresourcetiming::InitiatorType;
42use crate::dom::security::csp::GlobalCspReporting;
43use crate::dom::texttrack::TextTrack;
44use crate::dom::virtualmethods::VirtualMethods;
45use crate::dom::webvtt::vttcue::VTTCue;
46use crate::dom::{AttributeMutation, cors_setting_for_element};
47use crate::event_loop::script_thread::ScriptThread;
48use crate::fetch::fetch::{
49 FetchCanceller, RequestWithGlobalScope, create_a_potential_cors_request,
50};
51use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
52use crate::realms::enter_auto_realm;
53use crate::runtime::job_queue::MicrotaskRunnable;
54
55#[derive(Clone, Copy, Default, JSTraceable, MallocSizeOf, PartialEq)]
56#[repr(u16)]
57/// <https://html.spec.whatwg.org/multipage/#text-track-readiness-state>
58pub(crate) enum TextTrackReadinessState {
59 /// <https://html.spec.whatwg.org/multipage/#text-track-not-loaded>
60 #[default]
61 None = HTMLTrackElementConstants::NONE,
62 /// <https://html.spec.whatwg.org/multipage/#text-track-loading>
63 Loading = HTMLTrackElementConstants::LOADING,
64 /// <https://html.spec.whatwg.org/multipage/#text-track-loaded>
65 Loaded = HTMLTrackElementConstants::LOADED,
66 /// <https://html.spec.whatwg.org/multipage/#text-track-failed-to-load>
67 FailedToLoad = HTMLTrackElementConstants::ERROR,
68}
69
70#[dom_struct]
71pub(crate) struct HTMLTrackElement {
72 htmlelement: HTMLElement,
73 /// <https://html.spec.whatwg.org/multipage/#text-track-readiness-state>
74 readiness_state: Cell<TextTrackReadinessState>,
75 /// <https://html.spec.whatwg.org/multipage/#text-track>
76 track: Dom<TextTrack>,
77 /// <https://html.spec.whatwg.org/multipage/#track-url>
78 #[no_trace]
79 track_url: DomRefCell<Option<ServoUrl>>,
80 /// The track_url used for the last load that was successful.
81 #[no_trace]
82 last_successful_load: DomRefCell<Option<ServoUrl>>,
83 /// Used as part of
84 /// <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
85 /// whether the algorithm is running or not.
86 is_running_processing_model_algorithm: Cell<bool>,
87 /// Used as part of
88 /// <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
89 /// whether the algorithm has at least once progressed past step 5.
90 has_started_tracking_processing_model: Cell<bool>,
91 /// Used to cancel previous fetch request if a new one has been scheduled.
92 canceller: DomRefCell<Option<FetchCanceller>>,
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 canceller: 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 canceller) = self.canceller.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 canceller.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 let Some(parent) = context
413 .old_parent
414 .and_then(|node| node.downcast::<HTMLMediaElement>())
415 {
416 // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
417 // > When a track element's parent element changes and the old parent was a media element,
418 // > then the user agent must remove the track element's corresponding text track from
419 // > the media element's list of text tracks, and then queue a media element task
420 // > given the media element to fire an event named removetrack at the media element's
421 // > textTracks attribute's TextTrackList object, using TrackEvent,
422 // > with the track attribute initialized to the text track's TextTrack object.
423 parent.TextTracks(cx).remove(&self.track);
424 }
425
426 self.check_if_track_parent_element_changed(cx);
427 }
428
429 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
430 if let Some(super_type) = self.super_type() {
431 super_type.bind_to_tree(cx, context);
432 }
433
434 self.check_if_track_parent_element_changed(cx);
435 }
436
437 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
438 if let Some(s) = self.super_type() {
439 s.unbind_from_tree(cx, context);
440 }
441
442 if let Some(parent) = context.parent.downcast::<HTMLMediaElement>() {
443 // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks
444 // > When a track element's parent element changes and the old parent was a media element,
445 // > then the user agent must remove the track element's corresponding text track from
446 // > the media element's list of text tracks, and then queue a media element task
447 // > given the media element to fire an event named removetrack at the media element's
448 // > textTracks attribute's TextTrackList object, using TrackEvent,
449 // > with the track attribute initialized to the text track's TextTrack object.
450 parent.TextTracks(cx).remove(&self.track);
451 }
452 }
453}
454
455#[derive(JSTraceable, MallocSizeOf)]
456pub(crate) enum TrackElementMicrotask {
457 ProcessingModel { element: Dom<HTMLTrackElement> },
458}
459
460impl MicrotaskRunnable for TrackElementMicrotask {
461 fn handler(&self, cx: &mut JSContext) {
462 let _realm = match self {
463 TrackElementMicrotask::ProcessingModel { element, .. } => {
464 enter_auto_realm(cx, &**element)
465 },
466 };
467 match self {
468 // https://html.spec.whatwg.org/multipage/#start-the-track-processing-model
469 TrackElementMicrotask::ProcessingModel { element } => {
470 // Not specced, but required for browser compatibility:
471 // https://github.com/whatwg/html/issues/12796
472 if element.readiness_state.get() == TextTrackReadinessState::Loaded &&
473 *element.track_url.borrow() == *element.last_successful_load.borrow()
474 {
475 element.is_running_processing_model_algorithm.set(false);
476 return;
477 }
478
479 let media_parent = element
480 .upcast::<Node>()
481 .GetParentNode()
482 .and_then(DomRoot::downcast::<HTMLMediaElement>);
483
484 // The synchronous section consists of the following steps.
485 // (The steps in the synchronous section are marked with ⌛.)
486 // Step 6. ⌛ Set the text track readiness state to loading.
487 element
488 .readiness_state
489 .set(TextTrackReadinessState::Loading);
490 // Step 7. ⌛ Let URL be the track URL of the track element.
491 let url = element.track_url.borrow().clone();
492 // Step 8. ⌛ If the track element's parent is a media element,
493 // then let corsAttributeState be the state of the parent media element's
494 // crossorigin content attribute. Otherwise, let corsAttributeState be No CORS.
495 let cors_attribute_state =
496 media_parent.and_then(|parent| cors_setting_for_element(parent.upcast()));
497 // Step 9. End the synchronous section, continuing the remaining steps in parallel.
498 // TODO
499 // Step 10. If URL is not the empty string:
500 if let Some(url) = url {
501 // Step 10.1. Let request be the result of creating a potential-CORS request given URL,
502 // "track", and corsAttributeState, and with the same-origin fallback flag set.
503 let global = element.global();
504 let document = element.owner_document();
505 let request = create_a_potential_cors_request(
506 Some(document.webview_id()),
507 url.clone(),
508 Destination::Track,
509 cors_attribute_state,
510 Some(true),
511 global.get_referrer(),
512 )
513 // Step 10.2. Set request's client to the track element's node document's relevant
514 // settings object.
515 .with_global_scope(&global);
516 // Step 10.3. Set request's initiator type to "track".
517 //
518 // Set in listener
519
520 // Step 10.4. Fetch request.
521 let listener = HTMLTrackElementFetchListener {
522 element: Trusted::new(element),
523 url,
524 payload: vec![],
525 };
526 element.cancel_ongoing_request();
527 *element.canceller.borrow_mut() = Some(FetchCanceller::new(
528 request.id,
529 false,
530 global.core_resource_thread(),
531 ));
532 document.fetch_background(request, listener);
533 } else {
534 // > If fetching fails for any reason (network error, the server returns an error code, CORS fails, etc.),
535 // > or if URL is the empty string, then queue an element task on the DOM manipulation task source
536 // > given the media element to first change the text track readiness state to failed to load
537 // > and then fire an event named error at the track element.
538 //
539 // This is the "URL is the empty string" case
540 element.is_running_processing_model_algorithm.set(false);
541 *element.canceller.borrow_mut() = None;
542 element.fire_error_event_during_fetching();
543 }
544 // Step 11. Wait until the text track readiness state is no longer set to loading.
545 // TODO
546 // Step 12. Wait until the track URL is no longer equal to URL,
547 // at the same time as the text track mode is set to hidden or showing.
548 // TODO
549 // Step 13. Jump to the step labeled top.
550 // TODO
551 },
552 }
553 }
554}
555
556struct TextTrackCueSink {
557 track_element: Trusted<HTMLTrackElement>,
558}
559
560impl WebVttParserSink<JSContext> for TextTrackCueSink {
561 fn consume_cue(&self, cx: &mut JSContext, cue: WebVttCue) {
562 let element = self.track_element.root();
563 let global = element.global();
564 let text_track = &element.track;
565
566 let cue = VTTCue::create_from_vtt(cx, cue, global.as_window(), Some(text_track));
567 text_track.text_track_cue_list(cx).add(cx, cue.upcast());
568 }
569}
570
571struct HTMLTrackElementFetchListener {
572 /// The element that initiated the request.
573 element: Trusted<HTMLTrackElement>,
574 /// URL for the resource.
575 url: ServoUrl,
576 /// The payload received
577 payload: Vec<u8>,
578}
579
580impl HTMLTrackElementFetchListener {
581 fn has_track_url_changed_since_fetch(&self) -> bool {
582 let element = self.element.root();
583 element
584 .track_url
585 .borrow()
586 .as_ref()
587 .is_none_or(|url| url != &self.url)
588 }
589}
590
591impl FetchResponseListener for HTMLTrackElementFetchListener {
592 fn process_request_body(&mut self, _: RequestId) {}
593
594 fn process_response(
595 &mut self,
596 _: &mut JSContext,
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(&mut self, _: &mut JSContext, _: RequestId, payload: Bytes) {
604 // > If, while fetching is ongoing, either:
605 // > the track URL changes so that it is no longer equal to URL,
606 // > while the text track mode is set to hidden or showing; or
607 // > the text track mode changes to hidden or showing,
608 // > while the track URL is not equal to URL,
609 // > ...then the user agent must abort fetching,
610 // > discarding any pending tasks generated by that algorithm
611 // > (and in particular, not adding any cues to the text track
612 // > list of cues after the moment the URL changed),
613 // > and then queue an element task on the DOM manipulation task source
614 // > given the track element that first changes the text track readiness state
615 // > to failed to load and then fires an event named error at the track element.
616 //
617 // Note that we don't abort here, since we do that when we schedule the new fetch.
618 // We only bail out here to avoid adding new cues to the list of cues
619 let element = self.element.root();
620 if self.has_track_url_changed_since_fetch() && element.is_hidden_or_showing() {
621 return;
622 }
623
624 self.payload.extend_from_slice(&payload);
625 }
626
627 /// Step 10.4 of <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
628 fn process_response_eof(
629 self,
630 cx: &mut JSContext,
631 _: RequestId,
632 status: Result<(), NetworkError>,
633 timing: ResourceFetchTiming,
634 ) {
635 let track = self.element.clone();
636 let element = self.element.root();
637 if status.is_err() || self.has_track_url_changed_since_fetch() {
638 // > If fetching fails for any reason (network error, the server returns an error code, CORS fails, etc.),
639 // > or if URL is the empty string, then queue an element task on the DOM manipulation task source
640 // > given the media element to first change the text track readiness state to failed to load
641 // > and then fire an event named error at the track element.
642 element.fire_error_event_during_fetching();
643 } else {
644 // > The tasks queued by the fetching algorithm on the networking task source to
645 // > process the data as it is being fetched must determine the type of the resource.
646 // > If the type of the resource is not a supported text track format, the load will fail,
647 // > as described below. Otherwise, the resource's data must be passed to the appropriate parser
648 // > (e.g., the WebVTT parser) as it is received, with the text track list of cues
649 // > being used for that parser's output. [WEBVTT]
650 let result = str::from_utf8(&self.payload)
651 .map_err(|str_error| debug!("WebVTT file contains non-utf8 data: {str_error}"))
652 .and_then(|payload| {
653 let sink = TextTrackCueSink {
654 track_element: track.clone(),
655 };
656 IncrementalWebVTTParser::new(sink)
657 .parse_sync(cx, payload)
658 .map_err(|parser_error| {
659 debug!("Failed to parse WEBVTT file: {parser_error}")
660 })
661 });
662 if result.is_ok() {
663 // > If fetching does not fail, and the file was successfully processed,
664 // > then the final task that is queued by the networking task source,
665 // > after it has finished parsing the data, must change the text track readiness state to loaded,
666 // > and fire an event named load at the track element.
667 let url = self.url.clone();
668 element
669 .global()
670 .task_manager()
671 .networking_task_source()
672 .queue(task!(successfully_loaded: move |cx| {
673 let track = track.root();
674 *track.last_successful_load.borrow_mut() = Some(url);
675 track.readiness_state.set(TextTrackReadinessState::Loaded);
676 track.upcast::<EventTarget>().fire_event(cx, atom!("load"));
677 }));
678 } else {
679 // > If fetching does not fail, but the type of the resource is not a supported text track format,
680 // > or the file was not successfully processed (e.g., the format in question is an XML format
681 // > and the file contained a well-formedness error that XML requires be detected
682 // > and reported to the application), then the task that is queued on the networking task source
683 // > in which the aforementioned problem is found must change the text track readiness state
684 // > to failed to load and fire an event named error at the track element.
685 element.fire_error_event_during_fetching();
686 }
687 }
688 *element.canceller.borrow_mut() = None;
689 element.is_running_processing_model_algorithm.set(false);
690 network_listener::submit_timing(cx, &self, &status, &timing);
691 }
692
693 fn process_csp_violations(
694 &mut self,
695 cx: &mut JSContext,
696 _: RequestId,
697 violations: Vec<Violation>,
698 ) {
699 let global = &self.resource_timing_global();
700 global.report_csp_violations(cx, violations, None, None);
701 }
702
703 fn should_invoke(&self) -> bool {
704 true
705 }
706}
707
708impl ResourceTimingListener for HTMLTrackElementFetchListener {
709 /// Step 10.3. of <https://html.spec.whatwg.org/multipage/#start-the-track-processing-model>
710 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
711 (InitiatorType::Track, self.url.clone())
712 }
713
714 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
715 self.element.root().owner_document().global()
716 }
717}