htmlmediaelement: Support seek requests for non seekable fetch context

According to specification the data:// URL protocol doesn't support range request
so be able make any seek request to required content position let's allow
for non seekable fetch context discard fetched content bytes until seek offset.
https://fetch.spec.whatwg.org/#scheme-fetch

Some scheme URLs (like data:// URL) doesn't expose "Content-Length" header in response
so the total expected size of the stream is unknown and it causes some additional
seek request (SeekData) from the media player. Try to post configure stream size
after we reached fetch EOS response.

Related source code which breaks WPT tests:
tests/wpt/tests/html/canvas/element/manual/imagebitmap/common.sub.js#L56-L78

Testing: Improvements in the following tests:
 - html/canvas/element/manual/imagebitmap/createImageBitmap*
 - mixed-content/gen/top.***/opt-in/video-tag.https.html
 - webgl/tests/conformance/textures/misc/texture-srgb-upload.html

Fixes: servo#32645
Fixes: servo#32745
Fixes: servo#34119
Fixes: servo#34120
Fixes: servo#34151

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-06-06 13:23:50 +03:00
parent d246e80844
commit 0706594469
10 changed files with 52 additions and 42 deletions

View file

@ -2892,12 +2892,12 @@ struct HTMLMediaElementFetchListener {
url: ServoUrl,
/// Expected content length of the media asset being fetched or played.
expected_content_length: Option<u64>,
/// Number of the last byte fetched from the network for the ongoing
/// request. It is only reset to 0 if we reach EOS. Seek requests
/// set it to the requested position. Requests triggered after an
/// EnoughData event uses this value to restart the download from
/// the last fetched position.
latest_fetched_content: u64,
/// Actual content length of the media asset was fetched.
fetched_content_length: u64,
/// Discarded content length from the network for the ongoing
/// request if range requests are not supported. Seek requests set it
/// to the required position (in bytes).
content_length_to_discard: u64,
}
// https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list
@ -2986,10 +2986,10 @@ impl FetchResponseListener for HTMLMediaElementFetchListener {
}
}
fn process_response_chunk(&mut self, _: RequestId, payload: Vec<u8>) {
fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
let elem = self.elem.root();
let payload_len = payload.len() as u64;
self.fetched_content_length += chunk.len() as u64;
// If an error was received previously, we skip processing the payload.
if let Some(ref mut current_fetch_context) = *elem.current_fetch_context.borrow_mut() {
@ -2997,6 +2997,24 @@ impl FetchResponseListener for HTMLMediaElementFetchListener {
return;
}
// Discard chunk of the response body if fetch context doesn't
// support range requests.
let payload = if !current_fetch_context.is_seekable() &&
self.content_length_to_discard != 0
{
if chunk.len() as u64 > self.content_length_to_discard {
let shrink_chunk = chunk[self.content_length_to_discard as usize..].to_vec();
self.content_length_to_discard = 0;
shrink_chunk
} else {
// Completely discard this response chunk.
self.content_length_to_discard -= chunk.len() as u64;
return;
}
} else {
chunk
};
if let Err(e) = {
let mut data_source = current_fetch_context.data_source().borrow_mut();
data_source.add_buffer_to_queue(DataBuffer::Payload(payload));
@ -3013,8 +3031,6 @@ impl FetchResponseListener for HTMLMediaElementFetchListener {
}
}
self.latest_fetched_content += payload_len;
// https://html.spec.whatwg.org/multipage/#concept-media-load-resource step 4,
// => "If mode is remote" step 2
if Instant::now() > self.next_progress_event {
@ -3039,6 +3055,26 @@ impl FetchResponseListener for HTMLMediaElementFetchListener {
// There are no more chunks of the response body forthcoming, so we can
// go ahead and notify the media backend not to expect any further data.
if let Some(ref mut current_fetch_context) = *elem.current_fetch_context.borrow_mut() {
// On initial state change READY -> PAUSED the media player perform
// seek to initial position by event with seek segment (TIME format)
// while media stack operates in BYTES format and configuring segment
// start and stop positions without the total size of the stream is not
// possible. As fallback the media player perform seek with BYTES format
// and initiate seek request via "seek-data" callback with required offset.
if self.expected_content_length.is_none() && self.fetched_content_length != 0 {
if let Err(e) = elem
.player
.borrow()
.as_ref()
.unwrap()
.lock()
.unwrap()
.set_input_size(self.fetched_content_length)
{
warn!("Could not set player input size {:?}", e);
}
}
let mut data_source = current_fetch_context.data_source().borrow_mut();
data_source.add_buffer_to_queue(DataBuffer::EndOfStream);
@ -3051,7 +3087,7 @@ impl FetchResponseListener for HTMLMediaElementFetchListener {
}
}
if status.is_ok() && self.latest_fetched_content != 0 {
if status.is_ok() && self.fetched_content_length != 0 {
elem.upcast::<EventTarget>()
.fire_event(atom!("progress"), CanGc::note());
@ -3157,7 +3193,8 @@ impl HTMLMediaElementFetchListener {
resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
url,
expected_content_length: None,
latest_fetched_content: offset,
fetched_content_length: 0,
content_length_to_discard: offset,
}
}
}

View file

@ -1,5 +1,4 @@
[createImageBitmap-colorSpaceConversion.html]
expected: ERROR
[createImageBitmap from a bitmap HTMLImageElement, and drawImage on the created ImageBitmap with colorSpaceConversion: none]
expected: FAIL

View file

@ -1,5 +1,4 @@
[createImageBitmap-drawImage.html]
expected: ERROR
[createImageBitmap from an OffscreenCanvas resized, and drawImage on the created ImageBitmap]
expected: FAIL

View file

@ -1,5 +1,4 @@
[createImageBitmap-flipY.html]
expected: ERROR
[createImageBitmap from a vector SVGImageElement imageOrientation: "none", and drawImage on the created ImageBitmap]
expected: NOTRUN

View file

@ -1,5 +1,4 @@
[createImageBitmap-invalid-args.html]
expected: ERROR
[createImageBitmap with a vector HTMLImageElement source and sw set to 0]
expected: FAIL
@ -138,18 +137,12 @@
[createImageBitmap with a vector SVGImageElement source and a value between 0 and 1 in resizeHeight]
expected: FAIL
[createImageBitmap with an HTMLVideoElement from a data URL source and a value between 0 and 1 in resizeWidth]
expected: FAIL
[createImageBitmap with a vector SVGImageElement source and a value of 0 in resizeHeight]
expected: FAIL
[createImageBitmap with a bitmap SVGImageElement source and a value between 0 and 1 in resizeHeight]
expected: FAIL
[createImageBitmap with an HTMLVideoElement from a data URL source and a value of 0 in resizeHeight]
expected: FAIL
[createImageBitmap with an ImageBitmap source and a value of 0 in resizeHeight]
expected: FAIL
@ -162,11 +155,5 @@
[createImageBitmap with a bitmap SVGImageElement source and a value of 0 int resizeWidth]
expected: FAIL
[createImageBitmap with an HTMLVideoElement from a data URL source and a value of 0 int resizeWidth]
expected: FAIL
[createImageBitmap with an HTMLVideoElement from a data URL source and a value between 0 and 1 in resizeHeight]
expected: FAIL
[createImageBitmap with a vector SVGImageElement source and a value between 0 and 1 in resizeWidth]
expected: FAIL

View file

@ -1,5 +1,4 @@
[createImageBitmap-premultiplyAlpha.html]
expected: ERROR
[createImageBitmap: from ImageData, unpremultiplied, drawn to canvas]
expected: FAIL

View file

@ -1,5 +1,4 @@
[createImageBitmap-transfer.html]
expected: ERROR
[Transfer ImageBitmap created from a vector HTMLImageElement]
expected: FAIL

View file

@ -1,13 +1,7 @@
[video-tag.https.html]
expected: TIMEOUT
[Mixed-Content: Expects allowed for video-tag to same-https origin and keep-scheme redirection from https context.]
expected: TIMEOUT
[Mixed-Content: Expects allowed for video-tag to same-https origin and no-redirect redirection from https context.]
expected: NOTRUN
[Mixed-Content: Expects blocked for video-tag to cross-http origin and keep-scheme redirection from https context.]
expected: NOTRUN
expected: TIMEOUT
[Mixed-Content: Expects blocked for video-tag to cross-http origin and no-redirect redirection from https context.]
expected: NOTRUN

View file

@ -1,10 +1,7 @@
[video-tag.https.html]
expected: TIMEOUT
[Mixed-Content: Expects allowed for video-tag to same-https origin and no-redirect redirection from https context.]
expected: TIMEOUT
[Mixed-Content: Expects blocked for video-tag to cross-http origin and no-redirect redirection from https context.]
expected: NOTRUN
expected: TIMEOUT
[Mixed-Content: Expects blocked for video-tag to same-http origin and no-redirect redirection from https context.]
expected: NOTRUN

View file

@ -1,4 +1,4 @@
[texture-srgb-upload.html]
expected: TIMEOUT
expected: ERROR
[Overall test]
expected: NOTRUN