tor-browser

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

commit 67add2edd1f5791adec1ea8f576dbf8c183755fd
parent 0108e9bc0a817ca1f47fcf1f3768f0c1e1aff226
Author: Kagami Sascha Rosylight <krosylight@proton.me>
Date:   Mon, 17 Nov 2025 13:11:08 +0000

Bug 1921583 - Part 4: Support brotli in CompressionStream r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D269810

Diffstat:
Mdom/compression/CompressionStream.cpp | 26+++++++++++++++++++-------
Mdom/compression/FormatBrotli.cpp | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/compression/FormatBrotli.h | 31+++++++++++++++++++++++++++++++
Mtesting/web-platform/meta/compression/compression-bad-chunks.any.js.ini | 84-------------------------------------------------------------------------------
Mtesting/web-platform/meta/compression/compression-output-length.any.js.ini | 12------------
5 files changed, 147 insertions(+), 103 deletions(-)

diff --git a/dom/compression/CompressionStream.cpp b/dom/compression/CompressionStream.cpp @@ -6,6 +6,7 @@ #include "mozilla/dom/CompressionStream.h" +#include "FormatBrotli.h" #include "FormatZlib.h" #include "js/TypeDecls.h" #include "mozilla/dom/CompressionStreamBinding.h" @@ -28,6 +29,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStream) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END +/* + * Constructs either a ZLibDecompressionStreamAlgorithms or a + * BrotliDecompressionStreamAlgorithms, based on the CompressionFormat. + */ +static Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> +CreateCompressionStreamAlgorithms(CompressionFormat aFormat) { + if (aFormat == CompressionFormat::Brotli) { + RefPtr<CompressionStreamAlgorithms> brotliAlgos = + MOZ_TRY(BrotliCompressionStreamAlgorithms::Create()); + return brotliAlgos.forget(); + } + + RefPtr<CompressionStreamAlgorithms> zlibAlgos = + MOZ_TRY(ZLibCompressionStreamAlgorithms::Create(aFormat)); + return zlibAlgos.forget(); +} + CompressionStream::CompressionStream(nsISupports* aGlobal, TransformStream& aStream) : mGlobal(aGlobal), mStream(&aStream) {} @@ -42,12 +60,6 @@ JSObject* CompressionStream::WrapObject(JSContext* aCx, // https://wicg.github.io/compression/#dom-compressionstream-compressionstream already_AddRefed<CompressionStream> CompressionStream::Constructor( const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) { - if (aFormat == CompressionFormat::Brotli) { - aRv.ThrowTypeError( - "'brotli' (value of argument 1) is not a valid value for enumeration " - "CompressionFormat."); - return nullptr; - } if (aFormat == CompressionFormat::Zstd) { aRv.ThrowTypeError( "'zstd' (value of argument 1) is not a valid value for enumeration " @@ -66,7 +78,7 @@ already_AddRefed<CompressionStream> CompressionStream::Constructor( // Step 6: Set up this's transform with transformAlgorithm set to // transformAlgorithm and flushAlgorithm set to flushAlgorithm. Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> algorithms = - ZLibCompressionStreamAlgorithms::Create(aFormat); + CreateCompressionStreamAlgorithms(aFormat); if (algorithms.isErr()) { aRv.ThrowUnknownError("Not enough memory"); return nullptr; diff --git a/dom/compression/FormatBrotli.cpp b/dom/compression/FormatBrotli.cpp @@ -10,10 +10,20 @@ #include "BaseAlgorithms.h" #include "brotli/decode.h" +#include "brotli/encode.h" #include "mozilla/dom/TransformStreamDefaultController.h" namespace mozilla::dom::compression { +NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliCompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_IMPL_ADDREF_INHERITED(BrotliCompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(BrotliCompressionStreamAlgorithms, + TransformerAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliCompressionStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) + NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliDecompressionStreamAlgorithms, TransformerAlgorithmsBase) NS_IMPL_ADDREF_INHERITED(BrotliDecompressionStreamAlgorithms, @@ -23,6 +33,93 @@ NS_IMPL_RELEASE_INHERITED(BrotliDecompressionStreamAlgorithms, NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliDecompressionStreamAlgorithms) NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) +inline BrotliEncoderOperation intoBrotliOp(Flush aFlush) { + switch (aFlush) { + case Flush::No: { + return BROTLI_OPERATION_PROCESS; + } + case Flush::Yes: { + return BROTLI_OPERATION_FINISH; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown flush mode"); + return BROTLI_OPERATION_PROCESS; + } + } +} + +Result<already_AddRefed<BrotliCompressionStreamAlgorithms>, nsresult> +BrotliCompressionStreamAlgorithms::Create() { + RefPtr<BrotliCompressionStreamAlgorithms> alg = + new BrotliCompressionStreamAlgorithms(); + MOZ_TRY(alg->Init()); + return alg.forget(); +} + +[[nodiscard]] nsresult BrotliCompressionStreamAlgorithms::Init() { + mState = std::unique_ptr<BrotliEncoderStateStruct, BrotliDeleter>( + BrotliEncoderCreateInstance(nullptr, nullptr, nullptr)); + if (!mState) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +// Shared by: +// https://wicg.github.io/compression/#compress-and-enqueue-a-chunk +// https://wicg.github.io/compression/#compress-flush-and-enqueue +// All data errors throw TypeError by step 2: If this results in an error, +// then throw a TypeError. +void BrotliCompressionStreamAlgorithms::Compress( + JSContext* aCx, Span<const uint8_t> aInput, + JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush, + ErrorResult& aRv) { + size_t inputLength = aInput.Length(); + const uint8_t* inputBuffer = aInput.Elements(); + + do { + std::unique_ptr<uint8_t[], JS::FreePolicy> buffer( + static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); + if (!buffer) { + aRv.ThrowTypeError("Out of memory"); + return; + } + + size_t outputLength = kBufferSize; + uint8_t* outputBuffer = buffer.get(); + bool succeeded = BrotliEncoderCompressStream( + mState.get(), intoBrotliOp(aFlush), &inputLength, &inputBuffer, + &outputLength, &outputBuffer, nullptr); + if (!succeeded) { + aRv.ThrowTypeError("Unexpected compression error"); + return; + } + + // 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 = kBufferSize - outputLength; + if (written > 0) { + JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( + aCx, written, std::move(buffer))); + if (!view || !aOutput.append(view)) { + JS_ClearPendingException(aCx); + aRv.ThrowTypeError("Out of memory"); + return; + } + } + } while (BrotliEncoderHasMoreOutput(mState.get())); +} + +void BrotliCompressionStreamAlgorithms::BrotliDeleter::operator()( + BrotliEncoderStateStruct* aState) { + BrotliEncoderDestroyInstance(aState); +} + Result<already_AddRefed<BrotliDecompressionStreamAlgorithms>, nsresult> BrotliDecompressionStreamAlgorithms::Create() { RefPtr<BrotliDecompressionStreamAlgorithms> alg = diff --git a/dom/compression/FormatBrotli.h b/dom/compression/FormatBrotli.h @@ -10,12 +10,43 @@ #include "BaseAlgorithms.h" struct BrotliDecoderStateStruct; +struct BrotliEncoderStateStruct; // See the brotli manual // https://searchfox.org/firefox-main/source/modules/brotli/include/brotli/decode.h namespace mozilla::dom::compression { +class BrotliCompressionStreamAlgorithms : public CompressionStreamAlgorithms { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BrotliCompressionStreamAlgorithms, + CompressionStreamAlgorithms) + + static Result<already_AddRefed<BrotliCompressionStreamAlgorithms>, nsresult> + Create(); + + private: + BrotliCompressionStreamAlgorithms() = default; + + [[nodiscard]] nsresult Init(); + + // Shared by: + // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk + // https://wicg.github.io/compression/#compress-flush-and-enqueue + void Compress(JSContext* aCx, Span<const uint8_t> aInput, + JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush, + ErrorResult& aRv) override; + + ~BrotliCompressionStreamAlgorithms() = default; + + struct BrotliDeleter { + void operator()(BrotliEncoderStateStruct* aState); + }; + + std::unique_ptr<BrotliEncoderStateStruct, BrotliDeleter> mState; +}; + class BrotliDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms { public: diff --git a/testing/web-platform/meta/compression/compression-bad-chunks.any.js.ini b/testing/web-platform/meta/compression/compression-bad-chunks.any.js.ini @@ -1,71 +1,8 @@ [compression-bad-chunks.any.sharedworker.html] - [chunk of type undefined should error the stream for brotli] - expected: FAIL - - [chunk of type null should error the stream for brotli] - expected: FAIL - - [chunk of type numeric should error the stream for brotli] - expected: FAIL - - [chunk of type object, not BufferSource should error the stream for brotli] - expected: FAIL - - [chunk of type array should error the stream for brotli] - expected: FAIL - - [chunk of type SharedArrayBuffer should error the stream for brotli] - expected: FAIL - - [chunk of type shared Uint8Array should error the stream for brotli] - expected: FAIL - [compression-bad-chunks.any.serviceworker.html] - [chunk of type undefined should error the stream for brotli] - expected: FAIL - - [chunk of type null should error the stream for brotli] - expected: FAIL - - [chunk of type numeric should error the stream for brotli] - expected: FAIL - - [chunk of type object, not BufferSource should error the stream for brotli] - expected: FAIL - - [chunk of type array should error the stream for brotli] - expected: FAIL - - [chunk of type SharedArrayBuffer should error the stream for brotli] - expected: FAIL - - [chunk of type shared Uint8Array should error the stream for brotli] - expected: FAIL - [compression-bad-chunks.any.html] - [chunk of type undefined should error the stream for brotli] - expected: FAIL - - [chunk of type null should error the stream for brotli] - expected: FAIL - - [chunk of type numeric should error the stream for brotli] - expected: FAIL - - [chunk of type object, not BufferSource should error the stream for brotli] - expected: FAIL - - [chunk of type array should error the stream for brotli] - expected: FAIL - - [chunk of type SharedArrayBuffer should error the stream for brotli] - expected: FAIL - - [chunk of type shared Uint8Array should error the stream for brotli] - expected: FAIL - [compression-bad-chunks.any.shadowrealm.html] expected: @@ -75,27 +12,6 @@ [compression-bad-chunks.any.worker.html] expected: if (os == "android") and sessionHistoryInParent and not debug: [OK, TIMEOUT] - [chunk of type undefined should error the stream for brotli] - expected: FAIL - - [chunk of type null should error the stream for brotli] - expected: FAIL - - [chunk of type numeric should error the stream for brotli] - expected: FAIL - - [chunk of type object, not BufferSource should error the stream for brotli] - expected: FAIL - - [chunk of type array should error the stream for brotli] - expected: FAIL - - [chunk of type SharedArrayBuffer should error the stream for brotli] - expected: FAIL - - [chunk of type shared Uint8Array should error the stream for brotli] - expected: FAIL - [compression-bad-chunks.any.shadowrealm-in-window.html] expected: ERROR diff --git a/testing/web-platform/meta/compression/compression-output-length.any.js.ini b/testing/web-platform/meta/compression/compression-output-length.any.js.ini @@ -1,7 +1,4 @@ [compression-output-length.any.sharedworker.html] - [the length of brotli data should be shorter than that of the original data] - expected: FAIL - [compression-output-length.any.shadowrealm.html] expected: @@ -9,22 +6,13 @@ ERROR [compression-output-length.any.worker.html] - [the length of brotli data should be shorter than that of the original data] - expected: FAIL - [compression-output-length.any.html] - [the length of brotli data should be shorter than that of the original data] - expected: FAIL - [compression-output-length.any.serviceworker.html] expected: if (os == "android") and not sessionHistoryInParent and not debug: [OK, TIMEOUT] if (os == "mac") and not debug: [OK, ERROR] - [the length of brotli data should be shorter than that of the original data] - expected: FAIL - [compression-output-length.any.shadowrealm-in-shadowrealm.html] expected: ERROR