1use crate::host::{invoke, with_host, JsObj};
26use fusevm::Value;
27use indexmap::IndexMap;
28use std::io::{Read, Write};
29
30pub const CLASSES: &[&str] = &[
35 "ReadableStream",
36 "ReadableStreamDefaultReader",
37 "ReadableStreamBYOBReader",
38 "ReadableStreamDefaultController",
39 "ReadableByteStreamController",
40 "ReadableStreamBYOBRequest",
41 "WritableStream",
42 "WritableStreamDefaultWriter",
43 "WritableStreamDefaultController",
44 "TransformStream",
45 "TransformStreamDefaultController",
46 "ByteLengthQueuingStrategy",
47 "CountQueuingStrategy",
48 "TextEncoderStream",
49 "TextDecoderStream",
50 "CompressionStream",
51 "DecompressionStream",
52];
53
54pub const METHODS: &[&str] = &[];
56
57pub const READABLE_STREAM_TAG: &str = "ReadableStream";
59pub const RS_DEFAULT_READER_TAG: &str = "ReadableStreamDefaultReader";
60pub const RS_BYOB_READER_TAG: &str = "ReadableStreamBYOBReader";
61pub const RS_DEFAULT_CONTROLLER_TAG: &str = "ReadableStreamDefaultController";
62pub const RS_BYTE_CONTROLLER_TAG: &str = "ReadableByteStreamController";
63pub const RS_BYOB_REQUEST_TAG: &str = "ReadableStreamBYOBRequest";
64pub const WRITABLE_STREAM_TAG: &str = "WritableStream";
65pub const WS_DEFAULT_WRITER_TAG: &str = "WritableStreamDefaultWriter";
66pub const WS_DEFAULT_CONTROLLER_TAG: &str = "WritableStreamDefaultController";
67pub const TRANSFORM_STREAM_TAG: &str = "TransformStream";
68pub const TS_DEFAULT_CONTROLLER_TAG: &str = "TransformStreamDefaultController";
69pub const BYTE_LENGTH_STRATEGY_TAG: &str = "ByteLengthQueuingStrategy";
70pub const COUNT_STRATEGY_TAG: &str = "CountQueuingStrategy";
71pub const TEXT_ENCODER_STREAM_TAG: &str = "TextEncoderStream";
72pub const TEXT_DECODER_STREAM_TAG: &str = "TextDecoderStream";
73pub const COMPRESSION_STREAM_TAG: &str = "CompressionStream";
74pub const DECOMPRESSION_STREAM_TAG: &str = "DecompressionStream";
75
76pub const READABLE_STREAM_METHODS: &[&str] =
78 &["getReader", "cancel", "tee", "pipeTo", "pipeThrough"];
79pub const RS_DEFAULT_READER_METHODS: &[&str] = &["read", "releaseLock", "cancel"];
80pub const RS_BYOB_READER_METHODS: &[&str] = &["read", "releaseLock", "cancel"];
81pub const RS_DEFAULT_CONTROLLER_METHODS: &[&str] = &["enqueue", "close", "error"];
82pub const RS_BYTE_CONTROLLER_METHODS: &[&str] = &["enqueue", "close", "error"];
83pub const RS_BYOB_REQUEST_METHODS: &[&str] = &["respond", "respondWithNewView"];
84pub const WRITABLE_STREAM_METHODS: &[&str] = &["getWriter", "abort", "close"];
85pub const WS_DEFAULT_WRITER_METHODS: &[&str] = &["write", "close", "abort", "releaseLock"];
86pub const WS_DEFAULT_CONTROLLER_METHODS: &[&str] = &["error"];
87pub const TS_DEFAULT_CONTROLLER_METHODS: &[&str] = &["enqueue", "terminate", "error"];
88pub const STRATEGY_METHODS: &[&str] = &["size"];
89
90pub fn is_class(name: &str) -> bool {
92 CLASSES.contains(&name)
93}
94
95pub fn constant(name: &str) -> Option<Value> {
97 if is_class(name) {
98 Some(with_host(|h| h.alloc(JsObj::Builtin(name.to_string()))))
99 } else {
100 None
101 }
102}
103
104pub fn methods_for(tag: &str) -> &'static [&'static str] {
106 match tag {
107 READABLE_STREAM_TAG => READABLE_STREAM_METHODS,
108 RS_DEFAULT_READER_TAG => RS_DEFAULT_READER_METHODS,
109 RS_BYOB_READER_TAG => RS_BYOB_READER_METHODS,
110 RS_DEFAULT_CONTROLLER_TAG => RS_DEFAULT_CONTROLLER_METHODS,
111 RS_BYTE_CONTROLLER_TAG => RS_BYTE_CONTROLLER_METHODS,
112 RS_BYOB_REQUEST_TAG => RS_BYOB_REQUEST_METHODS,
113 WRITABLE_STREAM_TAG => WRITABLE_STREAM_METHODS,
114 WS_DEFAULT_WRITER_TAG => WS_DEFAULT_WRITER_METHODS,
115 WS_DEFAULT_CONTROLLER_TAG => WS_DEFAULT_CONTROLLER_METHODS,
116 TS_DEFAULT_CONTROLLER_TAG => TS_DEFAULT_CONTROLLER_METHODS,
117 BYTE_LENGTH_STRATEGY_TAG | COUNT_STRATEGY_TAG => STRATEGY_METHODS,
118 _ => &[],
119 }
120}
121
122fn get_prop(recv: &Value, key: &str) -> Option<Value> {
125 with_host(|h| match h.get(recv) {
126 Some(JsObj::Object(p)) => p.get(key).cloned(),
127 _ => None,
128 })
129}
130
131fn set_prop(recv: &Value, key: &str, val: Value) {
132 with_host(|h| {
133 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
134 p.insert(key.to_string(), val);
135 }
136 });
137}
138
139fn remove_prop(recv: &Value, key: &str) {
140 with_host(|h| {
141 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
142 p.shift_remove(key);
143 }
144 });
145}
146
147fn get_str(recv: &Value, key: &str) -> Option<String> {
148 get_prop(recv, key).map(|v| with_host(|h| h.str_of(&v)))
149}
150
151fn state_of(stream: &Value) -> String {
152 get_str(stream, "@@state").unwrap_or_else(|| "readable".into())
153}
154
155fn is_locked(stream: &Value) -> bool {
156 get_prop(stream, "locked")
157 .map(|v| with_host(|h| h.truthy(&v)))
158 .unwrap_or(false)
159}
160
161fn is_callable_val(v: &Value) -> bool {
162 with_host(|h| crate::host::is_callable(h, v))
163}
164
165fn opt_cb(obj: &Value, key: &str) -> Option<Value> {
167 get_prop(obj, key).filter(is_callable_val)
168}
169
170fn iter_result(value: Value, done: bool) -> Value {
172 with_host(|h| {
173 let mut m = IndexMap::new();
174 m.insert("value".into(), value);
175 m.insert("done".into(), Value::Bool(done));
176 h.new_object(m)
177 })
178}
179
180fn synth(msg: &str) -> Value {
181 with_host(|h| crate::builtins::synth_error(h, msg))
182}
183
184fn new_pending() -> Value {
185 with_host(|h| h.new_promise())
186}
187
188fn settle(p: &Value, val: Value) {
189 if let Some(id) = with_host(|h| h.promise_id(p)) {
190 crate::host::resolve_promise_val(id, val);
191 }
192}
193
194fn settle_reject(p: &Value, err: Value) {
195 if let Some(id) = with_host(|h| h.promise_id(p)) {
196 crate::host::reject_promise_val(id, err);
197 }
198}
199
200fn resolved(v: Value) -> Value {
201 crate::host::promise_of(&v)
202}
203
204fn rejected(err: Value) -> Value {
205 let p = new_pending();
206 settle_reject(&p, err);
207 p
208}
209
210fn chunk_bytes(v: &Value) -> Vec<u8> {
213 let via_field = with_host(|h| match h.get(v) {
214 Some(JsObj::Object(p)) => {
215 let field = if p.contains_key("@@bytes") {
216 Some("@@bytes")
217 } else if p.contains_key("@@elems") {
218 Some("@@elems")
219 } else {
220 None
221 };
222 field.and_then(|f| match p.get(f).and_then(|a| h.get(a)) {
223 Some(JsObj::Array(items)) => Some(
224 items
225 .iter()
226 .map(|x| h.to_number(x) as u8)
227 .collect::<Vec<u8>>(),
228 ),
229 _ => None,
230 })
231 }
232 _ => None,
233 });
234 via_field.unwrap_or_else(|| with_host(|h| h.str_of(v)).into_bytes())
235}
236
237fn chunk_byte_length(v: &Value) -> f64 {
239 if let Some(bl) = get_prop(v, "byteLength") {
240 return with_host(|h| h.to_number(&bl));
241 }
242 chunk_bytes(v).len() as f64
243}
244
245fn arr_push(recv: &Value, key: &str, val: Value) {
248 with_host(|h| {
249 let arr = match h.get(recv) {
250 Some(JsObj::Object(p)) => p.get(key).cloned(),
251 _ => None,
252 };
253 if let Some(a) = arr {
254 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
255 items.push(val);
256 }
257 }
258 });
259}
260
261fn arr_shift(recv: &Value, key: &str) -> Option<Value> {
262 with_host(|h| {
263 let arr = match h.get(recv) {
264 Some(JsObj::Object(p)) => p.get(key).cloned(),
265 _ => None,
266 }?;
267 match h.get_mut(&arr) {
268 Some(JsObj::Array(items)) if !items.is_empty() => Some(items.remove(0)),
269 _ => None,
270 }
271 })
272}
273
274fn arr_len(recv: &Value, key: &str) -> usize {
275 with_host(|h| match h.get(recv) {
276 Some(JsObj::Object(p)) => match p.get(key).and_then(|a| h.get(a)) {
277 Some(JsObj::Array(items)) => items.len(),
278 _ => 0,
279 },
280 _ => 0,
281 })
282}
283
284fn arr_get(recv: &Value, key: &str, i: usize) -> Option<Value> {
285 with_host(|h| match h.get(recv) {
286 Some(JsObj::Object(p)) => match p.get(key).and_then(|a| h.get(a)) {
287 Some(JsObj::Array(items)) => items.get(i).cloned(),
288 _ => None,
289 },
290 _ => None,
291 })
292}
293
294fn arr_drain(recv: &Value, key: &str) -> Vec<Value> {
295 with_host(|h| {
296 let arr = match h.get(recv) {
297 Some(JsObj::Object(p)) => p.get(key).cloned(),
298 _ => None,
299 };
300 match arr.as_ref().and_then(|a| h.get_mut(a)) {
301 Some(JsObj::Array(items)) => std::mem::take(items),
302 _ => Vec::new(),
303 }
304 })
305}
306
307fn new_readable_shell() -> Value {
311 with_host(|h| {
312 let queue = h.new_array(Vec::new());
313 let waiters = h.new_array(Vec::new());
314 let mut m = IndexMap::new();
315 m.insert("@@native".into(), h.new_str(READABLE_STREAM_TAG));
316 m.insert("@@state".into(), h.new_str("readable"));
317 m.insert("@@queue".into(), queue);
318 m.insert("@@waiters".into(), waiters);
319 m.insert("locked".into(), Value::Bool(false));
320 h.new_object(m)
321 })
322}
323
324fn new_readable_controller(stream: &Value, byte: bool) -> Value {
326 let tag = if byte {
327 RS_BYTE_CONTROLLER_TAG
328 } else {
329 RS_DEFAULT_CONTROLLER_TAG
330 };
331 let ctrl = with_host(|h| {
332 let mut m = IndexMap::new();
333 m.insert("@@native".into(), h.new_str(tag));
334 m.insert("@@stream".into(), stream.clone());
335 m.insert("desiredSize".into(), Value::Float(1.0));
336 if byte {
337 m.insert("byobRequest".into(), h.null());
338 }
339 h.new_object(m)
340 });
341 set_prop(stream, "@@controller", ctrl.clone());
342 ctrl
343}
344
345fn stream_enqueue(stream: &Value, chunk: Value) {
348 if state_of(stream) != "readable" {
349 return;
350 }
351 if let Some(waiter) = arr_shift(stream, "@@waiters") {
352 let r = iter_result(chunk, false);
353 settle(&waiter, r);
354 } else {
355 arr_push(stream, "@@queue", chunk);
356 }
357}
358
359fn stream_close(stream: &Value) {
361 if state_of(stream) != "readable" {
362 return;
363 }
364 set_prop(stream, "@@state", with_host(|h| h.new_str("closed")));
365 for waiter in arr_drain(stream, "@@waiters") {
366 let r = iter_result(Value::Undef, true);
367 settle(&waiter, r);
368 }
369 if let Some(reader) = get_prop(stream, "@@reader") {
371 if let Some(cp) = get_prop(&reader, "closed") {
372 settle(&cp, Value::Undef);
373 }
374 }
375}
376
377fn stream_error(stream: &Value, err: Value) {
379 if state_of(stream) != "readable" {
380 return;
381 }
382 set_prop(stream, "@@state", with_host(|h| h.new_str("errored")));
383 set_prop(stream, "@@stored_error", err.clone());
384 for waiter in arr_drain(stream, "@@waiters") {
385 settle_reject(&waiter, err.clone());
386 }
387 if let Some(reader) = get_prop(stream, "@@reader") {
388 if let Some(cp) = get_prop(&reader, "closed") {
389 settle_reject(&cp, err.clone());
390 }
391 }
392}
393
394fn try_immediate_read(stream: &Value) -> Option<Value> {
396 match state_of(stream).as_str() {
397 "errored" => {
398 let e = get_prop(stream, "@@stored_error")
399 .unwrap_or_else(|| synth("TypeError: stream errored"));
400 Some(rejected(e))
401 }
402 _ if arr_len(stream, "@@queue") > 0 => {
403 let chunk = arr_shift(stream, "@@queue").unwrap_or(Value::Undef);
404 Some(resolved(iter_result(chunk, false)))
405 }
406 "closed" => Some(resolved(iter_result(Value::Undef, true))),
407 _ => None,
408 }
409}
410
411fn drive_pull(stream: &Value) {
414 if let Some(kind) = get_str(stream, "@@native_pull") {
415 if kind == "tee" {
416 tee_pull(stream);
417 }
418 return;
419 }
420 if let Some(pull) = opt_cb(stream, "@@pull") {
421 let ctrl = get_prop(stream, "@@controller").unwrap_or(Value::Undef);
422 if let Err(msg) = invoke(&pull, vec![ctrl], None) {
425 stream_error(stream, synth(&msg));
426 }
427 }
428}
429
430fn stream_read(stream: &Value) -> Value {
433 if let Some(p) = try_immediate_read(stream) {
434 return p;
435 }
436 drive_pull(stream);
437 if let Some(p) = try_immediate_read(stream) {
438 return p;
439 }
440 let p = new_pending();
442 arr_push(stream, "@@waiters", p.clone());
443 p
444}
445
446fn stream_cancel(stream: &Value, reason: Value) -> Value {
448 if state_of(stream) == "readable" {
449 let _ = arr_drain(stream, "@@queue");
451 if let Some(cancel) = opt_cb(stream, "@@cancel") {
452 if let Err(msg) = invoke(&cancel, vec![reason], None) {
453 return rejected(synth(&msg));
454 }
455 }
456 stream_close(stream);
457 }
458 resolved(Value::Undef)
459}
460
461pub fn construct_readable(args: &[Value]) -> Result<Value, String> {
465 let source = args.first().cloned().unwrap_or(Value::Undef);
466 let byte = get_str(&source, "type").as_deref() == Some("bytes");
467
468 let stream = new_readable_shell();
469 if byte {
470 set_prop(&stream, "@@type", with_host(|h| h.new_str("bytes")));
471 }
472 if let Some(pull) = opt_cb(&source, "pull") {
473 set_prop(&stream, "@@pull", pull);
474 }
475 if let Some(cancel) = opt_cb(&source, "cancel") {
476 set_prop(&stream, "@@cancel", cancel);
477 }
478 let ctrl = new_readable_controller(&stream, byte);
479
480 if let Some(start) = opt_cb(&source, "start") {
481 if let Err(msg) = invoke(&start, vec![ctrl], None) {
482 stream_error(&stream, synth(&msg));
483 }
484 }
485 Ok(stream)
486}
487
488fn get_reader(stream: &Value, args: &[Value]) -> Result<Value, String> {
490 if is_locked(stream) {
491 return Err(crate::host::type_error("ReadableStream is locked"));
492 }
493 let byob = args.first().and_then(|o| get_str(o, "mode")).as_deref() == Some("byob");
494 let tag = if byob {
495 RS_BYOB_READER_TAG
496 } else {
497 RS_DEFAULT_READER_TAG
498 };
499
500 let closed = new_pending();
502 match state_of(stream).as_str() {
503 "closed" => settle(&closed, Value::Undef),
504 "errored" => settle_reject(
505 &closed,
506 get_prop(stream, "@@stored_error")
507 .unwrap_or_else(|| synth("TypeError: stream errored")),
508 ),
509 _ => {}
510 }
511
512 let reader = with_host(|h| {
513 let mut m = IndexMap::new();
514 m.insert("@@native".into(), h.new_str(tag));
515 m.insert("@@stream".into(), stream.clone());
516 m.insert("closed".into(), closed);
517 h.new_object(m)
518 });
519 set_prop(stream, "locked", Value::Bool(true));
520 set_prop(stream, "@@reader", reader.clone());
521 Ok(reader)
522}
523
524fn reader_release(reader: &Value) {
525 if let Some(stream) = get_prop(reader, "@@stream") {
526 set_prop(&stream, "locked", Value::Bool(false));
527 remove_prop(&stream, "@@reader");
528 }
529 remove_prop(reader, "@@stream");
530}
531
532fn ws_accept_chunk(ws: &Value, chunk: Value) -> Result<(), String> {
537 if let Some(kind) = get_str(ws, "@@xform_kind") {
538 let readable = get_prop(ws, "@@readable");
539 match kind.as_str() {
540 "textencode" => {
541 if let Some(r) = &readable {
542 let bytes = with_host(|h| h.str_of(&chunk)).into_bytes();
543 let buf = super::buffer::from_bytes(&bytes);
544 stream_enqueue(r, buf);
545 }
546 }
547 "textdecode" => {
548 if let Some(r) = &readable {
549 let bytes = chunk_bytes(&chunk);
550 let s = with_host(|h| h.new_str(String::from_utf8_lossy(&bytes).into_owned()));
551 stream_enqueue(r, s);
552 }
553 }
554 k if k.starts_with("compress:") || k.starts_with("decompress:") => {
555 let mut bytes = chunk_bytes(&chunk);
556 arr_push_bytes(ws, &mut bytes);
557 }
558 "js" => {
559 let ctrl = get_prop(ws, "@@tcontroller").unwrap_or(Value::Undef);
560 if let Some(transform) = opt_cb(ws, "@@transform") {
561 invoke(&transform, vec![chunk, ctrl], None)?;
562 } else if let Some(r) = &readable {
563 stream_enqueue(r, chunk);
565 }
566 }
567 _ => {}
568 }
569 return Ok(());
570 }
571 if let Some(write) = opt_cb(ws, "@@write") {
572 let ctrl = get_prop(ws, "@@controller").unwrap_or(Value::Undef);
573 invoke(&write, vec![chunk, ctrl], None)?;
574 }
575 Ok(())
576}
577
578fn arr_push_bytes(ws: &Value, bytes: &mut Vec<u8>) {
580 with_host(|h| {
581 let accum = match h.get(ws) {
582 Some(JsObj::Object(p)) => p.get("@@accum").cloned(),
583 _ => None,
584 };
585 if let Some(a) = accum {
586 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
587 items.extend(bytes.drain(..).map(|b| Value::Float(b as f64)));
588 }
589 }
590 });
591}
592
593fn ws_accum_bytes(ws: &Value) -> Vec<u8> {
594 with_host(|h| match h.get(ws) {
595 Some(JsObj::Object(p)) => match p.get("@@accum").and_then(|a| h.get(a)) {
596 Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
597 _ => Vec::new(),
598 },
599 _ => Vec::new(),
600 })
601}
602
603fn ws_finish(ws: &Value) -> Result<(), String> {
606 if let Some(kind) = get_str(ws, "@@xform_kind") {
607 let readable = get_prop(ws, "@@readable");
608 match kind.as_str() {
609 "js" => {
610 let ctrl = get_prop(ws, "@@tcontroller").unwrap_or(Value::Undef);
611 if let Some(flush) = opt_cb(ws, "@@flush") {
612 invoke(&flush, vec![ctrl], None)?;
613 }
614 if let Some(r) = &readable {
615 stream_close(r);
616 }
617 }
618 k if k.starts_with("compress:") || k.starts_with("decompress:") => {
619 let data = ws_accum_bytes(ws);
620 let out = run_codec(&kind, &data)?;
621 if let Some(r) = &readable {
622 stream_enqueue(r, super::buffer::from_bytes(&out));
623 stream_close(r);
624 }
625 }
626 _ => {
627 if let Some(r) = &readable {
628 stream_close(r);
629 }
630 }
631 }
632 } else if let Some(close) = opt_cb(ws, "@@close") {
633 invoke(&close, vec![], None)?;
634 }
635 set_prop(ws, "@@state", with_host(|h| h.new_str("closed")));
636 if let Some(writer) = get_prop(ws, "@@writer") {
637 if let Some(cp) = get_prop(&writer, "closed") {
638 settle(&cp, Value::Undef);
639 }
640 }
641 Ok(())
642}
643
644fn ws_abort(ws: &Value, reason: Value) -> Result<(), String> {
646 if get_str(ws, "@@xform_kind").is_some() {
647 if let Some(r) = get_prop(ws, "@@readable") {
648 stream_error(&r, reason.clone());
649 }
650 } else if let Some(abort) = opt_cb(ws, "@@abort") {
651 invoke(&abort, vec![reason.clone()], None)?;
652 }
653 set_prop(ws, "@@state", with_host(|h| h.new_str("errored")));
654 set_prop(ws, "@@stored_error", reason.clone());
655 if let Some(writer) = get_prop(ws, "@@writer") {
656 if let Some(cp) = get_prop(&writer, "closed") {
657 settle_reject(&cp, reason);
658 }
659 }
660 Ok(())
661}
662
663pub fn construct_writable(args: &[Value]) -> Result<Value, String> {
665 let sink = args.first().cloned().unwrap_or(Value::Undef);
666 let ws = with_host(|h| {
667 let mut m = IndexMap::new();
668 m.insert("@@native".into(), h.new_str(WRITABLE_STREAM_TAG));
669 m.insert("@@state".into(), h.new_str("writable"));
670 m.insert("locked".into(), Value::Bool(false));
671 h.new_object(m)
672 });
673 for key in ["write", "close", "abort"] {
674 if let Some(cb) = opt_cb(&sink, key) {
675 set_prop(&ws, &format!("@@{key}"), cb);
676 }
677 }
678 let ctrl = with_host(|h| {
679 let mut m = IndexMap::new();
680 m.insert("@@native".into(), h.new_str(WS_DEFAULT_CONTROLLER_TAG));
681 m.insert("@@stream".into(), ws.clone());
682 h.new_object(m)
683 });
684 set_prop(&ws, "@@controller", ctrl.clone());
685 if let Some(start) = opt_cb(&sink, "start") {
686 if let Err(msg) = invoke(&start, vec![ctrl], None) {
687 let _ = ws_abort(&ws, synth(&msg));
688 }
689 }
690 Ok(ws)
691}
692
693fn get_writer(ws: &Value) -> Result<Value, String> {
695 if is_locked(ws) {
696 return Err(crate::host::type_error("WritableStream is locked"));
697 }
698 let ready = resolved(Value::Undef);
699 let closed = new_pending();
700 if state_of(ws) == "closed" {
701 settle(&closed, Value::Undef);
702 } else if state_of(ws) == "errored" {
703 settle_reject(
704 &closed,
705 get_prop(ws, "@@stored_error").unwrap_or(Value::Undef),
706 );
707 }
708 let writer = with_host(|h| {
709 let mut m = IndexMap::new();
710 m.insert("@@native".into(), h.new_str(WS_DEFAULT_WRITER_TAG));
711 m.insert("@@stream".into(), ws.clone());
712 m.insert("ready".into(), ready);
713 m.insert("closed".into(), closed);
714 m.insert("desiredSize".into(), Value::Float(1.0));
715 h.new_object(m)
716 });
717 set_prop(ws, "locked", Value::Bool(true));
718 set_prop(ws, "@@writer", writer.clone());
719 Ok(writer)
720}
721
722fn writer_release(writer: &Value) {
723 if let Some(ws) = get_prop(writer, "@@stream") {
724 set_prop(&ws, "locked", Value::Bool(false));
725 remove_prop(&ws, "@@writer");
726 }
727 remove_prop(writer, "@@stream");
728}
729
730fn build_transform_pair(kind: &str) -> (Value, Value) {
735 let readable = new_readable_shell();
736 let ws = with_host(|h| {
737 let accum = h.new_array(Vec::new());
738 let mut m = IndexMap::new();
739 m.insert("@@native".into(), h.new_str(WRITABLE_STREAM_TAG));
740 m.insert("@@state".into(), h.new_str("writable"));
741 m.insert("locked".into(), Value::Bool(false));
742 m.insert("@@xform_kind".into(), h.new_str(kind));
743 m.insert("@@accum".into(), accum);
744 h.new_object(m)
745 });
746 set_prop(&ws, "@@readable", readable.clone());
747 (readable, ws)
748}
749
750pub fn construct_transform(args: &[Value]) -> Result<Value, String> {
752 let transformer = args.first().cloned().unwrap_or(Value::Undef);
753 let (readable, writable) = build_transform_pair("js");
754
755 let tctrl = with_host(|h| {
757 let mut m = IndexMap::new();
758 m.insert("@@native".into(), h.new_str(TS_DEFAULT_CONTROLLER_TAG));
759 m.insert("@@readable".into(), readable.clone());
760 m.insert("@@writable".into(), writable.clone());
761 m.insert("desiredSize".into(), Value::Float(1.0));
762 h.new_object(m)
763 });
764 set_prop(&writable, "@@tcontroller", tctrl.clone());
765 if let Some(t) = opt_cb(&transformer, "transform") {
766 set_prop(&writable, "@@transform", t);
767 }
768 if let Some(f) = opt_cb(&transformer, "flush") {
769 set_prop(&writable, "@@flush", f);
770 }
771
772 let ts = with_host(|h| {
773 let mut m = IndexMap::new();
774 m.insert("@@native".into(), h.new_str(TRANSFORM_STREAM_TAG));
775 m.insert("readable".into(), readable);
776 m.insert("writable".into(), writable);
777 h.new_object(m)
778 });
779
780 if let Some(start) = opt_cb(&transformer, "start") {
781 invoke(&start, vec![tctrl], None)?;
782 }
783 Ok(ts)
784}
785
786fn build_codec_stream(tag: &str, kind: &str, extra: Vec<(&str, Value)>) -> Value {
789 let (readable, writable) = build_transform_pair(kind);
790 with_host(|h| {
791 let mut m = IndexMap::new();
792 m.insert("@@native".into(), h.new_str(tag));
793 m.insert("readable".into(), readable);
794 m.insert("writable".into(), writable);
795 for (k, v) in extra {
796 m.insert(k.to_string(), v);
797 }
798 h.new_object(m)
799 })
800}
801
802pub fn construct_text_encoder_stream() -> Result<Value, String> {
803 let enc = with_host(|h| h.new_str("utf-8"));
804 Ok(build_codec_stream(
805 TEXT_ENCODER_STREAM_TAG,
806 "textencode",
807 vec![("encoding", enc)],
808 ))
809}
810
811pub fn construct_text_decoder_stream(args: &[Value]) -> Result<Value, String> {
812 let label = args
813 .first()
814 .map(|v| with_host(|h| h.str_of(v)))
815 .filter(|s| !s.is_empty() && s.as_str() != "undefined")
816 .unwrap_or_else(|| "utf-8".into());
817 let extra = with_host(|h| {
818 vec![
819 ("encoding", h.new_str(label.to_lowercase())),
820 ("fatal", Value::Bool(false)),
821 ("ignoreBOM", Value::Bool(false)),
822 ]
823 });
824 Ok(build_codec_stream(
825 TEXT_DECODER_STREAM_TAG,
826 "textdecode",
827 extra,
828 ))
829}
830
831pub fn construct_compression(args: &[Value], decompress: bool) -> Result<Value, String> {
833 let format = super::arg_str(args, 0);
834 if !matches!(format.as_str(), "gzip" | "deflate" | "deflate-raw") {
835 return Err(crate::host::type_error(&format!(
836 "Unsupported compression format: '{format}'"
837 )));
838 }
839 let (tag, prefix) = if decompress {
840 (DECOMPRESSION_STREAM_TAG, "decompress")
841 } else {
842 (COMPRESSION_STREAM_TAG, "compress")
843 };
844 Ok(build_codec_stream(
845 tag,
846 &format!("{prefix}:{format}"),
847 Vec::new(),
848 ))
849}
850
851fn run_codec(kind: &str, data: &[u8]) -> Result<Vec<u8>, String> {
853 use flate2::read::{DeflateDecoder, GzDecoder, ZlibDecoder};
854 use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
855 use flate2::Compression;
856 let io = |e: std::io::Error| format!("Error: {e}");
857 match kind {
858 "compress:gzip" => {
859 let mut e = GzEncoder::new(Vec::new(), Compression::default());
860 e.write_all(data).map_err(io)?;
861 e.finish().map_err(io)
862 }
863 "compress:deflate" => {
864 let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
865 e.write_all(data).map_err(io)?;
866 e.finish().map_err(io)
867 }
868 "compress:deflate-raw" => {
869 let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
870 e.write_all(data).map_err(io)?;
871 e.finish().map_err(io)
872 }
873 "decompress:gzip" => {
874 let mut out = Vec::new();
875 GzDecoder::new(data).read_to_end(&mut out).map_err(io)?;
876 Ok(out)
877 }
878 "decompress:deflate" => {
879 let mut out = Vec::new();
880 ZlibDecoder::new(data).read_to_end(&mut out).map_err(io)?;
881 Ok(out)
882 }
883 "decompress:deflate-raw" => {
884 let mut out = Vec::new();
885 DeflateDecoder::new(data)
886 .read_to_end(&mut out)
887 .map_err(io)?;
888 Ok(out)
889 }
890 _ => Err(crate::host::type_error("unknown codec")),
891 }
892}
893
894pub fn construct_strategy(tag: &str, args: &[Value]) -> Result<Value, String> {
897 let hwm = args
898 .first()
899 .and_then(|o| get_prop(o, "highWaterMark"))
900 .map(|v| with_host(|h| h.to_number(&v)))
901 .unwrap_or(f64::NAN);
902 Ok(with_host(|h| {
903 let mut m = IndexMap::new();
904 m.insert("@@native".into(), h.new_str(tag));
905 m.insert("highWaterMark".into(), Value::Float(hwm));
906 h.new_object(m)
907 }))
908}
909
910enum Pulled {
914 Chunk(Value),
915 Done,
916 Errored(Value),
917}
918
919fn pull_one(stream: &Value) -> Pulled {
921 if state_of(stream) == "errored" {
922 return Pulled::Errored(get_prop(stream, "@@stored_error").unwrap_or(Value::Undef));
923 }
924 if let Some(c) = arr_shift(stream, "@@queue") {
925 return Pulled::Chunk(c);
926 }
927 if state_of(stream) == "closed" {
928 return Pulled::Done;
929 }
930 drive_pull(stream);
931 if state_of(stream) == "errored" {
932 return Pulled::Errored(get_prop(stream, "@@stored_error").unwrap_or(Value::Undef));
933 }
934 if let Some(c) = arr_shift(stream, "@@queue") {
935 return Pulled::Chunk(c);
936 }
937 Pulled::Done
939}
940
941fn byob_read(reader: &Value, args: &[Value]) -> Value {
943 let Some(stream) = get_prop(reader, "@@stream") else {
944 return rejected(synth("TypeError: reader has no associated stream"));
945 };
946 let view = args.first().cloned().unwrap_or(Value::Undef);
947 match pull_one(&stream) {
948 Pulled::Chunk(c) => {
949 let bytes = chunk_bytes(&c);
950 resolved(iter_result(super::buffer::from_bytes(&bytes), false))
953 }
954 Pulled::Done => resolved(iter_result(view, true)),
955 Pulled::Errored(e) => rejected(e),
956 }
957}
958
959fn tee_pull(branch: &Value) {
964 let Some(shared) = get_prop(branch, "@@tee_shared") else {
965 return;
966 };
967 let idx = get_prop(branch, "@@tee_index")
968 .map(|v| with_host(|h| h.to_number(&v)) as usize)
969 .unwrap_or(0);
970
971 if let Some(chunk) = arr_get(&shared, "@@buf", idx) {
972 set_prop(branch, "@@tee_index", Value::Float((idx + 1) as f64));
973 stream_enqueue(branch, chunk);
974 return;
975 }
976 let done = get_prop(&shared, "@@done")
977 .map(|v| with_host(|h| h.truthy(&v)))
978 .unwrap_or(false);
979 if done {
980 stream_close(branch);
981 return;
982 }
983 let Some(source) = get_prop(&shared, "@@source") else {
984 stream_close(branch);
985 return;
986 };
987 match pull_one(&source) {
988 Pulled::Chunk(c) => {
989 arr_push(&shared, "@@buf", c.clone());
990 set_prop(branch, "@@tee_index", Value::Float((idx + 1) as f64));
991 stream_enqueue(branch, c);
992 }
993 Pulled::Done => {
994 set_prop(&shared, "@@done", Value::Bool(true));
995 stream_close(branch);
996 }
997 Pulled::Errored(e) => stream_error(branch, e),
998 }
999}
1000
1001fn tee(stream: &Value) -> Value {
1003 set_prop(stream, "locked", Value::Bool(true));
1004 let shared = with_host(|h| {
1005 let buf = h.new_array(Vec::new());
1006 let mut m = IndexMap::new();
1007 m.insert("@@source".into(), stream.clone());
1008 m.insert("@@buf".into(), buf);
1009 m.insert("@@done".into(), Value::Bool(false));
1010 h.new_object(m)
1011 });
1012 let make_branch = || {
1013 let b = new_readable_shell();
1014 new_readable_controller(&b, false);
1015 set_prop(&b, "@@native_pull", with_host(|h| h.new_str("tee")));
1016 set_prop(&b, "@@tee_shared", shared.clone());
1017 set_prop(&b, "@@tee_index", Value::Float(0.0));
1018 b
1019 };
1020 let b1 = make_branch();
1021 let b2 = make_branch();
1022 with_host(|h| h.new_array(vec![b1, b2]))
1023}
1024
1025fn pipe_to(stream: &Value, dest: &Value) -> Value {
1030 set_prop(stream, "locked", Value::Bool(true));
1031 set_prop(dest, "locked", Value::Bool(true));
1032 loop {
1033 match pull_one(stream) {
1034 Pulled::Chunk(c) => {
1035 if let Err(msg) = ws_accept_chunk(dest, c) {
1036 return rejected(synth(&msg));
1037 }
1038 }
1039 Pulled::Done => break,
1040 Pulled::Errored(e) => {
1041 let _ = ws_abort(dest, e.clone());
1042 return rejected(e);
1043 }
1044 }
1045 }
1046 if let Err(msg) = ws_finish(dest) {
1047 return rejected(synth(&msg));
1048 }
1049 resolved(Value::Undef)
1050}
1051
1052fn pipe_through(stream: &Value, args: &[Value]) -> Result<Value, String> {
1055 let transform = args.first().cloned().unwrap_or(Value::Undef);
1056 let writable = get_prop(&transform, "writable").ok_or_else(|| {
1057 crate::host::type_error("pipeThrough argument must have a writable and readable")
1058 })?;
1059 let readable = get_prop(&transform, "readable").ok_or_else(|| {
1060 crate::host::type_error("pipeThrough argument must have a writable and readable")
1061 })?;
1062 let _ = pipe_to(stream, &writable);
1063 Ok(readable)
1064}
1065
1066pub fn construct(name: &str, args: &[Value]) -> Option<Result<Value, String>> {
1070 Some(match name {
1071 "ReadableStream" => construct_readable(args),
1072 "WritableStream" => construct_writable(args),
1073 "TransformStream" => construct_transform(args),
1074 "TextEncoderStream" => construct_text_encoder_stream(),
1075 "TextDecoderStream" => construct_text_decoder_stream(args),
1076 "CompressionStream" => construct_compression(args, false),
1077 "DecompressionStream" => construct_compression(args, true),
1078 "ByteLengthQueuingStrategy" => construct_strategy(BYTE_LENGTH_STRATEGY_TAG, args),
1079 "CountQueuingStrategy" => construct_strategy(COUNT_STRATEGY_TAG, args),
1080 "ReadableStreamDefaultReader" => Ok(bare(RS_DEFAULT_READER_TAG)),
1084 "ReadableStreamBYOBReader" => Ok(bare(RS_BYOB_READER_TAG)),
1085 "ReadableStreamDefaultController" => Ok(bare(RS_DEFAULT_CONTROLLER_TAG)),
1086 "ReadableByteStreamController" => Ok(bare(RS_BYTE_CONTROLLER_TAG)),
1087 "ReadableStreamBYOBRequest" => Ok(bare(RS_BYOB_REQUEST_TAG)),
1088 "WritableStreamDefaultWriter" => Ok(bare(WS_DEFAULT_WRITER_TAG)),
1089 "WritableStreamDefaultController" => Ok(bare(WS_DEFAULT_CONTROLLER_TAG)),
1090 "TransformStreamDefaultController" => Ok(bare(TS_DEFAULT_CONTROLLER_TAG)),
1091 _ => return None,
1092 })
1093}
1094
1095fn bare(tag: &str) -> Value {
1096 with_host(|h| {
1097 let mut m = IndexMap::new();
1098 m.insert("@@native".into(), h.new_str(tag));
1099 h.new_object(m)
1100 })
1101}
1102
1103pub fn instance_call(
1107 tag: &str,
1108 recv: &Value,
1109 method: &str,
1110 args: Vec<Value>,
1111) -> Result<Value, String> {
1112 match tag {
1113 READABLE_STREAM_TAG => match method {
1114 "getReader" => get_reader(recv, &args),
1115 "cancel" => Ok(stream_cancel(
1116 recv,
1117 args.first().cloned().unwrap_or(Value::Undef),
1118 )),
1119 "tee" => Ok(tee(recv)),
1120 "pipeTo" => Ok(pipe_to(
1121 recv,
1122 &args.first().cloned().unwrap_or(Value::Undef),
1123 )),
1124 "pipeThrough" => pipe_through(recv, &args),
1125 _ => unknown(tag, method),
1126 },
1127 RS_DEFAULT_READER_TAG => match method {
1128 "read" => match get_prop(recv, "@@stream") {
1129 Some(stream) => Ok(stream_read(&stream)),
1130 None => Ok(rejected(synth(
1131 "TypeError: reader has no associated stream",
1132 ))),
1133 },
1134 "releaseLock" => {
1135 reader_release(recv);
1136 Ok(Value::Undef)
1137 }
1138 "cancel" => match get_prop(recv, "@@stream") {
1139 Some(stream) => Ok(stream_cancel(
1140 &stream,
1141 args.first().cloned().unwrap_or(Value::Undef),
1142 )),
1143 None => Ok(resolved(Value::Undef)),
1144 },
1145 _ => unknown(tag, method),
1146 },
1147 RS_BYOB_READER_TAG => match method {
1148 "read" => Ok(byob_read(recv, &args)),
1149 "releaseLock" => {
1150 reader_release(recv);
1151 Ok(Value::Undef)
1152 }
1153 "cancel" => match get_prop(recv, "@@stream") {
1154 Some(stream) => Ok(stream_cancel(
1155 &stream,
1156 args.first().cloned().unwrap_or(Value::Undef),
1157 )),
1158 None => Ok(resolved(Value::Undef)),
1159 },
1160 _ => unknown(tag, method),
1161 },
1162 RS_DEFAULT_CONTROLLER_TAG | RS_BYTE_CONTROLLER_TAG => {
1163 let stream = get_prop(recv, "@@stream").unwrap_or(Value::Undef);
1164 match method {
1165 "enqueue" => {
1166 stream_enqueue(&stream, args.first().cloned().unwrap_or(Value::Undef));
1167 Ok(Value::Undef)
1168 }
1169 "close" => {
1170 stream_close(&stream);
1171 Ok(Value::Undef)
1172 }
1173 "error" => {
1174 stream_error(&stream, args.first().cloned().unwrap_or(Value::Undef));
1175 Ok(Value::Undef)
1176 }
1177 _ => unknown(tag, method),
1178 }
1179 }
1180 RS_BYOB_REQUEST_TAG => match method {
1181 "respond" | "respondWithNewView" => Ok(Value::Undef),
1184 _ => unknown(tag, method),
1185 },
1186 WRITABLE_STREAM_TAG => match method {
1187 "getWriter" => get_writer(recv),
1188 "close" => match ws_finish(recv) {
1189 Ok(()) => Ok(resolved(Value::Undef)),
1190 Err(msg) => Ok(rejected(synth(&msg))),
1191 },
1192 "abort" => match ws_abort(recv, args.first().cloned().unwrap_or(Value::Undef)) {
1193 Ok(()) => Ok(resolved(Value::Undef)),
1194 Err(msg) => Ok(rejected(synth(&msg))),
1195 },
1196 _ => unknown(tag, method),
1197 },
1198 WS_DEFAULT_WRITER_TAG => {
1199 let ws = get_prop(recv, "@@stream").unwrap_or(Value::Undef);
1200 match method {
1201 "write" => {
1202 if state_of(&ws) == "errored" {
1203 return Ok(rejected(
1204 get_prop(&ws, "@@stored_error").unwrap_or(Value::Undef),
1205 ));
1206 }
1207 match ws_accept_chunk(&ws, args.first().cloned().unwrap_or(Value::Undef)) {
1208 Ok(()) => Ok(resolved(Value::Undef)),
1209 Err(msg) => Ok(rejected(synth(&msg))),
1210 }
1211 }
1212 "close" => match ws_finish(&ws) {
1213 Ok(()) => Ok(resolved(Value::Undef)),
1214 Err(msg) => Ok(rejected(synth(&msg))),
1215 },
1216 "abort" => match ws_abort(&ws, args.first().cloned().unwrap_or(Value::Undef)) {
1217 Ok(()) => Ok(resolved(Value::Undef)),
1218 Err(msg) => Ok(rejected(synth(&msg))),
1219 },
1220 "releaseLock" => {
1221 writer_release(recv);
1222 Ok(Value::Undef)
1223 }
1224 _ => unknown(tag, method),
1225 }
1226 }
1227 WS_DEFAULT_CONTROLLER_TAG => match method {
1228 "error" => {
1229 let ws = get_prop(recv, "@@stream").unwrap_or(Value::Undef);
1230 let _ = ws_abort(&ws, args.first().cloned().unwrap_or(Value::Undef));
1231 Ok(Value::Undef)
1232 }
1233 _ => unknown(tag, method),
1234 },
1235 TS_DEFAULT_CONTROLLER_TAG => {
1236 let readable = get_prop(recv, "@@readable").unwrap_or(Value::Undef);
1237 match method {
1238 "enqueue" => {
1239 stream_enqueue(&readable, args.first().cloned().unwrap_or(Value::Undef));
1240 Ok(Value::Undef)
1241 }
1242 "terminate" => {
1243 stream_close(&readable);
1244 Ok(Value::Undef)
1245 }
1246 "error" => {
1247 let e = args.first().cloned().unwrap_or(Value::Undef);
1248 stream_error(&readable, e.clone());
1249 if let Some(ws) = get_prop(recv, "@@writable") {
1250 let _ = ws_abort(&ws, e);
1251 }
1252 Ok(Value::Undef)
1253 }
1254 _ => unknown(tag, method),
1255 }
1256 }
1257 BYTE_LENGTH_STRATEGY_TAG => match method {
1258 "size" => Ok(Value::Float(chunk_byte_length(
1259 &args.first().cloned().unwrap_or(Value::Undef),
1260 ))),
1261 _ => unknown(tag, method),
1262 },
1263 COUNT_STRATEGY_TAG => match method {
1264 "size" => Ok(Value::Float(1.0)),
1265 _ => unknown(tag, method),
1266 },
1267 _ => unknown(tag, method),
1268 }
1269}
1270
1271fn unknown(tag: &str, method: &str) -> Result<Value, String> {
1272 Err(crate::host::type_error(&format!(
1273 "{tag}.{method} is not a function"
1274 )))
1275}