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