tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 2d634201d5e3e496c68d21d9edae11bc29a9fe59
parent 509967b1d6fbe5c7178cda42356bbe47c71835d4
Author: Atila Butkovits <abutkovits@mozilla.com>
Date:   Mon, 20 Oct 2025 20:02:34 +0300

Revert "Bug 1994230 - Part 4: Split Zstd algorithms to a separate file r=smaug" for causing bustages at BindingUtils.h.

This reverts commit 3de0f7485272036cb4e87e392423da01897710b1.

Revert "Bug 1994230 - Part 3: Split Zlib algorithms to a separate file r=smaug"

This reverts commit 9ba4d97f23063c8ddc41b65b9a3faf83b57a791b.

Revert "Bug 1994230 - Part 2: Split DecompressionStreamAlgorithms to a separate file r=smaug"

This reverts commit b0add5032fb92dfe027049229e636f68dd6afdbb.

Revert "Bug 1994230 - Part 1: Put compression streams to dom/compression r=smaug"

This reverts commit ecb0acf11ebe146b7f56733d50b77a7d4cddba4e.

Diffstat:
Adom/base/CompressionStream.cpp | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adom/base/CompressionStream.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adom/base/CompressionStreamHelper.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adom/base/DecompressionStream.cpp | 524+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adom/base/DecompressionStream.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/base/moz.build | 16++++++++++++++++
Ddom/compression/BaseAlgorithms.cpp | 80-------------------------------------------------------------------------------
Ddom/compression/BaseAlgorithms.h | 54------------------------------------------------------
Ddom/compression/CompressionStream.cpp | 86-------------------------------------------------------------------------------
Ddom/compression/CompressionStream.h | 59-----------------------------------------------------------
Ddom/compression/DecompressionStream.cpp | 108-------------------------------------------------------------------------------
Ddom/compression/DecompressionStream.h | 59-----------------------------------------------------------
Ddom/compression/FormatZlib.cpp | 409-------------------------------------------------------------------------------
Ddom/compression/FormatZlib.h | 97-------------------------------------------------------------------------------
Ddom/compression/FormatZstd.cpp | 142-------------------------------------------------------------------------------
Ddom/compression/FormatZstd.h | 49-------------------------------------------------
Ddom/compression/moz.build | 23-----------------------
Mdom/moz.build | 1-
18 files changed, 1005 insertions(+), 1167 deletions(-)

diff --git a/dom/base/CompressionStream.cpp b/dom/base/CompressionStream.cpp @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/CompressionStream.h" + +#include "CompressionStreamHelper.h" +#include "js/TypeDecls.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/BufferSourceBinding.h" +#include "mozilla/dom/BufferSourceBindingFwd.h" +#include "mozilla/dom/CompressionStreamBinding.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/TextDecoderStream.h" +#include "mozilla/dom/TransformStream.h" +#include "mozilla/dom/TransformerCallbackHelpers.h" +#include "mozilla/dom/UnionTypes.h" +#include "mozilla/dom/WritableStream.h" + +// See the zlib manual in https://www.zlib.net/manual.html or in +// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h + +namespace mozilla::dom { +using namespace compression; + +class CompressionStreamAlgorithms : public TransformerAlgorithmsWrapper { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CompressionStreamAlgorithms, + TransformerAlgorithmsBase) + + static Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> Create( + CompressionFormat format) { + RefPtr<CompressionStreamAlgorithms> alg = new CompressionStreamAlgorithms(); + MOZ_TRY(alg->Init(format)); + return alg.forget(); + } + + private: + CompressionStreamAlgorithms() = default; + + [[nodiscard]] nsresult Init(CompressionFormat format) { + int8_t err = deflateInit2(&mZStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + ZLibWindowBits(format), 8 /* default memLevel */, + Z_DEFAULT_STRATEGY); + if (err == Z_MEM_ERROR) { + return NS_ERROR_OUT_OF_MEMORY; + } + MOZ_ASSERT(err == Z_OK); + return NS_OK; + } + + // Step 3 of + // https://wicg.github.io/compression/#dom-compressionstream-compressionstream + // Let transformAlgorithm be an algorithm which takes a chunk argument and + // runs the compress and enqueue a chunk algorithm with this and chunk. + MOZ_CAN_RUN_SCRIPT + void TransformCallbackImpl(JS::Handle<JS::Value> aChunk, + TransformStreamDefaultController& aController, + ErrorResult& aRv) override { + AutoJSAPI jsapi; + if (!jsapi.Init(aController.GetParentObject())) { + aRv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + + // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk + + // Step 1: If chunk is not a BufferSource type, then throw a TypeError. + RootedUnion<OwningBufferSource> bufferSource(cx); + if (!bufferSource.Init(cx, aChunk)) { + aRv.MightThrowJSException(); + aRv.StealExceptionFromJSContext(cx); + return; + } + + // Step 2: Let buffer be the result of compressing chunk with cs's format + // and context. + // Step 3 - 5: (Done in CompressAndEnqueue) + ProcessTypedArraysFixed( + bufferSource, + [&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY { + CompressAndEnqueue(cx, aData, Flush::No, aController, aRv); + }); + } + + // Step 4 of + // https://wicg.github.io/compression/#dom-compressionstream-compressionstream + // Let flushAlgorithm be an algorithm which takes no argument and runs the + // compress flush and enqueue algorithm with this. + MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl( + TransformStreamDefaultController& aController, + ErrorResult& aRv) override { + AutoJSAPI jsapi; + if (!jsapi.Init(aController.GetParentObject())) { + aRv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + + // https://wicg.github.io/compression/#compress-flush-and-enqueue + + // Step 1: Let buffer be the result of compressing an empty input with cs's + // format and context, with the finish flag. + // Step 2 - 4: (Done in CompressAndEnqueue) + CompressAndEnqueue(cx, Span<const uint8_t>(), Flush::Yes, aController, aRv); + } + + private: + // Shared by: + // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk + // https://wicg.github.io/compression/#compress-flush-and-enqueue + MOZ_CAN_RUN_SCRIPT void CompressAndEnqueue( + JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, + TransformStreamDefaultController& aController, ErrorResult& aRv) { + MOZ_ASSERT_IF(aFlush == Flush::Yes, !aInput.Length()); + + mZStream.avail_in = aInput.Length(); + mZStream.next_in = const_cast<uint8_t*>(aInput.Elements()); + + JS::RootedVector<JSObject*> array(aCx); + + do { + static uint16_t kBufferSize = 16384; + UniquePtr<uint8_t[], JS::FreePolicy> buffer( + static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); + if (!buffer) { + aRv.ThrowTypeError("Out of memory"); + return; + } + + mZStream.avail_out = kBufferSize; + mZStream.next_out = buffer.get(); + + int8_t err = deflate(&mZStream, intoZLibFlush(aFlush)); + + // From the manual: deflate() returns ... + switch (err) { + case Z_OK: + case Z_STREAM_END: + case Z_BUF_ERROR: + // * Z_OK if some progress has been made + // * Z_STREAM_END if all input has been consumed and all output has + // been produced (only when flush is set to Z_FINISH) + // * Z_BUF_ERROR if no progress is possible (for example avail_in or + // avail_out was zero). Note that Z_BUF_ERROR is not fatal, and + // deflate() can be called again with more input and more output space + // to continue compressing. + // + // (But of course no input should be given after Z_FINISH) + break; + case Z_STREAM_ERROR: + default: + // * Z_STREAM_ERROR if the stream state was inconsistent + // (which is fatal) + MOZ_ASSERT_UNREACHABLE("Unexpected compression error code"); + aRv.ThrowTypeError("Unexpected compression error"); + return; + } + + // Stream should end only when flushed, see above + // The reverse is not true as zlib may have big data to be flushed that is + // larger than the buffer size + MOZ_ASSERT_IF(err == Z_STREAM_END, aFlush == Flush::Yes); + + // At this point we either exhausted the input or the output buffer + MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out); + + size_t written = kBufferSize - mZStream.avail_out; + if (!written) { + break; + } + + // Step 3: If buffer is empty, return. + // (We'll implicitly return when the array is empty.) + + // Step 4: Split buffer into one or more non-empty pieces and convert them + // into Uint8Arrays. + // (The buffer is 'split' by having a fixed sized buffer above.) + + JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( + aCx, written, std::move(buffer))); + if (!view || !array.append(view)) { + JS_ClearPendingException(aCx); + aRv.ThrowTypeError("Out of memory"); + return; + } + } while (mZStream.avail_out == 0); + // From the manual: + // If deflate returns with avail_out == 0, this function must be called + // again with the same value of the flush parameter and more output space + // (updated avail_out) + + // Step 5: For each Uint8Array array, enqueue array in cs's transform. + for (const auto& view : array) { + JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view)); + aController.Enqueue(aCx, value, aRv); + if (aRv.Failed()) { + return; + } + } + } + + ~CompressionStreamAlgorithms() override { deflateEnd(&mZStream); }; + + z_stream mZStream = {}; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(CompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_IMPL_ADDREF_INHERITED(CompressionStreamAlgorithms, TransformerAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(CompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CompressionStream, mGlobal, mStream) +NS_IMPL_CYCLE_COLLECTING_ADDREF(CompressionStream) +NS_IMPL_CYCLE_COLLECTING_RELEASE(CompressionStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStream) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +CompressionStream::CompressionStream(nsISupports* aGlobal, + TransformStream& aStream) + : mGlobal(aGlobal), mStream(&aStream) {} + +CompressionStream::~CompressionStream() = default; + +JSObject* CompressionStream::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return CompressionStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// https://wicg.github.io/compression/#dom-compressionstream-compressionstream +already_AddRefed<CompressionStream> CompressionStream::Constructor( + const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) { + if (aFormat == CompressionFormat::Zstd) { + aRv.ThrowTypeError( + "'zstd' (value of argument 1) is not a valid value for enumeration " + "CompressionFormat."); + return nullptr; + } + + // Step 1: If format is unsupported in CompressionStream, then throw a + // TypeError. + // XXX: Skipped as we are using enum for this + + // Step 2 - 4: (Done in CompressionStreamAlgorithms) + + // Step 5: Set this's transform to a new TransformStream. + + // Step 6: Set up this's transform with transformAlgorithm set to + // transformAlgorithm and flushAlgorithm set to flushAlgorithm. + Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> algorithms = + CompressionStreamAlgorithms::Create(aFormat); + if (algorithms.isErr()) { + aRv.ThrowUnknownError("Not enough memory"); + return nullptr; + } + + RefPtr<CompressionStreamAlgorithms> alg = algorithms.unwrap(); + RefPtr<TransformStream> stream = + TransformStream::CreateGeneric(aGlobal, *alg, aRv); + if (aRv.Failed()) { + return nullptr; + } + return do_AddRef(new CompressionStream(aGlobal.GetAsSupports(), *stream)); +} + +already_AddRefed<ReadableStream> CompressionStream::Readable() const { + return do_AddRef(mStream->Readable()); +} + +already_AddRefed<WritableStream> CompressionStream::Writable() const { + return do_AddRef(mStream->Writable()); +} + +} // namespace mozilla::dom diff --git a/dom/base/CompressionStream.h b/dom/base/CompressionStream.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_COMPRESSIONSTREAM_H_ +#define DOM_COMPRESSIONSTREAM_H_ + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +namespace mozilla::dom { + +class ReadableStream; +class WritableStream; +class TransformStream; + +enum class CompressionFormat : uint8_t; + +class CompressionStream final : public nsISupports, public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CompressionStream) + + public: + CompressionStream(nsISupports* aGlobal, TransformStream& aStream); + + protected: + ~CompressionStream(); + + public: + nsISupports* GetParentObject() const { return mGlobal; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // TODO: Mark as MOZ_CAN_RUN_SCRIPT when IDL constructors can be (bug 1749042) + MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<CompressionStream> + Constructor(const GlobalObject& aGlobal, CompressionFormat aFormat, + ErrorResult& aRv); + + already_AddRefed<ReadableStream> Readable() const; + + already_AddRefed<WritableStream> Writable() const; + + private: + nsCOMPtr<nsISupports> mGlobal; + + RefPtr<TransformStream> mStream; +}; + +} // namespace mozilla::dom + +#endif // DOM_COMPRESSIONSTREAM_H_ diff --git a/dom/base/CompressionStreamHelper.h b/dom/base/CompressionStreamHelper.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_COMPRESSION_STREAM_HELPER_H_ +#define DOM_COMPRESSION_STREAM_HELPER_H_ + +#include "js/TypeDecls.h" +#include "mozilla/dom/CompressionStreamBinding.h" +#include "zlib.h" + +namespace mozilla::dom::compression { + +// A top-level, library-agnostic flush enum that should be converted +// into the native flush values for a given (de)compression library +// with a function defined below. +enum class Flush : bool { No, Yes }; + +inline uint8_t intoZLibFlush(Flush aFlush) { + switch (aFlush) { + case Flush::No: { + return Z_NO_FLUSH; + } + case Flush::Yes: { + return Z_FINISH; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown flush mode"); + return Z_NO_FLUSH; + } + } +} + +// From the docs in +// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h +inline int8_t ZLibWindowBits(CompressionFormat format) { + switch (format) { + case CompressionFormat::Deflate: + // The windowBits parameter is the base two logarithm of the window size + // (the size of the history buffer). It should be in the range 8..15 for + // this version of the library. Larger values of this parameter result + // in better compression at the expense of memory usage. + return 15; + case CompressionFormat::Deflate_raw: + // windowBits can also be –8..–15 for raw deflate. In this case, + // -windowBits determines the window size. + return -15; + case CompressionFormat::Gzip: + // windowBits can also be greater than 15 for optional gzip encoding. + // Add 16 to windowBits to write a simple gzip header and trailer around + // the compressed data instead of a zlib wrapper. + return 31; + default: + MOZ_ASSERT_UNREACHABLE("Unknown compression format"); + return 0; + } +} + +} // namespace mozilla::dom::compression + +#endif // DOM_COMPRESSION_STREAM_HELPER_H_ diff --git a/dom/base/DecompressionStream.cpp b/dom/base/DecompressionStream.cpp @@ -0,0 +1,524 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/DecompressionStream.h" + +#include "CompressionStreamHelper.h" +#include "js/TypeDecls.h" +#include "mozilla/Assertions.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/BufferSourceBinding.h" +#include "mozilla/dom/BufferSourceBindingFwd.h" +#include "mozilla/dom/DecompressionStreamBinding.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/TextDecoderStream.h" +#include "mozilla/dom/TransformStream.h" +#include "mozilla/dom/TransformerCallbackHelpers.h" +#include "mozilla/dom/UnionTypes.h" +#include "mozilla/dom/WritableStream.h" +#include "zstd/zstd.h" + +namespace mozilla::dom { +using namespace compression; + +class DecompressionStreamAlgorithms : public TransformerAlgorithmsWrapper { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecompressionStreamAlgorithms, + TransformerAlgorithmsBase) + + // Step 3 of + // https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream + // Let transformAlgorithm be an algorithm which takes a chunk argument and + // runs the compress and enqueue a chunk algorithm with this and chunk. + MOZ_CAN_RUN_SCRIPT + void TransformCallbackImpl(JS::Handle<JS::Value> aChunk, + TransformStreamDefaultController& aController, + ErrorResult& aRv) override { + AutoJSAPI jsapi; + if (!jsapi.Init(aController.GetParentObject())) { + aRv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + + // https://compression.spec.whatwg.org/#decompress-and-enqueue-a-chunk + + // Step 1: If chunk is not a BufferSource type, then throw a TypeError. + RootedUnion<OwningBufferSource> bufferSource(cx); + if (!bufferSource.Init(cx, aChunk)) { + aRv.MightThrowJSException(); + aRv.StealExceptionFromJSContext(cx); + return; + } + + // Step 2: Let buffer be the result of decompressing chunk with ds's format + // and context. If this results in an error, then throw a TypeError. + // Step 3 - 5: (Done in DecompressAndEnqueue) + ProcessTypedArraysFixed( + bufferSource, + [&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY { + DecompressAndEnqueue(cx, aData, Flush::No, aController, aRv); + }); + } + + // Step 4 of + // https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream + // Let flushAlgorithm be an algorithm which takes no argument and runs the + // compress flush and enqueue algorithm with this. + MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl( + TransformStreamDefaultController& aController, + ErrorResult& aRv) override { + AutoJSAPI jsapi; + if (!jsapi.Init(aController.GetParentObject())) { + aRv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + + // https://wicg.github.io/compression/#decompress-flush-and-enqueue + + // Step 1: Let buffer be the result of decompressing an empty input with + // ds's format and context, with the finish flag. + // Step 2 - 4: (Done in DecompressAndEnqueue) + DecompressAndEnqueue(cx, Span<const uint8_t>(), Flush::Yes, aController, + aRv); + } + + protected: + static const uint16_t kBufferSize = 16384; + + ~DecompressionStreamAlgorithms() = default; + + MOZ_CAN_RUN_SCRIPT + virtual void DecompressAndEnqueue( + JSContext* aCx, Span<const uint8_t> aInput, Flush, + TransformStreamDefaultController& aController, ErrorResult& aRv) = 0; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(DecompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_IMPL_ADDREF_INHERITED(DecompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(DecompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) + +// See the zlib manual in https://www.zlib.net/manual.html or in +// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h +class ZLibDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ZLibDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) + + static Result<already_AddRefed<ZLibDecompressionStreamAlgorithms>, nsresult> + Create(CompressionFormat format) { + RefPtr<ZLibDecompressionStreamAlgorithms> alg = + new ZLibDecompressionStreamAlgorithms(); + MOZ_TRY(alg->Init(format)); + return alg.forget(); + } + + private: + ZLibDecompressionStreamAlgorithms() = default; + + [[nodiscard]] nsresult Init(CompressionFormat format) { + int8_t err = inflateInit2(&mZStream, ZLibWindowBits(format)); + if (err == Z_MEM_ERROR) { + return NS_ERROR_OUT_OF_MEMORY; + } + MOZ_ASSERT(err == Z_OK); + return NS_OK; + } + + private: + // Shared by: + // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk + // https://wicg.github.io/compression/#decompress-flush-and-enqueue + // All data errors throw TypeError by step 2: If this results in an error, + // then throw a TypeError. + MOZ_CAN_RUN_SCRIPT void DecompressAndEnqueue( + JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, + TransformStreamDefaultController& aController, + ErrorResult& aRv) override { + MOZ_ASSERT_IF(aFlush == Flush::Yes, !aInput.Length()); + + mZStream.avail_in = aInput.Length(); + mZStream.next_in = const_cast<uint8_t*>(aInput.Elements()); + + JS::RootedVector<JSObject*> array(aCx); + + do { + UniquePtr<uint8_t[], JS::FreePolicy> buffer( + static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); + if (!buffer) { + aRv.ThrowTypeError("Out of memory"); + return; + } + + mZStream.avail_out = kBufferSize; + mZStream.next_out = buffer.get(); + + int8_t err = inflate(&mZStream, intoZLibFlush(aFlush)); + + // From the manual: inflate() returns ... + switch (err) { + case Z_DATA_ERROR: + // Z_DATA_ERROR if the input data was corrupted (input stream not + // conforming to the zlib format or incorrect check value, in which + // case strm->msg points to a string with a more specific error) + aRv.ThrowTypeError("The input data is corrupted: "_ns + + nsDependentCString(mZStream.msg)); + return; + case Z_MEM_ERROR: + // Z_MEM_ERROR if there was not enough memory + aRv.ThrowTypeError("Out of memory"); + return; + case Z_NEED_DICT: + // Z_NEED_DICT if a preset dictionary is needed at this point + // + // From the `deflate` section of + // https://wicg.github.io/compression/#supported-formats: + // * The FDICT flag is not supported by these APIs, and will error the + // stream if set. + // And FDICT means preset dictionary per + // https://datatracker.ietf.org/doc/html/rfc1950#page-5. + aRv.ThrowTypeError( + "The stream needs a preset dictionary but such setup is " + "unsupported"); + return; + case Z_STREAM_END: + // Z_STREAM_END if the end of the compressed data has been reached and + // all uncompressed output has been produced + // + // https://wicg.github.io/compression/#supported-formats has error + // conditions for each compression format when additional input comes + // after stream end. + // Note that additional calls for inflate() immediately emits + // Z_STREAM_END after this point. + mObservedStreamEnd = true; + break; + case Z_OK: + case Z_BUF_ERROR: + // * Z_OK if some progress has been made + // * Z_BUF_ERROR if no progress was possible or if there was not + // enough room in the output buffer when Z_FINISH is used. Note that + // Z_BUF_ERROR is not fatal, and inflate() can be called again with + // more input and more output space to continue decompressing. + // + // (But of course no input should be given after Z_FINISH) + break; + case Z_STREAM_ERROR: + default: + // * Z_STREAM_ERROR if the stream state was inconsistent + // (which is fatal) + MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code"); + aRv.ThrowTypeError("Unexpected decompression error"); + return; + } + + // At this point we either exhausted the input or the output buffer, or + // met the stream end. + MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out || + mObservedStreamEnd); + + size_t written = kBufferSize - mZStream.avail_out; + if (!written) { + break; + } + + // Step 3: If buffer is empty, return. + // (We'll implicitly return when the array is empty.) + + // Step 4: Split buffer into one or more non-empty pieces and convert them + // into Uint8Arrays. + // (The buffer is 'split' by having a fixed sized buffer above.) + + JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( + aCx, written, std::move(buffer))); + if (!view || !array.append(view)) { + JS_ClearPendingException(aCx); + aRv.ThrowTypeError("Out of memory"); + return; + } + } while (mZStream.avail_out == 0 && !mObservedStreamEnd); + // From the manual: + // * It must update next_out and avail_out when avail_out has dropped to + // zero. + // * inflate() should normally be called until it returns Z_STREAM_END or an + // error. + + // Step 5: For each Uint8Array array, enqueue array in ds's transform. + for (const auto& view : array) { + JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view)); + aController.Enqueue(aCx, value, aRv); + if (aRv.Failed()) { + return; + } + } + + // Step 6: If the end of the compressed input has been reached, and ds's + // context has not fully consumed chunk, then throw a TypeError. + if (mObservedStreamEnd && mZStream.avail_in > 0) { + aRv.ThrowTypeError("Unexpected input after the end of stream"); + return; + } + + // Step 3 of + // https://wicg.github.io/compression/#decompress-flush-and-enqueue + // If the end of the compressed input has not been reached, then throw a + // TypeError. + if (aFlush == Flush::Yes && !mObservedStreamEnd) { + aRv.ThrowTypeError("The input is ended without reaching the stream end"); + return; + } + } + + ~ZLibDecompressionStreamAlgorithms() override { inflateEnd(&mZStream); } + + z_stream mZStream = {}; + bool mObservedStreamEnd = false; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ZLibDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) +NS_IMPL_ADDREF_INHERITED(ZLibDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) +NS_IMPL_RELEASE_INHERITED(ZLibDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ZLibDecompressionStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(DecompressionStreamAlgorithms) + +// See the zstd manual in https://facebook.github.io/zstd/zstd_manual.html or in +// https://searchfox.org/mozilla-central/source/third_party/zstd/lib/zstd.h +class ZstdDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ZstdDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) + + static Result<already_AddRefed<ZstdDecompressionStreamAlgorithms>, nsresult> + Create() { + RefPtr<ZstdDecompressionStreamAlgorithms> alg = + new ZstdDecompressionStreamAlgorithms(); + MOZ_TRY(alg->Init()); + return alg.forget(); + } + + private: + ZstdDecompressionStreamAlgorithms() = default; + + [[nodiscard]] nsresult Init() { + mDStream = ZSTD_createDStream(); + if (!mDStream) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Refuse any frame requiring larger than (1 << WINDOW_LOG_MAX) window size. + // Note: 1 << 23 == 8 * 1024 * 1024 + static const uint8_t WINDOW_LOG_MAX = 23; + ZSTD_DCtx_setParameter(mDStream, ZSTD_d_windowLogMax, WINDOW_LOG_MAX); + + return NS_OK; + } + + // Shared by: + // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk + // https://wicg.github.io/compression/#decompress-flush-and-enqueue + // All data errors throw TypeError by step 2: If this results in an error, + // then throw a TypeError. + MOZ_CAN_RUN_SCRIPT void DecompressAndEnqueue( + JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, + TransformStreamDefaultController& aController, + ErrorResult& aRv) override { + MOZ_ASSERT_IF(aFlush == Flush::Yes, !aInput.Length()); + + if (mObservedStreamEnd && aInput.Length() > 0) { + aRv.ThrowTypeError("Unexpected input after the end of stream"); + return; + } + + ZSTD_inBuffer inBuffer = { + /* src */ const_cast<uint8_t*>(aInput.Elements()), + /* size */ aInput.Length(), + /* pos */ 0}; + + JS::RootedVector<JSObject*> array(aCx); + + while (inBuffer.pos < inBuffer.size && !mObservedStreamEnd) { + UniquePtr<uint8_t[], JS::FreePolicy> buffer( + static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); + if (!buffer) { + aRv.ThrowTypeError("Out of memory"); + return; + } + + ZSTD_outBuffer outBuffer = {/* dst */ buffer.get(), + /* size */ kBufferSize, + /* pos */ 0}; + + size_t rv = ZSTD_decompressStream(mDStream, &outBuffer, &inBuffer); + if (ZSTD_isError(rv)) { + aRv.ThrowTypeError("zstd decompression error: "_ns + + nsDependentCString(ZSTD_getErrorName(rv))); + return; + } + + if (rv == 0) { + mObservedStreamEnd = true; + } + + // Step 3: If buffer is empty, return. + // (We'll implicitly return when the array is empty.) + + // Step 4: Split buffer into one or more non-empty pieces and convert them + // into Uint8Arrays. + // (The buffer is 'split' by having a fixed sized buffer above.) + + size_t written = outBuffer.pos; + if (written > 0) { + JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( + aCx, written, std::move(buffer))); + if (!view || !array.append(view)) { + JS_ClearPendingException(aCx); + aRv.ThrowTypeError("Out of memory"); + return; + } + } + } + + // Step 5: For each Uint8Array array, enqueue array in ds's transform. + for (const auto& view : array) { + JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view)); + aController.Enqueue(aCx, value, aRv); + if (aRv.Failed()) { + return; + } + } + + // Step 6: If the end of the compressed input has been reached, and ds's + // context has not fully consumed chunk, then throw a TypeError. + if (mObservedStreamEnd && inBuffer.pos < inBuffer.size) { + aRv.ThrowTypeError("Unexpected input after the end of stream"); + return; + } + + // Step 3 of + // https://wicg.github.io/compression/#decompress-flush-and-enqueue + // If the end of the compressed input has not been reached, then throw a + // TypeError. + if (aFlush == Flush::Yes && !mObservedStreamEnd) { + aRv.ThrowTypeError("The input is ended without reaching the stream end"); + return; + } + } + + ~ZstdDecompressionStreamAlgorithms() override { + if (mDStream) { + ZSTD_freeDStream(mDStream); + mDStream = nullptr; + } + } + + ZSTD_DStream* mDStream = nullptr; + bool mObservedStreamEnd = false; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ZstdDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) +NS_IMPL_ADDREF_INHERITED(ZstdDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) +NS_IMPL_RELEASE_INHERITED(ZstdDecompressionStreamAlgorithms, + DecompressionStreamAlgorithms) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ZstdDecompressionStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(DecompressionStreamAlgorithms) + +/* + * Constructs either a ZLibDecompressionStreamAlgorithms or a + * ZstdDecompressionStreamAlgorithms, based on the CompressionFormat. + */ +static Result<already_AddRefed<DecompressionStreamAlgorithms>, nsresult> +CreateDecompressionStreamAlgorithms(CompressionFormat aFormat) { + if (aFormat == CompressionFormat::Zstd) { + RefPtr<DecompressionStreamAlgorithms> zstdAlgos = + MOZ_TRY(ZstdDecompressionStreamAlgorithms::Create()); + return zstdAlgos.forget(); + } + + RefPtr<DecompressionStreamAlgorithms> zlibAlgos = + MOZ_TRY(ZLibDecompressionStreamAlgorithms::Create(aFormat)); + return zlibAlgos.forget(); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DecompressionStream, mGlobal, mStream) +NS_IMPL_CYCLE_COLLECTING_ADDREF(DecompressionStream) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DecompressionStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStream) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +DecompressionStream::DecompressionStream(nsISupports* aGlobal, + TransformStream& aStream) + : mGlobal(aGlobal), mStream(&aStream) {} + +DecompressionStream::~DecompressionStream() = default; + +JSObject* DecompressionStream::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return DecompressionStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream +already_AddRefed<DecompressionStream> DecompressionStream::Constructor( + const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) { + if (aFormat == CompressionFormat::Zstd && + aGlobal.CallerType() != CallerType::System && + !StaticPrefs::dom_compression_streams_zstd_enabled()) { + aRv.ThrowTypeError( + "'zstd' (value of argument 1) is not a valid value for enumeration " + "CompressionFormat."); + return nullptr; + } + + // Step 1: If format is unsupported in DecompressionStream, then throw a + // TypeError. + // XXX: Skipped as we are using enum for this + + // Step 2 - 4: (Done in {ZLib|Zstd}DecompressionStreamAlgorithms) + + // Step 5: Set this's transform to a new TransformStream. + + // Step 6: Set up this's transform with transformAlgorithm set to + // transformAlgorithm and flushAlgorithm set to flushAlgorithm. + + Result<already_AddRefed<DecompressionStreamAlgorithms>, nsresult> algorithms = + CreateDecompressionStreamAlgorithms(aFormat); + if (algorithms.isErr()) { + aRv.ThrowUnknownError("Not enough memory"); + return nullptr; + } + + RefPtr<DecompressionStreamAlgorithms> alg = algorithms.unwrap(); + RefPtr<TransformStream> stream = + TransformStream::CreateGeneric(aGlobal, *alg, aRv); + if (aRv.Failed()) { + return nullptr; + } + return do_AddRef(new DecompressionStream(aGlobal.GetAsSupports(), *stream)); +} + +already_AddRefed<ReadableStream> DecompressionStream::Readable() const { + return do_AddRef(mStream->Readable()); +}; + +already_AddRefed<WritableStream> DecompressionStream::Writable() const { + return do_AddRef(mStream->Writable()); +}; + +} // namespace mozilla::dom diff --git a/dom/base/DecompressionStream.h b/dom/base/DecompressionStream.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_DECOMPRESSIONSTREAM_H_ +#define DOM_DECOMPRESSIONSTREAM_H_ + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +namespace mozilla::dom { + +class ReadableStream; +class WritableStream; +class TransformStream; + +enum class CompressionFormat : uint8_t; + +class DecompressionStream final : public nsISupports, public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DecompressionStream) + + public: + DecompressionStream(nsISupports* aGlobal, TransformStream& aStream); + + protected: + ~DecompressionStream(); + + public: + nsISupports* GetParentObject() const { return mGlobal; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // TODO: Mark as MOZ_CAN_RUN_SCRIPT when IDL constructors can be (bug 1749042) + MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<DecompressionStream> + Constructor(const GlobalObject& global, CompressionFormat format, + ErrorResult& aRv); + + already_AddRefed<ReadableStream> Readable() const; + + already_AddRefed<WritableStream> Writable() const; + + private: + nsCOMPtr<nsISupports> mGlobal; + + RefPtr<TransformStream> mStream; +}; + +} // namespace mozilla::dom + +#endif // DOM_DECOMPRESSIONSTREAM_H_ diff --git a/dom/base/moz.build b/dom/base/moz.build @@ -162,10 +162,12 @@ EXPORTS.mozilla.dom += [ "ChromeNodeList.h", "ChromeUtils.h", "Comment.h", + "CompressionStream.h", "ContentFrameMessageManager.h", "ContentProcessMessageManager.h", "CrossShadowBoundaryRange.h", "CustomElementRegistry.h", + "DecompressionStream.h", "DirectionalityUtils.h", "DocGroup.h", "Document.h", @@ -513,6 +515,20 @@ if CONFIG["MOZ_PLACES"]: "PlacesWeakCallbackWrapper.cpp", ] +# on win32 if we add these files to UNIFIED_SOURCES then the compiler generates +# larger stack frames for some recursive functions that cause us to hit stack +# overflows (see bug 1824565) +if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["TARGET_CPU"] == "x86": + SOURCES += [ + "CompressionStream.cpp", + "DecompressionStream.cpp", + ] +else: + UNIFIED_SOURCES += [ + "CompressionStream.cpp", + "DecompressionStream.cpp", + ] + # these files couldn't be in UNIFIED_SOURCES for now for reasons given below: SOURCES += [ # Several conflicts with other bindings. diff --git a/dom/compression/BaseAlgorithms.cpp b/dom/compression/BaseAlgorithms.cpp @@ -1,80 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "BaseAlgorithms.h" - -#include "mozilla/dom/BufferSourceBindingFwd.h" -#include "mozilla/dom/TransformStreamDefaultController.h" -#include "mozilla/dom/UnionTypes.h" - -namespace mozilla::dom::compression { - -// Step 3 of -// https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream -// Let transformAlgorithm be an algorithm which takes a chunk argument and -// runs the compress and enqueue a chunk algorithm with this and chunk. -MOZ_CAN_RUN_SCRIPT -void DecompressionStreamAlgorithms::TransformCallbackImpl( - JS::Handle<JS::Value> aChunk, TransformStreamDefaultController& aController, - ErrorResult& aRv) { - AutoJSAPI jsapi; - if (!jsapi.Init(aController.GetParentObject())) { - aRv.ThrowUnknownError("Internal error"); - return; - } - JSContext* cx = jsapi.cx(); - - // https://compression.spec.whatwg.org/#decompress-and-enqueue-a-chunk - - // Step 1: If chunk is not a BufferSource type, then throw a TypeError. - RootedUnion<OwningBufferSource> bufferSource(cx); - if (!bufferSource.Init(cx, aChunk)) { - aRv.MightThrowJSException(); - aRv.StealExceptionFromJSContext(cx); - return; - } - - // Step 2: Let buffer be the result of decompressing chunk with ds's format - // and context. If this results in an error, then throw a TypeError. - // Step 3 - 5: (Done in DecompressAndEnqueue) - ProcessTypedArraysFixed( - bufferSource, - [&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY { - DecompressAndEnqueue(cx, aData, Flush::No, aController, aRv); - }); -} - -// Step 4 of -// https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream -// Let flushAlgorithm be an algorithm which takes no argument and runs the -// compress flush and enqueue algorithm with this. -MOZ_CAN_RUN_SCRIPT void DecompressionStreamAlgorithms::FlushCallbackImpl( - TransformStreamDefaultController& aController, ErrorResult& aRv) { - AutoJSAPI jsapi; - if (!jsapi.Init(aController.GetParentObject())) { - aRv.ThrowUnknownError("Internal error"); - return; - } - JSContext* cx = jsapi.cx(); - - // https://wicg.github.io/compression/#decompress-flush-and-enqueue - - // Step 1: Let buffer be the result of decompressing an empty input with - // ds's format and context, with the finish flag. - // Step 2 - 4: (Done in DecompressAndEnqueue) - DecompressAndEnqueue(cx, Span<const uint8_t>(), Flush::Yes, aController, aRv); -} - -NS_IMPL_CYCLE_COLLECTION_INHERITED(DecompressionStreamAlgorithms, - TransformerAlgorithmsBase) -NS_IMPL_ADDREF_INHERITED(DecompressionStreamAlgorithms, - TransformerAlgorithmsBase) -NS_IMPL_RELEASE_INHERITED(DecompressionStreamAlgorithms, - TransformerAlgorithmsBase) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStreamAlgorithms) -NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) - -} // namespace mozilla::dom::compression diff --git a/dom/compression/BaseAlgorithms.h b/dom/compression/BaseAlgorithms.h @@ -1,54 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef DOM_COMPRESSION_BASEALGORITHMS_H_ -#define DOM_COMPRESSION_BASEALGORITHMS_H_ - -#include "mozilla/dom/TransformerCallbackHelpers.h" - -namespace mozilla::dom::compression { - -// A top-level, library-agnostic flush enum that should be converted -// into the native flush values for a given (de)compression library -// with a function defined below. -enum class Flush : bool { No, Yes }; - -class DecompressionStreamAlgorithms : public TransformerAlgorithmsWrapper { - public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecompressionStreamAlgorithms, - TransformerAlgorithmsBase) - - // Step 3 of - // https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream - // Let transformAlgorithm be an algorithm which takes a chunk argument and - // runs the compress and enqueue a chunk algorithm with this and chunk. - MOZ_CAN_RUN_SCRIPT - void TransformCallbackImpl(JS::Handle<JS::Value> aChunk, - TransformStreamDefaultController& aController, - ErrorResult& aRv) override; - - // Step 4 of - // https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream - // Let flushAlgorithm be an algorithm which takes no argument and runs the - // compress flush and enqueue algorithm with this. - MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl( - TransformStreamDefaultController& aController, ErrorResult& aRv) override; - - protected: - static const uint16_t kBufferSize = 16384; - - ~DecompressionStreamAlgorithms() = default; - - MOZ_CAN_RUN_SCRIPT - virtual void DecompressAndEnqueue( - JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, - TransformStreamDefaultController& aController, ErrorResult& aRv) = 0; -}; - -} // namespace mozilla::dom::compression - -#endif // DOM_COMPRESSION_BASEALGORITHMS_H_ diff --git a/dom/compression/CompressionStream.cpp b/dom/compression/CompressionStream.cpp @@ -1,86 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "mozilla/dom/CompressionStream.h" - -#include "FormatZlib.h" -#include "js/TypeDecls.h" -#include "mozilla/dom/CompressionStreamBinding.h" -#include "mozilla/dom/ReadableStream.h" -#include "mozilla/dom/TextDecoderStream.h" -#include "mozilla/dom/TransformStream.h" -#include "mozilla/dom/WritableStream.h" - -// See the zlib manual in https://www.zlib.net/manual.html or in -// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h - -namespace mozilla::dom { -using namespace compression; - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CompressionStream, mGlobal, mStream) -NS_IMPL_CYCLE_COLLECTING_ADDREF(CompressionStream) -NS_IMPL_CYCLE_COLLECTING_RELEASE(CompressionStream) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStream) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -CompressionStream::CompressionStream(nsISupports* aGlobal, - TransformStream& aStream) - : mGlobal(aGlobal), mStream(&aStream) {} - -CompressionStream::~CompressionStream() = default; - -JSObject* CompressionStream::WrapObject(JSContext* aCx, - JS::Handle<JSObject*> aGivenProto) { - return CompressionStream_Binding::Wrap(aCx, this, aGivenProto); -} - -// https://wicg.github.io/compression/#dom-compressionstream-compressionstream -already_AddRefed<CompressionStream> CompressionStream::Constructor( - const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) { - if (aFormat == CompressionFormat::Zstd) { - aRv.ThrowTypeError( - "'zstd' (value of argument 1) is not a valid value for enumeration " - "CompressionFormat."); - return nullptr; - } - - // Step 1: If format is unsupported in CompressionStream, then throw a - // TypeError. - // XXX: Skipped as we are using enum for this - - // Step 2 - 4: (Done in CompressionStreamAlgorithms) - - // Step 5: Set this's transform to a new TransformStream. - - // Step 6: Set up this's transform with transformAlgorithm set to - // transformAlgorithm and flushAlgorithm set to flushAlgorithm. - Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> algorithms = - CompressionStreamAlgorithms::Create(aFormat); - if (algorithms.isErr()) { - aRv.ThrowUnknownError("Not enough memory"); - return nullptr; - } - - RefPtr<CompressionStreamAlgorithms> alg = algorithms.unwrap(); - RefPtr<TransformStream> stream = - TransformStream::CreateGeneric(aGlobal, *alg, aRv); - if (aRv.Failed()) { - return nullptr; - } - return do_AddRef(new CompressionStream(aGlobal.GetAsSupports(), *stream)); -} - -already_AddRefed<ReadableStream> CompressionStream::Readable() const { - return do_AddRef(mStream->Readable()); -} - -already_AddRefed<WritableStream> CompressionStream::Writable() const { - return do_AddRef(mStream->Writable()); -} - -} // namespace mozilla::dom diff --git a/dom/compression/CompressionStream.h b/dom/compression/CompressionStream.h @@ -1,59 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef DOM_COMPRESSION_COMPRESSIONSTREAM_H_ -#define DOM_COMPRESSION_COMPRESSIONSTREAM_H_ - -#include "js/TypeDecls.h" -#include "mozilla/Attributes.h" -#include "mozilla/ErrorResult.h" -#include "mozilla/dom/BindingDeclarations.h" -#include "nsCycleCollectionParticipant.h" -#include "nsWrapperCache.h" - -namespace mozilla::dom { - -class ReadableStream; -class WritableStream; -class TransformStream; - -enum class CompressionFormat : uint8_t; - -class CompressionStream final : public nsISupports, public nsWrapperCache { - public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CompressionStream) - - public: - CompressionStream(nsISupports* aGlobal, TransformStream& aStream); - - protected: - ~CompressionStream(); - - public: - nsISupports* GetParentObject() const { return mGlobal; } - - JSObject* WrapObject(JSContext* aCx, - JS::Handle<JSObject*> aGivenProto) override; - - // TODO: Mark as MOZ_CAN_RUN_SCRIPT when IDL constructors can be (bug 1749042) - MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<CompressionStream> - Constructor(const GlobalObject& aGlobal, CompressionFormat aFormat, - ErrorResult& aRv); - - already_AddRefed<ReadableStream> Readable() const; - - already_AddRefed<WritableStream> Writable() const; - - private: - nsCOMPtr<nsISupports> mGlobal; - - RefPtr<TransformStream> mStream; -}; - -} // namespace mozilla::dom - -#endif // DOM_COMPRESSION_COMPRESSIONSTREAM_H_ diff --git a/dom/compression/DecompressionStream.cpp b/dom/compression/DecompressionStream.cpp @@ -1,108 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "mozilla/dom/DecompressionStream.h" - -#include "BaseAlgorithms.h" -#include "FormatZlib.h" -#include "FormatZstd.h" -#include "js/TypeDecls.h" -#include "mozilla/Assertions.h" -#include "mozilla/StaticPrefs_dom.h" -#include "mozilla/dom/DecompressionStreamBinding.h" -#include "mozilla/dom/ReadableStream.h" -#include "mozilla/dom/TextDecoderStream.h" -#include "mozilla/dom/TransformStream.h" -#include "mozilla/dom/UnionTypes.h" -#include "mozilla/dom/WritableStream.h" - -namespace mozilla::dom { -using namespace compression; - -/* - * Constructs either a ZLibDecompressionStreamAlgorithms or a - * ZstdDecompressionStreamAlgorithms, based on the CompressionFormat. - */ -static Result<already_AddRefed<DecompressionStreamAlgorithms>, nsresult> -CreateDecompressionStreamAlgorithms(CompressionFormat aFormat) { - if (aFormat == CompressionFormat::Zstd) { - RefPtr<DecompressionStreamAlgorithms> zstdAlgos = - MOZ_TRY(ZstdDecompressionStreamAlgorithms::Create()); - return zstdAlgos.forget(); - } - - RefPtr<DecompressionStreamAlgorithms> zlibAlgos = - MOZ_TRY(ZLibDecompressionStreamAlgorithms::Create(aFormat)); - return zlibAlgos.forget(); -} - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DecompressionStream, mGlobal, mStream) -NS_IMPL_CYCLE_COLLECTING_ADDREF(DecompressionStream) -NS_IMPL_CYCLE_COLLECTING_RELEASE(DecompressionStream) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStream) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -DecompressionStream::DecompressionStream(nsISupports* aGlobal, - TransformStream& aStream) - : mGlobal(aGlobal), mStream(&aStream) {} - -DecompressionStream::~DecompressionStream() = default; - -JSObject* DecompressionStream::WrapObject(JSContext* aCx, - JS::Handle<JSObject*> aGivenProto) { - return DecompressionStream_Binding::Wrap(aCx, this, aGivenProto); -} - -// https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream -already_AddRefed<DecompressionStream> DecompressionStream::Constructor( - const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) { - if (aFormat == CompressionFormat::Zstd && - aGlobal.CallerType() != CallerType::System && - !StaticPrefs::dom_compression_streams_zstd_enabled()) { - aRv.ThrowTypeError( - "'zstd' (value of argument 1) is not a valid value for enumeration " - "CompressionFormat."); - return nullptr; - } - - // Step 1: If format is unsupported in DecompressionStream, then throw a - // TypeError. - // XXX: Skipped as we are using enum for this - - // Step 2 - 4: (Done in {ZLib|Zstd}DecompressionStreamAlgorithms) - - // Step 5: Set this's transform to a new TransformStream. - - // Step 6: Set up this's transform with transformAlgorithm set to - // transformAlgorithm and flushAlgorithm set to flushAlgorithm. - - Result<already_AddRefed<DecompressionStreamAlgorithms>, nsresult> algorithms = - CreateDecompressionStreamAlgorithms(aFormat); - if (algorithms.isErr()) { - aRv.ThrowUnknownError("Not enough memory"); - return nullptr; - } - - RefPtr<DecompressionStreamAlgorithms> alg = algorithms.unwrap(); - RefPtr<TransformStream> stream = - TransformStream::CreateGeneric(aGlobal, *alg, aRv); - if (aRv.Failed()) { - return nullptr; - } - return do_AddRef(new DecompressionStream(aGlobal.GetAsSupports(), *stream)); -} - -already_AddRefed<ReadableStream> DecompressionStream::Readable() const { - return do_AddRef(mStream->Readable()); -}; - -already_AddRefed<WritableStream> DecompressionStream::Writable() const { - return do_AddRef(mStream->Writable()); -}; - -} // namespace mozilla::dom diff --git a/dom/compression/DecompressionStream.h b/dom/compression/DecompressionStream.h @@ -1,59 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef DOM_COMPRESSION_DECOMPRESSIONSTREAM_H_ -#define DOM_COMPRESSION_DECOMPRESSIONSTREAM_H_ - -#include "js/TypeDecls.h" -#include "mozilla/Attributes.h" -#include "mozilla/ErrorResult.h" -#include "mozilla/dom/BindingDeclarations.h" -#include "nsCycleCollectionParticipant.h" -#include "nsWrapperCache.h" - -namespace mozilla::dom { - -class ReadableStream; -class WritableStream; -class TransformStream; - -enum class CompressionFormat : uint8_t; - -class DecompressionStream final : public nsISupports, public nsWrapperCache { - public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DecompressionStream) - - public: - DecompressionStream(nsISupports* aGlobal, TransformStream& aStream); - - protected: - ~DecompressionStream(); - - public: - nsISupports* GetParentObject() const { return mGlobal; } - - JSObject* WrapObject(JSContext* aCx, - JS::Handle<JSObject*> aGivenProto) override; - - // TODO: Mark as MOZ_CAN_RUN_SCRIPT when IDL constructors can be (bug 1749042) - MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<DecompressionStream> - Constructor(const GlobalObject& global, CompressionFormat format, - ErrorResult& aRv); - - already_AddRefed<ReadableStream> Readable() const; - - already_AddRefed<WritableStream> Writable() const; - - private: - nsCOMPtr<nsISupports> mGlobal; - - RefPtr<TransformStream> mStream; -}; - -} // namespace mozilla::dom - -#endif // DOM_COMPRESSION_DECOMPRESSIONSTREAM_H_ diff --git a/dom/compression/FormatZlib.cpp b/dom/compression/FormatZlib.cpp @@ -1,409 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "FormatZlib.h" - -#include "BaseAlgorithms.h" -#include "mozilla/dom/BufferSourceBindingFwd.h" -#include "mozilla/dom/CompressionStreamBinding.h" -#include "mozilla/dom/TransformStreamDefaultController.h" -#include "mozilla/dom/UnionTypes.h" - -namespace mozilla::dom::compression { - -NS_IMPL_CYCLE_COLLECTION_INHERITED(CompressionStreamAlgorithms, - TransformerAlgorithmsBase) -NS_IMPL_ADDREF_INHERITED(CompressionStreamAlgorithms, TransformerAlgorithmsBase) -NS_IMPL_RELEASE_INHERITED(CompressionStreamAlgorithms, - TransformerAlgorithmsBase) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStreamAlgorithms) -NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) - -NS_IMPL_CYCLE_COLLECTION_INHERITED(ZLibDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) -NS_IMPL_ADDREF_INHERITED(ZLibDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) -NS_IMPL_RELEASE_INHERITED(ZLibDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ZLibDecompressionStreamAlgorithms) -NS_INTERFACE_MAP_END_INHERITING(DecompressionStreamAlgorithms) - -inline uint8_t intoZLibFlush(Flush aFlush) { - switch (aFlush) { - case Flush::No: { - return Z_NO_FLUSH; - } - case Flush::Yes: { - return Z_FINISH; - } - default: { - MOZ_ASSERT_UNREACHABLE("Unknown flush mode"); - return Z_NO_FLUSH; - } - } -} - -// From the docs in -// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h -inline int8_t ZLibWindowBits(CompressionFormat format) { - switch (format) { - case CompressionFormat::Deflate: - // The windowBits parameter is the base two logarithm of the window size - // (the size of the history buffer). It should be in the range 8..15 for - // this version of the library. Larger values of this parameter result - // in better compression at the expense of memory usage. - return 15; - case CompressionFormat::Deflate_raw: - // windowBits can also be –8..–15 for raw deflate. In this case, - // -windowBits determines the window size. - return -15; - case CompressionFormat::Gzip: - // windowBits can also be greater than 15 for optional gzip encoding. - // Add 16 to windowBits to write a simple gzip header and trailer around - // the compressed data instead of a zlib wrapper. - return 31; - default: - MOZ_ASSERT_UNREACHABLE("Unknown compression format"); - return 0; - } -} - -Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> -CompressionStreamAlgorithms::Create(CompressionFormat format) { - RefPtr<CompressionStreamAlgorithms> alg = new CompressionStreamAlgorithms(); - MOZ_TRY(alg->Init(format)); - return alg.forget(); -} - -[[nodiscard]] nsresult CompressionStreamAlgorithms::Init( - CompressionFormat format) { - int8_t err = deflateInit2(&mZStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, - ZLibWindowBits(format), 8 /* default memLevel */, - Z_DEFAULT_STRATEGY); - if (err == Z_MEM_ERROR) { - return NS_ERROR_OUT_OF_MEMORY; - } - MOZ_ASSERT(err == Z_OK); - return NS_OK; -} - -// Step 3 of -// https://wicg.github.io/compression/#dom-compressionstream-compressionstream -// Let transformAlgorithm be an algorithm which takes a chunk argument and -// runs the compress and enqueue a chunk algorithm with this and chunk. -MOZ_CAN_RUN_SCRIPT -void CompressionStreamAlgorithms::TransformCallbackImpl( - JS::Handle<JS::Value> aChunk, TransformStreamDefaultController& aController, - ErrorResult& aRv) { - AutoJSAPI jsapi; - if (!jsapi.Init(aController.GetParentObject())) { - aRv.ThrowUnknownError("Internal error"); - return; - } - JSContext* cx = jsapi.cx(); - - // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk - - // Step 1: If chunk is not a BufferSource type, then throw a TypeError. - RootedUnion<OwningBufferSource> bufferSource(cx); - if (!bufferSource.Init(cx, aChunk)) { - aRv.MightThrowJSException(); - aRv.StealExceptionFromJSContext(cx); - return; - } - - // Step 2: Let buffer be the result of compressing chunk with cs's format - // and context. - // Step 3 - 5: (Done in CompressAndEnqueue) - ProcessTypedArraysFixed( - bufferSource, - [&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY { - CompressAndEnqueue(cx, aData, Flush::No, aController, aRv); - }); -} - -// Step 4 of -// https://wicg.github.io/compression/#dom-compressionstream-compressionstream -// Let flushAlgorithm be an algorithm which takes no argument and runs the -// compress flush and enqueue algorithm with this. -MOZ_CAN_RUN_SCRIPT void CompressionStreamAlgorithms::FlushCallbackImpl( - TransformStreamDefaultController& aController, ErrorResult& aRv) { - AutoJSAPI jsapi; - if (!jsapi.Init(aController.GetParentObject())) { - aRv.ThrowUnknownError("Internal error"); - return; - } - JSContext* cx = jsapi.cx(); - - // https://wicg.github.io/compression/#compress-flush-and-enqueue - - // Step 1: Let buffer be the result of compressing an empty input with cs's - // format and context, with the finish flag. - // Step 2 - 4: (Done in CompressAndEnqueue) - CompressAndEnqueue(cx, Span<const uint8_t>(), Flush::Yes, aController, aRv); -} - -// Shared by: -// https://wicg.github.io/compression/#compress-and-enqueue-a-chunk -// https://wicg.github.io/compression/#compress-flush-and-enqueue -MOZ_CAN_RUN_SCRIPT void CompressionStreamAlgorithms::CompressAndEnqueue( - JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, - TransformStreamDefaultController& aController, ErrorResult& aRv) { - MOZ_ASSERT_IF(aFlush == Flush::Yes, !aInput.Length()); - - mZStream.avail_in = aInput.Length(); - mZStream.next_in = const_cast<uint8_t*>(aInput.Elements()); - - JS::RootedVector<JSObject*> array(aCx); - - do { - static uint16_t kBufferSize = 16384; - UniquePtr<uint8_t[], JS::FreePolicy> buffer( - static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); - if (!buffer) { - aRv.ThrowTypeError("Out of memory"); - return; - } - - mZStream.avail_out = kBufferSize; - mZStream.next_out = buffer.get(); - - int8_t err = deflate(&mZStream, intoZLibFlush(aFlush)); - - // From the manual: deflate() returns ... - switch (err) { - case Z_OK: - case Z_STREAM_END: - case Z_BUF_ERROR: - // * Z_OK if some progress has been made - // * Z_STREAM_END if all input has been consumed and all output has - // been produced (only when flush is set to Z_FINISH) - // * Z_BUF_ERROR if no progress is possible (for example avail_in or - // avail_out was zero). Note that Z_BUF_ERROR is not fatal, and - // deflate() can be called again with more input and more output space - // to continue compressing. - // - // (But of course no input should be given after Z_FINISH) - break; - case Z_STREAM_ERROR: - default: - // * Z_STREAM_ERROR if the stream state was inconsistent - // (which is fatal) - MOZ_ASSERT_UNREACHABLE("Unexpected compression error code"); - aRv.ThrowTypeError("Unexpected compression error"); - return; - } - - // Stream should end only when flushed, see above - // The reverse is not true as zlib may have big data to be flushed that is - // larger than the buffer size - MOZ_ASSERT_IF(err == Z_STREAM_END, aFlush == Flush::Yes); - - // At this point we either exhausted the input or the output buffer - MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out); - - size_t written = kBufferSize - mZStream.avail_out; - if (!written) { - break; - } - - // Step 3: If buffer is empty, return. - // (We'll implicitly return when the array is empty.) - - // Step 4: Split buffer into one or more non-empty pieces and convert them - // into Uint8Arrays. - // (The buffer is 'split' by having a fixed sized buffer above.) - - JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( - aCx, written, std::move(buffer))); - if (!view || !array.append(view)) { - JS_ClearPendingException(aCx); - aRv.ThrowTypeError("Out of memory"); - return; - } - } while (mZStream.avail_out == 0); - // From the manual: - // If deflate returns with avail_out == 0, this function must be called - // again with the same value of the flush parameter and more output space - // (updated avail_out) - - // Step 5: For each Uint8Array array, enqueue array in cs's transform. - for (const auto& view : array) { - JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view)); - aController.Enqueue(aCx, value, aRv); - if (aRv.Failed()) { - return; - } - } -} - -CompressionStreamAlgorithms::~CompressionStreamAlgorithms() { - deflateEnd(&mZStream); -}; - -Result<already_AddRefed<ZLibDecompressionStreamAlgorithms>, nsresult> -ZLibDecompressionStreamAlgorithms::Create(CompressionFormat format) { - RefPtr<ZLibDecompressionStreamAlgorithms> alg = - new ZLibDecompressionStreamAlgorithms(); - MOZ_TRY(alg->Init(format)); - return alg.forget(); -} - -[[nodiscard]] nsresult ZLibDecompressionStreamAlgorithms::Init( - CompressionFormat format) { - int8_t err = inflateInit2(&mZStream, ZLibWindowBits(format)); - if (err == Z_MEM_ERROR) { - return NS_ERROR_OUT_OF_MEMORY; - } - MOZ_ASSERT(err == Z_OK); - return NS_OK; -} - -// Shared by: -// https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk -// https://wicg.github.io/compression/#decompress-flush-and-enqueue -// All data errors throw TypeError by step 2: If this results in an error, -// then throw a TypeError. -MOZ_CAN_RUN_SCRIPT void ZLibDecompressionStreamAlgorithms::DecompressAndEnqueue( - JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, - TransformStreamDefaultController& aController, ErrorResult& aRv) { - MOZ_ASSERT_IF(aFlush == Flush::Yes, !aInput.Length()); - - mZStream.avail_in = aInput.Length(); - mZStream.next_in = const_cast<uint8_t*>(aInput.Elements()); - - JS::RootedVector<JSObject*> array(aCx); - - do { - UniquePtr<uint8_t[], JS::FreePolicy> buffer( - static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); - if (!buffer) { - aRv.ThrowTypeError("Out of memory"); - return; - } - - mZStream.avail_out = kBufferSize; - mZStream.next_out = buffer.get(); - - int8_t err = inflate(&mZStream, intoZLibFlush(aFlush)); - - // From the manual: inflate() returns ... - switch (err) { - case Z_DATA_ERROR: - // Z_DATA_ERROR if the input data was corrupted (input stream not - // conforming to the zlib format or incorrect check value, in which - // case strm->msg points to a string with a more specific error) - aRv.ThrowTypeError("The input data is corrupted: "_ns + - nsDependentCString(mZStream.msg)); - return; - case Z_MEM_ERROR: - // Z_MEM_ERROR if there was not enough memory - aRv.ThrowTypeError("Out of memory"); - return; - case Z_NEED_DICT: - // Z_NEED_DICT if a preset dictionary is needed at this point - // - // From the `deflate` section of - // https://wicg.github.io/compression/#supported-formats: - // * The FDICT flag is not supported by these APIs, and will error the - // stream if set. - // And FDICT means preset dictionary per - // https://datatracker.ietf.org/doc/html/rfc1950#page-5. - aRv.ThrowTypeError( - "The stream needs a preset dictionary but such setup is " - "unsupported"); - return; - case Z_STREAM_END: - // Z_STREAM_END if the end of the compressed data has been reached and - // all uncompressed output has been produced - // - // https://wicg.github.io/compression/#supported-formats has error - // conditions for each compression format when additional input comes - // after stream end. - // Note that additional calls for inflate() immediately emits - // Z_STREAM_END after this point. - mObservedStreamEnd = true; - break; - case Z_OK: - case Z_BUF_ERROR: - // * Z_OK if some progress has been made - // * Z_BUF_ERROR if no progress was possible or if there was not - // enough room in the output buffer when Z_FINISH is used. Note that - // Z_BUF_ERROR is not fatal, and inflate() can be called again with - // more input and more output space to continue decompressing. - // - // (But of course no input should be given after Z_FINISH) - break; - case Z_STREAM_ERROR: - default: - // * Z_STREAM_ERROR if the stream state was inconsistent - // (which is fatal) - MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code"); - aRv.ThrowTypeError("Unexpected decompression error"); - return; - } - - // At this point we either exhausted the input or the output buffer, or - // met the stream end. - MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out || mObservedStreamEnd); - - size_t written = kBufferSize - mZStream.avail_out; - if (!written) { - break; - } - - // Step 3: If buffer is empty, return. - // (We'll implicitly return when the array is empty.) - - // Step 4: Split buffer into one or more non-empty pieces and convert them - // into Uint8Arrays. - // (The buffer is 'split' by having a fixed sized buffer above.) - - JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( - aCx, written, std::move(buffer))); - if (!view || !array.append(view)) { - JS_ClearPendingException(aCx); - aRv.ThrowTypeError("Out of memory"); - return; - } - } while (mZStream.avail_out == 0 && !mObservedStreamEnd); - // From the manual: - // * It must update next_out and avail_out when avail_out has dropped to - // zero. - // * inflate() should normally be called until it returns Z_STREAM_END or an - // error. - - // Step 5: For each Uint8Array array, enqueue array in ds's transform. - for (const auto& view : array) { - JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view)); - aController.Enqueue(aCx, value, aRv); - if (aRv.Failed()) { - return; - } - } - - // Step 6: If the end of the compressed input has been reached, and ds's - // context has not fully consumed chunk, then throw a TypeError. - if (mObservedStreamEnd && mZStream.avail_in > 0) { - aRv.ThrowTypeError("Unexpected input after the end of stream"); - return; - } - - // Step 3 of - // https://wicg.github.io/compression/#decompress-flush-and-enqueue - // If the end of the compressed input has not been reached, then throw a - // TypeError. - if (aFlush == Flush::Yes && !mObservedStreamEnd) { - aRv.ThrowTypeError("The input is ended without reaching the stream end"); - return; - } -} - -ZLibDecompressionStreamAlgorithms::~ZLibDecompressionStreamAlgorithms() { - inflateEnd(&mZStream); -} - -} // namespace mozilla::dom::compression diff --git a/dom/compression/FormatZlib.h b/dom/compression/FormatZlib.h @@ -1,97 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef DOM_COMPRESSION_FORMATZLIB_H_ -#define DOM_COMPRESSION_FORMATZLIB_H_ - -#include "BaseAlgorithms.h" -#include "mozilla/dom/TransformerCallbackHelpers.h" -#include "zlib.h" - -// See the zlib manual in https://www.zlib.net/manual.html or in -// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h - -namespace mozilla::dom { -enum class CompressionFormat : uint8_t; -} - -namespace mozilla::dom::compression { - -class CompressionStreamAlgorithms : public TransformerAlgorithmsWrapper { - public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CompressionStreamAlgorithms, - TransformerAlgorithmsBase) - - static Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> Create( - CompressionFormat format); - - private: - CompressionStreamAlgorithms() = default; - - [[nodiscard]] nsresult Init(CompressionFormat format); - - // Step 3 of - // https://wicg.github.io/compression/#dom-compressionstream-compressionstream - // Let transformAlgorithm be an algorithm which takes a chunk argument and - // runs the compress and enqueue a chunk algorithm with this and chunk. - MOZ_CAN_RUN_SCRIPT - void TransformCallbackImpl(JS::Handle<JS::Value> aChunk, - TransformStreamDefaultController& aController, - ErrorResult& aRv) override; - - // Step 4 of - // https://wicg.github.io/compression/#dom-compressionstream-compressionstream - // Let flushAlgorithm be an algorithm which takes no argument and runs the - // compress flush and enqueue algorithm with this. - MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl( - TransformStreamDefaultController& aController, ErrorResult& aRv) override; - - // Shared by: - // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk - // https://wicg.github.io/compression/#compress-flush-and-enqueue - MOZ_CAN_RUN_SCRIPT void CompressAndEnqueue( - JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, - TransformStreamDefaultController& aController, ErrorResult& aRv); - - ~CompressionStreamAlgorithms() override; - - z_stream mZStream = {}; -}; - -class ZLibDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms { - public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ZLibDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) - - static Result<already_AddRefed<ZLibDecompressionStreamAlgorithms>, nsresult> - Create(CompressionFormat format); - - private: - ZLibDecompressionStreamAlgorithms() = default; - - [[nodiscard]] nsresult Init(CompressionFormat format); - - private: - // Shared by: - // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk - // https://wicg.github.io/compression/#decompress-flush-and-enqueue - // All data errors throw TypeError by step 2: If this results in an error, - // then throw a TypeError. - MOZ_CAN_RUN_SCRIPT void DecompressAndEnqueue( - JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, - TransformStreamDefaultController& aController, ErrorResult& aRv) override; - - ~ZLibDecompressionStreamAlgorithms() override; - - z_stream mZStream = {}; - bool mObservedStreamEnd = false; -}; - -} // namespace mozilla::dom::compression - -#endif // DOM_COMPRESSION_FORMATZLIB_H_ diff --git a/dom/compression/FormatZstd.cpp b/dom/compression/FormatZstd.cpp @@ -1,142 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "FormatZstd.h" - -#include "BaseAlgorithms.h" -#include "mozilla/dom/TransformStreamDefaultController.h" -#include "zstd/zstd.h" - -namespace mozilla::dom::compression { - -NS_IMPL_CYCLE_COLLECTION_INHERITED(ZstdDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) -NS_IMPL_ADDREF_INHERITED(ZstdDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) -NS_IMPL_RELEASE_INHERITED(ZstdDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ZstdDecompressionStreamAlgorithms) -NS_INTERFACE_MAP_END_INHERITING(DecompressionStreamAlgorithms) - -Result<already_AddRefed<ZstdDecompressionStreamAlgorithms>, nsresult> -ZstdDecompressionStreamAlgorithms::Create() { - RefPtr<ZstdDecompressionStreamAlgorithms> alg = - new ZstdDecompressionStreamAlgorithms(); - MOZ_TRY(alg->Init()); - return alg.forget(); -} - -[[nodiscard]] nsresult ZstdDecompressionStreamAlgorithms::Init() { - mDStream = ZSTD_createDStream(); - if (!mDStream) { - return NS_ERROR_OUT_OF_MEMORY; - } - - // Refuse any frame requiring larger than (1 << WINDOW_LOG_MAX) window size. - // Note: 1 << 23 == 8 * 1024 * 1024 - static const uint8_t WINDOW_LOG_MAX = 23; - ZSTD_DCtx_setParameter(mDStream, ZSTD_d_windowLogMax, WINDOW_LOG_MAX); - - return NS_OK; -} - -// Shared by: -// https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk -// https://wicg.github.io/compression/#decompress-flush-and-enqueue -// All data errors throw TypeError by step 2: If this results in an error, -// then throw a TypeError. -MOZ_CAN_RUN_SCRIPT void ZstdDecompressionStreamAlgorithms::DecompressAndEnqueue( - JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, - TransformStreamDefaultController& aController, ErrorResult& aRv) { - MOZ_ASSERT_IF(aFlush == Flush::Yes, !aInput.Length()); - - if (mObservedStreamEnd && aInput.Length() > 0) { - aRv.ThrowTypeError("Unexpected input after the end of stream"); - return; - } - - ZSTD_inBuffer inBuffer = {/* src */ const_cast<uint8_t*>(aInput.Elements()), - /* size */ aInput.Length(), - /* pos */ 0}; - - JS::RootedVector<JSObject*> array(aCx); - - while (inBuffer.pos < inBuffer.size && !mObservedStreamEnd) { - UniquePtr<uint8_t[], JS::FreePolicy> buffer( - static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); - if (!buffer) { - aRv.ThrowTypeError("Out of memory"); - return; - } - - ZSTD_outBuffer outBuffer = {/* dst */ buffer.get(), - /* size */ kBufferSize, - /* pos */ 0}; - - size_t rv = ZSTD_decompressStream(mDStream, &outBuffer, &inBuffer); - if (ZSTD_isError(rv)) { - aRv.ThrowTypeError("zstd decompression error: "_ns + - nsDependentCString(ZSTD_getErrorName(rv))); - return; - } - - if (rv == 0) { - mObservedStreamEnd = true; - } - - // Step 3: If buffer is empty, return. - // (We'll implicitly return when the array is empty.) - - // Step 4: Split buffer into one or more non-empty pieces and convert them - // into Uint8Arrays. - // (The buffer is 'split' by having a fixed sized buffer above.) - - size_t written = outBuffer.pos; - if (written > 0) { - JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( - aCx, written, std::move(buffer))); - if (!view || !array.append(view)) { - JS_ClearPendingException(aCx); - aRv.ThrowTypeError("Out of memory"); - return; - } - } - } - - // Step 5: For each Uint8Array array, enqueue array in ds's transform. - for (const auto& view : array) { - JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view)); - aController.Enqueue(aCx, value, aRv); - if (aRv.Failed()) { - return; - } - } - - // Step 6: If the end of the compressed input has been reached, and ds's - // context has not fully consumed chunk, then throw a TypeError. - if (mObservedStreamEnd && inBuffer.pos < inBuffer.size) { - aRv.ThrowTypeError("Unexpected input after the end of stream"); - return; - } - - // Step 3 of - // https://wicg.github.io/compression/#decompress-flush-and-enqueue - // If the end of the compressed input has not been reached, then throw a - // TypeError. - if (aFlush == Flush::Yes && !mObservedStreamEnd) { - aRv.ThrowTypeError("The input is ended without reaching the stream end"); - return; - } -} - -ZstdDecompressionStreamAlgorithms::~ZstdDecompressionStreamAlgorithms() { - if (mDStream) { - ZSTD_freeDStream(mDStream); - mDStream = nullptr; - } -} - -} // namespace mozilla::dom::compression diff --git a/dom/compression/FormatZstd.h b/dom/compression/FormatZstd.h @@ -1,49 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef DOM_COMPRESSION_FORMATZSTD_H_ -#define DOM_COMPRESSION_FORMATZSTD_H_ - -#include "BaseAlgorithms.h" - -struct ZSTD_DCtx_s; - -// See the zstd manual in https://facebook.github.io/zstd/zstd_manual.html or in -// https://searchfox.org/mozilla-central/source/third_party/zstd/lib/zstd.h - -namespace mozilla::dom::compression { - -class ZstdDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms { - public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ZstdDecompressionStreamAlgorithms, - DecompressionStreamAlgorithms) - - static Result<already_AddRefed<ZstdDecompressionStreamAlgorithms>, nsresult> - Create(); - - private: - ZstdDecompressionStreamAlgorithms() = default; - - [[nodiscard]] nsresult Init(); - - // Shared by: - // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk - // https://wicg.github.io/compression/#decompress-flush-and-enqueue - // All data errors throw TypeError by step 2: If this results in an error, - // then throw a TypeError. - MOZ_CAN_RUN_SCRIPT void DecompressAndEnqueue( - JSContext* aCx, Span<const uint8_t> aInput, Flush aFlush, - TransformStreamDefaultController& aController, ErrorResult& aRv) override; - - ~ZstdDecompressionStreamAlgorithms() override; - - ZSTD_DCtx_s* mDStream = nullptr; - bool mObservedStreamEnd = false; -}; -} // namespace mozilla::dom::compression - -#endif // DOM_COMPRESSION_FORMATZSTD_H_ diff --git a/dom/compression/moz.build b/dom/compression/moz.build @@ -1,23 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -with Files("**"): - BUG_COMPONENT = ("Core", "DOM: Streams") - -EXPORTS.mozilla.dom += [ - "CompressionStream.h", - "DecompressionStream.h", -] - -UNIFIED_SOURCES += [ - "BaseAlgorithms.cpp", - "CompressionStream.cpp", - "DecompressionStream.cpp", - "FormatZlib.cpp", - "FormatZstd.cpp", -] - -FINAL_LIBRARY = "xul" diff --git a/dom/moz.build b/dom/moz.build @@ -36,7 +36,6 @@ DIRS += [ "clients", "closewatcher", "commandhandler", - "compression", "cookiestore", "credentialmanagement", "crypto",