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_and_cx};
21
22use crate::dom::bindings::buffer_source::create_buffer_source;
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};
34use crate::script_runtime::CanGc;
35
36pub(crate) const BROTLI_BUFFER_SIZE: usize = 4096;
37const BROTLI_QUALITIY_LEVEL: u32 = 5;
38const BROTLI_WINDOW_SIZE: u32 = 22;
39
40#[dom_struct]
42pub(crate) struct CompressionStream {
43 reflector_: Reflector,
44
45 transform: Dom<TransformStream>,
47
48 format: CompressionFormat,
50
51 #[no_trace]
53 context: RefCell<CompressionContext>,
54}
55
56impl CompressionStream {
57 fn new_inherited(transform: &TransformStream, format: CompressionFormat) -> CompressionStream {
58 CompressionStream {
59 reflector_: Reflector::new(),
60 transform: Dom::from_ref(transform),
61 format,
62 context: RefCell::new(CompressionContext::new(format)),
63 }
64 }
65
66 fn new_with_proto(
67 cx: &mut JSContext,
68 global: &GlobalScope,
69 proto: Option<SafeHandleObject>,
70 transform: &TransformStream,
71 format: CompressionFormat,
72 ) -> DomRoot<CompressionStream> {
73 reflect_dom_object_with_proto_and_cx(
74 Box::new(CompressionStream::new_inherited(transform, format)),
75 global,
76 proto,
77 cx,
78 )
79 }
80}
81
82impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
83 fn Constructor(
85 cx: &mut JSContext,
86 global: &GlobalScope,
87 proto: Option<SafeHandleObject>,
88 format: CompressionFormat,
89 ) -> Fallible<DomRoot<CompressionStream>> {
90 let transform = TransformStream::new_with_proto(global, None, CanGc::from_cx(cx));
96 let compression_stream =
97 CompressionStream::new_with_proto(cx, global, proto, &transform, format);
98
99 let transformer_type = TransformerType::Compressor(compression_stream.clone());
104
105 transform.set_up(cx, global, transformer_type)?;
108
109 Ok(compression_stream)
110 }
111
112 fn Readable(&self) -> DomRoot<ReadableStream> {
114 self.transform.get_readable()
116 }
117
118 fn Writable(&self) -> DomRoot<WritableStream> {
120 self.transform.get_writable()
122 }
123}
124
125pub(crate) fn compress_and_enqueue_a_chunk(
127 cx: &mut JSContext,
128 global: &GlobalScope,
129 cs: &CompressionStream,
130 chunk: SafeHandleValue,
131 controller: &TransformStreamDefaultController,
132) -> Fallible<()> {
133 let chunk = convert_chunk_to_vec(cx, chunk)?;
135
136 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
148 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
153 let buffer_source = create_buffer_source::<Uint8>(
154 cx.into(),
155 &buffer,
156 js_object.handle_mut(),
157 CanGc::from_cx(cx),
158 )
159 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
160 rooted!(&in(cx) let mut rval = UndefinedValue());
161 buffer_source.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
162 controller.enqueue(cx, global, rval.handle())?;
163
164 Ok(())
165}
166
167pub(crate) fn compress_flush_and_enqueue(
169 cx: &mut JSContext,
170 global: &GlobalScope,
171 cs: &CompressionStream,
172 controller: &TransformStreamDefaultController,
173) -> Fallible<()> {
174 let mut compression_context = cs.context.borrow_mut();
178 let buffer = compression_context
179 .finalize()
180 .map_err(|_| Error::Operation(Some("Failed to finalize the compression stream".into())))?;
181
182 if buffer.is_empty() {
184 return Ok(());
185 }
186
187 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
192 let buffer_source = create_buffer_source::<Uint8>(
193 cx.into(),
194 &buffer,
195 js_object.handle_mut(),
196 CanGc::from_cx(cx),
197 )
198 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
199 rooted!(&in(cx) let mut rval = UndefinedValue());
200 buffer_source.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
201 controller.enqueue(cx, global, rval.handle())?;
202
203 Ok(())
204}
205
206enum Encoder {
208 Brotli(Box<BrotliEncoder<Vec<u8>>>),
209 Deflate(ZlibEncoder<Vec<u8>>),
210 DeflateRaw(DeflateEncoder<Vec<u8>>),
211 Gzip(GzEncoder<Vec<u8>>),
212}
213
214impl MallocSizeOf for Encoder {
215 #[expect(unsafe_code)]
216 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
217 match self {
218 Encoder::Brotli(encoder) => unsafe { ops.malloc_size_of(&**encoder) },
219 Encoder::Deflate(encoder) => encoder.size_of(ops),
220 Encoder::DeflateRaw(encoder) => encoder.size_of(ops),
221 Encoder::Gzip(encoder) => encoder.size_of(ops),
222 }
223 }
224}
225
226#[derive(MallocSizeOf)]
229struct CompressionContext {
230 encoder: Encoder,
231}
232
233impl CompressionContext {
234 fn new(format: CompressionFormat) -> CompressionContext {
235 let encoder = match format {
236 CompressionFormat::Brotli => Encoder::Brotli(Box::new(BrotliEncoder::new(
237 Vec::new(),
238 BROTLI_BUFFER_SIZE,
239 BROTLI_QUALITIY_LEVEL,
240 BROTLI_WINDOW_SIZE,
241 ))),
242 CompressionFormat::Deflate => {
243 Encoder::Deflate(ZlibEncoder::new(Vec::new(), Compression::default()))
244 },
245 CompressionFormat::Deflate_raw => {
246 Encoder::DeflateRaw(DeflateEncoder::new(Vec::new(), Compression::default()))
247 },
248 CompressionFormat::Gzip => {
249 Encoder::Gzip(GzEncoder::new(Vec::new(), Compression::default()))
250 },
251 };
252 CompressionContext { encoder }
253 }
254
255 fn compress(&mut self, chunk: &[u8]) -> Result<Vec<u8>, io::Error> {
256 let mut result = Vec::new();
257
258 match &mut self.encoder {
259 Encoder::Brotli(encoder) => {
260 encoder.write_all(chunk)?;
261 encoder.flush()?;
262 result.append(encoder.get_mut());
263 },
264 Encoder::Deflate(encoder) => {
265 encoder.write_all(chunk)?;
266 encoder.flush()?;
267 result.append(encoder.get_mut());
268 },
269 Encoder::DeflateRaw(encoder) => {
270 encoder.write_all(chunk)?;
271 encoder.flush()?;
272 result.append(encoder.get_mut());
273 },
274 Encoder::Gzip(encoder) => {
275 encoder.write_all(chunk)?;
276 encoder.flush()?;
277 result.append(encoder.get_mut());
278 },
279 }
280
281 Ok(result)
282 }
283
284 fn finalize(&mut self) -> Result<Vec<u8>, io::Error> {
285 let mut result = Vec::new();
286
287 match &mut self.encoder {
288 Encoder::Brotli(encoder) => {
289 let encoder = std::mem::replace(
290 encoder.borrow_mut(),
291 BrotliEncoder::new(
292 Vec::new(),
293 BROTLI_BUFFER_SIZE,
294 BROTLI_QUALITIY_LEVEL,
295 BROTLI_WINDOW_SIZE,
296 ),
297 );
298 result = encoder.into_inner();
299 },
300 Encoder::Deflate(encoder) => {
301 encoder.try_finish()?;
302 result.append(encoder.get_mut());
303 },
304 Encoder::DeflateRaw(encoder) => {
305 encoder.try_finish()?;
306 result.append(encoder.get_mut());
307 },
308 Encoder::Gzip(encoder) => {
309 encoder.try_finish()?;
310 result.append(encoder.get_mut());
311 },
312 }
313
314 Ok(result)
315 }
316}
317
318pub(crate) fn convert_chunk_to_vec(
319 cx: &mut JSContext,
320 chunk: SafeHandleValue,
321) -> Result<Vec<u8>, Error> {
322 let conversion_result =
323 ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, ()).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}