From 07065944694ec4e236c206e39676be60e510707d Mon Sep 17 00:00:00 2001 From: Andrei Volykhin Date: Fri, 6 Jun 2025 13:23:50 +0300 Subject: [PATCH] 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 --- components/script/dom/htmlmediaelement.rs | 61 +++++++++++++++---- ...eImageBitmap-colorSpaceConversion.html.ini | 1 - .../createImageBitmap-drawImage.html.ini | 1 - .../createImageBitmap-flipY.html.ini | 1 - .../createImageBitmap-invalid-args.html.ini | 13 ---- ...reateImageBitmap-premultiplyAlpha.html.ini | 1 - .../createImageBitmap-transfer.html.ini | 1 - .../opt-in/video-tag.https.html.ini | 8 +-- .../top.meta/opt-in/video-tag.https.html.ini | 5 +- .../misc/texture-srgb-upload.html.ini | 2 +- 10 files changed, 52 insertions(+), 42 deletions(-) diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index aa1323cdc5f..5192b2274a8 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -2892,12 +2892,12 @@ struct HTMLMediaElementFetchListener { url: ServoUrl, /// Expected content length of the media asset being fetched or played. expected_content_length: Option, - /// 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) { + fn process_response_chunk(&mut self, _: RequestId, chunk: Vec) { 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::() .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, } } } diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini index 1cb6ce30242..1629ecdc8f3 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini @@ -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 diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini index 9cf5b2af86a..c999a8d9b18 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini @@ -1,5 +1,4 @@ [createImageBitmap-drawImage.html] - expected: ERROR [createImageBitmap from an OffscreenCanvas resized, and drawImage on the created ImageBitmap] expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini index 0cb93c5abc9..f279cd2dd85 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini @@ -1,5 +1,4 @@ [createImageBitmap-flipY.html] - expected: ERROR [createImageBitmap from a vector SVGImageElement imageOrientation: "none", and drawImage on the created ImageBitmap] expected: NOTRUN diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini index ee557e4bc27..94caf82cb7f 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini @@ -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 diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-premultiplyAlpha.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-premultiplyAlpha.html.ini index 65a62f89001..15eea07cff8 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-premultiplyAlpha.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-premultiplyAlpha.html.ini @@ -1,5 +1,4 @@ [createImageBitmap-premultiplyAlpha.html] - expected: ERROR [createImageBitmap: from ImageData, unpremultiplied, drawn to canvas] expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini index 495a1789274..20be1f3b9e5 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini @@ -1,5 +1,4 @@ [createImageBitmap-transfer.html] - expected: ERROR [Transfer ImageBitmap created from a vector HTMLImageElement] expected: FAIL diff --git a/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/video-tag.https.html.ini b/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/video-tag.https.html.ini index 25b2d40022f..d6038010ff0 100644 --- a/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/video-tag.https.html.ini +++ b/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/video-tag.https.html.ini @@ -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 diff --git a/tests/wpt/meta/mixed-content/gen/top.meta/opt-in/video-tag.https.html.ini b/tests/wpt/meta/mixed-content/gen/top.meta/opt-in/video-tag.https.html.ini index 933ea12e10c..a725690bbc6 100644 --- a/tests/wpt/meta/mixed-content/gen/top.meta/opt-in/video-tag.https.html.ini +++ b/tests/wpt/meta/mixed-content/gen/top.meta/opt-in/video-tag.https.html.ini @@ -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 diff --git a/tests/wpt/webgl/meta/conformance/textures/misc/texture-srgb-upload.html.ini b/tests/wpt/webgl/meta/conformance/textures/misc/texture-srgb-upload.html.ini index 4afbf9ef9a0..a3acae77b7a 100644 --- a/tests/wpt/webgl/meta/conformance/textures/misc/texture-srgb-upload.html.ini +++ b/tests/wpt/webgl/meta/conformance/textures/misc/texture-srgb-upload.html.ini @@ -1,4 +1,4 @@ [texture-srgb-upload.html] - expected: TIMEOUT + expected: ERROR [Overall test] expected: NOTRUN