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::jsapi::JSObject;
15use js::jsval::UndefinedValue;
16use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
17use js::typedarray::Uint8;
18use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
19
20use crate::dom::bindings::buffer_source::create_buffer_source;
21use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::{
22 CompressionFormat, CompressionStreamMethods,
23};
24use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
25use crate::dom::bindings::conversions::{SafeFromJSValConvertible, SafeToJSValConvertible};
26use crate::dom::bindings::error::{Error, Fallible};
27use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
28use crate::dom::bindings::root::{Dom, DomRoot};
29use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
30use crate::dom::types::{
31 GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
32};
33use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
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 js::context::JSContext,
67 global: &GlobalScope,
68 proto: Option<SafeHandleObject>,
69 transform: &TransformStream,
70 format: CompressionFormat,
71 ) -> DomRoot<CompressionStream> {
72 reflect_dom_object_with_proto_and_cx(
73 Box::new(CompressionStream::new_inherited(transform, format)),
74 global,
75 proto,
76 cx,
77 )
78 }
79}
80
81impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
82 fn Constructor(
84 cx: &mut js::context::JSContext,
85 global: &GlobalScope,
86 proto: Option<SafeHandleObject>,
87 format: CompressionFormat,
88 ) -> Fallible<DomRoot<CompressionStream>> {
89 let transform = TransformStream::new_with_proto(global, None, CanGc::from_cx(cx));
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 js::context::JSContext,
127 global: &GlobalScope,
128 cs: &CompressionStream,
129 chunk: SafeHandleValue,
130 controller: &TransformStreamDefaultController,
131) -> Fallible<()> {
132 let chunk = convert_chunk_to_vec(cx.into(), chunk, CanGc::from_cx(cx))?;
134
135 let mut compression_context = cs.context.borrow_mut();
138 let buffer = compression_context
139 .compress(&chunk)
140 .map_err(|_| Error::Operation(Some("Failed to compress a chunk of input".into())))?;
141
142 if buffer.is_empty() {
144 return Ok(());
145 }
146
147 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
152 let buffer_source = create_buffer_source::<Uint8>(
153 cx.into(),
154 &buffer,
155 js_object.handle_mut(),
156 CanGc::from_cx(cx),
157 )
158 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
159 rooted!(&in(cx) let mut rval = UndefinedValue());
160 buffer_source.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
161 controller.enqueue(cx, global, rval.handle())?;
162
163 Ok(())
164}
165
166pub(crate) fn compress_flush_and_enqueue(
168 cx: &mut js::context::JSContext,
169 global: &GlobalScope,
170 cs: &CompressionStream,
171 controller: &TransformStreamDefaultController,
172) -> Fallible<()> {
173 let mut compression_context = cs.context.borrow_mut();
177 let buffer = compression_context
178 .finalize()
179 .map_err(|_| Error::Operation(Some("Failed to finalize the compression stream".into())))?;
180
181 if buffer.is_empty() {
183 return Ok(());
184 }
185
186 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
191 let buffer_source = create_buffer_source::<Uint8>(
192 cx.into(),
193 &buffer,
194 js_object.handle_mut(),
195 CanGc::from_cx(cx),
196 )
197 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
198 rooted!(&in(cx) let mut rval = UndefinedValue());
199 buffer_source.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
200 controller.enqueue(cx, global, rval.handle())?;
201
202 Ok(())
203}
204
205enum Encoder {
207 Brotli(Box<BrotliEncoder<Vec<u8>>>),
208 Deflate(ZlibEncoder<Vec<u8>>),
209 DeflateRaw(DeflateEncoder<Vec<u8>>),
210 Gzip(GzEncoder<Vec<u8>>),
211}
212
213impl MallocSizeOf for Encoder {
214 #[expect(unsafe_code)]
215 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
216 match self {
217 Encoder::Brotli(encoder) => unsafe { ops.malloc_size_of(&**encoder) },
218 Encoder::Deflate(encoder) => encoder.size_of(ops),
219 Encoder::DeflateRaw(encoder) => encoder.size_of(ops),
220 Encoder::Gzip(encoder) => encoder.size_of(ops),
221 }
222 }
223}
224
225#[derive(MallocSizeOf)]
228struct CompressionContext {
229 encoder: Encoder,
230}
231
232impl CompressionContext {
233 fn new(format: CompressionFormat) -> CompressionContext {
234 let encoder = match format {
235 CompressionFormat::Brotli => Encoder::Brotli(Box::new(BrotliEncoder::new(
236 Vec::new(),
237 BROTLI_BUFFER_SIZE,
238 BROTLI_QUALITIY_LEVEL,
239 BROTLI_WINDOW_SIZE,
240 ))),
241 CompressionFormat::Deflate => {
242 Encoder::Deflate(ZlibEncoder::new(Vec::new(), Compression::default()))
243 },
244 CompressionFormat::Deflate_raw => {
245 Encoder::DeflateRaw(DeflateEncoder::new(Vec::new(), Compression::default()))
246 },
247 CompressionFormat::Gzip => {
248 Encoder::Gzip(GzEncoder::new(Vec::new(), Compression::default()))
249 },
250 };
251 CompressionContext { encoder }
252 }
253
254 fn compress(&mut self, chunk: &[u8]) -> Result<Vec<u8>, io::Error> {
255 let mut result = Vec::new();
256
257 match &mut self.encoder {
258 Encoder::Brotli(encoder) => {
259 encoder.write_all(chunk)?;
260 encoder.flush()?;
261 result.append(encoder.get_mut());
262 },
263 Encoder::Deflate(encoder) => {
264 encoder.write_all(chunk)?;
265 encoder.flush()?;
266 result.append(encoder.get_mut());
267 },
268 Encoder::DeflateRaw(encoder) => {
269 encoder.write_all(chunk)?;
270 encoder.flush()?;
271 result.append(encoder.get_mut());
272 },
273 Encoder::Gzip(encoder) => {
274 encoder.write_all(chunk)?;
275 encoder.flush()?;
276 result.append(encoder.get_mut());
277 },
278 }
279
280 Ok(result)
281 }
282
283 fn finalize(&mut self) -> Result<Vec<u8>, io::Error> {
284 let mut result = Vec::new();
285
286 match &mut self.encoder {
287 Encoder::Brotli(encoder) => {
288 let encoder = std::mem::replace(
289 encoder.borrow_mut(),
290 BrotliEncoder::new(
291 Vec::new(),
292 BROTLI_BUFFER_SIZE,
293 BROTLI_QUALITIY_LEVEL,
294 BROTLI_WINDOW_SIZE,
295 ),
296 );
297 result = encoder.into_inner();
298 },
299 Encoder::Deflate(encoder) => {
300 encoder.try_finish()?;
301 result.append(encoder.get_mut());
302 },
303 Encoder::DeflateRaw(encoder) => {
304 encoder.try_finish()?;
305 result.append(encoder.get_mut());
306 },
307 Encoder::Gzip(encoder) => {
308 encoder.try_finish()?;
309 result.append(encoder.get_mut());
310 },
311 }
312
313 Ok(result)
314 }
315}
316
317pub(crate) fn convert_chunk_to_vec(
318 cx: SafeJSContext,
319 chunk: SafeHandleValue,
320 can_gc: CanGc,
321) -> Result<Vec<u8>, Error> {
322 let conversion_result = ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, (), can_gc)
323 .map_err(|_| {
324 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
325 })?;
326 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
327 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
328 })?;
329 match buffer_source {
330 ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => Ok(view.to_vec()),
331 ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => Ok(buffer.to_vec()),
332 }
333}