1#[cfg(feature = "brotli-compression-stream")]
6use std::borrow::BorrowMut;
7use std::cell::RefCell;
8use std::io::{self, Write};
9use std::ptr;
10
11#[cfg(feature = "brotli-compression-stream")]
12use brotli::CompressorWriter as BrotliEncoder;
13use dom_struct::dom_struct;
14use flate2::Compression;
15use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
16use js::context::JSContext;
17use js::jsapi::JSObject;
18use js::jsval::UndefinedValue;
19use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
20use js::typedarray::Uint8;
21use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
22use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
23
24use crate::dom::bindings::buffer_source::{create_buffer_source, get_buffer_source_copy};
25use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::{
26 CompressionFormat, CompressionStreamMethods,
27};
28use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
29use crate::dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible};
30use crate::dom::bindings::error::{Error, Fallible};
31use crate::dom::bindings::root::{Dom, DomRoot};
32use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
33use crate::dom::types::{
34 GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
35};
36
37#[cfg(feature = "brotli-compression-stream")]
38pub(crate) const BROTLI_BUFFER_SIZE: usize = 4096;
39#[cfg(feature = "brotli-compression-stream")]
40const BROTLI_QUALITIY_LEVEL: u32 = 5;
41#[cfg(feature = "brotli-compression-stream")]
42const BROTLI_WINDOW_SIZE: u32 = 22;
43
44#[dom_struct]
46pub(crate) struct CompressionStream {
47 reflector_: Reflector,
48
49 transform: Dom<TransformStream>,
51
52 format: CompressionFormat,
54
55 #[no_trace]
57 context: RefCell<CompressionContext>,
58}
59
60impl CompressionStream {
61 fn new_inherited(
62 transform: &TransformStream,
63 format: CompressionFormat,
64 ) -> Fallible<CompressionStream> {
65 Ok(CompressionStream {
66 reflector_: Reflector::new(),
67 transform: Dom::from_ref(transform),
68 format,
69 context: RefCell::new(CompressionContext::new(format)?),
70 })
71 }
72 #[cfg_attr(crown, expect(crown::unrooted_must_root))] fn new_with_proto(
74 cx: &mut JSContext,
75 global: &GlobalScope,
76 proto: Option<SafeHandleObject>,
77 transform: &TransformStream,
78 format: CompressionFormat,
79 ) -> Fallible<DomRoot<CompressionStream>> {
80 Ok(reflect_dom_object_with_proto(
81 cx,
82 Box::new(CompressionStream::new_inherited(transform, format)?),
83 global,
84 proto,
85 ))
86 }
87}
88
89impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
90 fn Constructor(
92 cx: &mut JSContext,
93 global: &GlobalScope,
94 proto: Option<SafeHandleObject>,
95 format: CompressionFormat,
96 ) -> Fallible<DomRoot<CompressionStream>> {
97 let transform = TransformStream::new_with_proto(cx, global, None);
103 let compression_stream =
104 CompressionStream::new_with_proto(cx, global, proto, &transform, format)?;
105
106 transform.set_up(
114 cx,
115 global,
116 TransformerType::Compressor(compression_stream.as_traced()),
117 )?;
118
119 Ok(compression_stream)
120 }
121
122 fn Readable(&self) -> DomRoot<ReadableStream> {
124 self.transform.get_readable()
126 }
127
128 fn Writable(&self) -> DomRoot<WritableStream> {
130 self.transform.get_writable()
132 }
133}
134
135pub(crate) fn compress_and_enqueue_a_chunk(
137 cx: &mut JSContext,
138 global: &GlobalScope,
139 cs: &CompressionStream,
140 chunk: SafeHandleValue,
141 controller: &TransformStreamDefaultController,
142) -> Fallible<()> {
143 let chunk = convert_chunk_to_vec(cx, chunk)?;
145
146 let buffer = {
149 let mut compression_context = cs.context.borrow_mut();
150 let buffer = compression_context
151 .compress(&chunk)
152 .map_err(|_| Error::Operation(Some("Failed to compress a chunk of input".into())))?;
153
154 if buffer.is_empty() {
156 return Ok(());
157 }
158 buffer
159 };
160
161 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
166 let buffer_source = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
167 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
168 rooted!(&in(cx) let mut rval = UndefinedValue());
169 buffer_source.safe_to_jsval(cx, rval.handle_mut());
170 controller.enqueue(cx, global, rval.handle())?;
171
172 Ok(())
173}
174
175pub(crate) fn compress_flush_and_enqueue(
177 cx: &mut JSContext,
178 global: &GlobalScope,
179 cs: &CompressionStream,
180 controller: &TransformStreamDefaultController,
181) -> Fallible<()> {
182 let buffer = {
186 let mut compression_context = cs.context.borrow_mut();
187 let buffer = compression_context.finalize().map_err(|_| {
188 Error::Operation(Some("Failed to finalize the compression stream".into()))
189 })?;
190
191 if buffer.is_empty() {
193 return Ok(());
194 }
195 buffer
196 };
197
198 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
203 let buffer_source = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
204 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
205 rooted!(&in(cx) let mut rval = UndefinedValue());
206 buffer_source.safe_to_jsval(cx, rval.handle_mut());
207 controller.enqueue(cx, global, rval.handle())?;
208
209 Ok(())
210}
211
212enum Encoder {
214 #[cfg(feature = "brotli-compression-stream")]
215 Brotli(Box<BrotliEncoder<Vec<u8>>>),
216 Deflate(ZlibEncoder<Vec<u8>>),
217 DeflateRaw(DeflateEncoder<Vec<u8>>),
218 Gzip(GzEncoder<Vec<u8>>),
219}
220
221impl MallocSizeOf for Encoder {
222 #[cfg_attr(feature = "brotli-compression-stream", expect(unsafe_code))]
223 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
224 match self {
225 #[cfg(feature = "brotli-compression-stream")]
226 Encoder::Brotli(encoder) => unsafe { ops.malloc_size_of(&**encoder) },
227 Encoder::Deflate(encoder) => encoder.size_of(ops),
228 Encoder::DeflateRaw(encoder) => encoder.size_of(ops),
229 Encoder::Gzip(encoder) => encoder.size_of(ops),
230 }
231 }
232}
233
234#[derive(MallocSizeOf)]
237struct CompressionContext {
238 encoder: Encoder,
239}
240
241impl CompressionContext {
242 fn new(format: CompressionFormat) -> Fallible<CompressionContext> {
243 let encoder = match format {
244 #[cfg(feature = "brotli-compression-stream")]
245 CompressionFormat::Brotli => Encoder::Brotli(Box::new(BrotliEncoder::new(
246 Vec::new(),
247 BROTLI_BUFFER_SIZE,
248 BROTLI_QUALITIY_LEVEL,
249 BROTLI_WINDOW_SIZE,
250 ))),
251 #[cfg(not(feature = "brotli-compression-stream"))]
252 CompressionFormat::Brotli => {
253 return Err(Error::NotSupported(Some("Brotli not supported".into())));
254 },
255 CompressionFormat::Deflate => {
256 Encoder::Deflate(ZlibEncoder::new(Vec::new(), Compression::default()))
257 },
258 CompressionFormat::Deflate_raw => {
259 Encoder::DeflateRaw(DeflateEncoder::new(Vec::new(), Compression::default()))
260 },
261 CompressionFormat::Gzip => {
262 Encoder::Gzip(GzEncoder::new(Vec::new(), Compression::default()))
263 },
264 };
265 Ok(CompressionContext { encoder })
266 }
267
268 fn compress(&mut self, chunk: &[u8]) -> Result<Vec<u8>, io::Error> {
269 let mut result = Vec::new();
270
271 match &mut self.encoder {
272 #[cfg(feature = "brotli-compression-stream")]
273 Encoder::Brotli(encoder) => {
274 encoder.write_all(chunk)?;
275 encoder.flush()?;
276 result.append(encoder.get_mut());
277 },
278 Encoder::Deflate(encoder) => {
279 encoder.write_all(chunk)?;
280 encoder.flush()?;
281 result.append(encoder.get_mut());
282 },
283 Encoder::DeflateRaw(encoder) => {
284 encoder.write_all(chunk)?;
285 encoder.flush()?;
286 result.append(encoder.get_mut());
287 },
288 Encoder::Gzip(encoder) => {
289 encoder.write_all(chunk)?;
290 encoder.flush()?;
291 result.append(encoder.get_mut());
292 },
293 }
294
295 Ok(result)
296 }
297
298 fn finalize(&mut self) -> Result<Vec<u8>, io::Error> {
299 let mut result = Vec::new();
300
301 match &mut self.encoder {
302 #[cfg(feature = "brotli-compression-stream")]
303 Encoder::Brotli(encoder) => {
304 let encoder = std::mem::replace(
305 encoder.borrow_mut(),
306 BrotliEncoder::new(
307 Vec::new(),
308 BROTLI_BUFFER_SIZE,
309 BROTLI_QUALITIY_LEVEL,
310 BROTLI_WINDOW_SIZE,
311 ),
312 );
313 result = encoder.into_inner();
314 },
315 Encoder::Deflate(encoder) => {
316 encoder.try_finish()?;
317 result.append(encoder.get_mut());
318 },
319 Encoder::DeflateRaw(encoder) => {
320 encoder.try_finish()?;
321 result.append(encoder.get_mut());
322 },
323 Encoder::Gzip(encoder) => {
324 encoder.try_finish()?;
325 result.append(encoder.get_mut());
326 },
327 }
328
329 Ok(result)
330 }
331}
332
333pub(crate) fn convert_chunk_to_vec(
334 cx: &mut JSContext,
335 chunk: SafeHandleValue,
336) -> Result<Vec<u8>, Error> {
337 let conversion_result =
338 ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, ()).map_err(|_| {
339 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
340 })?;
341 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
342 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
343 })?;
344 Ok(get_buffer_source_copy(buffer_source.into()))
345}