1#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::Cell;
8use std::rc::Rc;
9
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use js::jsapi::Heap;
13use js::jsval::{JSVal, UndefinedValue};
14use js::typedarray::ArrayBufferViewU8;
15use script_bindings::error::Fallible;
16use script_bindings::reflector::{Reflector, reflect_dom_object};
17
18use super::byteteeunderlyingsource::ByteTeePullAlgorithm;
19use crate::dom::bindings::buffer_source::HeapBufferSource;
20use crate::dom::bindings::error::{Error, ErrorToJsval};
21use crate::dom::bindings::reflector::DomGlobal;
22use crate::dom::bindings::root::{Dom, DomRoot};
23use crate::dom::bindings::trace::RootedTraceableBox;
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::promise::{RootedPromise, TracedPromise};
26use crate::dom::stream::byteteeunderlyingsource::ByteTeeUnderlyingSource;
27use crate::dom::stream::readablestream::ReadableStream;
28use crate::realms::enter_auto_realm;
29use crate::runtime::job_queue::MicrotaskRunnable;
30
31#[derive(JSTraceable, MallocSizeOf)]
32#[cfg_attr(crown, expect(crown::unrooted_must_root))]
33pub(crate) struct ByteTeeReadRequestMicrotask {
34 #[ignore_malloc_size_of = "mozjs"]
35 chunk: Box<Heap<JSVal>>,
36 tee_read_request: Dom<ByteTeeReadRequest>,
37}
38
39impl MicrotaskRunnable for ByteTeeReadRequestMicrotask {
40 fn handler(&self, cx: &mut JSContext) {
41 let mut realm = enter_auto_realm(cx, &*self.tee_read_request);
42 let cx = &mut realm;
43 self.tee_read_request
44 .chunk_steps(&self.chunk, cx)
45 .expect("ByteTeeReadRequestMicrotask::microtask_chunk_steps failed");
46 }
47}
48
49#[dom_struct]
50pub(crate) struct ByteTeeReadRequest {
52 reflector_: Reflector,
53 branch_1: Dom<ReadableStream>,
54 branch_2: Dom<ReadableStream>,
55 stream: Dom<ReadableStream>,
56 #[conditional_malloc_size_of]
57 read_again_for_branch_1: Rc<Cell<bool>>,
58 #[conditional_malloc_size_of]
59 read_again_for_branch_2: Rc<Cell<bool>>,
60 #[conditional_malloc_size_of]
61 reading: Rc<Cell<bool>>,
62 #[conditional_malloc_size_of]
63 canceled_1: Rc<Cell<bool>>,
64 #[conditional_malloc_size_of]
65 canceled_2: Rc<Cell<bool>>,
66 cancel_promise: TracedPromise,
67 tee_underlying_source: Dom<ByteTeeUnderlyingSource>,
68}
69impl ByteTeeReadRequest {
70 #[allow(clippy::too_many_arguments)]
71 pub(crate) fn new(
72 cx: &mut JSContext,
73 branch_1: &ReadableStream,
74 branch_2: &ReadableStream,
75 stream: &ReadableStream,
76 read_again_for_branch_1: Rc<Cell<bool>>,
77 read_again_for_branch_2: Rc<Cell<bool>>,
78 reading: Rc<Cell<bool>>,
79 canceled_1: Rc<Cell<bool>>,
80 canceled_2: Rc<Cell<bool>>,
81 cancel_promise: &RootedPromise,
82 tee_underlying_source: &ByteTeeUnderlyingSource,
83 global: &GlobalScope,
84 ) -> DomRoot<Self> {
85 reflect_dom_object(
86 cx,
87 Box::new(ByteTeeReadRequest {
88 reflector_: Reflector::new(),
89 branch_1: Dom::from_ref(branch_1),
90 branch_2: Dom::from_ref(branch_2),
91 stream: Dom::from_ref(stream),
92 read_again_for_branch_1,
93 read_again_for_branch_2,
94 reading,
95 canceled_1,
96 canceled_2,
97 cancel_promise: cancel_promise.to_traced(),
98 tee_underlying_source: Dom::from_ref(tee_underlying_source),
99 }),
100 global,
101 )
102 }
103
104 pub(crate) fn enqueue_chunk_steps(
107 &self,
108 cx: &mut JSContext,
109 global: &GlobalScope,
110 chunk: RootedTraceableBox<Heap<JSVal>>,
111 ) {
112 let byte_tee_read_request_chunk = ByteTeeReadRequestMicrotask {
114 chunk: Heap::boxed(*chunk.handle()),
115 tee_read_request: Dom::from_ref(self),
116 };
117 global.enqueue_microtask(cx, Box::new(byte_tee_read_request_chunk));
118 }
119
120 #[allow(clippy::borrowed_box)]
122 pub(crate) fn chunk_steps(&self, chunk: &Box<Heap<JSVal>>, cx: &mut JSContext) -> Fallible<()> {
123 self.read_again_for_branch_1.set(false);
125
126 self.read_again_for_branch_2.set(false);
128
129 rooted!(&in(cx) let chunk_object = chunk.get().to_object());
131
132 let handle_clone_error = |cx: &mut JSContext, error: Error| {
134 rooted!(&in(cx) let mut error_value = UndefinedValue());
135 error.to_jsval(cx, &self.global(), error_value.handle_mut());
136
137 let branch_1_controller = self.branch_1.get_byte_controller();
138 let branch_2_controller = self.branch_2.get_byte_controller();
139
140 branch_1_controller.error(cx, error_value.handle());
141 branch_2_controller.error(cx, error_value.handle());
142
143 let cancel_result = self
144 .stream
145 .cancel(cx, &self.stream.global(), error_value.handle());
146 self.cancel_promise.resolve_native(cx, &cancel_result);
147 };
148
149 let chunk1_view = if !self.canceled_1.get() {
151 Some(RootedTraceableBox::new(
152 HeapBufferSource::<ArrayBufferViewU8>::new(chunk_object.handle()),
153 ))
154 } else {
155 None
156 };
157
158 let mut chunk2_view = None;
159
160 if !self.canceled_1.get() && !self.canceled_2.get() {
162 let chunk2_source = RootedTraceableBox::new(
164 HeapBufferSource::<ArrayBufferViewU8>::new(chunk_object.handle()),
165 );
166 let clone_result = chunk2_source.clone_as_uint8_array(cx);
167
168 if let Err(error) = clone_result {
170 handle_clone_error(cx, error);
171 return Ok(());
172 } else {
173 chunk2_view = clone_result.ok();
175 }
176 } else if !self.canceled_2.get() {
177 let chunk2_source = RootedTraceableBox::new(
179 HeapBufferSource::<ArrayBufferViewU8>::new(chunk_object.handle()),
180 );
181 match chunk2_source.clone_as_uint8_array(cx) {
182 Ok(clone) => chunk2_view = Some(clone),
183 Err(error) => {
184 handle_clone_error(cx, error);
185 return Ok(());
186 },
187 }
188 }
189
190 if let Some(chunk1_view) = chunk1_view {
192 let branch_1_controller = self.branch_1.get_byte_controller();
193 branch_1_controller.enqueue(cx, chunk1_view)?;
194 }
195
196 if let Some(chunk2_view) = chunk2_view {
198 let branch_2_controller = self.branch_2.get_byte_controller();
199 branch_2_controller.enqueue(cx, chunk2_view)?;
200 }
201
202 self.reading.set(false);
204
205 if self.read_again_for_branch_1.get() {
207 self.pull_algorithm(cx, Some(ByteTeePullAlgorithm::Pull1Algorithm));
208 } else if self.read_again_for_branch_2.get() {
209 self.pull_algorithm(cx, Some(ByteTeePullAlgorithm::Pull2Algorithm));
211 }
212
213 Ok(())
214 }
215
216 pub(crate) fn close_steps(&self, cx: &mut JSContext) -> Fallible<()> {
218 let branch_1_controller = self.branch_1.get_byte_controller();
219 let branch_2_controller = self.branch_2.get_byte_controller();
220
221 self.reading.set(false);
223
224 if !self.canceled_1.get() {
226 branch_1_controller.close(cx)?;
227 }
228
229 if !self.canceled_2.get() {
231 branch_2_controller.close(cx)?;
232 }
233
234 if branch_1_controller.get_pending_pull_intos_size() > 0 {
237 branch_1_controller.respond(cx, 0)?;
238 }
239
240 if branch_2_controller.get_pending_pull_intos_size() > 0 {
243 branch_2_controller.respond(cx, 0)?;
244 }
245
246 if !self.canceled_1.get() || !self.canceled_2.get() {
248 self.cancel_promise.resolve_native(cx, &());
249 }
250
251 Ok(())
252 }
253
254 pub(crate) fn error_steps(&self) {
256 self.reading.set(false);
258 }
259
260 pub(crate) fn pull_algorithm(
261 &self,
262 cx: &mut JSContext,
263 byte_tee_pull_algorithm: Option<ByteTeePullAlgorithm>,
264 ) {
265 self.tee_underlying_source
266 .pull_algorithm(cx, byte_tee_pull_algorithm);
267 }
268}