Skip to content

Commit eea3a13

Browse files
committed
Bug 1921583 - Part 2: Support brotli in DecompressionStream r=smaug,webidl
Differential Revision: https://phabricator.services.mozilla.com/D269038
1 parent f48ae44 commit eea3a13

13 files changed

Lines changed: 299 additions & 1 deletion

dom/compression/CompressionStream.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ JSObject* CompressionStream::WrapObject(JSContext* aCx,
4242
// https://wicg.github.io/compression/#dom-compressionstream-compressionstream
4343
already_AddRefed<CompressionStream> CompressionStream::Constructor(
4444
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+
}
4551
if (aFormat == CompressionFormat::Zstd) {
4652
aRv.ThrowTypeError(
4753
"'zstd' (value of argument 1) is not a valid value for enumeration "

dom/compression/DecompressionStream.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
#include "mozilla/dom/DecompressionStream.h"
88

99
#include "BaseAlgorithms.h"
10+
#include "FormatBrotli.h"
1011
#include "FormatZlib.h"
1112
#include "FormatZstd.h"
1213
#include "js/TypeDecls.h"
1314
#include "mozilla/StaticPrefs_dom.h"
15+
#include "mozilla/dom/CompressionStreamBinding.h"
1416
#include "mozilla/dom/DecompressionStreamBinding.h"
1517
#include "mozilla/dom/ReadableStream.h"
1618
#include "mozilla/dom/TextDecoderStream.h"
@@ -23,10 +25,15 @@ using namespace compression;
2325

2426
/*
2527
* Constructs either a ZLibDecompressionStreamAlgorithms or a
26-
* ZstdDecompressionStreamAlgorithms, based on the CompressionFormat.
28+
* Brotli/ZstdDecompressionStreamAlgorithms, based on the CompressionFormat.
2729
*/
2830
static Result<already_AddRefed<DecompressionStreamAlgorithms>, nsresult>
2931
CreateDecompressionStreamAlgorithms(CompressionFormat aFormat) {
32+
if (aFormat == CompressionFormat::Brotli) {
33+
RefPtr<DecompressionStreamAlgorithms> brotliAlgos =
34+
MOZ_TRY(BrotliDecompressionStreamAlgorithms::Create());
35+
return brotliAlgos.forget();
36+
}
3037
if (aFormat == CompressionFormat::Zstd) {
3138
RefPtr<DecompressionStreamAlgorithms> zstdAlgos =
3239
MOZ_TRY(ZstdDecompressionStreamAlgorithms::Create());

dom/compression/FormatBrotli.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2+
/* vim:set ts=2 sw=2 sts=2 et cindent: */
3+
/* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
#include "FormatBrotli.h"
8+
9+
#include <memory>
10+
11+
#include "BaseAlgorithms.h"
12+
#include "brotli/decode.h"
13+
#include "mozilla/dom/TransformStreamDefaultController.h"
14+
15+
namespace mozilla::dom::compression {
16+
17+
NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliDecompressionStreamAlgorithms,
18+
TransformerAlgorithmsBase)
19+
NS_IMPL_ADDREF_INHERITED(BrotliDecompressionStreamAlgorithms,
20+
TransformerAlgorithmsBase)
21+
NS_IMPL_RELEASE_INHERITED(BrotliDecompressionStreamAlgorithms,
22+
TransformerAlgorithmsBase)
23+
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliDecompressionStreamAlgorithms)
24+
NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
25+
26+
Result<already_AddRefed<BrotliDecompressionStreamAlgorithms>, nsresult>
27+
BrotliDecompressionStreamAlgorithms::Create() {
28+
RefPtr<BrotliDecompressionStreamAlgorithms> alg =
29+
new BrotliDecompressionStreamAlgorithms();
30+
MOZ_TRY(alg->Init());
31+
return alg.forget();
32+
}
33+
34+
[[nodiscard]] nsresult BrotliDecompressionStreamAlgorithms::Init() {
35+
mState = std::unique_ptr<BrotliDecoderStateStruct, BrotliDeleter>(
36+
BrotliDecoderCreateInstance(nullptr, nullptr, nullptr));
37+
if (!mState) {
38+
return NS_ERROR_OUT_OF_MEMORY;
39+
}
40+
return NS_OK;
41+
}
42+
43+
// Shared by:
44+
// https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
45+
// https://wicg.github.io/compression/#decompress-flush-and-enqueue
46+
// All data errors throw TypeError by step 2: If this results in an error,
47+
// then throw a TypeError.
48+
bool BrotliDecompressionStreamAlgorithms::Decompress(
49+
JSContext* aCx, Span<const uint8_t> aInput,
50+
JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
51+
ErrorResult& aRv) {
52+
size_t inputLength = aInput.Length();
53+
const uint8_t* inputBuffer = aInput.Elements();
54+
55+
do {
56+
std::unique_ptr<uint8_t[], JS::FreePolicy> buffer(
57+
static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
58+
if (!buffer) {
59+
aRv.ThrowTypeError("Out of memory");
60+
return false;
61+
}
62+
63+
size_t outputLength = kBufferSize;
64+
uint8_t* outputBuffer = buffer.get();
65+
BrotliDecoderResult rv =
66+
BrotliDecoderDecompressStream(mState.get(), &inputLength, &inputBuffer,
67+
&outputLength, &outputBuffer, nullptr);
68+
switch (rv) {
69+
case BROTLI_DECODER_RESULT_ERROR:
70+
aRv.ThrowTypeError("Brotli decompression error: "_ns +
71+
nsDependentCString(BrotliDecoderErrorString(
72+
BrotliDecoderGetErrorCode(mState.get()))));
73+
return false;
74+
case BROTLI_DECODER_RESULT_SUCCESS:
75+
mObservedStreamEnd = true;
76+
break;
77+
case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
78+
case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
79+
break;
80+
default:
81+
MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code");
82+
aRv.ThrowTypeError("Unexpected decompression error");
83+
return false;
84+
}
85+
86+
// Step 3: If buffer is empty, return.
87+
// (We'll implicitly return when the array is empty.)
88+
89+
// Step 4: Split buffer into one or more non-empty pieces and convert them
90+
// into Uint8Arrays.
91+
// (The buffer is 'split' by having a fixed sized buffer above.)
92+
93+
size_t written = kBufferSize - outputLength;
94+
if (written > 0) {
95+
JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
96+
aCx, written, std::move(buffer)));
97+
if (!view || !aOutput.append(view)) {
98+
JS_ClearPendingException(aCx);
99+
aRv.ThrowTypeError("Out of memory");
100+
return false;
101+
}
102+
}
103+
} while (BrotliDecoderHasMoreOutput(mState.get()));
104+
105+
return inputLength == 0;
106+
}
107+
108+
void BrotliDecompressionStreamAlgorithms::BrotliDeleter::operator()(
109+
BrotliDecoderStateStruct* aState) {
110+
BrotliDecoderDestroyInstance(aState);
111+
}
112+
113+
} // namespace mozilla::dom::compression

dom/compression/FormatBrotli.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2+
/* vim:set ts=2 sw=2 sts=2 et cindent: */
3+
/* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
#ifndef DOM_COMPRESSION_FORMATBROTLI_H_
8+
#define DOM_COMPRESSION_FORMATBROTLI_H_
9+
10+
#include "BaseAlgorithms.h"
11+
12+
struct BrotliDecoderStateStruct;
13+
14+
// See the brotli manual
15+
// https://searchfox.org/firefox-main/source/modules/brotli/include/brotli/decode.h
16+
17+
namespace mozilla::dom::compression {
18+
19+
class BrotliDecompressionStreamAlgorithms
20+
: public DecompressionStreamAlgorithms {
21+
public:
22+
NS_DECL_ISUPPORTS_INHERITED
23+
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BrotliDecompressionStreamAlgorithms,
24+
DecompressionStreamAlgorithms)
25+
26+
static Result<already_AddRefed<BrotliDecompressionStreamAlgorithms>, nsresult>
27+
Create();
28+
29+
private:
30+
BrotliDecompressionStreamAlgorithms() = default;
31+
32+
[[nodiscard]] nsresult Init();
33+
34+
// Shared by:
35+
// https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
36+
// https://wicg.github.io/compression/#decompress-flush-and-enqueue
37+
// All data errors throw TypeError by step 2: If this results in an error,
38+
// then throw a TypeError.
39+
bool Decompress(JSContext* aCx, Span<const uint8_t> aInput,
40+
JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
41+
ErrorResult& aRv) override;
42+
43+
~BrotliDecompressionStreamAlgorithms() = default;
44+
45+
struct BrotliDeleter {
46+
void operator()(BrotliDecoderStateStruct* aState);
47+
};
48+
49+
std::unique_ptr<BrotliDecoderStateStruct, BrotliDeleter> mState;
50+
};
51+
52+
} // namespace mozilla::dom::compression
53+
54+
#endif // DOM_COMPRESSION_FORMATBROTLI_H_

dom/compression/moz.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ UNIFIED_SOURCES += [
1616
"BaseAlgorithms.cpp",
1717
"CompressionStream.cpp",
1818
"DecompressionStream.cpp",
19+
"FormatBrotli.cpp",
1920
"FormatZlib.cpp",
2021
"FormatZstd.cpp",
2122
]

dom/webidl/CompressionStream.webidl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ enum CompressionFormat {
1111
"deflate",
1212
"deflate-raw",
1313
"gzip",
14+
"brotli",
15+
16+
// Mozilla specific
1417
"zstd",
1518
};
1619

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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,71 @@
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+
223

324
[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+
446

547
[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+
669

770
[compression-bad-chunks.any.shadowrealm.html]
871
expected:
@@ -12,6 +75,27 @@
1275
[compression-bad-chunks.any.worker.html]
1376
expected:
1477
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+
1599

16100
[compression-bad-chunks.any.shadowrealm-in-window.html]
17101
expected: ERROR

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
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+
25

36
[compression-output-length.any.shadowrealm.html]
47
expected:
58
if os == "android": [ERROR, CRASH]
69
ERROR
710

811
[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+
915

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

1221
[compression-output-length.any.serviceworker.html]
1322
expected:
1423
if (os == "android") and not sessionHistoryInParent and not debug: [OK, TIMEOUT]
1524
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+
1628

1729
[compression-output-length.any.shadowrealm-in-shadowrealm.html]
1830
expected: ERROR

testing/web-platform/tests/compression/decompression-buffersource.any.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ const compressedBytes = [
99
0x00, 0x06, 0x00, 0xf9, 0xff, 0x41, 0x42, 0x43,
1010
0x44, 0x45, 0x46, 0x01, 0x00, 0x00, 0xff, 0xff,
1111
]],
12+
["brotli", [0x21, 0x08, 0x00, 0x04, 0x66, 0x6F, 0x6F, 0x03]]
1213
];
1314
// These chunk values below were chosen to make the length of the compressed
1415
// output be a multiple of 8 bytes.
1516
const expectedChunkValue = new Map(Object.entries({
1617
"deflate": new TextEncoder().encode('a0123456'),
1718
"gzip": new TextEncoder().encode('a012'),
1819
"deflate-raw": new TextEncoder().encode('ABCDEF'),
20+
"brotli": new TextEncoder().encode('foo'),
1921
}));
2022

2123
const bufferSourceChunks = compressedBytes.map(([format, bytes]) => [format, [

testing/web-platform/tests/compression/decompression-corrupt-input.any.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ const expectations = [
222222
]
223223
}
224224
]
225+
},
226+
{
227+
format: 'brotli',
228+
229+
// Decompresses to 'expected output'.
230+
baseInput: brotliChunkValue,
231+
232+
fields: []
225233
}
226234
];
227235

0 commit comments

Comments
 (0)