script: Implement Blob::bytes() (#35151)

* script: Implement Blob.bytes()

Signed-off-by: yoseio <98276492+yoseio@users.noreply.github.com>

* improve read_all_bytes

Signed-off-by: yoseio <98276492+yoseio@users.noreply.github.com>

* fix read_all_bytes

Signed-off-by: yoseio <98276492+yoseio@users.noreply.github.com>

* fix bug

Signed-off-by: yoseio <98276492+yoseio@users.noreply.github.com>

* something went wrong

Signed-off-by: Kousuke Takaki <98276492+yoseio@users.noreply.github.com>

* fix read loop

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>

* add use of can_gc to promise code following rebase

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>

* fix rooting of fulfillment handler

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>

* Update test expectations

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* use dom for reader in read loop fulfillment handler

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>

* use the global of the reader in read loop fulfillment handler

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>

* remove FAIl expectations for blob methods in detached iframe

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>

---------

Signed-off-by: yoseio <98276492+yoseio@users.noreply.github.com>
Signed-off-by: Kousuke Takaki <98276492+yoseio@users.noreply.github.com>
Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>
Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>
Co-authored-by: gterzian <2792687+gterzian@users.noreply.github.com>
Co-authored-by: Taym Haddadi <haddadi.taym@gmail.com>
This commit is contained in:
Kousuke Takaki 2025-02-28 04:25:27 +09:00 committed by GitHub
parent a1ecce5502
commit 8a3f62933b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 205 additions and 80 deletions

View file

@ -4,17 +4,21 @@
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::ptr;
use std::rc::Rc;
use base::id::{BlobId, BlobIndex, PipelineNamespaceId};
use dom_struct::dom_struct;
use encoding_rs::UTF_8;
use js::jsapi::JSObject;
use js::rust::HandleObject;
use js::typedarray::Uint8;
use net_traits::filemanager_thread::RelativePos;
use script_traits::serializable::BlobImpl;
use uuid::Uuid;
use crate::body::{run_array_buffer_data_algorithm, FetchedData};
use crate::dom::bindings::buffer_source::create_buffer_source;
use crate::dom::bindings::codegen::Bindings::BlobBinding;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
@ -297,6 +301,46 @@ impl BlobMethods<crate::DomTypeHolder> for Blob {
);
p
}
/// <https://w3c.github.io/FileAPI/#dom-blob-bytes>
fn Bytes(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> {
let cx = GlobalScope::get_cx();
let global = GlobalScope::from_safe_context(cx, in_realm);
let p = Promise::new_in_current_realm(in_realm, can_gc);
// 1. Let stream be the result of calling get stream on this.
let stream = self.get_stream(can_gc);
// 2. Let reader be the result of getting a reader from stream.
// If that threw an exception, return a new promise rejected with that exception.
let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) {
Ok(r) => r,
Err(e) => {
p.reject_error(e, can_gc);
return p;
},
};
// 3. Let promise be the result of reading all bytes from stream with reader.
let p_success = p.clone();
let p_failure = p.clone();
reader.read_all_bytes(
cx,
&global,
Rc::new(move |bytes| {
rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
let arr = create_buffer_source::<Uint8>(cx, bytes, js_object.handle_mut(), can_gc)
.expect("Converting input to uint8 array should never fail");
p_success.resolve_native(&arr, can_gc);
}),
Rc::new(move |cx, v| {
p_failure.reject(cx, v, can_gc);
}),
in_realm,
can_gc,
);
p
}
}
/// Get the normalized, MIME-parsable type string

View file

@ -28,11 +28,137 @@ use crate::dom::defaultteereadrequest::DefaultTeeReadRequest;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::readablestream::ReadableStream;
use crate::dom::readablestream::{get_read_promise_bytes, get_read_promise_done, ReadableStream};
use crate::dom::readablestreamgenericreader::ReadableStreamGenericReader;
use crate::realms::{enter_realm, InRealm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
type ReadAllBytesSuccessSteps = dyn Fn(&[u8]);
type ReadAllBytesFailureSteps = dyn Fn(SafeJSContext, SafeHandleValue);
impl js::gc::Rootable for ReadLoopFulFillmentHandler {}
/// <https://streams.spec.whatwg.org/#read-loop>
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct ReadLoopFulFillmentHandler {
#[ignore_malloc_size_of = "Rc is hard"]
#[no_trace]
success_steps: Rc<ReadAllBytesSuccessSteps>,
#[ignore_malloc_size_of = "Rc is hard"]
#[no_trace]
failure_steps: Rc<ReadAllBytesFailureSteps>,
reader: Dom<ReadableStreamDefaultReader>,
#[ignore_malloc_size_of = "Rc is hard"]
bytes: Rc<DomRefCell<Vec<u8>>>,
}
impl Callback for ReadLoopFulFillmentHandler {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, realm: InRealm, can_gc: CanGc) {
let global = self.reader.global();
let is_done = match get_read_promise_done(cx, &v) {
Ok(is_done) => is_done,
Err(err) => {
self.reader
.release(can_gc)
.expect("Releasing the reader should succeed");
rooted!(in(*cx) let mut v = UndefinedValue());
err.to_jsval(cx, &global, v.handle_mut());
(self.failure_steps)(cx, v.handle());
return;
},
};
if is_done {
// <https://streams.spec.whatwg.org/#ref-for-read-request-close-steps%E2%91%A6>
// Call successSteps with bytes.
(self.success_steps)(&self.bytes.borrow());
self.reader
.release(can_gc)
.expect("Releasing the reader should succeed");
} else {
// <https://streams.spec.whatwg.org/#ref-for-read-request-chunk-steps%E2%91%A6>
let chunk = match get_read_promise_bytes(cx, &v) {
Ok(chunk) => chunk,
Err(err) => {
// If chunk is not a Uint8Array object, call failureSteps with a TypeError and abort these steps.
rooted!(in(*cx) let mut v = UndefinedValue());
err.to_jsval(cx, &global, v.handle_mut());
(self.failure_steps)(cx, v.handle());
self.reader
.release(can_gc)
.expect("Releasing the reader should succeed");
return;
},
};
// Append the bytes represented by chunk to bytes.
let mut bytes = self.bytes.borrow_mut();
bytes.extend_from_slice(&chunk);
// Read-loop given reader, bytes, successSteps, and failureSteps.
read_loop(
&global,
&mut Some(self.clone()),
Box::new(ReadLoopRejectionHandler {
failure_steps: self.failure_steps.clone(),
}),
realm,
can_gc,
);
}
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
/// <https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes>
struct ReadLoopRejectionHandler {
#[ignore_malloc_size_of = "Rc is hard"]
#[no_trace]
failure_steps: Rc<ReadAllBytesFailureSteps>,
}
impl Callback for ReadLoopRejectionHandler {
/// <https://streams.spec.whatwg.org/#ref-for-read-request-error-steps%E2%91%A6>
fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, _can_gc: CanGc) {
// Call failureSteps with e.
(self.failure_steps)(cx, v);
}
}
/// <https://streams.spec.whatwg.org/#read-loop>
fn read_loop(
global: &GlobalScope,
fulfillment_handler: &mut Option<ReadLoopFulFillmentHandler>,
rejection_handler: Box<ReadLoopRejectionHandler>,
realm: InRealm,
can_gc: CanGc,
) {
// Let readRequest be a new read request with the following items:
// Note: the custom read request logic is implemented
// using a native promise handler attached to the promise returned by `Read`
// (which internally uses a default read request).
// Perform ! ReadableStreamDefaultReaderRead(reader, readRequest).
let read_promise = fulfillment_handler
.as_ref()
.expect("Fulfillment handler should be some.")
.reader
.Read(can_gc);
let handler = PromiseNativeHandler::new(
global,
fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
Some(rejection_handler),
can_gc,
);
read_promise.append_native_handler(&handler, realm, can_gc);
}
/// <https://streams.spec.whatwg.org/#read-request>
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub(crate) enum ReadRequest {
@ -348,6 +474,37 @@ impl ReadableStreamDefaultReader {
.borrow()
.append_native_handler(&handler, comp, can_gc);
}
/// <https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes>
pub(crate) fn read_all_bytes(
&self,
cx: SafeJSContext,
global: &GlobalScope,
success_steps: Rc<ReadAllBytesSuccessSteps>,
failure_steps: Rc<ReadAllBytesFailureSteps>,
realm: InRealm,
can_gc: CanGc,
) {
// To read all bytes from a ReadableStreamDefaultReader reader,
// given successSteps, which is an algorithm accepting a byte sequence,
// and failureSteps, which is an algorithm accepting a JavaScript value:
// read-loop given reader, a new byte sequence, successSteps, and failureSteps.
// Note: read-loop done using native promise handlers.
rooted!(in(*cx) let mut fulfillment_handler = Some(ReadLoopFulFillmentHandler {
success_steps,
failure_steps: failure_steps.clone(),
reader: Dom::from_ref(self),
bytes: Rc::new(DomRefCell::new(Vec::new())),
}));
let rejection_handler = Box::new(ReadLoopRejectionHandler { failure_steps });
read_loop(
global,
&mut fulfillment_handler,
rejection_handler,
realm,
can_gc,
);
}
}
impl ReadableStreamDefaultReaderMethods<crate::DomTypeHolder> for ReadableStreamDefaultReader {

View file

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

View file

@ -20,6 +20,7 @@ interface Blob {
[NewObject, Throws] ReadableStream stream();
[NewObject] Promise<DOMString> text();
[NewObject] Promise<ArrayBuffer> arrayBuffer();
[NewObject] Promise<Uint8Array> bytes();
};
dictionary BlobPropertyBag {

View file

@ -1,6 +0,0 @@
[Blob-methods-from-detached-frame.html]
[slice()]
expected: FAIL
[bytes()]
expected: FAIL

View file

@ -1,32 +0,0 @@
[Blob-bytes.any.html]
[Blob.bytes()]
expected: FAIL
[Blob.bytes() empty Blob data]
expected: FAIL
[Blob.bytes() non-ascii input]
expected: FAIL
[Blob.bytes() non-unicode input]
expected: FAIL
[Blob.bytes() concurrent reads]
expected: FAIL
[Blob-bytes.any.worker.html]
[Blob.bytes()]
expected: FAIL
[Blob.bytes() empty Blob data]
expected: FAIL
[Blob.bytes() non-ascii input]
expected: FAIL
[Blob.bytes() non-unicode input]
expected: FAIL
[Blob.bytes() concurrent reads]
expected: FAIL

View file

@ -1,8 +1,4 @@
[idlharness.any.html]
[Blob interface: operation text()]
[Blob interface: operation arrayBuffer()]
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
@ -12,21 +8,8 @@
[FileReader interface: calling readAsBinaryString(Blob) on new FileReader() with too few arguments must throw TypeError]
expected: FAIL
[Blob interface: operation bytes()]
expected: FAIL
[Blob interface: new Blob(["TEST"\]) must inherit property "bytes()" with the proper type]
expected: FAIL
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "bytes()" with the proper type]
expected: FAIL
[idlharness.any.worker.html]
[Blob interface: operation text()]
[Blob interface: operation arrayBuffer()]
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
@ -35,12 +18,3 @@
[FileReader interface: calling readAsBinaryString(Blob) on new FileReader() with too few arguments must throw TypeError]
expected: FAIL
[Blob interface: operation bytes()]
expected: FAIL
[Blob interface: new Blob(["TEST"\]) must inherit property "bytes()" with the proper type]
expected: FAIL
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "bytes()" with the proper type]
expected: FAIL

View file

@ -1,8 +1,4 @@
[idlharness.html]
[Blob interface: operation text()]
[Blob interface: operation arrayBuffer()]
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
@ -17,6 +13,3 @@
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
expected: FAIL
[Blob interface: operation bytes()]
expected: FAIL

View file

@ -1,8 +1,4 @@
[idlharness.worker.html]
[Blob interface: operation text()]
[Blob interface: operation arrayBuffer()]
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
@ -17,6 +13,3 @@
[Blob interface: new File(["myFileBits"\], "myFileName") must inherit property "stream()" with the proper type]
expected: FAIL
[Blob interface: operation bytes()]
expected: FAIL