Propagate CanGc when interacting with readable streams. (#33975)

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2024-10-23 07:49:59 -04:00 committed by GitHub
parent f553bda7eb
commit 12e6ec25aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 80 additions and 57 deletions

View file

@ -438,21 +438,21 @@ impl ExtractedBody {
/// <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
pub trait Extractable {
fn extract(&self, global: &GlobalScope) -> Fallible<ExtractedBody>;
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody>;
}
impl Extractable for BodyInit {
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
fn extract(&self, global: &GlobalScope) -> Fallible<ExtractedBody> {
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
match self {
BodyInit::String(ref s) => s.extract(global),
BodyInit::URLSearchParams(ref usp) => usp.extract(global),
BodyInit::Blob(ref b) => b.extract(global),
BodyInit::FormData(ref formdata) => formdata.extract(global),
BodyInit::String(ref s) => s.extract(global, can_gc),
BodyInit::URLSearchParams(ref usp) => usp.extract(global, can_gc),
BodyInit::Blob(ref b) => b.extract(global, can_gc),
BodyInit::FormData(ref formdata) => formdata.extract(global, can_gc),
BodyInit::ArrayBuffer(ref typedarray) => {
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let stream = ReadableStream::new_from_bytes(global, bytes);
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -463,7 +463,7 @@ impl Extractable for BodyInit {
BodyInit::ArrayBufferView(ref typedarray) => {
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let stream = ReadableStream::new_from_bytes(global, bytes);
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -493,10 +493,10 @@ impl Extractable for BodyInit {
}
impl Extractable for Vec<u8> {
fn extract(&self, global: &GlobalScope) -> Fallible<ExtractedBody> {
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
let bytes = self.clone();
let total_bytes = self.len();
let stream = ReadableStream::new_from_bytes(global, bytes);
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -508,7 +508,7 @@ impl Extractable for Vec<u8> {
}
impl Extractable for Blob {
fn extract(&self, _global: &GlobalScope) -> Fallible<ExtractedBody> {
fn extract(&self, _global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
let blob_type = self.Type();
let content_type = if blob_type.as_ref().is_empty() {
None
@ -517,7 +517,7 @@ impl Extractable for Blob {
};
let total_bytes = self.Size() as usize;
Ok(ExtractedBody {
stream: self.get_stream(),
stream: self.get_stream(can_gc),
total_bytes: Some(total_bytes),
content_type,
source: BodySource::Object,
@ -526,11 +526,11 @@ impl Extractable for Blob {
}
impl Extractable for DOMString {
fn extract(&self, global: &GlobalScope) -> Fallible<ExtractedBody> {
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
let bytes = self.as_bytes().to_owned();
let total_bytes = bytes.len();
let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
let stream = ReadableStream::new_from_bytes(global, bytes);
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -541,7 +541,7 @@ impl Extractable for DOMString {
}
impl Extractable for FormData {
fn extract(&self, global: &GlobalScope) -> Fallible<ExtractedBody> {
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
let boundary = generate_boundary();
let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
let total_bytes = bytes.len();
@ -549,7 +549,7 @@ impl Extractable for FormData {
"multipart/form-data;boundary={}",
boundary
)));
let stream = ReadableStream::new_from_bytes(global, bytes);
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -560,13 +560,13 @@ impl Extractable for FormData {
}
impl Extractable for URLSearchParams {
fn extract(&self, global: &GlobalScope) -> Fallible<ExtractedBody> {
fn extract(&self, global: &GlobalScope, can_gc: CanGc) -> Fallible<ExtractedBody> {
let bytes = self.serialize_utf8().into_bytes();
let total_bytes = bytes.len();
let content_type = Some(DOMString::from(
"application/x-www-form-urlencoded;charset=UTF-8",
));
let stream = ReadableStream::new_from_bytes(global, bytes);
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc);
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -756,7 +756,7 @@ fn consume_body_with_promise<T: BodyMixin + DomObject>(
// Step 2.
let stream = match object.body() {
Some(stream) => stream,
None => ReadableStream::new_from_bytes(&global, Vec::with_capacity(0)),
None => ReadableStream::new_from_bytes(&global, Vec::with_capacity(0), can_gc),
};
// Step 3.

View file

@ -30,7 +30,7 @@ DOMInterfaces = {
'Blob': {
'weakReferenceable': True,
'canGc': ['Slice', 'Text', 'ArrayBuffer'],
'canGc': ['Slice', 'Text', 'ArrayBuffer', 'Stream'],
},
'Bluetooth': {

View file

@ -86,8 +86,8 @@ impl Blob {
}
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
pub fn get_stream(&self) -> DomRoot<ReadableStream> {
self.global().get_blob_stream(&self.blob_id)
pub fn get_stream(&self, can_gc: CanGc) -> DomRoot<ReadableStream> {
self.global().get_blob_stream(&self.blob_id, can_gc)
}
}
@ -237,8 +237,8 @@ impl BlobMethods for Blob {
}
// <https://w3c.github.io/FileAPI/#blob-get-stream>
fn Stream(&self, _cx: JSContext) -> NonNull<JSObject> {
self.get_stream().get_js_stream()
fn Stream(&self, _cx: JSContext, can_gc: CanGc) -> NonNull<JSObject> {
self.get_stream(can_gc).get_js_stream()
}
// https://w3c.github.io/FileAPI/#slice-method-algo

View file

@ -617,10 +617,10 @@ impl MessageListener {
}
/// Callback used to enqueue file chunks to streams as part of FileListener.
fn stream_handle_incoming(stream: &ReadableStream, bytes: Fallible<Vec<u8>>) {
fn stream_handle_incoming(stream: &ReadableStream, bytes: Fallible<Vec<u8>>, can_gc: CanGc) {
match bytes {
Ok(b) => {
stream.enqueue_native(b);
stream.enqueue_native(b, can_gc);
},
Err(e) => {
stream.error_native(e);
@ -643,7 +643,7 @@ impl FileListener {
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_incoming(&stream, Ok(blob_buf.bytes));
stream_handle_incoming(&stream, Ok(blob_buf.bytes), CanGc::note());
});
let _ = self
@ -667,7 +667,7 @@ impl FileListener {
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_incoming(&stream, Ok(bytes_in));
stream_handle_incoming(&stream, Ok(bytes_in), CanGc::note());
});
let _ = self
@ -733,7 +733,7 @@ impl FileListener {
let _ = self.task_source.queue_with_canceller(
task!(error_stream: move || {
let stream = trusted_stream.root();
stream_handle_incoming(&stream, error);
stream_handle_incoming(&stream, error, CanGc::note());
}),
&self.task_canceller,
);
@ -1988,11 +1988,11 @@ impl GlobalScope {
}
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
pub fn get_blob_stream(&self, blob_id: &BlobId) -> DomRoot<ReadableStream> {
pub fn get_blob_stream(&self, blob_id: &BlobId, can_gc: CanGc) -> DomRoot<ReadableStream> {
let (file_id, size) = match self.get_blob_bytes_or_file_id(blob_id) {
BlobResult::Bytes(bytes) => {
// If we have all the bytes in memory, queue them and close the stream.
let stream = ReadableStream::new_from_bytes(self, bytes);
let stream = ReadableStream::new_from_bytes(self, bytes, can_gc);
return stream;
},
BlobResult::File(id, size) => (id, size),

View file

@ -870,6 +870,7 @@ impl HTMLFormElement {
enctype,
encoding,
target_window,
can_gc,
);
},
// https://html.spec.whatwg.org/multipage/#submit-get-action
@ -920,6 +921,7 @@ impl HTMLFormElement {
enctype: FormEncType,
encoding: &'static Encoding,
target: &Window,
can_gc: CanGc,
) {
let boundary = generate_boundary();
let bytes = match enctype {
@ -957,7 +959,7 @@ impl HTMLFormElement {
let global = self.global();
let request_body = bytes
.extract(&global)
.extract(&global, can_gc)
.expect("Couldn't extract body.")
.into_net_request_body()
.0;

View file

@ -34,7 +34,7 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::js::conversions::FromJSValConvertible;
use crate::realms::{enter_realm, InRealm};
use crate::script_runtime::JSContext as SafeJSContext;
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
static UNDERLYING_SOURCE_TRAPS: ReadableStreamUnderlyingSourceTraps =
ReadableStreamUnderlyingSourceTraps {
@ -101,12 +101,16 @@ impl ReadableStream {
}
/// Build a stream backed by a Rust source that has already been read into memory.
pub fn new_from_bytes(global: &GlobalScope, bytes: Vec<u8>) -> DomRoot<ReadableStream> {
pub fn new_from_bytes(
global: &GlobalScope,
bytes: Vec<u8>,
can_gc: CanGc,
) -> DomRoot<ReadableStream> {
let stream = ReadableStream::new_with_external_underlying_source(
global,
ExternalUnderlyingSource::Memory(bytes.len()),
);
stream.enqueue_native(bytes);
stream.enqueue_native(bytes, can_gc);
stream.close_native();
stream
}
@ -153,7 +157,7 @@ impl ReadableStream {
}
#[allow(unsafe_code)]
pub fn enqueue_native(&self, bytes: Vec<u8>) {
pub fn enqueue_native(&self, bytes: Vec<u8>, can_gc: CanGc) {
let global = self.global();
let _ar = enter_realm(&*global);
let cx = GlobalScope::get_cx();
@ -163,7 +167,7 @@ impl ReadableStream {
self.external_underlying_source
.as_ref()
.expect("No external source to enqueue bytes.")
.enqueue_chunk(cx, handle, bytes);
.enqueue_chunk(cx, handle, bytes, can_gc);
}
#[allow(unsafe_code)]
@ -324,7 +328,12 @@ unsafe extern "C" fn request_data(
desired_size: usize,
) {
let source = &*(source as *const ExternalUnderlyingSourceController);
source.pull(SafeJSContext::from_ptr(cx), stream, desired_size);
source.pull(
SafeJSContext::from_ptr(cx),
stream,
desired_size,
CanGc::note(),
);
}
#[allow(unsafe_code)]
@ -427,12 +436,15 @@ impl ExternalUnderlyingSourceController {
}
/// Signal available bytes if the stream is currently readable.
/// The apparently unused CanGc argument represents that the JS API calls like
/// `ReadableStreamUpdateDataAvailableFromSource` can trigger a GC.
#[allow(unsafe_code)]
fn maybe_signal_available_bytes(
&self,
cx: SafeJSContext,
stream: HandleObject,
available: usize,
_can_gc: CanGc,
) {
if available == 0 {
return;
@ -467,17 +479,23 @@ impl ExternalUnderlyingSourceController {
self.maybe_close_js_stream(cx, stream);
}
fn enqueue_chunk(&self, cx: SafeJSContext, stream: HandleObject, mut chunk: Vec<u8>) {
fn enqueue_chunk(
&self,
cx: SafeJSContext,
stream: HandleObject,
mut chunk: Vec<u8>,
can_gc: CanGc,
) {
let available = {
let mut buffer = self.buffer.borrow_mut();
buffer.append(&mut chunk);
buffer.len()
};
self.maybe_signal_available_bytes(cx, stream, available);
self.maybe_signal_available_bytes(cx, stream, available, can_gc);
}
#[allow(unsafe_code)]
fn pull(&self, cx: SafeJSContext, stream: HandleObject, _desired_size: usize) {
fn pull(&self, cx: SafeJSContext, stream: HandleObject, _desired_size: usize, can_gc: CanGc) {
// Note: for pull sources,
// this would be the time to ask for a chunk.
@ -490,7 +508,7 @@ impl ExternalUnderlyingSourceController {
buffer.len()
};
self.maybe_signal_available_bytes(cx, stream, available);
self.maybe_signal_available_bytes(cx, stream, available, can_gc);
}
fn get_chunk_with_length(&self, length: usize) -> Vec<u8> {

View file

@ -477,7 +477,7 @@ impl RequestMethods for Request {
// Step 37.1 TODO "If init["keepalive"] exists and is true..."
// Step 37.2
let mut extracted_body = init_body.extract(global)?;
let mut extracted_body = init_body.extract(global, can_gc)?;
// Step 37.3
if let Some(contents) = extracted_body.content_type.take() {

View file

@ -190,7 +190,7 @@ impl ResponseMethods for Response {
total_bytes: _,
content_type,
source: _,
} = body.extract(global)?;
} = body.extract(global, can_gc)?;
r.body_stream.set(Some(&*stream));
@ -210,7 +210,7 @@ impl ResponseMethods for Response {
} else {
// Reset FetchResponse to an in-memory stream with empty byte sequence here for
// no-init-body case
let stream = ReadableStream::new_from_bytes(global, Vec::with_capacity(0));
let stream = ReadableStream::new_from_bytes(global, Vec::with_capacity(0), can_gc);
r.body_stream.set(Some(&*stream));
}
@ -444,12 +444,12 @@ impl Response {
*self.stream_consumer.borrow_mut() = sc;
}
pub fn stream_chunk(&self, chunk: Vec<u8>) {
pub fn stream_chunk(&self, chunk: Vec<u8>, can_gc: CanGc) {
// Note, are these two actually mutually exclusive?
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() {
stream_consumer.consume_chunk(chunk.as_slice());
} else if let Some(body) = self.body_stream.get() {
body.enqueue_native(chunk);
body.enqueue_native(chunk, can_gc);
}
}

View file

@ -576,7 +576,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
};
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
let stream = ReadableStream::new_from_bytes(&global, bytes, can_gc);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -585,7 +585,9 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
})
},
Some(DocumentOrXMLHttpRequestBodyInit::Blob(ref b)) => {
let extracted_body = b.extract(&self.global()).expect("Couldn't extract body.");
let extracted_body = b
.extract(&self.global(), can_gc)
.expect("Couldn't extract body.");
if !extracted_body.in_memory() && self.sync.get() {
warn!("Sync XHR with not in-memory Blob as body not supported");
None
@ -595,22 +597,23 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
},
Some(DocumentOrXMLHttpRequestBodyInit::FormData(ref formdata)) => Some(
formdata
.extract(&self.global())
.extract(&self.global(), can_gc)
.expect("Couldn't extract body."),
),
Some(DocumentOrXMLHttpRequestBodyInit::String(ref str)) => Some(
str.extract(&self.global(), can_gc)
.expect("Couldn't extract body."),
),
Some(DocumentOrXMLHttpRequestBodyInit::String(ref str)) => {
Some(str.extract(&self.global()).expect("Couldn't extract body."))
},
Some(DocumentOrXMLHttpRequestBodyInit::URLSearchParams(ref urlsp)) => Some(
urlsp
.extract(&self.global())
.extract(&self.global(), can_gc)
.expect("Couldn't extract body."),
),
Some(DocumentOrXMLHttpRequestBodyInit::ArrayBuffer(ref typedarray)) => {
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
let stream = ReadableStream::new_from_bytes(&global, bytes, can_gc);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
@ -622,7 +625,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
let stream = ReadableStream::new_from_bytes(&global, bytes, can_gc);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),

View file

@ -277,7 +277,7 @@ impl FetchResponseListener for FetchContext {
fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
let response = self.response_object.root();
response.stream_chunk(chunk);
response.stream_chunk(chunk, CanGc::note());
}
fn process_response_eof(