Skip to main content

script/dom/encoding/
textdecoderstream.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use encoding_rs::Encoding;
9use js::conversions::{FromJSValConvertible, ToJSValConvertible};
10use js::jsval::UndefinedValue;
11use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
13
14use crate::DomTypes;
15use crate::dom::bindings::codegen::Bindings::TextDecoderBinding;
16use crate::dom::bindings::codegen::Bindings::TextDecoderStreamBinding::TextDecoderStreamMethods;
17use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
18use crate::dom::bindings::error::{Error, Fallible};
19use crate::dom::bindings::root::{Dom, DomRoot};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::encoding::textdecodercommon::TextDecoderCommon;
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
24use crate::dom::types::{TransformStream, TransformStreamDefaultController};
25
26/// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
27#[expect(unsafe_code)]
28pub(crate) fn decode_and_enqueue_a_chunk(
29    cx: &mut js::context::JSContext,
30    global: &GlobalScope,
31    chunk: SafeHandleValue,
32    decoder: &TextDecoderCommon,
33    controller: &TransformStreamDefaultController,
34) -> Fallible<()> {
35    // Step 1. Let bufferSource be the result of converting chunk to an AllowSharedBufferSource.
36    let conversion_result =
37        ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, ()).map_err(|_| {
38            Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
39        })?;
40    let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
41        Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
42    })?;
43
44    // Step 2. Push a copy of bufferSource to decoder’s I/O queue.
45    // Step 3. Let output be the I/O queue of scalar values « end-of-queue ».
46    // Step 4. While true:
47    // Step 4.1 Let item be the result of reading from decoder’s I/O queue.
48    // Step 4.2 If item is end-of-queue:
49    // Step 4.2.1 Let outputChunk be the result of running serialize I/O queue with decoder and output.
50    // Step 4.2.3 Return.
51    // Step 4.3 Let result be the result of processing an item with item, decoder’s decoder,
52    //      decoder’s I/O queue, output, and decoder’s error mode.
53    // Step 4.4 If result is error, then throw a TypeError.
54    let output_chunk = decoder.decode(Some(buffer_source), false)?;
55
56    // Step 4.2.2 If outputChunk is not the empty string, then enqueue
57    //      outputChunk in decoder’s transform.
58    if output_chunk.is_empty() {
59        return Ok(());
60    }
61    rooted!(&in(cx) let mut rval = UndefinedValue());
62    unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
63    controller.enqueue(cx, global, rval.handle())
64}
65
66/// <https://encoding.spec.whatwg.org/#flush-and-enqueue>
67#[expect(unsafe_code)]
68pub(crate) fn flush_and_enqueue(
69    cx: &mut js::context::JSContext,
70    global: &GlobalScope,
71    decoder: &TextDecoderCommon,
72    controller: &TransformStreamDefaultController,
73) -> Fallible<()> {
74    // Step 1. Let output be the I/O queue of scalar values « end-of-queue ».
75    // Step 2. While true:
76    // Step 2.1 Let item be the result of reading from decoder’s I/O queue.
77    // Step 2.2 Let result be the result of processing an item with item,
78    //      decoder’s decoder, decoder’s I/O queue, output, and decoder’s error mode.
79    // Step 2.3 If result is finished:
80    // Step 2.3.1 Let outputChunk be the result of running serialize I/O queue
81    //      with decoder and output.
82    // Step 2.3.3 Return.
83    // Step 2.3.4 Otherwise, if result is error, throw a TypeError.
84    let output_chunk = decoder.decode(None, true)?;
85
86    // Step 2.3.2 If outputChunk is not the empty string, then enqueue
87    //      outputChunk in decoder’s transform.
88    if output_chunk.is_empty() {
89        return Ok(());
90    }
91    rooted!(&in(cx) let mut rval = UndefinedValue());
92    unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
93    controller.enqueue(cx, global, rval.handle())
94}
95
96/// <https://encoding.spec.whatwg.org/#textdecoderstream>
97#[dom_struct]
98pub(crate) struct TextDecoderStream {
99    reflector_: Reflector,
100
101    /// <https://encoding.spec.whatwg.org/#textdecodercommon>
102    #[conditional_malloc_size_of]
103    decoder: Rc<TextDecoderCommon>,
104
105    /// <https://streams.spec.whatwg.org/#generictransformstream>
106    transform: Dom<TransformStream>,
107}
108
109#[expect(non_snake_case)]
110impl TextDecoderStream {
111    fn new_inherited(
112        decoder: Rc<TextDecoderCommon>,
113        transform: &TransformStream,
114    ) -> TextDecoderStream {
115        TextDecoderStream {
116            reflector_: Reflector::new(),
117            decoder,
118            transform: Dom::from_ref(transform),
119        }
120    }
121
122    pub(crate) fn new_with_proto(
123        cx: &mut js::context::JSContext,
124        global: &GlobalScope,
125        proto: Option<SafeHandleObject>,
126        encoding: &'static Encoding,
127        fatal: bool,
128        ignoreBOM: bool,
129    ) -> Fallible<DomRoot<Self>> {
130        let decoder = Rc::new(TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM));
131        let transformer_type = TransformerType::Decoder(decoder.clone());
132
133        let transform_stream = TransformStream::new_with_proto(cx, global, None);
134        transform_stream.set_up(cx, global, transformer_type)?;
135
136        Ok(reflect_dom_object_with_proto_and_cx(
137            Box::new(TextDecoderStream::new_inherited(decoder, &transform_stream)),
138            global,
139            proto,
140            cx,
141        ))
142    }
143}
144
145impl TextDecoderStreamMethods<crate::DomTypeHolder> for TextDecoderStream {
146    /// <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
147    fn Constructor(
148        cx: &mut js::context::JSContext,
149        global: &GlobalScope,
150        proto: Option<SafeHandleObject>,
151        label: DOMString,
152        options: &TextDecoderBinding::TextDecoderOptions,
153    ) -> Fallible<DomRoot<TextDecoderStream>> {
154        let encoding = match Encoding::for_label_no_replacement(&label.as_bytes()) {
155            Some(enc) => enc,
156            None => {
157                return Err(Error::Range(
158                    c"The given encoding is not supported".to_owned(),
159                ));
160            },
161        };
162
163        Self::new_with_proto(
164            cx,
165            global,
166            proto,
167            encoding,
168            options.fatal,
169            options.ignoreBOM,
170        )
171    }
172
173    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-encoding>
174    fn Encoding(&self) -> DOMString {
175        DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
176    }
177
178    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-fatal>
179    fn Fatal(&self) -> bool {
180        self.decoder.fatal()
181    }
182
183    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom>
184    fn IgnoreBOM(&self) -> bool {
185        self.decoder.ignore_bom()
186    }
187
188    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
189    fn Readable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::ReadableStream> {
190        self.transform.get_readable()
191    }
192
193    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
194    fn Writable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::WritableStream> {
195        self.transform.get_writable()
196    }
197}