Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / DecompressionStream.cpp
blob3a63913eca8846ea0ff309be7634fea273b8ae4f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/DecompressionStream.h"
9 #include "js/TypeDecls.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/dom/DecompressionStreamBinding.h"
12 #include "mozilla/dom/ReadableStream.h"
13 #include "mozilla/dom/WritableStream.h"
14 #include "mozilla/dom/TextDecoderStream.h"
15 #include "mozilla/dom/TransformStream.h"
16 #include "mozilla/dom/TransformerCallbackHelpers.h"
17 #include "mozilla/dom/UnionTypes.h"
19 #include "ZLibHelper.h"
21 // See the zlib manual in https://www.zlib.net/manual.html or in
22 // https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
24 namespace mozilla::dom {
26 class DecompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
27 public:
28 NS_DECL_ISUPPORTS_INHERITED
29 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecompressionStreamAlgorithms,
30 TransformerAlgorithmsBase)
32 explicit DecompressionStreamAlgorithms(CompressionFormat format) {
33 int8_t err = inflateInit2(&mZStream, ZLibWindowBits(format));
34 if (err == Z_MEM_ERROR) {
35 MOZ_CRASH("Out of memory");
37 MOZ_ASSERT(err == Z_OK);
40 // Step 3 of
41 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
42 // Let transformAlgorithm be an algorithm which takes a chunk argument and
43 // runs the compress and enqueue a chunk algorithm with this and chunk.
44 MOZ_CAN_RUN_SCRIPT
45 void TransformCallbackImpl(JS::Handle<JS::Value> aChunk,
46 TransformStreamDefaultController& aController,
47 ErrorResult& aRv) override {
48 AutoJSAPI jsapi;
49 if (!jsapi.Init(aController.GetParentObject())) {
50 aRv.ThrowUnknownError("Internal error");
51 return;
53 JSContext* cx = jsapi.cx();
55 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
57 // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
58 RootedUnion<OwningArrayBufferViewOrArrayBuffer> bufferSource(cx);
59 if (!bufferSource.Init(cx, aChunk)) {
60 aRv.MightThrowJSException();
61 aRv.StealExceptionFromJSContext(cx);
62 return;
65 // Step 2: Let buffer be the result of decompressing chunk with ds's format
66 // and context. If this results in an error, then throw a TypeError.
67 // Step 3 - 5: (Done in CompressAndEnqueue)
68 ProcessTypedArraysFixed(
69 bufferSource,
70 [&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
71 DecompressAndEnqueue(cx, aData, ZLibFlush::No, aController, aRv);
72 });
75 // Step 4 of
76 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
77 // Let flushAlgorithm be an algorithm which takes no argument and runs the
78 // compress flush and enqueue algorithm with this.
79 MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
80 TransformStreamDefaultController& aController,
81 ErrorResult& aRv) override {
82 AutoJSAPI jsapi;
83 if (!jsapi.Init(aController.GetParentObject())) {
84 aRv.ThrowUnknownError("Internal error");
85 return;
87 JSContext* cx = jsapi.cx();
89 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
91 // Step 1: Let buffer be the result of decompressing an empty input with
92 // ds's format and context, with the finish flag.
93 // Step 2 - 4: (Done in CompressAndEnqueue)
94 DecompressAndEnqueue(cx, Span<const uint8_t>(), ZLibFlush::Yes, aController,
95 aRv);
98 private:
99 // Shared by:
100 // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
101 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
102 // All data errors throw TypeError by step 2: If this results in an error,
103 // then throw a TypeError.
104 MOZ_CAN_RUN_SCRIPT void DecompressAndEnqueue(
105 JSContext* aCx, Span<const uint8_t> aInput, ZLibFlush aFlush,
106 TransformStreamDefaultController& aController, ErrorResult& aRv) {
107 MOZ_ASSERT_IF(aFlush == ZLibFlush::Yes, !aInput.Length());
109 mZStream.avail_in = aInput.Length();
110 mZStream.next_in = const_cast<uint8_t*>(aInput.Elements());
112 JS::RootedVector<JSObject*> array(aCx);
114 do {
115 static uint16_t kBufferSize = 16384;
116 UniquePtr<uint8_t[], JS::FreePolicy> buffer(
117 static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
118 if (!buffer) {
119 aRv.ThrowTypeError("Out of memory");
120 return;
123 mZStream.avail_out = kBufferSize;
124 mZStream.next_out = buffer.get();
126 int8_t err = inflate(&mZStream, aFlush);
128 // From the manual: inflate() returns ...
129 switch (err) {
130 case Z_DATA_ERROR:
131 // Z_DATA_ERROR if the input data was corrupted (input stream not
132 // conforming to the zlib format or incorrect check value, in which
133 // case strm->msg points to a string with a more specific error)
134 aRv.ThrowTypeError("The input data is corrupted: "_ns +
135 nsDependentCString(mZStream.msg));
136 return;
137 case Z_MEM_ERROR:
138 // Z_MEM_ERROR if there was not enough memory
139 aRv.ThrowTypeError("Out of memory");
140 return;
141 case Z_NEED_DICT:
142 // Z_NEED_DICT if a preset dictionary is needed at this point
144 // From the `deflate` section of
145 // https://wicg.github.io/compression/#supported-formats:
146 // * The FDICT flag is not supported by these APIs, and will error the
147 // stream if set.
148 // And FDICT means preset dictionary per
149 // https://datatracker.ietf.org/doc/html/rfc1950#page-5.
150 aRv.ThrowTypeError(
151 "The stream needs a preset dictionary but such setup is "
152 "unsupported");
153 return;
154 case Z_STREAM_END:
155 // Z_STREAM_END if the end of the compressed data has been reached and
156 // all uncompressed output has been produced
158 // https://wicg.github.io/compression/#supported-formats has error
159 // conditions for each compression format when additional input comes
160 // after stream end.
161 // Note that additional calls for inflate() immediately emits
162 // Z_STREAM_END after this point.
163 if (mZStream.avail_in > 0) {
164 aRv.ThrowTypeError("Unexpected input after the end of stream");
165 return;
167 mObservedStreamEnd = true;
168 break;
169 case Z_OK:
170 case Z_BUF_ERROR:
171 // * Z_OK if some progress has been made
172 // * Z_BUF_ERROR if no progress was possible or if there was not
173 // enough room in the output buffer when Z_FINISH is used. Note that
174 // Z_BUF_ERROR is not fatal, and inflate() can be called again with
175 // more input and more output space to continue decompressing.
177 // (But of course no input should be given after Z_FINISH)
178 break;
179 case Z_STREAM_ERROR:
180 default:
181 // * Z_STREAM_ERROR if the stream state was inconsistent
182 // (which is fatal)
183 MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code");
184 aRv.ThrowTypeError("Unexpected decompression error");
185 return;
188 // At this point we either exhausted the input or the output buffer
189 MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out);
191 size_t written = kBufferSize - mZStream.avail_out;
192 if (!written) {
193 break;
196 // Step 3: If buffer is empty, return.
197 // (We'll implicitly return when the array is empty.)
199 // Step 4: Split buffer into one or more non-empty pieces and convert them
200 // into Uint8Arrays.
201 // (The buffer is 'split' by having a fixed sized buffer above.)
203 JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
204 aCx, written, std::move(buffer)));
205 if (!view || !array.append(view)) {
206 JS_ClearPendingException(aCx);
207 aRv.ThrowTypeError("Out of memory");
208 return;
210 } while (mZStream.avail_out == 0 && !mObservedStreamEnd);
211 // From the manual:
212 // * It must update next_out and avail_out when avail_out has dropped to
213 // zero.
214 // * inflate() should normally be called until it returns Z_STREAM_END or an
215 // error.
217 if (aFlush == ZLibFlush::Yes && !mObservedStreamEnd) {
218 // Step 2 of
219 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
220 // If the end of the compressed input has not been reached, then throw a
221 // TypeError.
222 aRv.ThrowTypeError("The input is ended without reaching the stream end");
223 return;
226 // Step 5: For each Uint8Array array, enqueue array in cs's transform.
227 for (const auto& view : array) {
228 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view));
229 aController.Enqueue(aCx, value, aRv);
230 if (aRv.Failed()) {
231 return;
236 ~DecompressionStreamAlgorithms() override { inflateEnd(&mZStream); };
238 z_stream mZStream = {};
239 bool mObservedStreamEnd = false;
242 NS_IMPL_CYCLE_COLLECTION_INHERITED(DecompressionStreamAlgorithms,
243 TransformerAlgorithmsBase)
244 NS_IMPL_ADDREF_INHERITED(DecompressionStreamAlgorithms,
245 TransformerAlgorithmsBase)
246 NS_IMPL_RELEASE_INHERITED(DecompressionStreamAlgorithms,
247 TransformerAlgorithmsBase)
248 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStreamAlgorithms)
249 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
251 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DecompressionStream, mGlobal, mStream)
252 NS_IMPL_CYCLE_COLLECTING_ADDREF(DecompressionStream)
253 NS_IMPL_CYCLE_COLLECTING_RELEASE(DecompressionStream)
254 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStream)
255 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
256 NS_INTERFACE_MAP_ENTRY(nsISupports)
257 NS_INTERFACE_MAP_END
259 DecompressionStream::DecompressionStream(nsISupports* aGlobal,
260 TransformStream& aStream)
261 : mGlobal(aGlobal), mStream(&aStream) {}
263 DecompressionStream::~DecompressionStream() = default;
265 JSObject* DecompressionStream::WrapObject(JSContext* aCx,
266 JS::Handle<JSObject*> aGivenProto) {
267 return DecompressionStream_Binding::Wrap(aCx, this, aGivenProto);
270 // https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream
271 already_AddRefed<DecompressionStream> DecompressionStream::Constructor(
272 const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) {
273 // Step 1: If format is unsupported in DecompressionStream, then throw a
274 // TypeError.
275 // XXX: Skipped as we are using enum for this
277 // Step 2 - 4: (Done in DecompressionStreamAlgorithms)
279 // Step 5: Set this's transform to a new TransformStream.
281 // Step 6: Set up this's transform with transformAlgorithm set to
282 // transformAlgorithm and flushAlgorithm set to flushAlgorithm.
283 auto algorithms = MakeRefPtr<DecompressionStreamAlgorithms>(aFormat);
285 RefPtr<TransformStream> stream =
286 TransformStream::CreateGeneric(aGlobal, *algorithms, aRv);
287 if (aRv.Failed()) {
288 return nullptr;
290 return do_AddRef(new DecompressionStream(aGlobal.GetAsSupports(), *stream));
293 already_AddRefed<ReadableStream> DecompressionStream::Readable() const {
294 return do_AddRef(mStream->Readable());
297 already_AddRefed<WritableStream> DecompressionStream::Writable() const {
298 return do_AddRef(mStream->Writable());
301 } // namespace mozilla::dom