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

View file

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

View file

@ -86,8 +86,8 @@ impl Blob {
} }
/// <https://w3c.github.io/FileAPI/#blob-get-stream> /// <https://w3c.github.io/FileAPI/#blob-get-stream>
pub fn get_stream(&self) -> DomRoot<ReadableStream> { pub fn get_stream(&self, can_gc: CanGc) -> DomRoot<ReadableStream> {
self.global().get_blob_stream(&self.blob_id) 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> // <https://w3c.github.io/FileAPI/#blob-get-stream>
fn Stream(&self, _cx: JSContext) -> NonNull<JSObject> { fn Stream(&self, _cx: JSContext, can_gc: CanGc) -> NonNull<JSObject> {
self.get_stream().get_js_stream() self.get_stream(can_gc).get_js_stream()
} }
// https://w3c.github.io/FileAPI/#slice-method-algo // 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. /// 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 { match bytes {
Ok(b) => { Ok(b) => {
stream.enqueue_native(b); stream.enqueue_native(b, can_gc);
}, },
Err(e) => { Err(e) => {
stream.error_native(e); stream.error_native(e);
@ -643,7 +643,7 @@ impl FileListener {
let task = task!(enqueue_stream_chunk: move || { let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root(); let stream = trusted.root();
stream_handle_incoming(&stream, Ok(blob_buf.bytes)); stream_handle_incoming(&stream, Ok(blob_buf.bytes), CanGc::note());
}); });
let _ = self let _ = self
@ -667,7 +667,7 @@ impl FileListener {
let task = task!(enqueue_stream_chunk: move || { let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root(); let stream = trusted.root();
stream_handle_incoming(&stream, Ok(bytes_in)); stream_handle_incoming(&stream, Ok(bytes_in), CanGc::note());
}); });
let _ = self let _ = self
@ -733,7 +733,7 @@ impl FileListener {
let _ = self.task_source.queue_with_canceller( let _ = self.task_source.queue_with_canceller(
task!(error_stream: move || { task!(error_stream: move || {
let stream = trusted_stream.root(); let stream = trusted_stream.root();
stream_handle_incoming(&stream, error); stream_handle_incoming(&stream, error, CanGc::note());
}), }),
&self.task_canceller, &self.task_canceller,
); );
@ -1988,11 +1988,11 @@ impl GlobalScope {
} }
/// <https://w3c.github.io/FileAPI/#blob-get-stream> /// <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) { let (file_id, size) = match self.get_blob_bytes_or_file_id(blob_id) {
BlobResult::Bytes(bytes) => { BlobResult::Bytes(bytes) => {
// If we have all the bytes in memory, queue them and close the stream. // 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; return stream;
}, },
BlobResult::File(id, size) => (id, size), BlobResult::File(id, size) => (id, size),

View file

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

View file

@ -34,7 +34,7 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise; use crate::dom::promise::Promise;
use crate::js::conversions::FromJSValConvertible; use crate::js::conversions::FromJSValConvertible;
use crate::realms::{enter_realm, InRealm}; 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 = static UNDERLYING_SOURCE_TRAPS: ReadableStreamUnderlyingSourceTraps =
ReadableStreamUnderlyingSourceTraps { ReadableStreamUnderlyingSourceTraps {
@ -101,12 +101,16 @@ impl ReadableStream {
} }
/// Build a stream backed by a Rust source that has already been read into memory. /// 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( let stream = ReadableStream::new_with_external_underlying_source(
global, global,
ExternalUnderlyingSource::Memory(bytes.len()), ExternalUnderlyingSource::Memory(bytes.len()),
); );
stream.enqueue_native(bytes); stream.enqueue_native(bytes, can_gc);
stream.close_native(); stream.close_native();
stream stream
} }
@ -153,7 +157,7 @@ impl ReadableStream {
} }
#[allow(unsafe_code)] #[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 global = self.global();
let _ar = enter_realm(&*global); let _ar = enter_realm(&*global);
let cx = GlobalScope::get_cx(); let cx = GlobalScope::get_cx();
@ -163,7 +167,7 @@ impl ReadableStream {
self.external_underlying_source self.external_underlying_source
.as_ref() .as_ref()
.expect("No external source to enqueue bytes.") .expect("No external source to enqueue bytes.")
.enqueue_chunk(cx, handle, bytes); .enqueue_chunk(cx, handle, bytes, can_gc);
} }
#[allow(unsafe_code)] #[allow(unsafe_code)]
@ -324,7 +328,12 @@ unsafe extern "C" fn request_data(
desired_size: usize, desired_size: usize,
) { ) {
let source = &*(source as *const ExternalUnderlyingSourceController); 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)] #[allow(unsafe_code)]
@ -427,12 +436,15 @@ impl ExternalUnderlyingSourceController {
} }
/// Signal available bytes if the stream is currently readable. /// 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)] #[allow(unsafe_code)]
fn maybe_signal_available_bytes( fn maybe_signal_available_bytes(
&self, &self,
cx: SafeJSContext, cx: SafeJSContext,
stream: HandleObject, stream: HandleObject,
available: usize, available: usize,
_can_gc: CanGc,
) { ) {
if available == 0 { if available == 0 {
return; return;
@ -467,17 +479,23 @@ impl ExternalUnderlyingSourceController {
self.maybe_close_js_stream(cx, stream); 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 available = {
let mut buffer = self.buffer.borrow_mut(); let mut buffer = self.buffer.borrow_mut();
buffer.append(&mut chunk); buffer.append(&mut chunk);
buffer.len() buffer.len()
}; };
self.maybe_signal_available_bytes(cx, stream, available); self.maybe_signal_available_bytes(cx, stream, available, can_gc);
} }
#[allow(unsafe_code)] #[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, // Note: for pull sources,
// this would be the time to ask for a chunk. // this would be the time to ask for a chunk.
@ -490,7 +508,7 @@ impl ExternalUnderlyingSourceController {
buffer.len() 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> { 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.1 TODO "If init["keepalive"] exists and is true..."
// Step 37.2 // Step 37.2
let mut extracted_body = init_body.extract(global)?; let mut extracted_body = init_body.extract(global, can_gc)?;
// Step 37.3 // Step 37.3
if let Some(contents) = extracted_body.content_type.take() { if let Some(contents) = extracted_body.content_type.take() {

View file

@ -190,7 +190,7 @@ impl ResponseMethods for Response {
total_bytes: _, total_bytes: _,
content_type, content_type,
source: _, source: _,
} = body.extract(global)?; } = body.extract(global, can_gc)?;
r.body_stream.set(Some(&*stream)); r.body_stream.set(Some(&*stream));
@ -210,7 +210,7 @@ impl ResponseMethods for Response {
} else { } else {
// Reset FetchResponse to an in-memory stream with empty byte sequence here for // Reset FetchResponse to an in-memory stream with empty byte sequence here for
// no-init-body case // 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)); r.body_stream.set(Some(&*stream));
} }
@ -444,12 +444,12 @@ impl Response {
*self.stream_consumer.borrow_mut() = sc; *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? // Note, are these two actually mutually exclusive?
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() { if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() {
stream_consumer.consume_chunk(chunk.as_slice()); stream_consumer.consume_chunk(chunk.as_slice());
} else if let Some(body) = self.body_stream.get() { } 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 total_bytes = bytes.len();
let global = self.global(); let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes); let stream = ReadableStream::new_from_bytes(&global, bytes, can_gc);
Some(ExtractedBody { Some(ExtractedBody {
stream, stream,
total_bytes: Some(total_bytes), total_bytes: Some(total_bytes),
@ -585,7 +585,9 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
}) })
}, },
Some(DocumentOrXMLHttpRequestBodyInit::Blob(ref b)) => { 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() { if !extracted_body.in_memory() && self.sync.get() {
warn!("Sync XHR with not in-memory Blob as body not supported"); warn!("Sync XHR with not in-memory Blob as body not supported");
None None
@ -595,22 +597,23 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
}, },
Some(DocumentOrXMLHttpRequestBodyInit::FormData(ref formdata)) => Some( Some(DocumentOrXMLHttpRequestBodyInit::FormData(ref formdata)) => Some(
formdata 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."), .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( Some(DocumentOrXMLHttpRequestBodyInit::URLSearchParams(ref urlsp)) => Some(
urlsp urlsp
.extract(&self.global()) .extract(&self.global(), can_gc)
.expect("Couldn't extract body."), .expect("Couldn't extract body."),
), ),
Some(DocumentOrXMLHttpRequestBodyInit::ArrayBuffer(ref typedarray)) => { Some(DocumentOrXMLHttpRequestBodyInit::ArrayBuffer(ref typedarray)) => {
let bytes = typedarray.to_vec(); let bytes = typedarray.to_vec();
let total_bytes = bytes.len(); let total_bytes = bytes.len();
let global = self.global(); let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes); let stream = ReadableStream::new_from_bytes(&global, bytes, can_gc);
Some(ExtractedBody { Some(ExtractedBody {
stream, stream,
total_bytes: Some(total_bytes), total_bytes: Some(total_bytes),
@ -622,7 +625,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
let bytes = typedarray.to_vec(); let bytes = typedarray.to_vec();
let total_bytes = bytes.len(); let total_bytes = bytes.len();
let global = self.global(); let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes); let stream = ReadableStream::new_from_bytes(&global, bytes, can_gc);
Some(ExtractedBody { Some(ExtractedBody {
stream, stream,
total_bytes: Some(total_bytes), 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>) { fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
let response = self.response_object.root(); let response = self.response_object.root();
response.stream_chunk(chunk); response.stream_chunk(chunk, CanGc::note());
} }
fn process_response_eof( fn process_response_eof(