ferrijs_std/text/
text_decoder_stream.rs1use std::sync::{Arc, Mutex};
4
5use crate::utils::bytes::ObjectBytes;
6use rquickjs::{
7 atom::PredefinedAtom, function::Opt, prelude::This, Ctx, Function, Object, Result, Value,
8};
9
10use crate::text::text_decoder::TextDecoder;
11
12#[rquickjs::class]
13#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)]
14pub struct TextDecoderStream<'js> {
15 #[qjs(skip_trace)]
16 encoding: String,
17 #[qjs(skip_trace)]
18 fatal: bool,
19 #[qjs(skip_trace)]
20 ignore_bom: bool,
21 readable: Value<'js>,
22 writable: Value<'js>,
23}
24
25#[rquickjs::methods(rename_all = "camelCase")]
26impl<'js> TextDecoderStream<'js> {
27 #[qjs(constructor)]
28 pub fn new(ctx: Ctx<'js>, label: Opt<String>, options: Opt<Object<'js>>) -> Result<Self> {
29 let decoder = TextDecoder::new(ctx.clone(), label, options)?;
30 let encoding = decoder.encoding().to_owned();
31 let fatal = decoder.fatal();
32 let ignore_bom = decoder.ignore_bom();
33 let decoder = Arc::new(Mutex::new(decoder));
34
35 let transform_decoder = decoder.clone();
36 let transform = Function::new(ctx.clone(), move |ctx, chunk, controller| {
37 transform(&transform_decoder, ctx, chunk, controller)
38 })?;
39
40 let flush_decoder = decoder.clone();
41 let flush = Function::new(ctx.clone(), move |ctx, controller| {
42 flush(&flush_decoder, ctx, controller)
43 })?;
44
45 let transformer = Object::new(ctx.clone())?;
46 transformer.set("transform", transform)?;
47 transformer.set("flush", flush)?;
48
49 let stream = crate::stream_web::create_transform_stream(&ctx, transformer)?;
50
51 Ok(Self {
52 encoding,
53 fatal,
54 ignore_bom,
55 readable: stream.get("readable")?,
56 writable: stream.get("writable")?,
57 })
58 }
59
60 #[qjs(get)]
61 fn encoding(&self) -> &str {
62 &self.encoding
63 }
64
65 #[qjs(get, rename = "fatal")]
66 fn fatal(&self) -> bool {
67 self.fatal
68 }
69
70 #[qjs(get, rename = "ignoreBOM")]
71 fn ignore_bom(&self) -> bool {
72 self.ignore_bom
73 }
74
75 #[qjs(get)]
76 fn readable(&self) -> Value<'js> {
77 self.readable.clone()
78 }
79
80 #[qjs(get)]
81 fn writable(&self) -> Value<'js> {
82 self.writable.clone()
83 }
84
85 #[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
86 pub fn to_string_tag() -> &'static str {
87 stringify!(TextDecoderStream)
88 }
89}
90
91fn transform<'js>(
92 decoder: &Mutex<TextDecoder>,
93 ctx: Ctx<'js>,
94 chunk: Value<'js>,
95 controller: Object<'js>,
96) -> Result<()> {
97 let bytes = ObjectBytes::from(&ctx, &chunk)?;
98 let opts = Object::new(ctx.clone())?;
99 opts.set("stream", true)?;
100 let text =
101 decoder
102 .lock()
103 .unwrap()
104 .decode(ctx, Opt(Some(bytes)), Opt(Some(opts.into_value())))?;
105 if !text.is_empty() {
106 let enqueue: Function = controller.get("enqueue")?;
107 enqueue.call::<_, ()>((This(controller), text))?;
108 }
109 Ok(())
110}
111
112fn flush<'js>(decoder: &Mutex<TextDecoder>, ctx: Ctx<'js>, controller: Object<'js>) -> Result<()> {
113 let text = decoder.lock().unwrap().decode(ctx, Opt(None), Opt(None))?;
114 if !text.is_empty() {
115 let enqueue: Function = controller.get("enqueue")?;
116 enqueue.call::<_, ()>((This(controller), text))?;
117 }
118 Ok(())
119}