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:
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