mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Streams: Implement stream pipe-to (#35650)
* implement PipeTo, stub pipe_to Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * define a data structure to manage the piping Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * implement propagation of errors forward and backward, stub shutdown and shutdown with action Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * adding more fine-grain shutdown variants to state Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * implement progagate closing backward and forward Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * implement shutdown and actions Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * implement reading and writing Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * implement shutdown continuation and finalize Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix typo Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add can_gc arguments Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * implement writer close with error propagation Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * move and document wait on pending write Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * more docs Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * write pending reads as part of shutdown Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * turn on piping test suite Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add comment about using Rust api improve comment on result add comment on backpressure Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix multiple propagations Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix writing of chunks Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix error and close propagation update test expectations Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix warnings Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * improve docs remove redundant logic in pending writes Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix clippy Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * remove unnecessary expansion of visibility of enqueued value to_jsval Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * remove unnecessary conditional accessing of streams when propagating states Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * improve docs Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * remove unused result var Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fix typo Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * remove redundant logic dealing with closed sources with pending writes Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add doc links for shutdown actions Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * add comments on the need to return early when shutting down before checking close and error states Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * fmt Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> * Update test expectations Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> * fix can_gc Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> Co-authored-by: Taym Haddadi <haddadi.taym@gmail.com>
This commit is contained in:
parent
67a5f285ed
commit
8d39d7706a
23 changed files with 1319 additions and 29 deletions
|
@ -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<ShutdownAction>),
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#rs-pipeTo-shutdown-with-action>
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum ShutdownAction {
|
||||
/// <https://streams.spec.whatwg.org/#writable-stream-abort>
|
||||
WritableStreamAbort,
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-cancel>
|
||||
ReadableStreamCancel,
|
||||
/// <https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation>
|
||||
WritableStreamDefaultWriterCloseWithErrorPropagation,
|
||||
}
|
||||
|
||||
impl js::gc::Rootable for PipeTo {}
|
||||
|
||||
/// The "in parallel, but not really" part of
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
|
||||
///
|
||||
/// 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 {
|
||||
/// <https://streams.spec.whatwg.org/#ref-for-readablestream%E2%91%A7%E2%91%A0>
|
||||
reader: Dom<ReadableStreamDefaultReader>,
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#ref-for-acquire-writable-stream-default-writer>
|
||||
writer: Dom<WritableStreamDefaultWriter>,
|
||||
|
||||
/// 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<RefCell<VecDeque<Rc<Promise>>>>,
|
||||
|
||||
/// The state machine.
|
||||
#[ignore_malloc_size_of = "Rc are hard"]
|
||||
#[no_trace]
|
||||
state: Rc<RefCell<PipeToState>>,
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#readablestream-pipe-to-preventabort>
|
||||
prevent_abort: bool,
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#readablestream-pipe-to-preventcancel>
|
||||
prevent_cancel: bool,
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#readablestream-pipe-to-preventclose>
|
||||
prevent_close: bool,
|
||||
|
||||
/// The `shuttingDown` variable of
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
|
||||
#[ignore_malloc_size_of = "Rc are hard"]
|
||||
shutting_down: Rc<Cell<bool>>,
|
||||
|
||||
/// The error potentially passed to shutdown,
|
||||
/// stored here because we must keep it across a microtask.
|
||||
#[ignore_malloc_size_of = "mozjs"]
|
||||
shutdown_error: Rc<Heap<JSVal>>,
|
||||
|
||||
/// 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<RefCell<Option<Rc<Promise>>>>,
|
||||
|
||||
/// The promise resolved or rejected at
|
||||
/// <https://streams.spec.whatwg.org/#rs-pipeTo-finalize>
|
||||
#[ignore_malloc_size_of = "Rc are hard"]
|
||||
result_promise: Rc<Promise>,
|
||||
}
|
||||
|
||||
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<Promise>,
|
||||
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
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
|
||||
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
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
|
||||
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
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
|
||||
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
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#rs-pipeTo-shutdown-with-action>
|
||||
/// <https://streams.spec.whatwg.org/#rs-pipeTo-shutdown>
|
||||
/// Combined into one method with an optional action.
|
||||
fn shutdown(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
global: &GlobalScope,
|
||||
action: Option<ShutdownAction>,
|
||||
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
|
||||
/// <https://streams.spec.whatwg.org/#rs-pipeTo-shutdown-with-action>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#rs-pipeTo-finalize>
|
||||
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
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-cancel>.
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
|
@ -977,6 +1595,98 @@ impl ReadableStream {
|
|||
Ok(vec![branch_1, branch_2])
|
||||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
|
||||
#[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<Promise> {
|
||||
// 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
|
||||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#readable-stream-tee>
|
||||
fn tee(
|
||||
&self,
|
||||
|
@ -1172,6 +1882,52 @@ impl ReadableStreamMethods<crate::DomTypeHolder> for ReadableStream {
|
|||
// Return ? ReadableStreamTee(this, false).
|
||||
self.tee(false, can_gc)
|
||||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#rs-pipe-to>
|
||||
fn PipeTo(
|
||||
&self,
|
||||
destination: &WritableStream,
|
||||
options: &StreamPipeOptions,
|
||||
realm: InRealm,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
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)]
|
||||
|
|
|
@ -263,7 +263,7 @@ impl WritableStreamDefaultWriter {
|
|||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#writable-stream-default-writer-write>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation>
|
||||
pub(crate) fn close_with_error_propagation(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
global: &GlobalScope,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
// 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<DomRoot<WritableStream>> {
|
||||
self.stream.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl WritableStreamDefaultWriterMethods<crate::DomTypeHolder> for WritableStreamDefaultWriter {
|
||||
|
|
|
@ -632,7 +632,8 @@ DOMInterfaces = {
|
|||
},
|
||||
|
||||
'ReadableStream': {
|
||||
'canGc': ['GetReader', 'Cancel', 'Tee'],
|
||||
'canGc': ['GetReader', 'Cancel', 'PipeTo', 'Tee'],
|
||||
'inRealms': ['PipeTo'],
|
||||
},
|
||||
|
||||
"ReadableStreamDefaultController": {
|
||||
|
|
|
@ -23,8 +23,8 @@ interface _ReadableStream {
|
|||
// [Throws]
|
||||
// ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {});
|
||||
|
||||
// [NewObject]
|
||||
// Promise<undefined> pipeTo(WritableStream destination, optional StreamPipeOptions options = {});
|
||||
[NewObject]
|
||||
Promise<undefined> pipeTo(WritableStream destination, optional StreamPipeOptions options = {});
|
||||
|
||||
[Throws]
|
||||
sequence<ReadableStream> tee();
|
||||
|
|
2
tests/wpt/include.ini
vendored
2
tests/wpt/include.ini
vendored
|
@ -241,6 +241,8 @@ skip: true
|
|||
skip: false
|
||||
[streams]
|
||||
skip: true
|
||||
[piping]
|
||||
skip: false
|
||||
[readable-streams]
|
||||
skip: false
|
||||
[readable-byte-streams]
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
29
tests/wpt/meta/streams/piping/abort.any.js.ini
vendored
Normal file
29
tests/wpt/meta/streams/piping/abort.any.js.ini
vendored
Normal file
|
@ -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
|
27
tests/wpt/meta/streams/piping/close-propagation-backward.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/close-propagation-backward.any.js.ini
vendored
Normal file
|
@ -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]
|
27
tests/wpt/meta/streams/piping/close-propagation-forward.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/close-propagation-forward.any.js.ini
vendored
Normal file
|
@ -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]
|
27
tests/wpt/meta/streams/piping/error-propagation-backward.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/error-propagation-backward.any.js.ini
vendored
Normal file
|
@ -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
|
27
tests/wpt/meta/streams/piping/error-propagation-forward.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/error-propagation-forward.any.js.ini
vendored
Normal file
|
@ -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
|
27
tests/wpt/meta/streams/piping/flow-control.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/flow-control.any.js.ini
vendored
Normal file
|
@ -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
|
27
tests/wpt/meta/streams/piping/general-addition.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/general-addition.any.js.ini
vendored
Normal file
|
@ -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]
|
27
tests/wpt/meta/streams/piping/general.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/general.any.js.ini
vendored
Normal file
|
@ -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
|
27
tests/wpt/meta/streams/piping/multiple-propagation.any.js.ini
vendored
Normal file
27
tests/wpt/meta/streams/piping/multiple-propagation.any.js.ini
vendored
Normal file
|
@ -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
|
142
tests/wpt/meta/streams/piping/pipe-through.any.js.ini
vendored
Normal file
142
tests/wpt/meta/streams/piping/pipe-through.any.js.ini
vendored
Normal file
|
@ -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
|
29
tests/wpt/meta/streams/piping/then-interception.any.js.ini
vendored
Normal file
29
tests/wpt/meta/streams/piping/then-interception.any.js.ini
vendored
Normal file
|
@ -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
|
59
tests/wpt/meta/streams/piping/throwing-options.any.js.ini
vendored
Normal file
59
tests/wpt/meta/streams/piping/throwing-options.any.js.ini
vendored
Normal file
|
@ -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
|
33
tests/wpt/meta/streams/piping/transform-streams.any.js.ini
vendored
Normal file
33
tests/wpt/meta/streams/piping/transform-streams.any.js.ini
vendored
Normal file
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue