diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index e66a3f0fba1..e6b1b57243e 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -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 for Blob { ); p } + + /// + fn Bytes(&self, in_realm: InRealm, can_gc: CanGc) -> Rc { + 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::()); + let arr = create_buffer_source::(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 diff --git a/components/script/dom/readablestreamdefaultreader.rs b/components/script/dom/readablestreamdefaultreader.rs index bb08682ff09..2be17b56426 100644 --- a/components/script/dom/readablestreamdefaultreader.rs +++ b/components/script/dom/readablestreamdefaultreader.rs @@ -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 {} + +/// +#[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, + + #[ignore_malloc_size_of = "Rc is hard"] + #[no_trace] + failure_steps: Rc, + + reader: Dom, + + #[ignore_malloc_size_of = "Rc is hard"] + bytes: Rc>>, +} + +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 { + // + // Call successSteps with bytes. + (self.success_steps)(&self.bytes.borrow()); + self.reader + .release(can_gc) + .expect("Releasing the reader should succeed"); + } else { + // + 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)] +/// +struct ReadLoopRejectionHandler { + #[ignore_malloc_size_of = "Rc is hard"] + #[no_trace] + failure_steps: Rc, +} + +impl Callback for ReadLoopRejectionHandler { + /// + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, _can_gc: CanGc) { + // Call failureSteps with e. + (self.failure_steps)(cx, v); + } +} + +/// +fn read_loop( + global: &GlobalScope, + fulfillment_handler: &mut Option, + rejection_handler: Box, + 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); +} + /// #[derive(Clone, JSTraceable, MallocSizeOf)] pub(crate) enum ReadRequest { @@ -348,6 +474,37 @@ impl ReadableStreamDefaultReader { .borrow() .append_native_handler(&handler, comp, can_gc); } + + /// + pub(crate) fn read_all_bytes( + &self, + cx: SafeJSContext, + global: &GlobalScope, + success_steps: Rc, + failure_steps: Rc, + 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 for ReadableStreamDefaultReader { diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 668c6da5fa9..6d1afdd4789 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -34,7 +34,8 @@ DOMInterfaces = { 'Blob': { 'weakReferenceable': True, - 'canGc': ['Slice', 'Text', 'ArrayBuffer', 'Stream'], + 'canGc': ['Slice', 'Text', 'ArrayBuffer', 'Stream', 'Bytes'], + 'inRealms': ['Bytes'], }, 'Bluetooth': { diff --git a/components/script_bindings/webidls/Blob.webidl b/components/script_bindings/webidls/Blob.webidl index 14af967c2f0..56c04a3be64 100644 --- a/components/script_bindings/webidls/Blob.webidl +++ b/components/script_bindings/webidls/Blob.webidl @@ -20,6 +20,7 @@ interface Blob { [NewObject, Throws] ReadableStream stream(); [NewObject] Promise text(); [NewObject] Promise arrayBuffer(); + [NewObject] Promise bytes(); }; dictionary BlobPropertyBag { diff --git a/tests/wpt/meta/FileAPI/Blob-methods-from-detached-frame.html.ini b/tests/wpt/meta/FileAPI/Blob-methods-from-detached-frame.html.ini deleted file mode 100644 index 9aefcc5a465..00000000000 --- a/tests/wpt/meta/FileAPI/Blob-methods-from-detached-frame.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[Blob-methods-from-detached-frame.html] - [slice()] - expected: FAIL - - [bytes()] - expected: FAIL diff --git a/tests/wpt/meta/FileAPI/blob/Blob-bytes.any.js.ini b/tests/wpt/meta/FileAPI/blob/Blob-bytes.any.js.ini deleted file mode 100644 index 0460f3d229d..00000000000 --- a/tests/wpt/meta/FileAPI/blob/Blob-bytes.any.js.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/FileAPI/idlharness.any.js.ini b/tests/wpt/meta/FileAPI/idlharness.any.js.ini index 7458c529274..e52611a4bca 100644 --- a/tests/wpt/meta/FileAPI/idlharness.any.js.ini +++ b/tests/wpt/meta/FileAPI/idlharness.any.js.ini @@ -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 diff --git a/tests/wpt/meta/FileAPI/idlharness.html.ini b/tests/wpt/meta/FileAPI/idlharness.html.ini index 200d08be20f..cde1db748fe 100644 --- a/tests/wpt/meta/FileAPI/idlharness.html.ini +++ b/tests/wpt/meta/FileAPI/idlharness.html.ini @@ -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 diff --git a/tests/wpt/meta/FileAPI/idlharness.worker.js.ini b/tests/wpt/meta/FileAPI/idlharness.worker.js.ini index efc1d7c665a..f8fee032964 100644 --- a/tests/wpt/meta/FileAPI/idlharness.worker.js.ini +++ b/tests/wpt/meta/FileAPI/idlharness.worker.js.ini @@ -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