use std::cell::Cell;
use std::rc::Rc;
use dom_struct::dom_struct;
use js::context::JSContext;
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue};
use js::realm::AutoRealm;
use js::rust::HandleValue as SafeHandleValue;
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::structuredclone;
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::stream::defaultteeunderlyingsource::DefaultTeeUnderlyingSource;
use crate::dom::stream::readablestream::ReadableStream;
use crate::microtask::{Microtask, MicrotaskRunnable};
use crate::realms::enter_auto_realm;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
pub(crate) struct DefaultTeeReadRequestMicrotask {
#[ignore_malloc_size_of = "mozjs"]
chunk: Box<Heap<JSVal>>,
tee_read_request: Dom<DefaultTeeReadRequest>,
}
impl MicrotaskRunnable for DefaultTeeReadRequestMicrotask {
fn handler(&self, cx: &mut JSContext) {
self.tee_read_request.chunk_steps(cx, &self.chunk);
}
fn enter_realm<'cx>(&self, cx: &'cx mut js::context::JSContext) -> AutoRealm<'cx> {
enter_auto_realm(cx, &*self.tee_read_request)
}
}
#[dom_struct]
pub(crate) struct DefaultTeeReadRequest {
reflector_: Reflector,
stream: Dom<ReadableStream>,
branch_1: Dom<ReadableStream>,
branch_2: Dom<ReadableStream>,
#[conditional_malloc_size_of]
reading: Rc<Cell<bool>>,
#[conditional_malloc_size_of]
read_again: Rc<Cell<bool>>,
#[conditional_malloc_size_of]
canceled_1: Rc<Cell<bool>>,
#[conditional_malloc_size_of]
canceled_2: Rc<Cell<bool>>,
#[conditional_malloc_size_of]
clone_for_branch_2: Rc<Cell<bool>>,
#[conditional_malloc_size_of]
cancel_promise: Rc<Promise>,
tee_underlying_source: Dom<DefaultTeeUnderlyingSource>,
}
impl DefaultTeeReadRequest {
#[expect(clippy::too_many_arguments)]
pub(crate) fn new(
stream: &ReadableStream,
branch_1: &ReadableStream,
branch_2: &ReadableStream,
reading: Rc<Cell<bool>>,
read_again: Rc<Cell<bool>>,
canceled_1: Rc<Cell<bool>>,
canceled_2: Rc<Cell<bool>>,
clone_for_branch_2: Rc<Cell<bool>>,
cancel_promise: Rc<Promise>,
tee_underlying_source: &DefaultTeeUnderlyingSource,
can_gc: CanGc,
) -> DomRoot<Self> {
reflect_dom_object(
Box::new(DefaultTeeReadRequest {
reflector_: Reflector::new(),
stream: Dom::from_ref(stream),
branch_1: Dom::from_ref(branch_1),
branch_2: Dom::from_ref(branch_2),
reading,
read_again,
canceled_1,
canceled_2,
clone_for_branch_2,
cancel_promise,
tee_underlying_source: Dom::from_ref(tee_underlying_source),
}),
&*stream.global(),
can_gc,
)
}
pub(crate) fn stream_cancel(
&self,
cx: &mut JSContext,
global: &GlobalScope,
reason: SafeHandleValue,
) {
self.stream.cancel(cx, global, reason);
}
pub(crate) fn enqueue_chunk_steps(&self, chunk: RootedTraceableBox<Heap<JSVal>>) {
let tee_read_request_chunk = DefaultTeeReadRequestMicrotask {
chunk: Heap::boxed(*chunk.handle()),
tee_read_request: Dom::from_ref(self),
};
self.stream
.global()
.enqueue_microtask(Microtask::ReadableStreamTeeReadRequest(
tee_read_request_chunk,
));
}
#[expect(clippy::borrowed_box)]
pub(crate) fn chunk_steps(&self, cx: &mut JSContext, chunk: &Box<Heap<JSVal>>) {
let global = &self.stream.global();
self.read_again.set(false);
let chunk1 = chunk;
let chunk2 = chunk;
rooted!(&in(cx) let chunk1_value = chunk1.get());
rooted!(&in(cx) let chunk2_value = chunk2.get());
if !self.canceled_2.get() && self.clone_for_branch_2.get() {
rooted!(&in(cx) let mut clone_result = UndefinedValue());
let data = structuredclone::write(cx.into(), chunk2_value.handle(), None).unwrap();
if structuredclone::read(global, data, clone_result.handle_mut(), CanGc::from_cx(cx))
.is_err()
{
self.readable_stream_default_controller_error(
cx,
&self.branch_1,
clone_result.handle(),
);
self.readable_stream_default_controller_error(
cx,
&self.branch_2,
clone_result.handle(),
);
self.stream_cancel(cx, global, clone_result.handle());
return;
} else {
chunk2.set(*clone_result);
}
}
if !self.canceled_1.get() {
self.readable_stream_default_controller_enqueue(
cx,
&self.branch_1,
chunk1_value.handle(),
);
}
if !self.canceled_2.get() {
self.readable_stream_default_controller_enqueue(
cx,
&self.branch_2,
chunk2_value.handle(),
);
}
self.reading.set(false);
if self.read_again.get() {
self.pull_algorithm(cx);
}
}
pub(crate) fn close_steps(&self, cx: &mut JSContext) {
self.reading.set(false);
if !self.canceled_1.get() {
self.readable_stream_default_controller_close(cx, &self.branch_1);
}
if !self.canceled_2.get() {
self.readable_stream_default_controller_close(cx, &self.branch_2);
}
if !self.canceled_1.get() || !self.canceled_2.get() {
self.cancel_promise.resolve_native(&(), CanGc::from_cx(cx));
}
}
pub(crate) fn error_steps(&self) {
self.reading.set(false);
}
fn readable_stream_default_controller_enqueue(
&self,
cx: &mut JSContext,
stream: &ReadableStream,
chunk: SafeHandleValue,
) {
stream
.get_default_controller()
.enqueue(cx, chunk)
.expect("enqueue failed for stream controller in DefaultTeeReadRequest");
}
fn readable_stream_default_controller_close(
&self,
cx: &mut JSContext,
stream: &ReadableStream,
) {
stream.get_default_controller().close(cx);
}
fn readable_stream_default_controller_error(
&self,
cx: &mut JSContext,
stream: &ReadableStream,
error: SafeHandleValue,
) {
stream.get_default_controller().error(cx, error);
}
pub(crate) fn pull_algorithm(&self, cx: &mut JSContext) {
self.tee_underlying_source.pull_algorithm(cx);
}
}