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