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_slice};
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 conversion_result =
147 ArrayBufferViewOrArrayBuffer::from_jsval(cx, chunk, ()).map_err(|_| {
148 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
149 })?;
150 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
151 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
152 })?;
153
154 let buffer = {
157 let mut compression_context = cs.context.borrow_mut();
158 let buffer = compression_context
159 .compress(get_buffer_source_slice(buffer_source, cx.no_gc()))
160 .map_err(|_| Error::Operation(Some("Failed to compress a chunk of input".into())))?;
161
162 if buffer.is_empty() {
164 return Ok(());
165 }
166 buffer
167 };
168
169 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
174 let buffer_source = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
175 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
176 rooted!(&in(cx) let mut rval = UndefinedValue());
177 buffer_source.to_jsval(cx, rval.handle_mut());
178 controller.enqueue(cx, global, rval.handle())?;
179
180 Ok(())
181}
182
183pub(crate) fn compress_flush_and_enqueue(
185 cx: &mut JSContext,
186 global: &GlobalScope,
187 cs: &CompressionStream,
188 controller: &TransformStreamDefaultController,
189) -> Fallible<()> {
190 let buffer = {
194 let mut compression_context = cs.context.borrow_mut();
195 let buffer = compression_context.finalize().map_err(|_| {
196 Error::Operation(Some("Failed to finalize the compression stream".into()))
197 })?;
198
199 if buffer.is_empty() {
201 return Ok(());
202 }
203 buffer
204 };
205
206 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
211 let buffer_source = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
212 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
213 rooted!(&in(cx) let mut rval = UndefinedValue());
214 buffer_source.to_jsval(cx, rval.handle_mut());
215 controller.enqueue(cx, global, rval.handle())?;
216
217 Ok(())
218}
219
220enum Encoder {
222 #[cfg(feature = "brotli-compression-stream")]
223 Brotli(Box<BrotliEncoder<Vec<u8>>>),
224 Deflate(ZlibEncoder<Vec<u8>>),
225 DeflateRaw(DeflateEncoder<Vec<u8>>),
226 Gzip(GzEncoder<Vec<u8>>),
227}
228
229impl MallocSizeOf for Encoder {
230 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
231 match self {
232 #[cfg(feature = "brotli-compression-stream")]
233 Encoder::Brotli(encoder) => encoder.shallow_size_of(ops),
234 Encoder::Deflate(encoder) => encoder.size_of(ops),
235 Encoder::DeflateRaw(encoder) => encoder.size_of(ops),
236 Encoder::Gzip(encoder) => encoder.size_of(ops),
237 }
238 }
239}
240
241#[derive(MallocSizeOf)]
244struct CompressionContext {
245 encoder: Encoder,
246}
247
248impl CompressionContext {
249 fn new(format: CompressionFormat) -> Fallible<CompressionContext> {
250 let encoder = match format {
251 #[cfg(feature = "brotli-compression-stream")]
252 CompressionFormat::Brotli => Encoder::Brotli(Box::new(BrotliEncoder::new(
253 Vec::new(),
254 BROTLI_BUFFER_SIZE,
255 BROTLI_QUALITIY_LEVEL,
256 BROTLI_WINDOW_SIZE,
257 ))),
258 #[cfg(not(feature = "brotli-compression-stream"))]
259 CompressionFormat::Brotli => {
260 return Err(Error::NotSupported(Some("Brotli not supported".into())));
261 },
262 CompressionFormat::Deflate => {
263 Encoder::Deflate(ZlibEncoder::new(Vec::new(), Compression::default()))
264 },
265 CompressionFormat::Deflate_raw => {
266 Encoder::DeflateRaw(DeflateEncoder::new(Vec::new(), Compression::default()))
267 },
268 CompressionFormat::Gzip => {
269 Encoder::Gzip(GzEncoder::new(Vec::new(), Compression::default()))
270 },
271 };
272 Ok(CompressionContext { encoder })
273 }
274
275 fn compress(&mut self, chunk: &[u8]) -> Result<Vec<u8>, io::Error> {
276 let mut result = Vec::new();
277
278 match &mut self.encoder {
279 #[cfg(feature = "brotli-compression-stream")]
280 Encoder::Brotli(encoder) => {
281 encoder.write_all(chunk)?;
282 encoder.flush()?;
283 result.append(encoder.get_mut());
284 },
285 Encoder::Deflate(encoder) => {
286 encoder.write_all(chunk)?;
287 encoder.flush()?;
288 result.append(encoder.get_mut());
289 },
290 Encoder::DeflateRaw(encoder) => {
291 encoder.write_all(chunk)?;
292 encoder.flush()?;
293 result.append(encoder.get_mut());
294 },
295 Encoder::Gzip(encoder) => {
296 encoder.write_all(chunk)?;
297 encoder.flush()?;
298 result.append(encoder.get_mut());
299 },
300 }
301
302 Ok(result)
303 }
304
305 fn finalize(&mut self) -> Result<Vec<u8>, io::Error> {
306 let mut result = Vec::new();
307
308 match &mut self.encoder {
309 #[cfg(feature = "brotli-compression-stream")]
310 Encoder::Brotli(encoder) => {
311 let encoder = std::mem::replace(
312 encoder.borrow_mut(),
313 BrotliEncoder::new(
314 Vec::new(),
315 BROTLI_BUFFER_SIZE,
316 BROTLI_QUALITIY_LEVEL,
317 BROTLI_WINDOW_SIZE,
318 ),
319 );
320 result = encoder.into_inner();
321 },
322 Encoder::Deflate(encoder) => {
323 encoder.try_finish()?;
324 result.append(encoder.get_mut());
325 },
326 Encoder::DeflateRaw(encoder) => {
327 encoder.try_finish()?;
328 result.append(encoder.get_mut());
329 },
330 Encoder::Gzip(encoder) => {
331 encoder.try_finish()?;
332 result.append(encoder.get_mut());
333 },
334 }
335
336 Ok(result)
337 }
338}