script/dom/
compressionstream.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 http://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6use std::io::{self, Write};
7use std::ptr;
8
9use dom_struct::dom_struct;
10use flate2::Compression;
11use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
12use js::jsapi::JSObject;
13use js::jsval::UndefinedValue;
14use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
15use js::typedarray::Uint8Array;
16use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
17
18use crate::dom::bindings::buffer_source::create_buffer_source;
19use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::{
20    CompressionFormat, CompressionStreamMethods,
21};
22use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
23use crate::dom::bindings::conversions::{SafeFromJSValConvertible, SafeToJSValConvertible};
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
26use crate::dom::bindings::root::{Dom, DomRoot};
27use crate::dom::transformstreamdefaultcontroller::TransformerType;
28use crate::dom::types::{
29    GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
30};
31use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
32
33/// A wrapper to blend ZlibEncoder<Vec<u8>>, DeflateEncoder<Vec<u8>> and GzEncoder<Vec<u8>>
34/// together as a single type.
35enum Compressor {
36    Deflate(ZlibEncoder<Vec<u8>>),
37    DeflateRaw(DeflateEncoder<Vec<u8>>),
38    Gzip(GzEncoder<Vec<u8>>),
39}
40
41/// Expose methods of the inner encoder.
42impl Compressor {
43    fn new(format: CompressionFormat) -> Compressor {
44        match format {
45            CompressionFormat::Deflate => {
46                Compressor::Deflate(ZlibEncoder::new(Vec::new(), Compression::default()))
47            },
48            CompressionFormat::Deflate_raw => {
49                Compressor::DeflateRaw(DeflateEncoder::new(Vec::new(), Compression::default()))
50            },
51            CompressionFormat::Gzip => {
52                Compressor::Gzip(GzEncoder::new(Vec::new(), Compression::default()))
53            },
54        }
55    }
56
57    fn get_ref(&self) -> &Vec<u8> {
58        match self {
59            Compressor::Deflate(zlib_encoder) => zlib_encoder.get_ref(),
60            Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.get_ref(),
61            Compressor::Gzip(gz_encoder) => gz_encoder.get_ref(),
62        }
63    }
64
65    fn get_mut(&mut self) -> &mut Vec<u8> {
66        match self {
67            Compressor::Deflate(zlib_encoder) => zlib_encoder.get_mut(),
68            Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.get_mut(),
69            Compressor::Gzip(gz_encoder) => gz_encoder.get_mut(),
70        }
71    }
72
73    fn write_all(&mut self, buf: &[u8]) -> Result<(), io::Error> {
74        match self {
75            Compressor::Deflate(zlib_encoder) => zlib_encoder.write_all(buf),
76            Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.write_all(buf),
77            Compressor::Gzip(gz_encoder) => gz_encoder.write_all(buf),
78        }
79    }
80
81    fn flush(&mut self) -> io::Result<()> {
82        match self {
83            Compressor::Deflate(zlib_encoder) => zlib_encoder.flush(),
84            Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.flush(),
85            Compressor::Gzip(gz_encoder) => gz_encoder.flush(),
86        }
87    }
88
89    fn try_finish(&mut self) -> io::Result<()> {
90        match self {
91            Compressor::Deflate(zlib_encoder) => zlib_encoder.try_finish(),
92            Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.try_finish(),
93            Compressor::Gzip(gz_encoder) => gz_encoder.try_finish(),
94        }
95    }
96}
97
98impl MallocSizeOf for Compressor {
99    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
100        match self {
101            Compressor::Deflate(zlib_encoder) => zlib_encoder.size_of(ops),
102            Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.size_of(ops),
103            Compressor::Gzip(gz_encoder) => gz_encoder.size_of(ops),
104        }
105    }
106}
107
108/// <https://compression.spec.whatwg.org/#compressionstream>
109#[dom_struct]
110pub(crate) struct CompressionStream {
111    reflector_: Reflector,
112
113    /// <https://streams.spec.whatwg.org/#generictransformstream>
114    transform: Dom<TransformStream>,
115
116    /// <https://compression.spec.whatwg.org/#compressionstream-format>
117    format: CompressionFormat,
118
119    // <https://compression.spec.whatwg.org/#compressionstream-context>
120    #[no_trace]
121    context: RefCell<Compressor>,
122}
123
124impl CompressionStream {
125    fn new_inherited(transform: &TransformStream, format: CompressionFormat) -> CompressionStream {
126        CompressionStream {
127            reflector_: Reflector::new(),
128            transform: Dom::from_ref(transform),
129            format,
130            context: RefCell::new(Compressor::new(format)),
131        }
132    }
133
134    fn new_with_proto(
135        global: &GlobalScope,
136        proto: Option<SafeHandleObject>,
137        transform: &TransformStream,
138        format: CompressionFormat,
139        can_gc: CanGc,
140    ) -> DomRoot<CompressionStream> {
141        reflect_dom_object_with_proto(
142            Box::new(CompressionStream::new_inherited(transform, format)),
143            global,
144            proto,
145            can_gc,
146        )
147    }
148}
149
150impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
151    /// <https://compression.spec.whatwg.org/#dom-compressionstream-compressionstream>
152    fn Constructor(
153        global: &GlobalScope,
154        proto: Option<SafeHandleObject>,
155        can_gc: CanGc,
156        format: CompressionFormat,
157    ) -> Fallible<DomRoot<CompressionStream>> {
158        // Step 1. If format is unsupported in CompressionStream, then throw a TypeError.
159        // NOTE: All of "deflate", "deflate_raw" and "gzip" are supported.
160
161        // Step 2. Set this’s format to format.
162        // Step 5. Set this’s transform to a new TransformStream.
163        let transform = TransformStream::new_with_proto(global, None, can_gc);
164        let compression_stream =
165            CompressionStream::new_with_proto(global, proto, &transform, format, can_gc);
166
167        // Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and runs the
168        // compress and enqueue a chunk algorithm with this and chunk.
169        // Step 4. Let flushAlgorithm be an algorithm which takes no argument and runs the compress
170        // flush and enqueue algorithm with this.
171        let transformer_type = TransformerType::Compressor(compression_stream.clone());
172
173        // Step 6. Set up this’s transform with transformAlgorithm set to transformAlgorithm and
174        // flushAlgorithm set to flushAlgorithm.
175        let cx = GlobalScope::get_cx();
176        transform.set_up(cx, global, transformer_type, can_gc)?;
177
178        Ok(compression_stream)
179    }
180
181    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
182    fn Readable(&self) -> DomRoot<ReadableStream> {
183        // The readable getter steps are to return this’s transform.[[readable]].
184        self.transform.get_readable()
185    }
186
187    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
188    fn Writable(&self) -> DomRoot<WritableStream> {
189        // The writable getter steps are to return this’s transform.[[writable]].
190        self.transform.get_writable()
191    }
192}
193
194/// <https://compression.spec.whatwg.org/#compress-and-enqueue-a-chunk>
195pub(crate) fn compress_and_enqueue_a_chunk(
196    cx: SafeJSContext,
197    global: &GlobalScope,
198    cs: &CompressionStream,
199    chunk: SafeHandleValue,
200    controller: &TransformStreamDefaultController,
201    can_gc: CanGc,
202) -> Fallible<()> {
203    // Step 1. If chunk is not a BufferSource type, then throw a TypeError.
204    let chunk = convert_chunk_to_vec(cx, chunk, can_gc)?;
205
206    // Step 2. Let buffer be the result of compressing chunk with cs’s format and context.
207    // NOTE: In our implementation, the enum type of context already indicates the format.
208    let mut compressor = cs.context.borrow_mut();
209    let offset = compressor.get_ref().len();
210    compressor
211        .write_all(&chunk)
212        .map_err(|_| Error::Type("CompressionStream: write_all() failed".to_string()))?;
213    compressor
214        .flush()
215        .map_err(|_| Error::Type("CompressionStream: flush() failed".to_string()))?;
216    let buffer = &compressor.get_ref()[offset..];
217
218    // Step 3. If buffer is empty, return.
219    if buffer.is_empty() {
220        return Ok(());
221    }
222
223    // Step 4. Let arrays be the result of splitting buffer into one or more non-empty pieces and
224    // converting them into Uint8Arrays.
225    // Step 5. For each Uint8Array array of arrays, enqueue array in cs’s transform.
226    // NOTE: We process the result in a single Uint8Array.
227    rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
228    let buffer_source: Uint8Array =
229        create_buffer_source(cx, buffer, js_object.handle_mut(), can_gc)
230            .map_err(|_| Error::Type("Cannot convert byte sequence to Uint8Array".to_owned()))?;
231    rooted!(in(*cx) let mut rval = UndefinedValue());
232    buffer_source.safe_to_jsval(cx, rval.handle_mut(), can_gc);
233    controller.enqueue(cx, global, rval.handle(), can_gc)?;
234
235    // NOTE: We don't need to keep result that has been copied to Uint8Array. Clear the inner
236    // buffer of compressor to save memory.
237    compressor.get_mut().clear();
238
239    Ok(())
240}
241
242/// <https://compression.spec.whatwg.org/#compress-flush-and-enqueue>
243pub(crate) fn compress_flush_and_enqueue(
244    cx: SafeJSContext,
245    global: &GlobalScope,
246    cs: &CompressionStream,
247    controller: &TransformStreamDefaultController,
248    can_gc: CanGc,
249) -> Fallible<()> {
250    // Step 1. Let buffer be the result of compressing an empty input with cs’s format and context,
251    // with the finish flag.
252    // NOTE: In our implementation, the enum type of context already indicates the format.
253    let mut compressor = cs.context.borrow_mut();
254    let offset = compressor.get_ref().len();
255    compressor
256        .try_finish()
257        .map_err(|_| Error::Type("CompressionStream: try_finish() failed".to_string()))?;
258    let buffer = &compressor.get_ref()[offset..];
259
260    // Step 2. If buffer is empty, return.
261    if buffer.is_empty() {
262        return Ok(());
263    }
264
265    // Step 3. Let arrays be the result of splitting buffer into one or more non-empty pieces and
266    // converting them into Uint8Arrays.
267    // Step 4. For each Uint8Array array of arrays, enqueue array in cs’s transform.
268    // NOTE: We process the result in a single Uint8Array.
269    rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
270    let buffer_source: Uint8Array =
271        create_buffer_source(cx, buffer, js_object.handle_mut(), can_gc)
272            .map_err(|_| Error::Type("Cannot convert byte sequence to Uint8Array".to_owned()))?;
273    rooted!(in(*cx) let mut rval = UndefinedValue());
274    buffer_source.safe_to_jsval(cx, rval.handle_mut(), can_gc);
275    controller.enqueue(cx, global, rval.handle(), can_gc)?;
276
277    // NOTE: We don't need to keep result that has been copied to Uint8Array. Clear the inner
278    // buffer of compressor to save memory.
279    compressor.get_mut().clear();
280
281    Ok(())
282}
283
284pub(crate) fn convert_chunk_to_vec(
285    cx: SafeJSContext,
286    chunk: SafeHandleValue,
287    can_gc: CanGc,
288) -> Result<Vec<u8>, Error> {
289    let conversion_result = ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, (), can_gc)
290        .map_err(|_| {
291            Error::Type("Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_string())
292        })?;
293    let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
294        Error::Type("Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_string())
295    })?;
296    match buffer_source {
297        ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => Ok(view.to_vec()),
298        ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => Ok(buffer.to_vec()),
299    }
300}