Skip to content

Commit 67add2e

Browse files
committed
Bug 1921583 - Part 4: Support brotli in CompressionStream r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D269810
1 parent 0108e9b commit 67add2e

5 files changed

Lines changed: 147 additions & 103 deletions

File tree

dom/compression/CompressionStream.cpp

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "mozilla/dom/CompressionStream.h"
88

9+
#include "FormatBrotli.h"
910
#include "FormatZlib.h"
1011
#include "js/TypeDecls.h"
1112
#include "mozilla/dom/CompressionStreamBinding.h"
@@ -28,6 +29,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStream)
2829
NS_INTERFACE_MAP_ENTRY(nsISupports)
2930
NS_INTERFACE_MAP_END
3031

32+
/*
33+
* Constructs either a ZLibDecompressionStreamAlgorithms or a
34+
* BrotliDecompressionStreamAlgorithms, based on the CompressionFormat.
35+
*/
36+
static Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult>
37+
CreateCompressionStreamAlgorithms(CompressionFormat aFormat) {
38+
if (aFormat == CompressionFormat::Brotli) {
39+
RefPtr<CompressionStreamAlgorithms> brotliAlgos =
40+
MOZ_TRY(BrotliCompressionStreamAlgorithms::Create());
41+
return brotliAlgos.forget();
42+
}
43+
44+
RefPtr<CompressionStreamAlgorithms> zlibAlgos =
45+
MOZ_TRY(ZLibCompressionStreamAlgorithms::Create(aFormat));
46+
return zlibAlgos.forget();
47+
}
48+
3149
CompressionStream::CompressionStream(nsISupports* aGlobal,
3250
TransformStream& aStream)
3351
: mGlobal(aGlobal), mStream(&aStream) {}
@@ -42,12 +60,6 @@ JSObject* CompressionStream::WrapObject(JSContext* aCx,
4260
// https://wicg.github.io/compression/#dom-compressionstream-compressionstream
4361
already_AddRefed<CompressionStream> CompressionStream::Constructor(
4462
const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) {
45-
if (aFormat == CompressionFormat::Brotli) {
46-
aRv.ThrowTypeError(
47-
"'brotli' (value of argument 1) is not a valid value for enumeration "
48-
"CompressionFormat.");
49-
return nullptr;
50-
}
5163
if (aFormat == CompressionFormat::Zstd) {
5264
aRv.ThrowTypeError(
5365
"'zstd' (value of argument 1) is not a valid value for enumeration "
@@ -66,7 +78,7 @@ already_AddRefed<CompressionStream> CompressionStream::Constructor(
6678
// Step 6: Set up this's transform with transformAlgorithm set to
6779
// transformAlgorithm and flushAlgorithm set to flushAlgorithm.
6880
Result<already_AddRefed<CompressionStreamAlgorithms>, nsresult> algorithms =
69-
ZLibCompressionStreamAlgorithms::Create(aFormat);
81+
CreateCompressionStreamAlgorithms(aFormat);
7082
if (algorithms.isErr()) {
7183
aRv.ThrowUnknownError("Not enough memory");
7284
return nullptr;

dom/compression/FormatBrotli.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@
1010

1111
#include "BaseAlgorithms.h"
1212
#include "brotli/decode.h"
13+
#include "brotli/encode.h"
1314
#include "mozilla/dom/TransformStreamDefaultController.h"
1415

1516
namespace mozilla::dom::compression {
1617

18+
NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliCompressionStreamAlgorithms,
19+
TransformerAlgorithmsBase)
20+
NS_IMPL_ADDREF_INHERITED(BrotliCompressionStreamAlgorithms,
21+
TransformerAlgorithmsBase)
22+
NS_IMPL_RELEASE_INHERITED(BrotliCompressionStreamAlgorithms,
23+
TransformerAlgorithmsBase)
24+
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliCompressionStreamAlgorithms)
25+
NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
26+
1727
NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliDecompressionStreamAlgorithms,
1828
TransformerAlgorithmsBase)
1929
NS_IMPL_ADDREF_INHERITED(BrotliDecompressionStreamAlgorithms,
@@ -23,6 +33,93 @@ NS_IMPL_RELEASE_INHERITED(BrotliDecompressionStreamAlgorithms,
2333
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliDecompressionStreamAlgorithms)
2434
NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
2535

36+
inline BrotliEncoderOperation intoBrotliOp(Flush aFlush) {
37+
switch (aFlush) {
38+
case Flush::No: {
39+
return BROTLI_OPERATION_PROCESS;
40+
}
41+
case Flush::Yes: {
42+
return BROTLI_OPERATION_FINISH;
43+
}
44+
default: {
45+
MOZ_ASSERT_UNREACHABLE("Unknown flush mode");
46+
return BROTLI_OPERATION_PROCESS;
47+
}
48+
}
49+
}
50+
51+
Result<already_AddRefed<BrotliCompressionStreamAlgorithms>, nsresult>
52+
BrotliCompressionStreamAlgorithms::Create() {
53+
RefPtr<BrotliCompressionStreamAlgorithms> alg =
54+
new BrotliCompressionStreamAlgorithms();
55+
MOZ_TRY(alg->Init());
56+
return alg.forget();
57+
}
58+
59+
[[nodiscard]] nsresult BrotliCompressionStreamAlgorithms::Init() {
60+
mState = std::unique_ptr<BrotliEncoderStateStruct, BrotliDeleter>(
61+
BrotliEncoderCreateInstance(nullptr, nullptr, nullptr));
62+
if (!mState) {
63+
return NS_ERROR_OUT_OF_MEMORY;
64+
}
65+
return NS_OK;
66+
}
67+
68+
// Shared by:
69+
// https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
70+
// https://wicg.github.io/compression/#compress-flush-and-enqueue
71+
// All data errors throw TypeError by step 2: If this results in an error,
72+
// then throw a TypeError.
73+
void BrotliCompressionStreamAlgorithms::Compress(
74+
JSContext* aCx, Span<const uint8_t> aInput,
75+
JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
76+
ErrorResult& aRv) {
77+
size_t inputLength = aInput.Length();
78+
const uint8_t* inputBuffer = aInput.Elements();
79+
80+
do {
81+
std::unique_ptr<uint8_t[], JS::FreePolicy> buffer(
82+
static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
83+
if (!buffer) {
84+
aRv.ThrowTypeError("Out of memory");
85+
return;
86+
}
87+
88+
size_t outputLength = kBufferSize;
89+
uint8_t* outputBuffer = buffer.get();
90+
bool succeeded = BrotliEncoderCompressStream(
91+
mState.get(), intoBrotliOp(aFlush), &inputLength, &inputBuffer,
92+
&outputLength, &outputBuffer, nullptr);
93+
if (!succeeded) {
94+
aRv.ThrowTypeError("Unexpected compression error");
95+
return;
96+
}
97+
98+
// Step 3: If buffer is empty, return.
99+
// (We'll implicitly return when the array is empty.)
100+
101+
// Step 4: Split buffer into one or more non-empty pieces and convert them
102+
// into Uint8Arrays.
103+
// (The buffer is 'split' by having a fixed sized buffer above.)
104+
105+
size_t written = kBufferSize - outputLength;
106+
if (written > 0) {
107+
JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
108+
aCx, written, std::move(buffer)));
109+
if (!view || !aOutput.append(view)) {
110+
JS_ClearPendingException(aCx);
111+
aRv.ThrowTypeError("Out of memory");
112+
return;
113+
}
114+
}
115+
} while (BrotliEncoderHasMoreOutput(mState.get()));
116+
}
117+
118+
void BrotliCompressionStreamAlgorithms::BrotliDeleter::operator()(
119+
BrotliEncoderStateStruct* aState) {
120+
BrotliEncoderDestroyInstance(aState);
121+
}
122+
26123
Result<already_AddRefed<BrotliDecompressionStreamAlgorithms>, nsresult>
27124
BrotliDecompressionStreamAlgorithms::Create() {
28125
RefPtr<BrotliDecompressionStreamAlgorithms> alg =

dom/compression/FormatBrotli.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,43 @@
1010
#include "BaseAlgorithms.h"
1111

1212
struct BrotliDecoderStateStruct;
13+
struct BrotliEncoderStateStruct;
1314

1415
// See the brotli manual
1516
// https://searchfox.org/firefox-main/source/modules/brotli/include/brotli/decode.h
1617

1718
namespace mozilla::dom::compression {
1819

20+
class BrotliCompressionStreamAlgorithms : public CompressionStreamAlgorithms {
21+
public:
22+
NS_DECL_ISUPPORTS_INHERITED
23+
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BrotliCompressionStreamAlgorithms,
24+
CompressionStreamAlgorithms)
25+
26+
static Result<already_AddRefed<BrotliCompressionStreamAlgorithms>, nsresult>
27+
Create();
28+
29+
private:
30+
BrotliCompressionStreamAlgorithms() = default;
31+
32+
[[nodiscard]] nsresult Init();
33+
34+
// Shared by:
35+
// https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
36+
// https://wicg.github.io/compression/#compress-flush-and-enqueue
37+
void Compress(JSContext* aCx, Span<const uint8_t> aInput,
38+
JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
39+
ErrorResult& aRv) override;
40+
41+
~BrotliCompressionStreamAlgorithms() = default;
42+
43+
struct BrotliDeleter {
44+
void operator()(BrotliEncoderStateStruct* aState);
45+
};
46+
47+
std::unique_ptr<BrotliEncoderStateStruct, BrotliDeleter> mState;
48+
};
49+
1950
class BrotliDecompressionStreamAlgorithms
2051
: public DecompressionStreamAlgorithms {
2152
public:

testing/web-platform/meta/compression/compression-bad-chunks.any.js.ini

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,8 @@
11
[compression-bad-chunks.any.sharedworker.html]
2-
[chunk of type undefined should error the stream for brotli]
3-
expected: FAIL
4-
5-
[chunk of type null should error the stream for brotli]
6-
expected: FAIL
7-
8-
[chunk of type numeric should error the stream for brotli]
9-
expected: FAIL
10-
11-
[chunk of type object, not BufferSource should error the stream for brotli]
12-
expected: FAIL
13-
14-
[chunk of type array should error the stream for brotli]
15-
expected: FAIL
16-
17-
[chunk of type SharedArrayBuffer should error the stream for brotli]
18-
expected: FAIL
19-
20-
[chunk of type shared Uint8Array should error the stream for brotli]
21-
expected: FAIL
22-
232

243
[compression-bad-chunks.any.serviceworker.html]
25-
[chunk of type undefined should error the stream for brotli]
26-
expected: FAIL
27-
28-
[chunk of type null should error the stream for brotli]
29-
expected: FAIL
30-
31-
[chunk of type numeric should error the stream for brotli]
32-
expected: FAIL
33-
34-
[chunk of type object, not BufferSource should error the stream for brotli]
35-
expected: FAIL
36-
37-
[chunk of type array should error the stream for brotli]
38-
expected: FAIL
39-
40-
[chunk of type SharedArrayBuffer should error the stream for brotli]
41-
expected: FAIL
42-
43-
[chunk of type shared Uint8Array should error the stream for brotli]
44-
expected: FAIL
45-
464

475
[compression-bad-chunks.any.html]
48-
[chunk of type undefined should error the stream for brotli]
49-
expected: FAIL
50-
51-
[chunk of type null should error the stream for brotli]
52-
expected: FAIL
53-
54-
[chunk of type numeric should error the stream for brotli]
55-
expected: FAIL
56-
57-
[chunk of type object, not BufferSource should error the stream for brotli]
58-
expected: FAIL
59-
60-
[chunk of type array should error the stream for brotli]
61-
expected: FAIL
62-
63-
[chunk of type SharedArrayBuffer should error the stream for brotli]
64-
expected: FAIL
65-
66-
[chunk of type shared Uint8Array should error the stream for brotli]
67-
expected: FAIL
68-
696

707
[compression-bad-chunks.any.shadowrealm.html]
718
expected:
@@ -75,27 +12,6 @@
7512
[compression-bad-chunks.any.worker.html]
7613
expected:
7714
if (os == "android") and sessionHistoryInParent and not debug: [OK, TIMEOUT]
78-
[chunk of type undefined should error the stream for brotli]
79-
expected: FAIL
80-
81-
[chunk of type null should error the stream for brotli]
82-
expected: FAIL
83-
84-
[chunk of type numeric should error the stream for brotli]
85-
expected: FAIL
86-
87-
[chunk of type object, not BufferSource should error the stream for brotli]
88-
expected: FAIL
89-
90-
[chunk of type array should error the stream for brotli]
91-
expected: FAIL
92-
93-
[chunk of type SharedArrayBuffer should error the stream for brotli]
94-
expected: FAIL
95-
96-
[chunk of type shared Uint8Array should error the stream for brotli]
97-
expected: FAIL
98-
9915

10016
[compression-bad-chunks.any.shadowrealm-in-window.html]
10117
expected: ERROR

testing/web-platform/meta/compression/compression-output-length.any.js.ini

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
11
[compression-output-length.any.sharedworker.html]
2-
[the length of brotli data should be shorter than that of the original data]
3-
expected: FAIL
4-
52

63
[compression-output-length.any.shadowrealm.html]
74
expected:
85
if os == "android": [ERROR, CRASH]
96
ERROR
107

118
[compression-output-length.any.worker.html]
12-
[the length of brotli data should be shorter than that of the original data]
13-
expected: FAIL
14-
159

1610
[compression-output-length.any.html]
17-
[the length of brotli data should be shorter than that of the original data]
18-
expected: FAIL
19-
2011

2112
[compression-output-length.any.serviceworker.html]
2213
expected:
2314
if (os == "android") and not sessionHistoryInParent and not debug: [OK, TIMEOUT]
2415
if (os == "mac") and not debug: [OK, ERROR]
25-
[the length of brotli data should be shorter than that of the original data]
26-
expected: FAIL
27-
2816

2917
[compression-output-length.any.shadowrealm-in-shadowrealm.html]
3018
expected: ERROR

0 commit comments

Comments
 (0)