Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / CompressionStream.cpp
blob65413b851bef0730f84e84f653e114f0871920af
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/CompressionStream.h"
9 #include "js/TypeDecls.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/dom/CompressionStreamBinding.h"
13 #include "mozilla/dom/ReadableStream.h"
14 #include "mozilla/dom/WritableStream.h"
15 #include "mozilla/dom/TransformStream.h"
16 #include "mozilla/dom/TextDecoderStream.h"
17 #include "mozilla/dom/TransformerCallbackHelpers.h"
18 #include "mozilla/dom/UnionTypes.h"
20 #include "ZLibHelper.h"
22 // See the zlib manual in https://www.zlib.net/manual.html or in
23 // https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
25 namespace mozilla::dom {
27 class CompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
28 public:
29 NS_DECL_ISUPPORTS_INHERITED
30 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CompressionStreamAlgorithms,
31 TransformerAlgorithmsBase)
33 explicit CompressionStreamAlgorithms(CompressionFormat format) {
34 int8_t err = deflateInit2(&mZStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
35 ZLibWindowBits(format), 8 /* default memLevel */,
36 Z_DEFAULT_STRATEGY);
37 if (err == Z_MEM_ERROR) {
38 MOZ_CRASH("Out of memory");
40 MOZ_ASSERT(err == Z_OK);
43 // Step 3 of
44 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
45 // Let transformAlgorithm be an algorithm which takes a chunk argument and
46 // runs the compress and enqueue a chunk algorithm with this and chunk.
47 MOZ_CAN_RUN_SCRIPT
48 void TransformCallbackImpl(JS::Handle<JS::Value> aChunk,
49 TransformStreamDefaultController& aController,
50 ErrorResult& aRv) override {
51 AutoJSAPI jsapi;
52 if (!jsapi.Init(aController.GetParentObject())) {
53 aRv.ThrowUnknownError("Internal error");
54 return;
56 JSContext* cx = jsapi.cx();
58 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
60 // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
61 RootedUnion<OwningArrayBufferViewOrArrayBuffer> bufferSource(cx);
62 if (!bufferSource.Init(cx, aChunk)) {
63 aRv.MightThrowJSException();
64 aRv.StealExceptionFromJSContext(cx);
65 return;
68 // Step 2: Let buffer be the result of compressing chunk with cs's format
69 // and context.
70 // Step 3 - 5: (Done in CompressAndEnqueue)
71 ProcessTypedArraysFixed(
72 bufferSource,
73 [&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
74 CompressAndEnqueue(cx, aData, ZLibFlush::No, aController, aRv);
75 });
78 // Step 4 of
79 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
80 // Let flushAlgorithm be an algorithm which takes no argument and runs the
81 // compress flush and enqueue algorithm with this.
82 MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
83 TransformStreamDefaultController& aController,
84 ErrorResult& aRv) override {
85 AutoJSAPI jsapi;
86 if (!jsapi.Init(aController.GetParentObject())) {
87 aRv.ThrowUnknownError("Internal error");
88 return;
90 JSContext* cx = jsapi.cx();
92 // https://wicg.github.io/compression/#compress-flush-and-enqueue
94 // Step 1: Let buffer be the result of compressing an empty input with cs's
95 // format and context, with the finish flag.
96 // Step 2 - 4: (Done in CompressAndEnqueue)
97 CompressAndEnqueue(cx, Span<const uint8_t>(), ZLibFlush::Yes, aController,
98 aRv);
101 private:
102 // Shared by:
103 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
104 // https://wicg.github.io/compression/#compress-flush-and-enqueue
105 MOZ_CAN_RUN_SCRIPT void CompressAndEnqueue(
106 JSContext* aCx, Span<const uint8_t> aInput, ZLibFlush aFlush,
107 TransformStreamDefaultController& aController, ErrorResult& aRv) {
108 MOZ_ASSERT_IF(aFlush == ZLibFlush::Yes, !aInput.Length());
110 mZStream.avail_in = aInput.Length();
111 mZStream.next_in = const_cast<uint8_t*>(aInput.Elements());
113 JS::RootedVector<JSObject*> array(aCx);
115 do {
116 static uint16_t kBufferSize = 16384;
117 UniquePtr<uint8_t[], JS::FreePolicy> buffer(
118 static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
119 if (!buffer) {
120 aRv.ThrowTypeError("Out of memory");
121 return;
124 mZStream.avail_out = kBufferSize;
125 mZStream.next_out = buffer.get();
127 int8_t err = deflate(&mZStream, aFlush);
129 // From the manual: deflate() returns ...
130 switch (err) {
131 case Z_OK:
132 case Z_STREAM_END:
133 case Z_BUF_ERROR:
134 // * Z_OK if some progress has been made
135 // * Z_STREAM_END if all input has been consumed and all output has
136 // been produced (only when flush is set to Z_FINISH)
137 // * Z_BUF_ERROR if no progress is possible (for example avail_in or
138 // avail_out was zero). Note that Z_BUF_ERROR is not fatal, and
139 // deflate() can be called again with more input and more output space
140 // to continue compressing.
142 // (But of course no input should be given after Z_FINISH)
143 break;
144 case Z_STREAM_ERROR:
145 default:
146 // * Z_STREAM_ERROR if the stream state was inconsistent
147 // (which is fatal)
148 MOZ_ASSERT_UNREACHABLE("Unexpected compression error code");
149 aRv.ThrowTypeError("Unexpected compression error");
150 return;
153 // Stream should end only when flushed, see above
154 // The reverse is not true as zlib may have big data to be flushed that is
155 // larger than the buffer size
156 MOZ_ASSERT_IF(err == Z_STREAM_END, aFlush == ZLibFlush::Yes);
158 // At this point we either exhausted the input or the output buffer
159 MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out);
161 size_t written = kBufferSize - mZStream.avail_out;
162 if (!written) {
163 break;
166 // Step 3: If buffer is empty, return.
167 // (We'll implicitly return when the array is empty.)
169 // Step 4: Split buffer into one or more non-empty pieces and convert them
170 // into Uint8Arrays.
171 // (The buffer is 'split' by having a fixed sized buffer above.)
173 JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
174 aCx, written, std::move(buffer)));
175 if (!view || !array.append(view)) {
176 JS_ClearPendingException(aCx);
177 aRv.ThrowTypeError("Out of memory");
178 return;
180 } while (mZStream.avail_out == 0);
181 // From the manual:
182 // If deflate returns with avail_out == 0, this function must be called
183 // again with the same value of the flush parameter and more output space
184 // (updated avail_out)
186 // Step 5: For each Uint8Array array, enqueue array in cs's transform.
187 for (const auto& view : array) {
188 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view));
189 aController.Enqueue(aCx, value, aRv);
190 if (aRv.Failed()) {
191 return;
196 ~CompressionStreamAlgorithms() override { deflateEnd(&mZStream); };
198 z_stream mZStream = {};
201 NS_IMPL_CYCLE_COLLECTION_INHERITED(CompressionStreamAlgorithms,
202 TransformerAlgorithmsBase)
203 NS_IMPL_ADDREF_INHERITED(CompressionStreamAlgorithms, TransformerAlgorithmsBase)
204 NS_IMPL_RELEASE_INHERITED(CompressionStreamAlgorithms,
205 TransformerAlgorithmsBase)
206 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStreamAlgorithms)
207 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
209 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CompressionStream, mGlobal, mStream)
210 NS_IMPL_CYCLE_COLLECTING_ADDREF(CompressionStream)
211 NS_IMPL_CYCLE_COLLECTING_RELEASE(CompressionStream)
212 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStream)
213 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
214 NS_INTERFACE_MAP_ENTRY(nsISupports)
215 NS_INTERFACE_MAP_END
217 CompressionStream::CompressionStream(nsISupports* aGlobal,
218 TransformStream& aStream)
219 : mGlobal(aGlobal), mStream(&aStream) {}
221 CompressionStream::~CompressionStream() = default;
223 JSObject* CompressionStream::WrapObject(JSContext* aCx,
224 JS::Handle<JSObject*> aGivenProto) {
225 return CompressionStream_Binding::Wrap(aCx, this, aGivenProto);
228 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
229 already_AddRefed<CompressionStream> CompressionStream::Constructor(
230 const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) {
231 // Step 1: If format is unsupported in CompressionStream, then throw a
232 // TypeError.
233 // XXX: Skipped as we are using enum for this
235 // Step 2 - 4: (Done in CompressionStreamAlgorithms)
237 // Step 5: Set this's transform to a new TransformStream.
239 // Step 6: Set up this's transform with transformAlgorithm set to
240 // transformAlgorithm and flushAlgorithm set to flushAlgorithm.
241 auto algorithms = MakeRefPtr<CompressionStreamAlgorithms>(aFormat);
243 RefPtr<TransformStream> stream =
244 TransformStream::CreateGeneric(aGlobal, *algorithms, aRv);
245 if (aRv.Failed()) {
246 return nullptr;
248 return do_AddRef(new CompressionStream(aGlobal.GetAsSupports(), *stream));
251 already_AddRefed<ReadableStream> CompressionStream::Readable() const {
252 return do_AddRef(mStream->Readable());
255 already_AddRefed<WritableStream> CompressionStream::Writable() const {
256 return do_AddRef(mStream->Writable());
259 } // namespace mozilla::dom