diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index 4233ded10bd..6f2fe166820 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -3,13 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::cell::{Cell, RefCell}; +use std::collections::VecDeque; use std::ptr::{self}; use std::rc::Rc; use dom_struct::dom_struct; use js::conversions::ToJSValConvertible; use js::jsapi::{Heap, JSObject}; -use js::jsval::{JSVal, ObjectValue, UndefinedValue}; +use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue}; use js::rust::{ HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, MutableHandleValue as SafeMutableHandleValue, @@ -18,19 +19,22 @@ use js::typedarray::ArrayBufferViewU8; use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy; use crate::dom::bindings::codegen::Bindings::ReadableStreamBinding::{ - ReadableStreamGetReaderOptions, ReadableStreamMethods, ReadableStreamReaderMode, + ReadableStreamGetReaderOptions, ReadableStreamMethods, ReadableStreamReaderMode, StreamPipeOptions }; use crate::dom::bindings::codegen::Bindings::ReadableStreamDefaultReaderBinding::ReadableStreamDefaultReaderMethods; use crate::dom::bindings::codegen::Bindings::ReadableStreamDefaultControllerBinding::ReadableStreamDefaultController_Binding::ReadableStreamDefaultControllerMethods; use crate::dom::bindings::codegen::Bindings::UnderlyingSourceBinding::UnderlyingSource as JsUnderlyingSource; use crate::dom::bindings::conversions::{ConversionBehavior, ConversionResult}; use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible}; +use crate::dom::bindings::codegen::GenericBindings::WritableStreamDefaultWriterBinding::WritableStreamDefaultWriter_Binding::WritableStreamDefaultWriterMethods; +use crate::dom::writablestream::WritableStream; use crate::dom::bindings::codegen::UnionTypes::ReadableStreamDefaultReaderOrReadableStreamBYOBReader as ReadableStreamReader; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::{DomRoot, MutNullableDom, Dom}; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::bindings::utils::get_dictionary_property; use crate::dom::countqueuingstrategy::{extract_high_water_mark, extract_size_algorithm}; +use crate::dom::readablestreamgenericreader::ReadableStreamGenericReader; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; use crate::dom::readablebytestreamcontroller::ReadableByteStreamController; @@ -40,6 +44,7 @@ use crate::dom::readablestreamdefaultreader::{ReadRequest, ReadableStreamDefault use crate::dom::defaultteeunderlyingsource::TeeCancelAlgorithm; use crate::dom::types::DefaultTeeUnderlyingSource; use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; +use crate::dom::writablestreamdefaultwriter::WritableStreamDefaultWriter; use crate::js::conversions::FromJSValConvertible; use crate::realms::{enter_realm, InRealm}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; @@ -49,6 +54,619 @@ use super::bindings::buffer_source::HeapBufferSource; use super::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::ReadableStreamBYOBReaderReadOptions; use super::readablestreambyobreader::ReadIntoRequest; +/// State Machine for `PipeTo`. +#[derive(Clone, Debug, Default, PartialEq)] +enum PipeToState { + /// The starting state + #[default] + Starting, + /// Waiting for the writer to be ready + PendingReady, + /// Waiting for a read to resolve. + PendingRead, + /// Waiting for all pending writes to finish, + /// as part of shutting down with an optional action. + ShuttingDownWithPendingWrites(Option), + /// When shutting down with an action, + /// waiting for the action to complete, + /// at which point we can `finalize`. + ShuttingDownPendingAction, + /// The pipe has been finalized, + /// no further actions should be performed. + Finalized, +} + +/// +#[derive(Clone, Debug, PartialEq)] +enum ShutdownAction { + /// + WritableStreamAbort, + /// + ReadableStreamCancel, + /// + WritableStreamDefaultWriterCloseWithErrorPropagation, +} + +impl js::gc::Rootable for PipeTo {} + +/// The "in parallel, but not really" part of +/// +/// +/// Note: the spec is flexible about how this is done, but requires the following constraints to apply: +/// - Public API must not be used: we'll only use Rust. +/// - Backpressure must be enforced: we'll only read from source when dest is ready. +/// - Shutdown must stop activity: we'll do this together with the below. +/// - Error and close states must be propagated: we'll do this by checking these states at every step. +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct PipeTo { + /// + reader: Dom, + + /// + writer: Dom, + + /// Pending writes are needed when shutting down(with an action), + /// because we can only finalize when all writes are finished. + #[ignore_malloc_size_of = "Rc are hard"] + pending_writes: Rc>>>, + + /// The state machine. + #[ignore_malloc_size_of = "Rc are hard"] + #[no_trace] + state: Rc>, + + /// + prevent_abort: bool, + + /// + prevent_cancel: bool, + + /// + prevent_close: bool, + + /// The `shuttingDown` variable of + /// + #[ignore_malloc_size_of = "Rc are hard"] + shutting_down: Rc>, + + /// The error potentially passed to shutdown, + /// stored here because we must keep it across a microtask. + #[ignore_malloc_size_of = "mozjs"] + shutdown_error: Rc>, + + /// The promise returned by a shutdown action. + /// We keep it to only continue when it is not pending anymore. + #[ignore_malloc_size_of = "Rc are hard"] + shutdown_action_promise: Rc>>>, + + /// The promise resolved or rejected at + /// + #[ignore_malloc_size_of = "Rc are hard"] + result_promise: Rc, +} + +impl Callback for PipeTo { + /// The pipe makes progress one microtask at a time. + /// Note: we use one struct as the callback for all promises, + /// and for both of their reactions. + /// + /// The context of the callback is determined from: + /// - the current state. + /// - the type of `result`. + /// - the state of a stored promise(in some cases). + fn callback(&self, cx: SafeJSContext, result: SafeHandleValue, realm: InRealm, can_gc: CanGc) { + let global = self.reader.global(); + + // Note: we only care about the result of writes when they are rejected, + // and the error is accessed not through handlers, + // but directly using `dest.get_stored_error`. + // So we must mark rejected promises as handled + // to prevent unhandled rejection errors. + self.pending_writes.borrow_mut().retain(|p| { + let pending = p.is_pending(); + if !pending { + p.set_promise_is_handled(); + } + pending + }); + + // Note: cloning to prevent re-borrow in methods called below. + let state_before_checks = self.state.borrow().clone(); + + // Note: if we are in a `PendingRead` state, + // and the source is closed, + // we try to write chunks before doing any shutdown, + // which is necessary to implement the + // "If any chunks have been read but not yet written, write them to dest." + // part of shutdown. + if state_before_checks == PipeToState::PendingRead { + let source = self.reader.get_stream().expect("Source stream must be set"); + if source.is_closed() { + let dest = self + .writer + .get_stream() + .expect("Destination stream must be set"); + + // If dest.[[state]] is "writable", + // and ! WritableStreamCloseQueuedOrInFlight(dest) is false, + if dest.is_writable() && !dest.close_queued_or_in_flight() { + let has_done = { + if !result.is_object() { + false + } else { + rooted!(in(*cx) let object = result.to_object()); + rooted!(in(*cx) let mut done = UndefinedValue()); + get_dictionary_property(*cx, object.handle(), "done", done.handle_mut()) + .unwrap() + } + }; + // If any chunks have been read but not yet written, write them to dest. + let contained_bytes = self.write_chunk(cx, &global, result, can_gc); + + if !contained_bytes && !has_done { + // This is the case that the microtask ran in reaction + // to the closed promise of the reader, + // so we should wait for subsequent chunks, + // and skip the shutdown below + // (reader is closed, but there are still pending reads). + // Shutdown will happen when the last chunk has been received. + return; + } + } + } + } + + self.check_and_propagate_errors_forward(cx, &global, realm, can_gc); + self.check_and_propagate_errors_backward(cx, &global, realm, can_gc); + self.check_and_propagate_closing_forward(cx, &global, realm, can_gc); + self.check_and_propagate_closing_backward(cx, &global, realm, can_gc); + + // Note: cloning to prevent re-borrow in methods called below. + let state = self.state.borrow().clone(); + + // If we switched to a shutdown state, + // return. + // Progress will be made at the next tick. + if state != state_before_checks { + return; + } + + match state { + PipeToState::Starting => unreachable!("PipeTo should not be in the Starting state."), + PipeToState::PendingReady => { + // Read a chunk. + self.read_chunk(&global, realm, can_gc); + }, + PipeToState::PendingRead => { + // Write the chunk. + self.write_chunk(cx, &global, result, can_gc); + + // Wait for the writer to be ready again. + self.wait_for_writer_ready(&global, realm, can_gc); + }, + PipeToState::ShuttingDownWithPendingWrites(action) => { + // Wait until every chunk that has been read has been written + // (i.e. the corresponding promises have settled). + if let Some(write) = self.pending_writes.borrow_mut().front().cloned() { + self.wait_on_pending_write(&global, write, realm, can_gc); + return; + } + + // Note: error is stored in `self.shutdown_error`. + if let Some(action) = action { + // Let p be the result of performing action. + self.perform_action(cx, &global, action, realm, can_gc); + } else { + // Finalize, passing along error if it was given. + self.finalize(cx, &global, can_gc); + } + }, + PipeToState::ShuttingDownPendingAction => { + let Some(ref promise) = *self.shutdown_action_promise.borrow() else { + unreachable!(); + }; + if promise.is_pending() { + // While waiting for the action to complete, + // we may get callbacks for other promises(closed, ready), + // and we should ignore those. + return; + } + + // Finalize, passing along error if it was given. + if !result.is_undefined() { + // All actions either resolve with undefined, + // or reject with an error, + // and the error should be used when finalizing. + self.shutdown_error.set(result.get()); + } + self.finalize(cx, &global, can_gc); + }, + PipeToState::Finalized => {}, + } + } +} + +impl PipeTo { + /// Wait for the writer to be ready, + /// which implements the constraint that backpressure must be enforced. + fn wait_for_writer_ready(&self, global: &GlobalScope, realm: InRealm, can_gc: CanGc) { + { + let mut state = self.state.borrow_mut(); + *state = PipeToState::PendingReady; + } + + let ready_promise = self.writer.Ready(); + if ready_promise.is_fulfilled() { + self.read_chunk(global, realm, can_gc); + } else { + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(self.clone())), + Some(Box::new(self.clone())), + can_gc, + ); + ready_promise.append_native_handler(&handler, realm, can_gc); + + // Note: if the writer is not ready, + // in order to ensure progress we must + // also react to the closure of the source(because source may close empty). + let closed_promise = self.reader.Closed(); + closed_promise.append_native_handler(&handler, realm, can_gc); + } + } + + /// Read a chunk + fn read_chunk(&self, global: &GlobalScope, realm: InRealm, can_gc: CanGc) { + *self.state.borrow_mut() = PipeToState::PendingRead; + let chunk_promise = self.reader.Read(can_gc); + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(self.clone())), + Some(Box::new(self.clone())), + can_gc, + ); + chunk_promise.append_native_handler(&handler, realm, can_gc); + + // Note: in order to ensure progress we must + // also react to the closure of the destination. + let ready_promise = self.writer.Closed(); + ready_promise.append_native_handler(&handler, realm, can_gc); + } + + /// Try to write a chunk using the jsval, and returns wether it succeeded + // It will fail if it is the last `done` chunk, or if it is not a chunk at all. + fn write_chunk( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> bool { + if chunk.is_object() { + rooted!(in(*cx) let object = chunk.to_object()); + rooted!(in(*cx) let mut bytes = UndefinedValue()); + let has_value = + get_dictionary_property(*cx, object.handle(), "value", bytes.handle_mut()) + .expect("Chunk should have a value."); + if !bytes.is_undefined() && has_value { + // Write the chunk. + let write_promise = self.writer.write(cx, global, bytes.handle(), can_gc); + self.pending_writes.borrow_mut().push_back(write_promise); + return true; + } + } + false + } + + /// Only as part of shutting-down do we wait on pending writes + /// (backpressure is communicated not through pending writes + /// but through the readiness of the writer). + fn wait_on_pending_write( + &self, + global: &GlobalScope, + promise: Rc, + realm: InRealm, + can_gc: CanGc, + ) { + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(self.clone())), + Some(Box::new(self.clone())), + can_gc, + ); + promise.append_native_handler(&handler, realm, can_gc); + } + + /// Errors must be propagated forward part of + /// + fn check_and_propagate_errors_forward( + &self, + cx: SafeJSContext, + global: &GlobalScope, + realm: InRealm, + can_gc: CanGc, + ) { + // An early return is necessary if we are shutting down, + // because in that case the source can already have been set to none. + if self.shutting_down.get() { + return; + } + + // if source.[[state]] is or becomes "errored", then + let source = self + .reader + .get_stream() + .expect("Reader should still have a stream"); + if source.is_errored() { + rooted!(in(*cx) let mut source_error = UndefinedValue()); + source.get_stored_error(source_error.handle_mut()); + self.shutdown_error.set(source_error.get()); + + // If preventAbort is false, + if !self.prevent_abort { + // shutdown with an action of ! WritableStreamAbort(dest, source.[[storedError]]) + // and with source.[[storedError]]. + self.shutdown( + cx, + global, + Some(ShutdownAction::WritableStreamAbort), + realm, + can_gc, + ) + } else { + // Otherwise, shutdown with source.[[storedError]]. + self.shutdown(cx, global, None, realm, can_gc); + } + } + } + + /// Errors must be propagated backward part of + /// + fn check_and_propagate_errors_backward( + &self, + cx: SafeJSContext, + global: &GlobalScope, + realm: InRealm, + can_gc: CanGc, + ) { + // An early return is necessary if we are shutting down, + // because in that case the destination can already have been set to none. + if self.shutting_down.get() { + return; + } + + // if dest.[[state]] is or becomes "errored", then + let dest = self + .writer + .get_stream() + .expect("Writer should still have a stream"); + if dest.is_errored() { + rooted!(in(*cx) let mut dest_error = UndefinedValue()); + dest.get_stored_error(dest_error.handle_mut()); + self.shutdown_error.set(dest_error.get()); + + // If preventCancel is false, + if !self.prevent_cancel { + // shutdown with an action of ! ReadableStreamCancel(source, dest.[[storedError]]) + // and with dest.[[storedError]]. + self.shutdown( + cx, + global, + Some(ShutdownAction::ReadableStreamCancel), + realm, + can_gc, + ) + } else { + // Otherwise, shutdown with dest.[[storedError]]. + self.shutdown(cx, global, None, realm, can_gc); + } + } + } + + /// Closing must be propagated forward part of + /// + fn check_and_propagate_closing_forward( + &self, + cx: SafeJSContext, + global: &GlobalScope, + realm: InRealm, + can_gc: CanGc, + ) { + // An early return is necessary if we are shutting down, + // because in that case the source can already have been set to none. + if self.shutting_down.get() { + return; + } + + // if source.[[state]] is or becomes "closed", then + let source = self + .reader + .get_stream() + .expect("Reader should still have a stream"); + if source.is_closed() { + // If preventClose is false, + if !self.prevent_close { + // shutdown with an action of ! WritableStreamAbort(dest, source.[[storedError]]) + // and with source.[[storedError]]. + self.shutdown( + cx, + global, + Some(ShutdownAction::WritableStreamDefaultWriterCloseWithErrorPropagation), + realm, + can_gc, + ) + } else { + // Otherwise, shutdown. + self.shutdown(cx, global, None, realm, can_gc); + } + } + } + + /// Closing must be propagated backward part of + /// + fn check_and_propagate_closing_backward( + &self, + cx: SafeJSContext, + global: &GlobalScope, + realm: InRealm, + can_gc: CanGc, + ) { + // An early return is necessary if we are shutting down, + // because in that case the destination can already have been set to none. + if self.shutting_down.get() { + return; + } + + // if ! WritableStreamCloseQueuedOrInFlight(dest) is true + // or dest.[[state]] is "closed" + let dest = self + .writer + .get_stream() + .expect("Writer should still have a stream"); + if dest.close_queued_or_in_flight() || dest.is_closed() { + // Assert: no chunks have been read or written. + // Note: unclear how to perform this assertion. + + // Let destClosed be a new TypeError. + rooted!(in(*cx) let mut dest_closed = UndefinedValue()); + let error = + Error::Type("Destination is closed or has closed queued or in flight".to_string()); + error.to_jsval(cx, global, dest_closed.handle_mut(), can_gc); + self.shutdown_error.set(dest_closed.get()); + + // If preventCancel is false, + if !self.prevent_cancel { + // shutdown with an action of ! ReadableStreamCancel(source, destClosed) + // and with destClosed. + self.shutdown( + cx, + global, + Some(ShutdownAction::ReadableStreamCancel), + realm, + can_gc, + ) + } else { + // Otherwise, shutdown with destClosed. + self.shutdown(cx, global, None, realm, can_gc); + } + } + } + + /// + /// + /// Combined into one method with an optional action. + fn shutdown( + &self, + cx: SafeJSContext, + global: &GlobalScope, + action: Option, + realm: InRealm, + can_gc: CanGc, + ) { + // If shuttingDown is true, abort these substeps. + // Set shuttingDown to true. + if !self.shutting_down.replace(true) { + let dest = self.writer.get_stream().expect("Stream must be set"); + // If dest.[[state]] is "writable", + // and ! WritableStreamCloseQueuedOrInFlight(dest) is false, + if dest.is_writable() && !dest.close_queued_or_in_flight() { + // If any chunks have been read but not yet written, write them to dest. + // Done at the top of `Callback`. + + // Wait until every chunk that has been read has been written + // (i.e. the corresponding promises have settled). + if let Some(write) = self.pending_writes.borrow_mut().front() { + *self.state.borrow_mut() = PipeToState::ShuttingDownWithPendingWrites(action); + self.wait_on_pending_write(global, write.clone(), realm, can_gc); + return; + } + } + + // Note: error is stored in `self.shutdown_error`. + if let Some(action) = action { + // Let p be the result of performing action. + self.perform_action(cx, global, action, realm, can_gc); + } else { + // Finalize, passing along error if it was given. + self.finalize(cx, global, can_gc); + } + } + } + + /// The perform action part of + /// + fn perform_action( + &self, + cx: SafeJSContext, + global: &GlobalScope, + action: ShutdownAction, + realm: InRealm, + can_gc: CanGc, + ) { + rooted!(in(*cx) let mut error = self.shutdown_error.get()); + *self.state.borrow_mut() = PipeToState::ShuttingDownPendingAction; + + // Let p be the result of performing action. + let promise = match action { + ShutdownAction::WritableStreamAbort => { + let dest = self.writer.get_stream().expect("Stream must be set"); + dest.abort(cx, global, error.handle(), can_gc) + }, + ShutdownAction::ReadableStreamCancel => { + let source = self + .reader + .get_stream() + .expect("Reader should have a stream."); + source.cancel(error.handle(), can_gc) + }, + ShutdownAction::WritableStreamDefaultWriterCloseWithErrorPropagation => { + self.writer.close_with_error_propagation(cx, global, can_gc) + }, + }; + + // Upon fulfillment of p, finalize, passing along originalError if it was given. + // Upon rejection of p with reason newError, finalize with newError. + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(self.clone())), + Some(Box::new(self.clone())), + can_gc, + ); + promise.append_native_handler(&handler, realm, can_gc); + *self.shutdown_action_promise.borrow_mut() = Some(promise); + } + + /// + fn finalize(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) { + *self.state.borrow_mut() = PipeToState::Finalized; + + // Perform ! WritableStreamDefaultWriterRelease(writer). + self.writer.release(cx, global, can_gc); + + // If reader implements ReadableStreamBYOBReader, + // perform ! ReadableStreamBYOBReaderRelease(reader). + // TODO. + + // Otherwise, perform ! ReadableStreamDefaultReaderRelease(reader). + self.reader + .release(can_gc) + .expect("Releasing the reader should not fail"); + + // If signal is not undefined, remove abortAlgorithm from signal. + // TODO: implement AbortSignal. + + rooted!(in(*cx) let mut error = self.shutdown_error.get()); + if !error.is_null() { + // If error was given, reject promise with error. + self.result_promise.reject_native(&error.handle(), can_gc); + } else { + // Otherwise, resolve promise with undefined. + self.result_promise.resolve_native(&(), can_gc); + } + } +} + /// The fulfillment handler for the reacting to sourceCancelPromise part of /// . #[derive(Clone, JSTraceable, MallocSizeOf)] @@ -977,6 +1595,98 @@ impl ReadableStream { Ok(vec![branch_1, branch_2]) } + /// + #[allow(clippy::too_many_arguments)] + fn pipe_to( + &self, + cx: SafeJSContext, + global: &GlobalScope, + dest: &WritableStream, + prevent_abort: bool, + prevent_cancel: bool, + prevent_close: bool, + realm: InRealm, + can_gc: CanGc, + ) -> Rc { + // Assert: source implements ReadableStream. + // Assert: dest implements WritableStream. + // Assert: prevent_close, prevent_abort, and prevent_cancel are all booleans. + // Done with method signature types. + + // If signal was not given, let signal be undefined. + // Assert: either signal is undefined, or signal implements AbortSignal. + // TODO: implement AbortSignal. + + // Assert: ! IsReadableStreamLocked(source) is false. + assert!(!self.is_locked()); + + // Assert: ! IsWritableStreamLocked(dest) is false. + assert!(!dest.is_locked()); + + // If source.[[controller]] implements ReadableByteStreamController, + // let reader be either ! AcquireReadableStreamBYOBReader(source) + // or ! AcquireReadableStreamDefaultReader(source), + // at the user agent’s discretion. + // Note: for now only using default readers. + + // Otherwise, let reader be ! AcquireReadableStreamDefaultReader(source). + let reader = self + .acquire_default_reader(can_gc) + .expect("Acquiring a default reader for pipe_to cannot fail"); + + // Let writer be ! AcquireWritableStreamDefaultWriter(dest). + let writer = dest + .aquire_default_writer(cx, global, can_gc) + .expect("Acquiring a default writer for pipe_to cannot fail"); + + // Set source.[[disturbed]] to true. + self.disturbed.set(true); + + // Let shuttingDown be false. + // Done below with default. + + // Let promise be a new promise. + let promise = Promise::new(global, can_gc); + + // If signal is not undefined, + // TODO: implement AbortSignal. + + // In parallel, but not really, using reader and writer, read all chunks from source and write them to dest. + rooted!(in(*cx) let pipe_to = PipeTo { + reader: Dom::from_ref(&reader), + writer: Dom::from_ref(&writer), + pending_writes: Default::default(), + state: Default::default(), + prevent_abort, + prevent_cancel, + prevent_close, + shutting_down: Default::default(), + shutdown_error: Default::default(), + shutdown_action_promise: Default::default(), + result_promise: promise.clone(), + }); + + // Note: set the shutdown error to null, + // to distinguish it from cases + // where the error is set to undefined. + pipe_to.shutdown_error.set(NullValue()); + + // Note: perfom checks now, since streams can start as closed or errored. + pipe_to.check_and_propagate_errors_forward(cx, global, realm, can_gc); + pipe_to.check_and_propagate_errors_backward(cx, global, realm, can_gc); + pipe_to.check_and_propagate_closing_forward(cx, global, realm, can_gc); + pipe_to.check_and_propagate_closing_backward(cx, global, realm, can_gc); + + // If we are not closed or errored, + if *pipe_to.state.borrow() == PipeToState::Starting { + // Start the pipe, by waiting on the writer being ready for a chunk. + pipe_to.wait_for_writer_ready(global, realm, can_gc); + } + + // Return promise. + promise + } + /// fn tee( &self, @@ -1172,6 +1882,52 @@ impl ReadableStreamMethods for ReadableStream { // Return ? ReadableStreamTee(this, false). self.tee(false, can_gc) } + + /// + fn PipeTo( + &self, + destination: &WritableStream, + options: &StreamPipeOptions, + realm: InRealm, + can_gc: CanGc, + ) -> Rc { + let cx = GlobalScope::get_cx(); + let global = self.global(); + + // If ! IsReadableStreamLocked(this) is true, + if self.is_locked() { + // return a promise rejected with a TypeError exception. + let promise = Promise::new(&global, can_gc); + promise.reject_error(Error::Type("Source stream is locked".to_owned()), can_gc); + return promise; + } + + // If ! IsWritableStreamLocked(destination) is true, + if destination.is_locked() { + // return a promise rejected with a TypeError exception. + let promise = Promise::new(&global, can_gc); + promise.reject_error( + Error::Type("Destination stream is locked".to_owned()), + can_gc, + ); + return promise; + } + + // Let signal be options["signal"] if it exists, or undefined otherwise. + // TODO: implement AbortSignal. + + // Return ! ReadableStreamPipeTo. + self.pipe_to( + cx, + &global, + destination, + options.preventAbort, + options.preventCancel, + options.preventClose, + realm, + can_gc, + ) + } } #[allow(unsafe_code)] diff --git a/components/script/dom/writablestreamdefaultwriter.rs b/components/script/dom/writablestreamdefaultwriter.rs index 99cda7e365b..60aab9598cc 100644 --- a/components/script/dom/writablestreamdefaultwriter.rs +++ b/components/script/dom/writablestreamdefaultwriter.rs @@ -263,7 +263,7 @@ impl WritableStreamDefaultWriter { } /// - fn write( + pub(crate) fn write( &self, cx: SafeJSContext, global: &GlobalScope, @@ -377,6 +377,52 @@ impl WritableStreamDefaultWriter { // Set this.[[stream]] to undefined. self.stream.set(None); } + + /// + pub(crate) fn close_with_error_propagation( + &self, + cx: SafeJSContext, + global: &GlobalScope, + can_gc: CanGc, + ) -> Rc { + // Let stream be writer.[[stream]]. + let Some(stream) = self.stream.get() else { + // Assert: stream is not undefined. + unreachable!("Stream should be set."); + }; + + // Let state be stream.[[state]]. + // Used via stream method calls. + + // If ! WritableStreamCloseQueuedOrInFlight(stream) is true + // or state is "closed", + if stream.close_queued_or_in_flight() || stream.is_closed() { + // return a promise resolved with undefined. + let promise = Promise::new(global, can_gc); + promise.resolve_native(&(), can_gc); + return promise; + } + + // If state is "errored", + if stream.is_errored() { + // return a promise rejected with stream.[[storedError]]. + rooted!(in(*cx) let mut error = UndefinedValue()); + stream.get_stored_error(error.handle_mut()); + let promise = Promise::new(global, can_gc); + promise.reject_native(&error.handle(), can_gc); + return promise; + } + + // Assert: state is "writable" or "erroring". + assert!(stream.is_writable() || stream.is_erroring()); + + // Return ! WritableStreamDefaultWriterClose(writer). + self.close(cx, global, can_gc) + } + + pub(crate) fn get_stream(&self) -> Option> { + self.stream.get() + } } impl WritableStreamDefaultWriterMethods for WritableStreamDefaultWriter { diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index dfd281417f3..8fb49121aed 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -632,7 +632,8 @@ DOMInterfaces = { }, 'ReadableStream': { - 'canGc': ['GetReader', 'Cancel', 'Tee'], + 'canGc': ['GetReader', 'Cancel', 'PipeTo', 'Tee'], + 'inRealms': ['PipeTo'], }, "ReadableStreamDefaultController": { diff --git a/components/script_bindings/webidls/ReadableStream.webidl b/components/script_bindings/webidls/ReadableStream.webidl index 9e3e1757722..12798262953 100644 --- a/components/script_bindings/webidls/ReadableStream.webidl +++ b/components/script_bindings/webidls/ReadableStream.webidl @@ -23,8 +23,8 @@ interface _ReadableStream { // [Throws] // ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); - // [NewObject] - // Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); + [NewObject] + Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); [Throws] sequence tee(); diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index 1e03287256f..f9ade033fe6 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -241,6 +241,8 @@ skip: true skip: false [streams] skip: true + [piping] + skip: false [readable-streams] skip: false [readable-byte-streams] diff --git a/tests/wpt/meta/fetch/api/response/response-body-read-task-handling.html.ini b/tests/wpt/meta/fetch/api/response/response-body-read-task-handling.html.ini deleted file mode 100644 index 30dbc35c06a..00000000000 --- a/tests/wpt/meta/fetch/api/response/response-body-read-task-handling.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[response-body-read-task-handling.html] - [piping from a body stream to a JS-written WritableStream should occur in a microtask scope] - expected: FAIL diff --git a/tests/wpt/meta/fetch/api/response/response-stream-disturbed-by-pipe.any.js.ini b/tests/wpt/meta/fetch/api/response/response-stream-disturbed-by-pipe.any.js.ini index 85121b02cc9..84fe7dfa19b 100644 --- a/tests/wpt/meta/fetch/api/response/response-stream-disturbed-by-pipe.any.js.ini +++ b/tests/wpt/meta/fetch/api/response/response-stream-disturbed-by-pipe.any.js.ini @@ -2,17 +2,11 @@ [using pipeThrough on Response body should disturb it synchronously] expected: FAIL - [using pipeTo on Response body should disturb it synchronously] - expected: FAIL - [response-stream-disturbed-by-pipe.any.html] [using pipeThrough on Response body should disturb it synchronously] expected: FAIL - [using pipeTo on Response body should disturb it synchronously] - expected: FAIL - [response-stream-disturbed-by-pipe.any.serviceworker.html] expected: ERROR diff --git a/tests/wpt/meta/fetch/api/response/response-stream-with-broken-then.any.js.ini b/tests/wpt/meta/fetch/api/response/response-stream-with-broken-then.any.js.ini index 41cc02b7a10..38d7cf59f8d 100644 --- a/tests/wpt/meta/fetch/api/response/response-stream-with-broken-then.any.js.ini +++ b/tests/wpt/meta/fetch/api/response/response-stream-with-broken-then.any.js.ini @@ -1,5 +1,4 @@ [response-stream-with-broken-then.any.worker.html] - expected: TIMEOUT [Attempt to inject {done: false, value: bye} via Object.prototype.then.] expected: FAIL @@ -23,7 +22,6 @@ expected: ERROR [response-stream-with-broken-then.any.html] - expected: CRASH [Attempt to inject {done: false, value: bye} via Object.prototype.then.] expected: FAIL diff --git a/tests/wpt/meta/streams/piping/abort.any.js.ini b/tests/wpt/meta/streams/piping/abort.any.js.ini new file mode 100644 index 00000000000..703ed108ffb --- /dev/null +++ b/tests/wpt/meta/streams/piping/abort.any.js.ini @@ -0,0 +1,29 @@ +[abort.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[abort.any.shadowrealm-in-window.html] + expected: ERROR + +[abort.any.html] + expected: ERROR + +[abort.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[abort.any.sharedworker.html] + expected: ERROR + +[abort.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[abort.any.serviceworker.html] + expected: ERROR + +[abort.any.worker.html] + expected: ERROR + +[abort.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[abort.any.shadowrealm-in-shadowrealm.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/close-propagation-backward.any.js.ini b/tests/wpt/meta/streams/piping/close-propagation-backward.any.js.ini new file mode 100644 index 00000000000..264444eaa3f --- /dev/null +++ b/tests/wpt/meta/streams/piping/close-propagation-backward.any.js.ini @@ -0,0 +1,27 @@ +[close-propagation-backward.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[close-propagation-backward.any.serviceworker.html] + expected: ERROR + +[close-propagation-backward.any.html] + +[close-propagation-backward.any.sharedworker.html] + expected: ERROR + +[close-propagation-backward.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[close-propagation-backward.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[close-propagation-backward.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[close-propagation-backward.any.shadowrealm-in-window.html] + expected: ERROR + +[close-propagation-backward.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[close-propagation-backward.any.worker.html] diff --git a/tests/wpt/meta/streams/piping/close-propagation-forward.any.js.ini b/tests/wpt/meta/streams/piping/close-propagation-forward.any.js.ini new file mode 100644 index 00000000000..0e6e96ba7ef --- /dev/null +++ b/tests/wpt/meta/streams/piping/close-propagation-forward.any.js.ini @@ -0,0 +1,27 @@ +[close-propagation-forward.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[close-propagation-forward.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[close-propagation-forward.any.shadowrealm-in-window.html] + expected: ERROR + +[close-propagation-forward.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[close-propagation-forward.any.worker.html] + +[close-propagation-forward.any.serviceworker.html] + expected: ERROR + +[close-propagation-forward.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[close-propagation-forward.any.sharedworker.html] + expected: ERROR + +[close-propagation-forward.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[close-propagation-forward.any.html] diff --git a/tests/wpt/meta/streams/piping/error-propagation-backward.any.js.ini b/tests/wpt/meta/streams/piping/error-propagation-backward.any.js.ini new file mode 100644 index 00000000000..8c35dada9c9 --- /dev/null +++ b/tests/wpt/meta/streams/piping/error-propagation-backward.any.js.ini @@ -0,0 +1,27 @@ +[error-propagation-backward.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[error-propagation-backward.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[error-propagation-backward.any.shadowrealm-in-window.html] + expected: ERROR + +[error-propagation-backward.any.worker.html] + +[error-propagation-backward.any.html] + +[error-propagation-backward.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[error-propagation-backward.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[error-propagation-backward.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[error-propagation-backward.any.sharedworker.html] + expected: ERROR + +[error-propagation-backward.any.serviceworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/error-propagation-forward.any.js.ini b/tests/wpt/meta/streams/piping/error-propagation-forward.any.js.ini new file mode 100644 index 00000000000..7e927f0f5bc --- /dev/null +++ b/tests/wpt/meta/streams/piping/error-propagation-forward.any.js.ini @@ -0,0 +1,27 @@ +[error-propagation-forward.any.html] + +[error-propagation-forward.any.serviceworker.html] + expected: ERROR + +[error-propagation-forward.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[error-propagation-forward.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[error-propagation-forward.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[error-propagation-forward.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[error-propagation-forward.any.sharedworker.html] + expected: ERROR + +[error-propagation-forward.any.worker.html] + +[error-propagation-forward.any.shadowrealm-in-window.html] + expected: ERROR + +[error-propagation-forward.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/flow-control.any.js.ini b/tests/wpt/meta/streams/piping/flow-control.any.js.ini new file mode 100644 index 00000000000..8ef5ad2d8b6 --- /dev/null +++ b/tests/wpt/meta/streams/piping/flow-control.any.js.ini @@ -0,0 +1,27 @@ +[flow-control.any.html] + +[flow-control.any.serviceworker.html] + expected: ERROR + +[flow-control.any.worker.html] + +[flow-control.any.sharedworker.html] + expected: ERROR + +[flow-control.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[flow-control.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[flow-control.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[flow-control.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[flow-control.any.shadowrealm-in-window.html] + expected: ERROR + +[flow-control.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/general-addition.any.js.ini b/tests/wpt/meta/streams/piping/general-addition.any.js.ini new file mode 100644 index 00000000000..91f53c8a385 --- /dev/null +++ b/tests/wpt/meta/streams/piping/general-addition.any.js.ini @@ -0,0 +1,27 @@ +[general-addition.any.sharedworker.html] + expected: ERROR + +[general-addition.any.html] + +[general-addition.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[general-addition.any.shadowrealm-in-window.html] + expected: ERROR + +[general-addition.any.serviceworker.html] + expected: ERROR + +[general-addition.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[general-addition.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[general-addition.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[general-addition.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[general-addition.any.worker.html] diff --git a/tests/wpt/meta/streams/piping/general.any.js.ini b/tests/wpt/meta/streams/piping/general.any.js.ini new file mode 100644 index 00000000000..cfcbb9cb3b5 --- /dev/null +++ b/tests/wpt/meta/streams/piping/general.any.js.ini @@ -0,0 +1,27 @@ +[general.any.sharedworker.html] + expected: ERROR + +[general.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[general.any.worker.html] + +[general.any.serviceworker.html] + expected: ERROR + +[general.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[general.any.shadowrealm-in-window.html] + expected: ERROR + +[general.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[general.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[general.any.html] + +[general.any.shadowrealm-in-shadowrealm.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/multiple-propagation.any.js.ini b/tests/wpt/meta/streams/piping/multiple-propagation.any.js.ini new file mode 100644 index 00000000000..1bd682af7da --- /dev/null +++ b/tests/wpt/meta/streams/piping/multiple-propagation.any.js.ini @@ -0,0 +1,27 @@ +[multiple-propagation.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[multiple-propagation.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[multiple-propagation.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[multiple-propagation.any.serviceworker.html] + expected: ERROR + +[multiple-propagation.any.shadowrealm-in-window.html] + expected: ERROR + +[multiple-propagation.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[multiple-propagation.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[multiple-propagation.any.worker.html] + +[multiple-propagation.any.html] + +[multiple-propagation.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/pipe-through.any.js.ini b/tests/wpt/meta/streams/piping/pipe-through.any.js.ini new file mode 100644 index 00000000000..1628289001f --- /dev/null +++ b/tests/wpt/meta/streams/piping/pipe-through.any.js.ini @@ -0,0 +1,142 @@ +[pipe-through.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[pipe-through.any.serviceworker.html] + expected: ERROR + +[pipe-through.any.worker.html] + expected: ERROR + [Piping through a duck-typed pass-through transform stream should work] + expected: FAIL + + [Piping through a transform errored on the writable end does not cause an unhandled promise rejection] + expected: FAIL + + [pipeThrough should not call pipeTo on this] + expected: FAIL + + [pipeThrough should not call pipeTo on the ReadableStream prototype] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'null'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'undefined'] + expected: FAIL + + [pipeThrough should brand-check this and not allow '0'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'NaN'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'true'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'ReadableStream'] + expected: FAIL + + [pipeThrough should brand-check this and not allow '[object ReadableStream\]'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'null'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'undefined'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow '0'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'NaN'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'true'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'WritableStream'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow '[object WritableStream\]'] + expected: FAIL + + [pipeThrough should rethrow errors from accessing readable or writable] + expected: FAIL + + +[pipe-through.any.sharedworker.html] + expected: ERROR + +[pipe-through.any.shadowrealm-in-window.html] + expected: ERROR + +[pipe-through.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[pipe-through.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[pipe-through.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[pipe-through.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[pipe-through.any.html] + expected: ERROR + [Piping through a duck-typed pass-through transform stream should work] + expected: FAIL + + [Piping through a transform errored on the writable end does not cause an unhandled promise rejection] + expected: FAIL + + [pipeThrough should not call pipeTo on this] + expected: FAIL + + [pipeThrough should not call pipeTo on the ReadableStream prototype] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'null'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'undefined'] + expected: FAIL + + [pipeThrough should brand-check this and not allow '0'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'NaN'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'true'] + expected: FAIL + + [pipeThrough should brand-check this and not allow 'ReadableStream'] + expected: FAIL + + [pipeThrough should brand-check this and not allow '[object ReadableStream\]'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'null'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'undefined'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow '0'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'NaN'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'true'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow 'WritableStream'] + expected: FAIL + + [pipeThrough should brand-check writable and not allow '[object WritableStream\]'] + expected: FAIL + + [pipeThrough should rethrow errors from accessing readable or writable] + expected: FAIL diff --git a/tests/wpt/meta/streams/piping/then-interception.any.js.ini b/tests/wpt/meta/streams/piping/then-interception.any.js.ini new file mode 100644 index 00000000000..f3380000385 --- /dev/null +++ b/tests/wpt/meta/streams/piping/then-interception.any.js.ini @@ -0,0 +1,29 @@ +[then-interception.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[then-interception.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[then-interception.any.serviceworker.html] + expected: ERROR + +[then-interception.any.worker.html] + expected: CRASH + +[then-interception.any.html] + expected: CRASH + +[then-interception.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[then-interception.any.shadowrealm-in-window.html] + expected: ERROR + +[then-interception.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[then-interception.any.sharedworker.html] + expected: ERROR + +[then-interception.any.shadowrealm-in-sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/throwing-options.any.js.ini b/tests/wpt/meta/streams/piping/throwing-options.any.js.ini new file mode 100644 index 00000000000..3b3bd453b5a --- /dev/null +++ b/tests/wpt/meta/streams/piping/throwing-options.any.js.ini @@ -0,0 +1,59 @@ +[throwing-options.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[throwing-options.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[throwing-options.any.serviceworker.html] + expected: ERROR + +[throwing-options.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[throwing-options.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[throwing-options.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[throwing-options.any.shadowrealm-in-window.html] + expected: ERROR + +[throwing-options.any.html] + expected: TIMEOUT + [pipeThrough should stop after getting preventAbort throws] + expected: FAIL + + [pipeThrough should stop after getting preventCancel throws] + expected: FAIL + + [pipeThrough should stop after getting preventClose throws] + expected: FAIL + + [pipeTo should stop after getting signal throws] + expected: TIMEOUT + + [pipeThrough should stop after getting signal throws] + expected: FAIL + + +[throwing-options.any.worker.html] + expected: TIMEOUT + [pipeThrough should stop after getting preventAbort throws] + expected: FAIL + + [pipeThrough should stop after getting preventCancel throws] + expected: FAIL + + [pipeThrough should stop after getting preventClose throws] + expected: FAIL + + [pipeTo should stop after getting signal throws] + expected: TIMEOUT + + [pipeThrough should stop after getting signal throws] + expected: FAIL + + +[throwing-options.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/piping/transform-streams.any.js.ini b/tests/wpt/meta/streams/piping/transform-streams.any.js.ini new file mode 100644 index 00000000000..b1cb2acddf3 --- /dev/null +++ b/tests/wpt/meta/streams/piping/transform-streams.any.js.ini @@ -0,0 +1,33 @@ +[transform-streams.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[transform-streams.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[transform-streams.any.sharedworker.html] + expected: ERROR + +[transform-streams.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[transform-streams.any.worker.html] + [Piping through an identity transform stream should close the destination when the source closes] + expected: FAIL + + +[transform-streams.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[transform-streams.any.html] + [Piping through an identity transform stream should close the destination when the source closes] + expected: FAIL + + +[transform-streams.any.shadowrealm-in-window.html] + expected: ERROR + +[transform-streams.any.serviceworker.html] + expected: ERROR + +[transform-streams.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-streams/patched-global.any.js.ini b/tests/wpt/meta/streams/readable-streams/patched-global.any.js.ini index 09e2ac91538..15fefae4642 100644 --- a/tests/wpt/meta/streams/readable-streams/patched-global.any.js.ini +++ b/tests/wpt/meta/streams/readable-streams/patched-global.any.js.ini @@ -5,17 +5,11 @@ expected: ERROR [patched-global.any.html] - [pipeTo() should not call Promise.prototype.then()] - expected: FAIL - [ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader methods] expected: FAIL [patched-global.any.worker.html] - [pipeTo() should not call Promise.prototype.then()] - expected: FAIL - [ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader methods] expected: FAIL diff --git a/tests/wpt/meta/streams/readable-streams/reentrant-strategies.any.js.ini b/tests/wpt/meta/streams/readable-streams/reentrant-strategies.any.js.ini index b1cee7aae94..524483078b2 100644 --- a/tests/wpt/meta/streams/readable-streams/reentrant-strategies.any.js.ini +++ b/tests/wpt/meta/streams/readable-streams/reentrant-strategies.any.js.ini @@ -5,14 +5,8 @@ expected: ERROR [reentrant-strategies.any.html] - [pipeTo() inside size() should behave as expected] - expected: FAIL - [reentrant-strategies.any.worker.html] - [pipeTo() inside size() should behave as expected] - expected: FAIL - [reentrant-strategies.any.shadowrealm.html] expected: TIMEOUT