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