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, NodeDamage, 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}
64
65impl HTMLVideoElement {
66 fn new_inherited(
67 local_name: LocalName,
68 prefix: Option<Prefix>,
69 document: &Document,
70 ) -> HTMLVideoElement {
71 HTMLVideoElement {
72 htmlmediaelement: HTMLMediaElement::new_inherited(local_name, prefix, document),
73 video_width: Cell::new(None),
74 video_height: Cell::new(None),
75 generation_id: Cell::new(0),
76 load_blocker: Default::default(),
77 last_frame: Default::default(),
78 }
79 }
80
81 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
82 pub(crate) fn new(
83 local_name: LocalName,
84 prefix: Option<Prefix>,
85 document: &Document,
86 proto: Option<HandleObject>,
87 can_gc: CanGc,
88 ) -> DomRoot<HTMLVideoElement> {
89 Node::reflect_node_with_proto(
90 Box::new(HTMLVideoElement::new_inherited(
91 local_name, prefix, document,
92 )),
93 document,
94 proto,
95 can_gc,
96 )
97 }
98
99 pub(crate) fn get_video_width(&self) -> Option<u32> {
100 self.video_width.get()
101 }
102
103 pub(crate) fn get_video_height(&self) -> Option<u32> {
104 self.video_height.get()
105 }
106
107 pub(crate) fn set_natural_dimensions(&self, width: Option<u32>, height: Option<u32>) -> bool {
108 if self.video_width.get() == width && self.video_height.get() == height {
109 return false;
110 }
111
112 self.video_width.set(width);
113 self.video_height.set(height);
114
115 self.upcast::<Node>().dirty(NodeDamage::Other);
116 true
117 }
118
119 pub(crate) fn get_current_frame_data(&self) -> Option<Snapshot> {
123 let frame = self.htmlmediaelement.get_current_frame();
124 if frame.is_some() {
125 *self.last_frame.borrow_mut() = frame;
126 }
127
128 match self.last_frame.borrow().as_ref() {
129 Some(frame) => {
130 let size = Size2D::new(frame.get_width() as u32, frame.get_height() as u32);
131 if !frame.is_gl_texture() {
132 let alpha_mode = SnapshotAlphaMode::Transparent {
133 premultiplied: false,
134 };
135
136 Some(Snapshot::from_vec(
137 size.cast(),
138 SnapshotPixelFormat::BGRA,
139 alpha_mode,
140 frame.get_data().to_vec(),
141 ))
142 } else {
143 Some(Snapshot::cleared(size.cast()))
145 }
146 },
147 None => None,
148 }
149 }
150
151 fn update_poster_frame(&self, poster_url: Option<&str>, can_gc: CanGc) {
153 self.generation_id.set(self.generation_id.get() + 1);
157
158 let Some(poster_url) = poster_url.filter(|poster_url| !poster_url.is_empty()) else {
161 self.htmlmediaelement.set_poster_frame(None);
162 return;
163 };
164
165 let poster_url = match self.owner_document().encoding_parse_a_url(poster_url) {
170 Ok(url) => url,
171 Err(_) => {
172 self.htmlmediaelement.set_poster_frame(None);
173 return;
174 },
175 };
176
177 let window = self.owner_window();
180 let image_cache = window.image_cache();
181 let cache_result = image_cache.get_cached_image_status(
182 poster_url.clone(),
183 window.origin().immutable().clone(),
184 None,
185 );
186
187 let id = match cache_result {
188 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
189 image,
190 url,
191 ..
192 }) => {
193 self.process_image_response(ImageResponse::Loaded(image, url), can_gc);
194 return;
195 },
196 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(_, id)) => id,
197 ImageCacheResult::ReadyForRequest(id) => {
198 self.do_fetch_poster_frame(poster_url, id, can_gc);
199 id
200 },
201 ImageCacheResult::FailedToLoadOrDecode => {
202 self.process_image_response(ImageResponse::FailedToLoadOrDecode, can_gc);
203 return;
204 },
205 ImageCacheResult::Pending(id) => id,
206 };
207
208 let trusted_node = Trusted::new(self);
209 let generation = self.generation_id();
210 let callback = window.register_image_cache_listener(id, move |response| {
211 let element = trusted_node.root();
212
213 if generation != element.generation_id() {
215 return;
216 }
217 element.process_image_response(response.response, CanGc::note());
218 });
219
220 image_cache.add_listener(ImageLoadListener::new(callback, window.pipeline_id(), id));
221 }
222
223 fn do_fetch_poster_frame(&self, poster_url: ServoUrl, id: PendingImageId, can_gc: CanGc) {
225 let document = self.owner_document();
229 let request = RequestBuilder::new(
230 Some(document.webview_id()),
231 poster_url.clone(),
232 document.global().get_referrer(),
233 )
234 .destination(Destination::Image)
235 .credentials_mode(CredentialsMode::Include)
236 .use_url_credentials(true)
237 .origin(document.origin().immutable().clone())
238 .pipeline_id(Some(document.global().pipeline_id()))
239 .insecure_requests_policy(document.insecure_requests_policy())
240 .has_trustworthy_ancestor_origin(document.has_trustworthy_ancestor_origin())
241 .policy_container(document.policy_container().to_owned());
242
243 let blocker = &self.load_blocker;
250 LoadBlocker::terminate(blocker, can_gc);
251 *blocker.borrow_mut() = Some(LoadBlocker::new(
252 &self.owner_document(),
253 LoadType::Image(poster_url.clone()),
254 ));
255
256 let context = PosterFrameFetchContext::new(
257 self,
258 poster_url,
259 id,
260 request.id,
261 self.global().core_resource_thread(),
262 );
263 self.owner_document().fetch_background(request, context);
264 }
265
266 fn generation_id(&self) -> u32 {
267 self.generation_id.get()
268 }
269
270 fn process_image_response(&self, response: ImageResponse, can_gc: CanGc) {
272 match response {
275 ImageResponse::Loaded(image, url) => {
276 debug!("Loaded poster image for video element: {:?}", url);
277 match image.as_raster_image() {
278 Some(image) => self.htmlmediaelement.set_poster_frame(Some(image)),
279 None => warn!("Vector images are not yet supported in video poster"),
280 }
281 LoadBlocker::terminate(&self.load_blocker, can_gc);
282 },
283 ImageResponse::MetadataLoaded(..) => {},
284 ImageResponse::FailedToLoadOrDecode => {
286 self.htmlmediaelement.set_poster_frame(None);
287 LoadBlocker::terminate(&self.load_blocker, can_gc);
289 },
290 }
291 }
292
293 pub(crate) fn is_usable(&self) -> bool {
295 !matches!(
296 self.htmlmediaelement.get_ready_state(),
297 ReadyState::HaveNothing | ReadyState::HaveMetadata
298 )
299 }
300
301 pub(crate) fn origin_is_clean(&self) -> bool {
302 self.htmlmediaelement.origin_is_clean()
303 }
304
305 pub(crate) fn is_network_state_empty(&self) -> bool {
306 self.htmlmediaelement.network_state() == NetworkState::Empty
307 }
308}
309
310impl HTMLVideoElementMethods<crate::DomTypeHolder> for HTMLVideoElement {
311 make_dimension_uint_getter!(Width, "width");
313
314 make_dimension_uint_setter!(SetWidth, "width");
316
317 make_dimension_uint_getter!(Height, "height");
319
320 make_dimension_uint_setter!(SetHeight, "height");
322
323 fn VideoWidth(&self) -> u32 {
325 if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
326 return 0;
327 }
328 self.video_width.get().unwrap_or(0)
329 }
330
331 fn VideoHeight(&self) -> u32 {
333 if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
334 return 0;
335 }
336 self.video_height.get().unwrap_or(0)
337 }
338
339 make_getter!(Poster, "poster");
341
342 make_setter!(SetPoster, "poster");
344
345 event_handler!(postershown, GetOnpostershown, SetOnpostershown);
348}
349
350impl VirtualMethods for HTMLVideoElement {
351 fn super_type(&self) -> Option<&dyn VirtualMethods> {
352 Some(self.upcast::<HTMLMediaElement>() as &dyn VirtualMethods)
353 }
354
355 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
356 self.super_type()
357 .unwrap()
358 .attribute_mutated(attr, mutation, can_gc);
359
360 if attr.local_name() == &local_name!("poster") {
361 if let Some(new_value) = mutation.new_value(attr) {
362 self.update_poster_frame(Some(&new_value), CanGc::note())
363 } else {
364 self.update_poster_frame(None, CanGc::note())
365 }
366 };
367 }
368
369 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
370 match attr.local_name() {
371 &local_name!("width") | &local_name!("height") => true,
372 _ => self
373 .super_type()
374 .unwrap()
375 .attribute_affects_presentational_hints(attr),
376 }
377 }
378
379 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
380 match name {
381 &local_name!("width") | &local_name!("height") => {
382 AttrValue::from_dimension(value.into())
383 },
384 _ => self
385 .super_type()
386 .unwrap()
387 .parse_plain_attribute(name, value),
388 }
389 }
390}
391
392struct PosterFrameFetchContext {
393 image_cache: Arc<dyn ImageCache>,
395 elem: Trusted<HTMLVideoElement>,
397 id: PendingImageId,
399 cancelled: bool,
401 url: ServoUrl,
403 fetch_canceller: FetchCanceller,
405}
406
407impl FetchResponseListener for PosterFrameFetchContext {
408 fn process_request_body(&mut self, _: RequestId) {}
409
410 fn process_request_eof(&mut self, _: RequestId) {
411 self.fetch_canceller.ignore()
412 }
413
414 fn process_response(
415 &mut self,
416 request_id: RequestId,
417 metadata: Result<FetchMetadata, NetworkError>,
418 ) {
419 self.image_cache.notify_pending_response(
420 self.id,
421 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
422 );
423
424 let metadata = metadata.ok().map(|meta| match meta {
425 FetchMetadata::Unfiltered(m) => m,
426 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
427 });
428
429 let status_is_ok = metadata
430 .as_ref()
431 .map_or(true, |m| m.status.in_range(200..300));
432
433 if !status_is_ok {
434 self.cancelled = true;
435 self.fetch_canceller.cancel();
436 }
437 }
438
439 fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
440 if self.cancelled {
441 return;
443 }
444
445 self.image_cache.notify_pending_response(
446 self.id,
447 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
448 );
449 }
450
451 fn process_response_eof(
452 self,
453 request_id: RequestId,
454 response: Result<ResourceFetchTiming, NetworkError>,
455 ) {
456 self.image_cache.notify_pending_response(
457 self.id,
458 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone()),
459 );
460 if let Ok(response) = response {
461 network_listener::submit_timing(&self, &response, CanGc::note());
462 }
463 }
464
465 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
466 let global = &self.resource_timing_global();
467 global.report_csp_violations(violations, None, None);
468 }
469}
470
471impl ResourceTimingListener for PosterFrameFetchContext {
472 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
473 let initiator_type = InitiatorType::LocalName(
474 self.elem
475 .root()
476 .upcast::<Element>()
477 .local_name()
478 .to_string(),
479 );
480 (initiator_type, self.url.clone())
481 }
482
483 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
484 self.elem.root().owner_document().global()
485 }
486}
487
488impl PosterFrameFetchContext {
489 fn new(
490 elem: &HTMLVideoElement,
491 url: ServoUrl,
492 id: PendingImageId,
493 request_id: RequestId,
494 core_resource_thread: CoreResourceThread,
495 ) -> PosterFrameFetchContext {
496 let window = elem.owner_window();
497 PosterFrameFetchContext {
498 image_cache: window.image_cache(),
499 elem: Trusted::new(elem),
500 id,
501 cancelled: false,
502 url,
503 fetch_canceller: FetchCanceller::new(request_id, core_resource_thread),
504 }
505 }
506}
507
508pub(crate) trait LayoutHTMLVideoElementHelpers {
509 fn data(self) -> HTMLMediaData;
510 fn get_width(self) -> LengthOrPercentageOrAuto;
511 fn get_height(self) -> LengthOrPercentageOrAuto;
512}
513
514impl LayoutHTMLVideoElementHelpers for LayoutDom<'_, HTMLVideoElement> {
515 fn data(self) -> HTMLMediaData {
516 let video = self.unsafe_get();
517
518 let current_frame = video.htmlmediaelement.get_current_frame_to_present();
520
521 let metadata = video
525 .get_video_width()
526 .zip(video.get_video_height())
527 .map(|(width, height)| MediaMetadata { width, height });
528
529 HTMLMediaData {
530 current_frame,
531 metadata,
532 }
533 }
534
535 fn get_width(self) -> LengthOrPercentageOrAuto {
536 self.upcast::<Element>()
537 .get_attr_for_layout(&ns!(), &local_name!("width"))
538 .map(AttrValue::as_dimension)
539 .cloned()
540 .unwrap_or(LengthOrPercentageOrAuto::Auto)
541 }
542
543 fn get_height(self) -> LengthOrPercentageOrAuto {
544 self.upcast::<Element>()
545 .get_attr_for_layout(&ns!(), &local_name!("height"))
546 .map(AttrValue::as_dimension)
547 .cloned()
548 .unwrap_or(LengthOrPercentageOrAuto::Auto)
549 }
550}