mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Part of https://github.com/servo/servo/issues/34676 --------- Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com> Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> Signed-off-by: Taym <haddadi.taym@gmail.com> Co-authored-by: gterzian <2792687+gterzian@users.noreply.github.com>
1088 lines
42 KiB
Rust
1088 lines
42 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||
|
||
use std::cell::{Cell, RefCell};
|
||
use std::ptr;
|
||
use std::rc::Rc;
|
||
|
||
use dom_struct::dom_struct;
|
||
use js::jsapi::{Heap, IsPromiseObject, JSObject};
|
||
use js::jsval::{JSVal, UndefinedValue};
|
||
use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, IntoHandle};
|
||
|
||
use super::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize;
|
||
use super::types::TransformStream;
|
||
use crate::dom::bindings::callback::ExceptionHandling;
|
||
use crate::dom::bindings::codegen::Bindings::UnderlyingSinkBinding::{
|
||
UnderlyingSinkAbortCallback, UnderlyingSinkCloseCallback, UnderlyingSinkStartCallback,
|
||
UnderlyingSinkWriteCallback,
|
||
};
|
||
use crate::dom::bindings::codegen::Bindings::WritableStreamDefaultControllerBinding::WritableStreamDefaultControllerMethods;
|
||
use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
|
||
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
|
||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||
use crate::dom::globalscope::GlobalScope;
|
||
use crate::dom::messageport::MessagePort;
|
||
use crate::dom::promise::Promise;
|
||
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
|
||
use crate::dom::readablestreamdefaultcontroller::{EnqueuedValue, QueueWithSizes, ValueWithSize};
|
||
use crate::dom::writablestream::WritableStream;
|
||
use crate::realms::{InRealm, enter_realm};
|
||
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
|
||
|
||
impl js::gc::Rootable for CloseAlgorithmFulfillmentHandler {}
|
||
|
||
/// The fulfillment handler for
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close>
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
struct CloseAlgorithmFulfillmentHandler {
|
||
stream: Dom<WritableStream>,
|
||
}
|
||
|
||
impl Callback for CloseAlgorithmFulfillmentHandler {
|
||
fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) {
|
||
let stream = self.stream.as_rooted();
|
||
|
||
// Perform ! WritableStreamFinishInFlightClose(stream).
|
||
stream.finish_in_flight_close(cx, can_gc);
|
||
}
|
||
}
|
||
|
||
impl js::gc::Rootable for CloseAlgorithmRejectionHandler {}
|
||
|
||
/// The rejection handler for
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close>
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
struct CloseAlgorithmRejectionHandler {
|
||
stream: Dom<WritableStream>,
|
||
}
|
||
|
||
impl Callback for CloseAlgorithmRejectionHandler {
|
||
fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, realm: InRealm, can_gc: CanGc) {
|
||
let stream = self.stream.as_rooted();
|
||
|
||
let global = GlobalScope::from_safe_context(cx, realm);
|
||
|
||
// Perform ! WritableStreamFinishInFlightCloseWithError(stream, reason).
|
||
stream.finish_in_flight_close_with_error(cx, &global, v, can_gc);
|
||
}
|
||
}
|
||
|
||
impl js::gc::Rootable for StartAlgorithmFulfillmentHandler {}
|
||
|
||
/// The fulfillment handler for
|
||
/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
struct StartAlgorithmFulfillmentHandler {
|
||
controller: Dom<WritableStreamDefaultController>,
|
||
}
|
||
|
||
impl Callback for StartAlgorithmFulfillmentHandler {
|
||
/// Continuation of <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
|
||
/// Upon fulfillment of startPromise,
|
||
fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, realm: InRealm, can_gc: CanGc) {
|
||
let controller = self.controller.as_rooted();
|
||
let stream = controller
|
||
.stream
|
||
.get()
|
||
.expect("Controller should have a stream.");
|
||
|
||
// Assert: stream.[[state]] is "writable" or "erroring".
|
||
assert!(stream.is_erroring() || stream.is_writable());
|
||
|
||
// Set controller.[[started]] to true.
|
||
controller.started.set(true);
|
||
|
||
let global = GlobalScope::from_safe_context(cx, realm);
|
||
|
||
// Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
|
||
controller.advance_queue_if_needed(cx, &global, can_gc)
|
||
}
|
||
}
|
||
|
||
impl js::gc::Rootable for StartAlgorithmRejectionHandler {}
|
||
|
||
/// The rejection handler for
|
||
/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
struct StartAlgorithmRejectionHandler {
|
||
controller: Dom<WritableStreamDefaultController>,
|
||
}
|
||
|
||
impl Callback for StartAlgorithmRejectionHandler {
|
||
/// Continuation of <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
|
||
/// Upon rejection of startPromise with reason r,
|
||
fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, realm: InRealm, can_gc: CanGc) {
|
||
let controller = self.controller.as_rooted();
|
||
let stream = controller
|
||
.stream
|
||
.get()
|
||
.expect("Controller should have a stream.");
|
||
|
||
// Assert: stream.[[state]] is "writable" or "erroring".
|
||
assert!(stream.is_erroring() || stream.is_writable());
|
||
|
||
// Set controller.[[started]] to true.
|
||
controller.started.set(true);
|
||
|
||
let global = GlobalScope::from_safe_context(cx, realm);
|
||
|
||
// Perform ! WritableStreamDealWithRejection(stream, r).
|
||
stream.deal_with_rejection(cx, &global, v, can_gc);
|
||
}
|
||
}
|
||
|
||
impl js::gc::Rootable for TransferBackPressurePromiseReaction {}
|
||
|
||
/// Reacting to backpressurePromise as part of the `writeAlgorithm` of
|
||
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
|
||
#[derive(JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
struct TransferBackPressurePromiseReaction {
|
||
/// The result of reacting to backpressurePromise.
|
||
#[ignore_malloc_size_of = "Rc is hard"]
|
||
result_promise: Rc<Promise>,
|
||
|
||
/// The backpressurePromise.
|
||
#[ignore_malloc_size_of = "Rc is hard"]
|
||
backpressure_promise: Rc<RefCell<Option<Rc<Promise>>>>,
|
||
|
||
/// The chunk received by the `writeAlgorithm`.
|
||
#[ignore_malloc_size_of = "mozjs"]
|
||
chunk: Box<Heap<JSVal>>,
|
||
|
||
/// The port used in the algorithm.
|
||
port: Dom<MessagePort>,
|
||
}
|
||
|
||
impl Callback for TransferBackPressurePromiseReaction {
|
||
/// Reacting to backpressurePromise with the following fulfillment steps:
|
||
fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) {
|
||
let global = self.result_promise.global();
|
||
// Set backpressurePromise to a new promise.
|
||
*self.backpressure_promise.borrow_mut() = Some(Promise::new(&global, can_gc));
|
||
|
||
// Let result be PackAndPostMessageHandlingError(port, "chunk", chunk).
|
||
rooted!(in(*cx) let mut chunk = UndefinedValue());
|
||
chunk.set(self.chunk.get());
|
||
let result =
|
||
self.port
|
||
.pack_and_post_message_handling_error("chunk", chunk.handle(), can_gc);
|
||
|
||
// If result is an abrupt completion,
|
||
if let Err(error) = result {
|
||
// Disentangle port.
|
||
global.disentangle_port(&self.port, can_gc);
|
||
|
||
// Return a promise rejected with result.[[Value]].
|
||
self.result_promise.reject_error(error, can_gc);
|
||
} else {
|
||
// Otherwise, return a promise resolved with undefined.
|
||
self.result_promise.resolve_native(&(), can_gc);
|
||
}
|
||
}
|
||
}
|
||
|
||
impl js::gc::Rootable for WriteAlgorithmFulfillmentHandler {}
|
||
|
||
/// The fulfillment handler for
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write>
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
struct WriteAlgorithmFulfillmentHandler {
|
||
controller: Dom<WritableStreamDefaultController>,
|
||
}
|
||
|
||
impl Callback for WriteAlgorithmFulfillmentHandler {
|
||
fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, realm: InRealm, can_gc: CanGc) {
|
||
let controller = self.controller.as_rooted();
|
||
let stream = controller
|
||
.stream
|
||
.get()
|
||
.expect("Controller should have a stream.");
|
||
|
||
// Perform ! WritableStreamFinishInFlightWrite(stream).
|
||
stream.finish_in_flight_write(can_gc);
|
||
|
||
// Let state be stream.[[state]].
|
||
// Assert: state is "writable" or "erroring".
|
||
assert!(stream.is_erroring() || stream.is_writable());
|
||
|
||
// Perform ! DequeueValue(controller).
|
||
{
|
||
rooted!(in(*cx) let mut rval = UndefinedValue());
|
||
let mut queue = controller.queue.borrow_mut();
|
||
queue.dequeue_value(cx, Some(rval.handle_mut()), can_gc);
|
||
}
|
||
|
||
let global = GlobalScope::from_safe_context(cx, realm);
|
||
|
||
// If ! WritableStreamCloseQueuedOrInFlight(stream) is false and state is "writable",
|
||
if !stream.close_queued_or_in_flight() && stream.is_writable() {
|
||
// Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller).
|
||
let backpressure = controller.get_backpressure();
|
||
|
||
// Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
|
||
stream.update_backpressure(backpressure, &global, can_gc);
|
||
}
|
||
|
||
// Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
|
||
controller.advance_queue_if_needed(cx, &global, can_gc)
|
||
}
|
||
}
|
||
|
||
impl js::gc::Rootable for WriteAlgorithmRejectionHandler {}
|
||
|
||
/// The rejection handler for
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write>
|
||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
struct WriteAlgorithmRejectionHandler {
|
||
controller: Dom<WritableStreamDefaultController>,
|
||
}
|
||
|
||
impl Callback for WriteAlgorithmRejectionHandler {
|
||
fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, realm: InRealm, can_gc: CanGc) {
|
||
let controller = self.controller.as_rooted();
|
||
let stream = controller
|
||
.stream
|
||
.get()
|
||
.expect("Controller should have a stream.");
|
||
|
||
// If stream.[[state]] is "writable",
|
||
if stream.is_writable() {
|
||
// perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
|
||
controller.clear_algorithms();
|
||
}
|
||
|
||
let global = GlobalScope::from_safe_context(cx, realm);
|
||
|
||
// Perform ! WritableStreamFinishInFlightWriteWithError(stream, reason).
|
||
stream.finish_in_flight_write_with_error(cx, &global, v, can_gc);
|
||
}
|
||
}
|
||
|
||
/// The type of sink algorithms we are using.
|
||
#[derive(JSTraceable, PartialEq)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
pub enum UnderlyingSinkType {
|
||
/// Algorithms are provided by Js callbacks.
|
||
Js {
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-abortalgorithm>
|
||
abort: RefCell<Option<Rc<UnderlyingSinkAbortCallback>>>,
|
||
|
||
start: RefCell<Option<Rc<UnderlyingSinkStartCallback>>>,
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-closealgorithm>
|
||
close: RefCell<Option<Rc<UnderlyingSinkCloseCallback>>>,
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-writealgorithm>
|
||
write: RefCell<Option<Rc<UnderlyingSinkWriteCallback>>>,
|
||
},
|
||
/// Algorithms supporting streams transfer are implemented in Rust.
|
||
/// The promise and port used in those algorithms are stored here.
|
||
Transfer {
|
||
backpressure_promise: Rc<RefCell<Option<Rc<Promise>>>>,
|
||
port: Dom<MessagePort>,
|
||
},
|
||
/// Algorithms supporting transform streams are implemented in Rust.
|
||
Transform(Dom<TransformStream>, Rc<Promise>),
|
||
}
|
||
|
||
impl UnderlyingSinkType {
|
||
pub(crate) fn new_js(
|
||
abort: Option<Rc<UnderlyingSinkAbortCallback>>,
|
||
start: Option<Rc<UnderlyingSinkStartCallback>>,
|
||
close: Option<Rc<UnderlyingSinkCloseCallback>>,
|
||
write: Option<Rc<UnderlyingSinkWriteCallback>>,
|
||
) -> Self {
|
||
UnderlyingSinkType::Js {
|
||
abort: RefCell::new(abort),
|
||
start: RefCell::new(start),
|
||
close: RefCell::new(close),
|
||
write: RefCell::new(write),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#ws-default-controller-class>
|
||
#[dom_struct]
|
||
pub struct WritableStreamDefaultController {
|
||
reflector_: Reflector,
|
||
|
||
/// The type of underlying sink used. Besides the default JS one,
|
||
/// there will be others for stream transfer, and for transform stream.
|
||
#[ignore_malloc_size_of = "underlying_sink_type"]
|
||
underlying_sink_type: UnderlyingSinkType,
|
||
|
||
/// The JS object used as `this` when invoking sink algorithms.
|
||
#[ignore_malloc_size_of = "mozjs"]
|
||
underlying_sink_obj: Heap<*mut JSObject>,
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-queue>
|
||
queue: RefCell<QueueWithSizes>,
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-started>
|
||
started: Cell<bool>,
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-strategyhwm>
|
||
strategy_hwm: f64,
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-strategysizealgorithm>
|
||
#[ignore_malloc_size_of = "Rc is hard"]
|
||
strategy_size: RefCell<Option<Rc<QueuingStrategySize>>>,
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-stream>
|
||
stream: MutNullableDom<WritableStream>,
|
||
}
|
||
|
||
impl WritableStreamDefaultController {
|
||
/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink>
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
fn new_inherited(
|
||
underlying_sink_type: UnderlyingSinkType,
|
||
strategy_hwm: f64,
|
||
strategy_size: Rc<QueuingStrategySize>,
|
||
) -> WritableStreamDefaultController {
|
||
WritableStreamDefaultController {
|
||
reflector_: Reflector::new(),
|
||
underlying_sink_type,
|
||
queue: Default::default(),
|
||
stream: Default::default(),
|
||
underlying_sink_obj: Default::default(),
|
||
strategy_hwm,
|
||
strategy_size: RefCell::new(Some(strategy_size)),
|
||
started: Default::default(),
|
||
}
|
||
}
|
||
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
pub(crate) fn new(
|
||
global: &GlobalScope,
|
||
underlying_sink_type: UnderlyingSinkType,
|
||
strategy_hwm: f64,
|
||
strategy_size: Rc<QueuingStrategySize>,
|
||
can_gc: CanGc,
|
||
) -> DomRoot<WritableStreamDefaultController> {
|
||
reflect_dom_object(
|
||
Box::new(WritableStreamDefaultController::new_inherited(
|
||
underlying_sink_type,
|
||
strategy_hwm,
|
||
strategy_size,
|
||
)),
|
||
global,
|
||
can_gc,
|
||
)
|
||
}
|
||
|
||
pub(crate) fn started(&self) -> bool {
|
||
self.started.get()
|
||
}
|
||
|
||
/// Setting the JS object after the heap has settled down.
|
||
pub(crate) fn set_underlying_sink_this_object(&self, this_object: SafeHandleObject) {
|
||
self.underlying_sink_obj.set(*this_object);
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-clear-algorithms>
|
||
fn clear_algorithms(&self) {
|
||
match &self.underlying_sink_type {
|
||
UnderlyingSinkType::Js {
|
||
abort,
|
||
start: _,
|
||
close,
|
||
write,
|
||
} => {
|
||
// Set controller.[[writeAlgorithm]] to undefined.
|
||
write.borrow_mut().take();
|
||
|
||
// Set controller.[[closeAlgorithm]] to undefined.
|
||
close.borrow_mut().take();
|
||
|
||
// Set controller.[[abortAlgorithm]] to undefined.
|
||
abort.borrow_mut().take();
|
||
},
|
||
UnderlyingSinkType::Transfer {
|
||
backpressure_promise,
|
||
..
|
||
} => {
|
||
backpressure_promise.borrow_mut().take();
|
||
},
|
||
UnderlyingSinkType::Transform(_, _) => {
|
||
return;
|
||
},
|
||
}
|
||
|
||
// Set controller.[[strategySizeAlgorithm]] to undefined.
|
||
self.strategy_size.borrow_mut().take();
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
|
||
pub(crate) fn setup(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
global: &GlobalScope,
|
||
stream: &WritableStream,
|
||
can_gc: CanGc,
|
||
) -> Result<(), Error> {
|
||
// Assert: stream implements WritableStream.
|
||
// Implied by stream type.
|
||
|
||
// Assert: stream.[[controller]] is undefined.
|
||
stream.assert_no_controller();
|
||
|
||
// Set controller.[[stream]] to stream.
|
||
self.stream.set(Some(stream));
|
||
|
||
// Set stream.[[controller]] to controller.
|
||
stream.set_default_controller(self);
|
||
|
||
// Perform ! ResetQueue(controller).
|
||
|
||
// Set controller.[[abortController]] to a new AbortController.
|
||
|
||
// Set controller.[[started]] to false.
|
||
|
||
// Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm.
|
||
|
||
// Set controller.[[strategyHWM]] to highWaterMark.
|
||
|
||
// Set controller.[[writeAlgorithm]] to writeAlgorithm.
|
||
|
||
// Set controller.[[closeAlgorithm]] to closeAlgorithm.
|
||
|
||
// Set controller.[[abortAlgorithm]] to abortAlgorithm.
|
||
|
||
// Note: above steps are done in `new_inherited`.
|
||
|
||
// Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller).
|
||
let backpressure = self.get_backpressure();
|
||
|
||
// Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
|
||
stream.update_backpressure(backpressure, global, can_gc);
|
||
|
||
// Let startResult be the result of performing startAlgorithm. (This may throw an exception.)
|
||
// Let startPromise be a promise resolved with startResult.
|
||
let start_promise = self.start_algorithm(cx, global, can_gc)?;
|
||
|
||
let rooted_default_controller = DomRoot::from_ref(self);
|
||
|
||
// Upon fulfillment of startPromise,
|
||
rooted!(in(*cx) let mut fulfillment_handler = Some(StartAlgorithmFulfillmentHandler {
|
||
controller: Dom::from_ref(&rooted_default_controller),
|
||
}));
|
||
|
||
// Upon rejection of startPromise with reason r,
|
||
rooted!(in(*cx) let mut rejection_handler = Some(StartAlgorithmRejectionHandler {
|
||
controller: Dom::from_ref(&rooted_default_controller),
|
||
}));
|
||
|
||
let handler = PromiseNativeHandler::new(
|
||
global,
|
||
fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
|
||
rejection_handler.take().map(|h| Box::new(h) as Box<_>),
|
||
can_gc,
|
||
);
|
||
let realm = enter_realm(global);
|
||
let comp = InRealm::Entered(&realm);
|
||
start_promise.append_native_handler(&handler, comp, can_gc);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-close>
|
||
pub(crate) fn close(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) {
|
||
// Perform ! EnqueueValueWithSize(controller, close sentinel, 0).
|
||
{
|
||
let mut queue = self.queue.borrow_mut();
|
||
queue
|
||
.enqueue_value_with_size(EnqueuedValue::CloseSentinel)
|
||
.expect("Enqueuing the close sentinel should not fail.");
|
||
}
|
||
// Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
|
||
self.advance_queue_if_needed(cx, global, can_gc);
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
fn start_algorithm(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
global: &GlobalScope,
|
||
can_gc: CanGc,
|
||
) -> Fallible<Rc<Promise>> {
|
||
match &self.underlying_sink_type {
|
||
UnderlyingSinkType::Js {
|
||
start,
|
||
abort: _,
|
||
close: _,
|
||
write: _,
|
||
} => {
|
||
let algo = start.borrow().clone();
|
||
let start_promise = if let Some(start) = algo {
|
||
rooted!(in(*cx) let mut result_object = ptr::null_mut::<JSObject>());
|
||
rooted!(in(*cx) let mut result: JSVal);
|
||
rooted!(in(*cx) let this_object = self.underlying_sink_obj.get());
|
||
start.Call_(
|
||
&this_object.handle(),
|
||
self,
|
||
result.handle_mut(),
|
||
ExceptionHandling::Rethrow,
|
||
can_gc,
|
||
)?;
|
||
let is_promise = unsafe {
|
||
if result.is_object() {
|
||
result_object.set(result.to_object());
|
||
IsPromiseObject(result_object.handle().into_handle())
|
||
} else {
|
||
false
|
||
}
|
||
};
|
||
if is_promise {
|
||
let promise = Promise::new_with_js_promise(result_object.handle(), cx);
|
||
promise
|
||
} else {
|
||
Promise::new_resolved(global, cx, result.get(), can_gc)
|
||
}
|
||
} else {
|
||
// Let startAlgorithm be an algorithm that returns undefined.
|
||
Promise::new_resolved(global, cx, (), can_gc)
|
||
};
|
||
|
||
Ok(start_promise)
|
||
},
|
||
UnderlyingSinkType::Transfer { .. } => {
|
||
// Let startAlgorithm be an algorithm that returns undefined.
|
||
Ok(Promise::new_resolved(global, cx, (), can_gc))
|
||
},
|
||
UnderlyingSinkType::Transform(_, start_promise) => {
|
||
// Let startAlgorithm be an algorithm that returns startPromise.
|
||
Ok(start_promise.clone())
|
||
},
|
||
}
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#ref-for-abstract-opdef-writablestreamcontroller-abortsteps>
|
||
pub(crate) fn abort_steps(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
global: &GlobalScope,
|
||
reason: SafeHandleValue,
|
||
can_gc: CanGc,
|
||
) -> Rc<Promise> {
|
||
let result = match &self.underlying_sink_type {
|
||
UnderlyingSinkType::Js {
|
||
abort,
|
||
start: _,
|
||
close: _,
|
||
write: _,
|
||
} => {
|
||
rooted!(in(*cx) let this_object = self.underlying_sink_obj.get());
|
||
let algo = abort.borrow().clone();
|
||
// Let result be the result of performing this.[[abortAlgorithm]], passing reason.
|
||
let result = if let Some(algo) = algo {
|
||
algo.Call_(
|
||
&this_object.handle(),
|
||
Some(reason),
|
||
ExceptionHandling::Rethrow,
|
||
can_gc,
|
||
)
|
||
} else {
|
||
Ok(Promise::new_resolved(global, cx, (), can_gc))
|
||
};
|
||
result.unwrap_or_else(|e| {
|
||
let promise = Promise::new(global, can_gc);
|
||
promise.reject_error(e, can_gc);
|
||
promise
|
||
})
|
||
},
|
||
UnderlyingSinkType::Transfer { port, .. } => {
|
||
// The steps from the `abortAlgorithm` at
|
||
// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
|
||
|
||
// Let result be PackAndPostMessageHandlingError(port, "error", reason).
|
||
let result = port.pack_and_post_message_handling_error("error", reason, can_gc);
|
||
|
||
// Disentangle port.
|
||
global.disentangle_port(port, can_gc);
|
||
|
||
let promise = Promise::new(global, can_gc);
|
||
|
||
// If result is an abrupt completion, return a promise rejected with result.[[Value]]
|
||
if let Err(error) = result {
|
||
promise.reject_error(error, can_gc);
|
||
} else {
|
||
// Otherwise, return a promise resolved with undefined.
|
||
promise.resolve_native(&(), can_gc);
|
||
}
|
||
promise
|
||
},
|
||
UnderlyingSinkType::Transform(stream, _) => {
|
||
// Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason).
|
||
stream
|
||
.transform_stream_default_sink_abort_algorithm(cx, global, reason, can_gc)
|
||
.expect("Transform stream default sink abort algorithm should not fail.")
|
||
},
|
||
};
|
||
|
||
// Perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
|
||
self.clear_algorithms();
|
||
|
||
result
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-writealgorithm>
|
||
fn call_write_algorithm(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
chunk: SafeHandleValue,
|
||
global: &GlobalScope,
|
||
can_gc: CanGc,
|
||
) -> Rc<Promise> {
|
||
match &self.underlying_sink_type {
|
||
UnderlyingSinkType::Js {
|
||
abort: _,
|
||
start: _,
|
||
close: _,
|
||
write,
|
||
} => {
|
||
rooted!(in(*cx) let this_object = self.underlying_sink_obj.get());
|
||
let algo = write.borrow().clone();
|
||
let result = if let Some(algo) = algo {
|
||
algo.Call_(
|
||
&this_object.handle(),
|
||
chunk,
|
||
self,
|
||
ExceptionHandling::Rethrow,
|
||
can_gc,
|
||
)
|
||
} else {
|
||
Ok(Promise::new_resolved(global, cx, (), can_gc))
|
||
};
|
||
result.unwrap_or_else(|e| {
|
||
let promise = Promise::new(global, can_gc);
|
||
promise.reject_error(e, can_gc);
|
||
promise
|
||
})
|
||
},
|
||
UnderlyingSinkType::Transfer {
|
||
backpressure_promise,
|
||
port,
|
||
} => {
|
||
// The steps from the `writeAlgorithm` at
|
||
// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
|
||
|
||
{
|
||
// If backpressurePromise is undefined,
|
||
// set backpressurePromise to a promise resolved with undefined.
|
||
let mut backpressure_promise = backpressure_promise.borrow_mut();
|
||
if backpressure_promise.is_none() {
|
||
*backpressure_promise = Some(Promise::new_resolved(global, cx, (), can_gc));
|
||
}
|
||
}
|
||
|
||
// Return the result of reacting to backpressurePromise with the following fulfillment steps:
|
||
let result_promise = Promise::new(global, can_gc);
|
||
rooted!(in(*cx) let mut fulfillment_handler = Some(TransferBackPressurePromiseReaction {
|
||
port: port.clone(),
|
||
backpressure_promise: backpressure_promise.clone(),
|
||
chunk: Heap::boxed(chunk.get()),
|
||
result_promise: result_promise.clone(),
|
||
}));
|
||
let handler = PromiseNativeHandler::new(
|
||
global,
|
||
fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
|
||
None,
|
||
can_gc,
|
||
);
|
||
let realm = enter_realm(global);
|
||
let comp = InRealm::Entered(&realm);
|
||
backpressure_promise
|
||
.borrow()
|
||
.as_ref()
|
||
.expect("Promise must be some by now.")
|
||
.append_native_handler(&handler, comp, can_gc);
|
||
result_promise
|
||
},
|
||
UnderlyingSinkType::Transform(stream, _) => {
|
||
// Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk).
|
||
stream
|
||
.transform_stream_default_sink_write_algorithm(cx, global, chunk, can_gc)
|
||
.expect("Transform stream default sink write algorithm should not fail.")
|
||
},
|
||
}
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-closealgorithm>
|
||
fn call_close_algorithm(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
global: &GlobalScope,
|
||
can_gc: CanGc,
|
||
) -> Rc<Promise> {
|
||
match &self.underlying_sink_type {
|
||
UnderlyingSinkType::Js {
|
||
abort: _,
|
||
start: _,
|
||
close,
|
||
write: _,
|
||
} => {
|
||
rooted!(in(*cx) let mut this_object = ptr::null_mut::<JSObject>());
|
||
this_object.set(self.underlying_sink_obj.get());
|
||
let algo = close.borrow().clone();
|
||
let result = if let Some(algo) = algo {
|
||
algo.Call_(&this_object.handle(), ExceptionHandling::Rethrow, can_gc)
|
||
} else {
|
||
Ok(Promise::new_resolved(global, cx, (), can_gc))
|
||
};
|
||
result.unwrap_or_else(|e| {
|
||
let promise = Promise::new(global, can_gc);
|
||
promise.reject_error(e, can_gc);
|
||
promise
|
||
})
|
||
},
|
||
UnderlyingSinkType::Transfer { port, .. } => {
|
||
// The steps from the `closeAlgorithm` at
|
||
// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
|
||
|
||
// Perform ! PackAndPostMessage(port, "close", undefined).
|
||
rooted!(in(*cx) let mut value = UndefinedValue());
|
||
port.pack_and_post_message("close", value.handle(), can_gc)
|
||
.expect("Sending close should not fail.");
|
||
|
||
// Disentangle port.
|
||
global.disentangle_port(port, can_gc);
|
||
|
||
// Return a promise resolved with undefined.
|
||
Promise::new_resolved(global, cx, (), can_gc)
|
||
},
|
||
UnderlyingSinkType::Transform(stream, _) => {
|
||
// Return ! TransformStreamDefaultSinkCloseAlgorithm(stream).
|
||
stream
|
||
.transform_stream_default_sink_close_algorithm(cx, global, can_gc)
|
||
.expect("Transform stream default sink close algorithm should not fail.")
|
||
},
|
||
}
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close>
|
||
pub(crate) fn process_close(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) {
|
||
// Let stream be controller.[[stream]].
|
||
let Some(stream) = self.stream.get() else {
|
||
unreachable!("Controller should have a stream");
|
||
};
|
||
|
||
// Perform ! WritableStreamMarkCloseRequestInFlight(stream).
|
||
stream.mark_close_request_in_flight();
|
||
|
||
// Perform ! DequeueValue(controller).
|
||
{
|
||
let mut queue = self.queue.borrow_mut();
|
||
queue.dequeue_value(cx, None, can_gc);
|
||
}
|
||
|
||
// Assert: controller.[[queue]] is empty.
|
||
assert!(self.queue.borrow().is_empty());
|
||
|
||
// Let sinkClosePromise be the result of performing controller.[[closeAlgorithm]].
|
||
let sink_close_promise = self.call_close_algorithm(cx, global, can_gc);
|
||
|
||
// Perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
|
||
self.clear_algorithms();
|
||
|
||
// Upon fulfillment of sinkClosePromise,
|
||
rooted!(in(*cx) let mut fulfillment_handler = Some(CloseAlgorithmFulfillmentHandler {
|
||
stream: Dom::from_ref(&stream),
|
||
}));
|
||
|
||
// Upon rejection of sinkClosePromise with reason reason,
|
||
rooted!(in(*cx) let mut rejection_handler = Some(CloseAlgorithmRejectionHandler {
|
||
stream: Dom::from_ref(&stream),
|
||
}));
|
||
|
||
// Attach handlers to the promise.
|
||
let handler = PromiseNativeHandler::new(
|
||
global,
|
||
fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
|
||
rejection_handler.take().map(|h| Box::new(h) as Box<_>),
|
||
can_gc,
|
||
);
|
||
let realm = enter_realm(global);
|
||
let comp = InRealm::Entered(&realm);
|
||
sink_close_promise.append_native_handler(&handler, comp, can_gc);
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-advance-queue-if-needed>
|
||
fn advance_queue_if_needed(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) {
|
||
// Let stream be controller.[[stream]].
|
||
let Some(stream) = self.stream.get() else {
|
||
unreachable!("Controller should have a stream");
|
||
};
|
||
|
||
// If controller.[[started]] is false, return.
|
||
if !self.started.get() {
|
||
return;
|
||
}
|
||
|
||
// If stream.[[inFlightWriteRequest]] is not undefined, return.
|
||
if stream.has_in_flight_write_request() {
|
||
return;
|
||
}
|
||
|
||
// Let state be stream.[[state]].
|
||
|
||
// Assert: state is not "closed" or "errored".
|
||
assert!(!(stream.is_errored() || stream.is_closed()));
|
||
|
||
// If state is "erroring",
|
||
if stream.is_erroring() {
|
||
// Perform ! WritableStreamFinishErroring(stream).
|
||
stream.finish_erroring(cx, global, can_gc);
|
||
|
||
// Return.
|
||
return;
|
||
}
|
||
|
||
// Let value be ! PeekQueueValue(controller).
|
||
rooted!(in(*cx) let mut value = UndefinedValue());
|
||
let is_closed = {
|
||
let queue = self.queue.borrow_mut();
|
||
|
||
// If controller.[[queue]] is empty, return.
|
||
if queue.is_empty() {
|
||
return;
|
||
}
|
||
queue.peek_queue_value(cx, value.handle_mut(), can_gc)
|
||
};
|
||
|
||
if is_closed {
|
||
// If value is the close sentinel, perform ! WritableStreamDefaultControllerProcessClose(controller).
|
||
self.process_close(cx, global, can_gc);
|
||
} else {
|
||
// Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(controller, value).
|
||
self.process_write(cx, value.handle(), global, can_gc);
|
||
};
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#ws-default-controller-private-error>
|
||
pub(crate) fn perform_error_steps(&self) {
|
||
// Perform ! ResetQueue(this).
|
||
self.queue.borrow_mut().reset();
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write>
|
||
fn process_write(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
chunk: SafeHandleValue,
|
||
global: &GlobalScope,
|
||
can_gc: CanGc,
|
||
) {
|
||
// Let stream be controller.[[stream]].
|
||
let Some(stream) = self.stream.get() else {
|
||
unreachable!("Controller should have a stream");
|
||
};
|
||
|
||
// Perform ! WritableStreamMarkFirstWriteRequestInFlight(stream).
|
||
stream.mark_first_write_request_in_flight();
|
||
|
||
// Let sinkWritePromise be the result of performing controller.[[writeAlgorithm]], passing in chunk.
|
||
let sink_write_promise = self.call_write_algorithm(cx, chunk, global, can_gc);
|
||
|
||
// Upon fulfillment of sinkWritePromise,
|
||
rooted!(in(*cx) let mut fulfillment_handler = Some(WriteAlgorithmFulfillmentHandler {
|
||
controller: Dom::from_ref(self),
|
||
}));
|
||
|
||
// Upon rejection of sinkWritePromise with reason,
|
||
rooted!(in(*cx) let mut rejection_handler = Some(WriteAlgorithmRejectionHandler {
|
||
controller: Dom::from_ref(self),
|
||
}));
|
||
|
||
// Attach handlers to the promise.
|
||
let handler = PromiseNativeHandler::new(
|
||
global,
|
||
fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
|
||
rejection_handler.take().map(|h| Box::new(h) as Box<_>),
|
||
can_gc,
|
||
);
|
||
let realm = enter_realm(global);
|
||
let comp = InRealm::Entered(&realm);
|
||
sink_write_promise.append_native_handler(&handler, comp, can_gc);
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-get-desired-size>
|
||
pub(crate) fn get_desired_size(&self) -> f64 {
|
||
// Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
|
||
let queue = self.queue.borrow();
|
||
let desired_size = self.strategy_hwm - queue.total_size.clamp(0.0, f64::MAX);
|
||
desired_size.clamp(desired_size, self.strategy_hwm)
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-get-backpressure>
|
||
fn get_backpressure(&self) -> bool {
|
||
// Let desiredSize be ! WritableStreamDefaultControllerGetDesiredSize(controller).
|
||
let desired_size = self.get_desired_size();
|
||
|
||
// Return true if desiredSize ≤ 0, or false otherwise.
|
||
desired_size == 0.0 || desired_size.is_sign_negative()
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-get-chunk-size>
|
||
pub(crate) fn get_chunk_size(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
global: &GlobalScope,
|
||
chunk: SafeHandleValue,
|
||
can_gc: CanGc,
|
||
) -> f64 {
|
||
// If controller.[[strategySizeAlgorithm]] is undefined, then:
|
||
let Some(strategy_size) = self.strategy_size.borrow().clone() else {
|
||
// Assert: controller.[[stream]].[[state]] is not "writable".
|
||
let Some(stream) = self.stream.get() else {
|
||
unreachable!("Controller should have a stream");
|
||
};
|
||
assert!(!stream.is_writable());
|
||
|
||
// Return 1.
|
||
return 1.0;
|
||
};
|
||
|
||
// Let returnValue be the result of performing controller.[[strategySizeAlgorithm]],
|
||
// passing in chunk, and interpreting the result as a completion record.
|
||
let result = strategy_size.Call__(chunk, ExceptionHandling::Rethrow, can_gc);
|
||
|
||
match result {
|
||
// Let chunkSize be result.[[Value]].
|
||
Ok(size) => size,
|
||
Err(error) => {
|
||
// If result is an abrupt completion,
|
||
|
||
// Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, returnValue.[[Value]]).
|
||
// Create a rooted value for the error.
|
||
rooted!(in(*cx) let mut rooted_error = UndefinedValue());
|
||
error.to_jsval(cx, global, rooted_error.handle_mut(), can_gc);
|
||
self.error_if_needed(cx, rooted_error.handle(), global, can_gc);
|
||
|
||
// Return 1.
|
||
1.0
|
||
},
|
||
}
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-write>
|
||
pub(crate) fn write(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
global: &GlobalScope,
|
||
chunk: SafeHandleValue,
|
||
chunk_size: f64,
|
||
can_gc: CanGc,
|
||
) {
|
||
// Let enqueueResult be EnqueueValueWithSize(controller, chunk, chunkSize).
|
||
let enqueue_result = {
|
||
let mut queue = self.queue.borrow_mut();
|
||
queue.enqueue_value_with_size(EnqueuedValue::Js(ValueWithSize {
|
||
value: Heap::boxed(chunk.get()),
|
||
size: chunk_size,
|
||
}))
|
||
};
|
||
|
||
// If enqueueResult is an abrupt completion,
|
||
if let Err(error) = enqueue_result {
|
||
// Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueResult.[[Value]]).
|
||
// Create a rooted value for the error.
|
||
rooted!(in(*cx) let mut rooted_error = UndefinedValue());
|
||
error.to_jsval(cx, global, rooted_error.handle_mut(), can_gc);
|
||
self.error_if_needed(cx, rooted_error.handle(), global, can_gc);
|
||
|
||
// Return.
|
||
return;
|
||
}
|
||
|
||
// Let stream be controller.[[stream]].
|
||
let Some(stream) = self.stream.get() else {
|
||
unreachable!("Controller should have a stream");
|
||
};
|
||
|
||
// If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[state]] is "writable",
|
||
if !stream.close_queued_or_in_flight() && stream.is_writable() {
|
||
// Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller).
|
||
let backpressure = self.get_backpressure();
|
||
|
||
// Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
|
||
stream.update_backpressure(backpressure, global, can_gc);
|
||
}
|
||
|
||
// Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
|
||
self.advance_queue_if_needed(cx, global, can_gc);
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-error-if-needed>
|
||
pub(crate) fn error_if_needed(
|
||
&self,
|
||
cx: SafeJSContext,
|
||
error: SafeHandleValue,
|
||
global: &GlobalScope,
|
||
can_gc: CanGc,
|
||
) {
|
||
// Let stream be controller.[[stream]].
|
||
let Some(stream) = self.stream.get() else {
|
||
unreachable!("Controller should have a stream");
|
||
};
|
||
|
||
// If stream.[[state]] is "writable",
|
||
if stream.is_writable() {
|
||
// Perform ! WritableStreamDefaultControllerError(controller, e).
|
||
self.error(&stream, cx, error, global, can_gc);
|
||
}
|
||
}
|
||
|
||
/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-error>
|
||
pub(crate) fn error(
|
||
&self,
|
||
stream: &WritableStream,
|
||
cx: SafeJSContext,
|
||
e: SafeHandleValue,
|
||
global: &GlobalScope,
|
||
can_gc: CanGc,
|
||
) {
|
||
// Let stream be controller.[[stream]].
|
||
// Done above with the argument.
|
||
|
||
// Assert: stream.[[state]] is "writable".
|
||
assert!(stream.is_writable());
|
||
|
||
// Perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
|
||
self.clear_algorithms();
|
||
|
||
// Perform ! WritableStreamStartErroring(stream, error).
|
||
stream.start_erroring(cx, global, e, can_gc);
|
||
}
|
||
}
|
||
|
||
impl WritableStreamDefaultControllerMethods<crate::DomTypeHolder>
|
||
for WritableStreamDefaultController
|
||
{
|
||
/// <https://streams.spec.whatwg.org/#ws-default-controller-error>
|
||
fn Error(&self, cx: SafeJSContext, e: SafeHandleValue, realm: InRealm, can_gc: CanGc) {
|
||
// Let state be this.[[stream]].[[state]].
|
||
let Some(stream) = self.stream.get() else {
|
||
unreachable!("Controller should have a stream");
|
||
};
|
||
|
||
// If state is not "writable", return.
|
||
if !stream.is_writable() {
|
||
return;
|
||
}
|
||
|
||
let global = GlobalScope::from_safe_context(cx, realm);
|
||
|
||
// Perform ! WritableStreamDefaultControllerError(this, e).
|
||
self.error(&stream, cx, e, &global, can_gc);
|
||
}
|
||
}
|