1use std::cell::Cell;
6use std::sync::Arc;
7
8use dom_struct::dom_struct;
9use euclid::default::Size2D;
10use html5ever::{LocalName, Prefix, local_name, ns};
11use js::rust::HandleObject;
12use layout_api::{HTMLMediaData, MediaMetadata};
13use net_traits::image_cache::{
14 ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable, ImageResponse,
15 PendingImageId,
16};
17use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
18use net_traits::{
19 CoreResourceThread, FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming,
20};
21use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
22use servo_media::player::video::VideoFrame;
23use servo_url::ServoUrl;
24use style::attr::{AttrValue, LengthOrPercentageOrAuto};
25
26use crate::document_loader::{LoadBlocker, LoadType};
27use crate::dom::attr::Attr;
28use crate::dom::bindings::cell::DomRefCell;
29use crate::dom::bindings::codegen::Bindings::HTMLVideoElementBinding::HTMLVideoElementMethods;
30use crate::dom::bindings::inheritance::Castable;
31use crate::dom::bindings::refcounted::Trusted;
32use crate::dom::bindings::reflector::DomGlobal;
33use crate::dom::bindings::root::{DomRoot, LayoutDom};
34use crate::dom::bindings::str::DOMString;
35use crate::dom::csp::{GlobalCspReporting, Violation};
36use crate::dom::document::Document;
37use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::html::htmlmediaelement::{HTMLMediaElement, NetworkState, ReadyState};
40use crate::dom::node::{Node, NodeTraits};
41use crate::dom::performance::performanceresourcetiming::InitiatorType;
42use crate::dom::virtualmethods::VirtualMethods;
43use crate::fetch::FetchCanceller;
44use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
45use crate::script_runtime::CanGc;
46
47#[dom_struct]
48pub(crate) struct HTMLVideoElement {
49 htmlmediaelement: HTMLMediaElement,
50 video_width: Cell<Option<u32>>,
52 video_height: Cell<Option<u32>>,
54 generation_id: Cell<u32>,
56 load_blocker: DomRefCell<Option<LoadBlocker>>,
59 #[ignore_malloc_size_of = "VideoFrame"]
61 #[no_trace]
62 last_frame: DomRefCell<Option<VideoFrame>>,
63 sent_resize: Cell<Option<(u32, u32)>>,
65}
66
67impl HTMLVideoElement {
68 fn new_inherited(
69 local_name: LocalName,
70 prefix: Option<Prefix>,
71 document: &Document,
72 ) -> HTMLVideoElement {
73 HTMLVideoElement {
74 htmlmediaelement: HTMLMediaElement::new_inherited(local_name, prefix, document),
75 video_width: Cell::new(None),
76 video_height: Cell::new(None),
77 generation_id: Cell::new(0),
78 load_blocker: Default::default(),
79 last_frame: Default::default(),
80 sent_resize: Cell::new(None),
81 }
82 }
83
84 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
85 pub(crate) fn new(
86 local_name: LocalName,
87 prefix: Option<Prefix>,
88 document: &Document,
89 proto: Option<HandleObject>,
90 can_gc: CanGc,
91 ) -> DomRoot<HTMLVideoElement> {
92 Node::reflect_node_with_proto(
93 Box::new(HTMLVideoElement::new_inherited(
94 local_name, prefix, document,
95 )),
96 document,
97 proto,
98 can_gc,
99 )
100 }
101
102 pub(crate) fn get_video_width(&self) -> Option<u32> {
103 self.video_width.get()
104 }
105
106 pub(crate) fn get_video_height(&self) -> Option<u32> {
107 self.video_height.get()
108 }
109
110 pub(crate) fn resize(&self, width: Option<u32>, height: Option<u32>) -> Option<(u32, u32)> {
112 self.video_width.set(width);
113 self.video_height.set(height);
114
115 let width = width?;
116 let height = height?;
117 if self.sent_resize.get() == Some((width, height)) {
118 return None;
119 }
120
121 let sent_resize = if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
122 None
123 } else {
124 self.owner_global()
125 .task_manager()
126 .media_element_task_source()
127 .queue_simple_event(self.upcast(), atom!("resize"));
128 Some((width, height))
129 };
130
131 self.sent_resize.set(sent_resize);
132 sent_resize
133 }
134
135 pub(crate) fn get_current_frame_data(&self) -> Option<Snapshot> {
139 let frame = self.htmlmediaelement.get_current_frame();
140 if frame.is_some() {
141 *self.last_frame.borrow_mut() = frame;
142 }
143
144 match self.last_frame.borrow().as_ref() {
145 Some(frame) => {
146 let size = Size2D::new(frame.get_width() as u32, frame.get_height() as u32);
147 if !frame.is_gl_texture() {
148 let alpha_mode = SnapshotAlphaMode::Transparent {
149 premultiplied: false,
150 };
151
152 Some(Snapshot::from_vec(
153 size.cast(),
154 SnapshotPixelFormat::BGRA,
155 alpha_mode,
156 frame.get_data().to_vec(),
157 ))
158 } else {
159 Some(Snapshot::cleared(size.cast()))
161 }
162 },
163 None => None,
164 }
165 }
166
167 fn update_poster_frame(&self, poster_url: Option<&str>, can_gc: CanGc) {
169 self.generation_id.set(self.generation_id.get() + 1);
173
174 let Some(poster_url) = poster_url.filter(|poster_url| !poster_url.is_empty()) else {
177 self.htmlmediaelement.set_poster_frame(None);
178 return;
179 };
180
181 let poster_url = match self.owner_document().encoding_parse_a_url(poster_url) {
186 Ok(url) => url,
187 Err(_) => {
188 self.htmlmediaelement.set_poster_frame(None);
189 return;
190 },
191 };
192
193 let window = self.owner_window();
196 let image_cache = window.image_cache();
197 let cache_result = image_cache.get_cached_image_status(
198 poster_url.clone(),
199 window.origin().immutable().clone(),
200 None,
201 );
202
203 let id = match cache_result {
204 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
205 image,
206 url,
207 ..
208 }) => {
209 self.process_image_response(ImageResponse::Loaded(image, url), can_gc);
210 return;
211 },
212 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(_, id)) => id,
213 ImageCacheResult::ReadyForRequest(id) => {
214 self.do_fetch_poster_frame(poster_url, id, can_gc);
215 id
216 },
217 ImageCacheResult::FailedToLoadOrDecode => {
218 self.process_image_response(ImageResponse::FailedToLoadOrDecode, can_gc);
219 return;
220 },
221 ImageCacheResult::Pending(id) => id,
222 };
223
224 let trusted_node = Trusted::new(self);
225 let generation = self.generation_id();
226 let callback = window.register_image_cache_listener(id, move |response| {
227 let element = trusted_node.root();
228
229 if generation != element.generation_id() {
231 return;
232 }
233 element.process_image_response(response.response, CanGc::note());
234 });
235
236 image_cache.add_listener(ImageLoadListener::new(callback, window.pipeline_id(), id));
237 }
238
239 fn do_fetch_poster_frame(&self, poster_url: ServoUrl, id: PendingImageId, can_gc: CanGc) {
241 let document = self.owner_document();
245 let request = RequestBuilder::new(
246 Some(document.webview_id()),
247 poster_url.clone(),
248 document.global().get_referrer(),
249 )
250 .destination(Destination::Image)
251 .credentials_mode(CredentialsMode::Include)
252 .use_url_credentials(true)
253 .origin(document.origin().immutable().clone())
254 .pipeline_id(Some(document.global().pipeline_id()))
255 .insecure_requests_policy(document.insecure_requests_policy())
256 .has_trustworthy_ancestor_origin(document.has_trustworthy_ancestor_origin())
257 .policy_container(document.policy_container().to_owned());
258
259 let blocker = &self.load_blocker;
266 LoadBlocker::terminate(blocker, can_gc);
267 *blocker.borrow_mut() = Some(LoadBlocker::new(
268 &self.owner_document(),
269 LoadType::Image(poster_url.clone()),
270 ));
271
272 let context = PosterFrameFetchContext::new(
273 self,
274 poster_url,
275 id,
276 request.id,
277 self.global().core_resource_thread(),
278 );
279 self.owner_document().fetch_background(request, context);
280 }
281
282 fn generation_id(&self) -> u32 {
283 self.generation_id.get()
284 }
285
286 fn process_image_response(&self, response: ImageResponse, can_gc: CanGc) {
288 match response {
291 ImageResponse::Loaded(image, url) => {
292 debug!("Loaded poster image for video element: {:?}", url);
293 match image.as_raster_image() {
294 Some(image) => self.htmlmediaelement.set_poster_frame(Some(image)),
295 None => warn!("Vector images are not yet supported in video poster"),
296 }
297 LoadBlocker::terminate(&self.load_blocker, can_gc);
298 },
299 ImageResponse::MetadataLoaded(..) => {},
300 ImageResponse::FailedToLoadOrDecode => {
302 self.htmlmediaelement.set_poster_frame(None);
303 LoadBlocker::terminate(&self.load_blocker, can_gc);
305 },
306 }
307 }
308
309 pub(crate) fn is_usable(&self) -> bool {
311 !matches!(
312 self.htmlmediaelement.get_ready_state(),
313 ReadyState::HaveNothing | ReadyState::HaveMetadata
314 )
315 }
316
317 pub(crate) fn origin_is_clean(&self) -> bool {
318 self.htmlmediaelement.origin_is_clean()
319 }
320
321 pub(crate) fn is_network_state_empty(&self) -> bool {
322 self.htmlmediaelement.network_state() == NetworkState::Empty
323 }
324}
325
326impl HTMLVideoElementMethods<crate::DomTypeHolder> for HTMLVideoElement {
327 make_dimension_uint_getter!(Width, "width");
329
330 make_dimension_uint_setter!(SetWidth, "width");
332
333 make_dimension_uint_getter!(Height, "height");
335
336 make_dimension_uint_setter!(SetHeight, "height");
338
339 fn VideoWidth(&self) -> u32 {
341 if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
342 return 0;
343 }
344 self.video_width.get().unwrap_or(0)
345 }
346
347 fn VideoHeight(&self) -> u32 {
349 if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
350 return 0;
351 }
352 self.video_height.get().unwrap_or(0)
353 }
354
355 make_getter!(Poster, "poster");
357
358 make_setter!(SetPoster, "poster");
360
361 event_handler!(postershown, GetOnpostershown, SetOnpostershown);
364}
365
366impl VirtualMethods for HTMLVideoElement {
367 fn super_type(&self) -> Option<&dyn VirtualMethods> {
368 Some(self.upcast::<HTMLMediaElement>() as &dyn VirtualMethods)
369 }
370
371 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
372 self.super_type()
373 .unwrap()
374 .attribute_mutated(attr, mutation, can_gc);
375
376 if attr.local_name() == &local_name!("poster") {
377 if let Some(new_value) = mutation.new_value(attr) {
378 self.update_poster_frame(Some(&new_value), CanGc::note())
379 } else {
380 self.update_poster_frame(None, CanGc::note())
381 }
382 };
383 }
384
385 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
386 match attr.local_name() {
387 &local_name!("width") | &local_name!("height") => true,
388 _ => self
389 .super_type()
390 .unwrap()
391 .attribute_affects_presentational_hints(attr),
392 }
393 }
394
395 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
396 match name {
397 &local_name!("width") | &local_name!("height") => {
398 AttrValue::from_dimension(value.into())
399 },
400 _ => self
401 .super_type()
402 .unwrap()
403 .parse_plain_attribute(name, value),
404 }
405 }
406}
407
408struct PosterFrameFetchContext {
409 image_cache: Arc<dyn ImageCache>,
411 elem: Trusted<HTMLVideoElement>,
413 id: PendingImageId,
415 cancelled: bool,
417 url: ServoUrl,
419 fetch_canceller: FetchCanceller,
421}
422
423impl FetchResponseListener for PosterFrameFetchContext {
424 fn process_request_body(&mut self, _: RequestId) {}
425
426 fn process_request_eof(&mut self, _: RequestId) {
427 self.fetch_canceller.ignore()
428 }
429
430 fn process_response(
431 &mut self,
432 request_id: RequestId,
433 metadata: Result<FetchMetadata, NetworkError>,
434 ) {
435 self.image_cache.notify_pending_response(
436 self.id,
437 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
438 );
439
440 let metadata = metadata.ok().map(|meta| match meta {
441 FetchMetadata::Unfiltered(m) => m,
442 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
443 });
444
445 let status_is_ok = metadata
446 .as_ref()
447 .map_or(true, |m| m.status.in_range(200..300));
448
449 if !status_is_ok {
450 self.cancelled = true;
451 self.fetch_canceller.cancel();
452 }
453 }
454
455 fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
456 if self.cancelled {
457 return;
459 }
460
461 self.image_cache.notify_pending_response(
462 self.id,
463 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
464 );
465 }
466
467 fn process_response_eof(
468 self,
469 request_id: RequestId,
470 response: Result<ResourceFetchTiming, NetworkError>,
471 ) {
472 self.image_cache.notify_pending_response(
473 self.id,
474 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone()),
475 );
476 if let Ok(response) = response {
477 network_listener::submit_timing(&self, &response, CanGc::note());
478 }
479 }
480
481 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
482 let global = &self.resource_timing_global();
483 global.report_csp_violations(violations, None, None);
484 }
485}
486
487impl ResourceTimingListener for PosterFrameFetchContext {
488 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
489 let initiator_type = InitiatorType::LocalName(
490 self.elem
491 .root()
492 .upcast::<Element>()
493 .local_name()
494 .to_string(),
495 );
496 (initiator_type, self.url.clone())
497 }
498
499 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
500 self.elem.root().owner_document().global()
501 }
502}
503
504impl PosterFrameFetchContext {
505 fn new(
506 elem: &HTMLVideoElement,
507 url: ServoUrl,
508 id: PendingImageId,
509 request_id: RequestId,
510 core_resource_thread: CoreResourceThread,
511 ) -> PosterFrameFetchContext {
512 let window = elem.owner_window();
513 PosterFrameFetchContext {
514 image_cache: window.image_cache(),
515 elem: Trusted::new(elem),
516 id,
517 cancelled: false,
518 url,
519 fetch_canceller: FetchCanceller::new(request_id, core_resource_thread),
520 }
521 }
522}
523
524pub(crate) trait LayoutHTMLVideoElementHelpers {
525 fn data(self) -> HTMLMediaData;
526 fn get_width(self) -> LengthOrPercentageOrAuto;
527 fn get_height(self) -> LengthOrPercentageOrAuto;
528}
529
530impl LayoutHTMLVideoElementHelpers for LayoutDom<'_, HTMLVideoElement> {
531 fn data(self) -> HTMLMediaData {
532 let video = self.unsafe_get();
533
534 let current_frame = video.htmlmediaelement.get_current_frame_to_present();
536
537 let metadata = video
541 .get_video_width()
542 .zip(video.get_video_height())
543 .map(|(width, height)| MediaMetadata { width, height });
544
545 HTMLMediaData {
546 current_frame,
547 metadata,
548 }
549 }
550
551 fn get_width(self) -> LengthOrPercentageOrAuto {
552 self.upcast::<Element>()
553 .get_attr_for_layout(&ns!(), &local_name!("width"))
554 .map(AttrValue::as_dimension)
555 .cloned()
556 .unwrap_or(LengthOrPercentageOrAuto::Auto)
557 }
558
559 fn get_height(self) -> LengthOrPercentageOrAuto {
560 self.upcast::<Element>()
561 .get_attr_for_layout(&ns!(), &local_name!("height"))
562 .map(AttrValue::as_dimension)
563 .cloned()
564 .unwrap_or(LengthOrPercentageOrAuto::Auto)
565 }
566}