Skip to main content

ferrijs_std/text/
text_decoder_stream.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use 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}