Skip to main content

script/dom/encoding/
textdecodercommon.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::cell::RefCell;
6
7use encoding_rs::{Decoder, DecoderResult, Encoding};
8use js::context::NoGC;
9
10use crate::dom::bindings::buffer_source::get_buffer_source_slice;
11use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
12use crate::dom::bindings::error::{Error, Fallible};
13
14/// The shared part of `TextDecoder` and `TextDecoderStream`
15///
16/// Note that other than the three attributes defined in the `TextDecoderCommon`
17/// interface in the WebIDL, this also performs decoding.
18///
19/// <https://encoding.spec.whatwg.org/#textdecodercommon>
20#[expect(non_snake_case)]
21#[derive(JSTraceable, MallocSizeOf)]
22pub(crate) struct TextDecoderCommon {
23    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-encoding>
24    #[no_trace]
25    encoding: &'static Encoding,
26
27    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-fatal>
28    fatal: bool,
29
30    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom>
31    ignoreBOM: bool,
32
33    /// The native decoder that is used to perform decoding
34    ///
35    /// <https://encoding.spec.whatwg.org/#textdecodercommon-decoder>
36    #[ignore_malloc_size_of = "defined in encoding_rs"]
37    #[no_trace]
38    decoder: RefCell<Decoder>,
39
40    /// <https://encoding.spec.whatwg.org/#textdecodercommon-i-o-queue>
41    io_queue: RefCell<Vec<u8>>,
42}
43
44#[expect(non_snake_case)]
45impl TextDecoderCommon {
46    pub(crate) fn new_inherited(
47        encoding: &'static Encoding,
48        fatal: bool,
49        ignoreBOM: bool,
50    ) -> TextDecoderCommon {
51        let decoder = if ignoreBOM {
52            encoding.new_decoder_without_bom_handling()
53        } else {
54            encoding.new_decoder_with_bom_removal()
55        };
56
57        TextDecoderCommon {
58            encoding,
59            fatal,
60            ignoreBOM,
61            decoder: RefCell::new(decoder),
62            io_queue: RefCell::new(Vec::new()),
63        }
64    }
65
66    /// <https://encoding.spec.whatwg.org/#textdecoder-encoding>
67    pub(crate) fn encoding(&self) -> &'static Encoding {
68        self.encoding
69    }
70
71    /// <https://encoding.spec.whatwg.org/#textdecodercommon-decoder>
72    pub(crate) fn decoder(&self) -> &RefCell<Decoder> {
73        &self.decoder
74    }
75
76    /// <https://encoding.spec.whatwg.org/#textdecodercommon-i-o-queue>
77    pub(crate) fn io_queue(&self) -> &RefCell<Vec<u8>> {
78        &self.io_queue
79    }
80
81    /// <https://encoding.spec.whatwg.org/#textdecoder-error-mode>
82    pub(crate) fn fatal(&self) -> bool {
83        self.fatal
84    }
85
86    /// <https://encoding.spec.whatwg.org/#textdecoder-ignore-bom-flag>
87    pub(crate) fn ignore_bom(&self) -> bool {
88        self.ignoreBOM
89    }
90
91    /// Shared by `TextDecoder` and `TextDecoderStream`
92    ///
93    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
94    /// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
95    pub(crate) fn decode(
96        &self,
97        no_gc: &NoGC,
98        input: Option<&ArrayBufferViewOrArrayBuffer>,
99        last: bool,
100    ) -> Fallible<String> {
101        // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
102        // Step 3. If input is given, then push a copy of input to this’s I/O queue.
103        //
104        // <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
105        // Step 2. Push a copy of bufferSource to decoder’s I/O queue.
106        //
107        // NOTE: try to avoid this copy unless there are bytes left
108        let mut io_queue = self.io_queue.borrow_mut();
109        let input = match input {
110            Some(input) => {
111                let slice = get_buffer_source_slice(input, no_gc);
112                if io_queue.is_empty() {
113                    slice
114                } else {
115                    io_queue.extend_from_slice(slice);
116                    &io_queue[..]
117                }
118            },
119            None => &io_queue[..],
120        };
121
122        let mut decoder = self.decoder.borrow_mut();
123        let (output, read) = if self.fatal {
124            // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
125            // Step 4. Let output be the I/O queue of scalar values « end-of-queue ».
126            //
127            // <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
128            // Step 3. Let output be the I/O queue of scalar values « end-of-queue ».
129            let mut output = String::with_capacity(
130                decoder
131                    .max_utf8_buffer_length_without_replacement(input.len())
132                    .ok_or_else(|| {
133                        Error::Type(c"Expected UTF8 buffer length would overflow".to_owned())
134                    })?,
135            );
136
137            // Note: The two algorithms below are implemented in
138            // `encoding_rs::Decoder::decode_to_string_without_replacement`
139            //
140            // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
141            // Step 5. While true:
142            // Step 5.1 Let item be the result of reading from this’s I/O queue.
143            // Step 5.2 If item is end-of-queue and this’s do not flush is true,
144            //      then return the result of running serialize I/O queue with this and output.
145            // Step 5.3 Otherwise:
146            // Step 5.3.1 Let result be the result of processing an item with item, this’s decoder,
147            //      this’s I/O queue, output, and this’s error mode.
148            //
149            // <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
150            // Step 4. While true:
151            // Step 4.1 Let item be the result of reading from decoder’s I/O queue.
152            // Step 4.2 If item is end-of-queue:
153            // Step 4.2.1 Let outputChunk be the result of running serialize I/O queue with decoder and output.
154            // Step 4.2.2 If outputChunk is not the empty string, then enqueue outputChunk in decoder’s transform.
155            // Step 4.2.3 Return.
156            // Step 4.3 Let result be the result of processing an item with item, decoder’s decoder,
157            //      decoder’s I/O queue, output, and decoder’s error mode.
158            // Step 4.4 If result is error, then throw a TypeError.
159            let (result, read) =
160                decoder.decode_to_string_without_replacement(input, &mut output, last);
161            match result {
162                // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
163                // Step 5.3.2 If result is finished, then return the result of running serialize I/O
164                //      queue with this and output.
165                DecoderResult::InputEmpty => (output, read),
166                // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
167                // Step 5.3.3 Otherwise, if result is error, throw a TypeError.
168                DecoderResult::Malformed(_, _) => {
169                    return Err(Error::Type(c"Decoding failed".to_owned()));
170                },
171                DecoderResult::OutputFull => {
172                    unreachable!("output is allocated with sufficient capacity")
173                },
174            }
175        } else {
176            // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
177            // Step 4. Let output be the I/O queue of scalar values « end-of-queue ».
178            let mut output =
179                String::with_capacity(decoder.max_utf8_buffer_length(input.len()).ok_or_else(
180                    || Error::Type(c"Expected UTF8 buffer length would overflow".to_owned()),
181                )?);
182
183            // Note: The two algorithms below are implemented in
184            // `encoding_rs::Decoder::decode_to_string`
185            //
186            // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
187            // Step 5. While true:
188            // Step 5.1 Let item be the result of reading from this’s I/O queue.
189            // Step 5.2 If item is end-of-queue and this’s do not flush is true,
190            //      then return the result of running serialize I/O queue with this and output.
191            // Step 5.3 Otherwise:
192            // Step 5.3.1 Let result be the result of processing an item with item, this’s decoder,
193            //      this’s I/O queue, output, and this’s error mode.
194            //
195            // <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
196            // Step 4. While true:
197            // Step 4.1 Let item be the result of reading from decoder’s I/O queue.
198            // Step 4.2 If item is end-of-queue:
199            // Step 4.2.1 Let outputChunk be the result of running serialize I/O queue with decoder and output.
200            // Step 4.2.2 If outputChunk is not the empty string, then enqueue outputChunk in decoder’s transform.
201            // Step 4.2.3 Return.
202            // Step 4.3 Let result be the result of processing an item with item, decoder’s decoder,
203            //      decoder’s I/O queue, output, and decoder’s error mode.
204            // Step 4.4 If result is error, then throw a TypeError.
205            let (result, read, _replaced) = decoder.decode_to_string(input, &mut output, last);
206            match result {
207                // <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
208                // Step 5.3.2 If result is finished, then return the result of running serialize I/O
209                //      queue with this and output.
210                encoding_rs::CoderResult::InputEmpty => (output, read),
211                encoding_rs::CoderResult::OutputFull => {
212                    unreachable!("output is allocated with sufficient capacity")
213                },
214            }
215        };
216
217        let (_consumed, remaining) = input.split_at(read);
218        *io_queue = remaining.to_vec();
219
220        Ok(output)
221    }
222}