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/. */
7 #include "mozilla/dom/TextDecoderStream.h"
9 #include "nsContentUtils.h"
10 #include "nsIGlobalObject.h"
11 #include "mozilla/Encoding.h"
12 #include "mozilla/dom/Promise.h"
13 #include "mozilla/dom/TextDecoderStreamBinding.h"
14 #include "mozilla/dom/TransformerCallbackHelpers.h"
15 #include "mozilla/dom/TransformStream.h"
16 #include "mozilla/dom/UnionTypes.h"
18 namespace mozilla::dom
{
20 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TextDecoderStream
, mGlobal
, mStream
)
21 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextDecoderStream
)
22 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextDecoderStream
)
23 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextDecoderStream
)
24 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
25 NS_INTERFACE_MAP_ENTRY(nsISupports
)
28 TextDecoderStream::TextDecoderStream(nsISupports
* aGlobal
,
29 const Encoding
& aEncoding
, bool aFatal
,
30 bool aIgnoreBOM
, TransformStream
& aStream
)
31 : mGlobal(aGlobal
), mStream(&aStream
) {
33 mIgnoreBOM
= aIgnoreBOM
;
34 aEncoding
.Name(mEncoding
);
36 mDecoder
= aEncoding
.NewDecoderWithoutBOMHandling();
38 mDecoder
= aEncoding
.NewDecoderWithBOMRemoval();
42 TextDecoderStream::~TextDecoderStream() = default;
44 JSObject
* TextDecoderStream::WrapObject(JSContext
* aCx
,
45 JS::Handle
<JSObject
*> aGivenProto
) {
46 return TextDecoderStream_Binding::Wrap(aCx
, this, aGivenProto
);
49 // TODO: This function should probably be generated by bindings layer. (Bug
51 // TODO: This does not allow shared array buffers, just as the non-stream
52 // TextDecoder/Encoder don't. (Bug 1561594)
53 static Span
<const uint8_t> ExtractSpanFromBufferSource(
54 JSContext
* aCx
, JS::Handle
<JS::Value
> aBufferSource
, ErrorResult
& aRv
) {
55 if (!aBufferSource
.isObject()) {
56 aRv
.ThrowTypeError("Input is not an ArrayBuffer nor an ArrayBufferView");
57 return Span
<const uint8_t>();
61 RootedUnion
<OwningArrayBufferViewOrArrayBuffer
> bufferSource(aCx
);
62 if (bufferSource
.TrySetToArrayBufferView(aCx
, aBufferSource
, tryNext
,
65 ArrayBufferView
& view
= bufferSource
.GetAsArrayBufferView();
67 return Span(view
.Data(), view
.Length());
70 aRv
.MightThrowJSException();
71 aRv
.StealExceptionFromJSContext(aCx
);
72 return Span
<const uint8_t>();
75 if (bufferSource
.TrySetToArrayBuffer(aCx
, aBufferSource
, tryNext
, false) &&
77 ArrayBuffer
& buffer
= bufferSource
.GetAsArrayBuffer();
78 buffer
.ComputeState();
79 return Span(buffer
.Data(), buffer
.Length());
82 aRv
.MightThrowJSException();
83 aRv
.StealExceptionFromJSContext(aCx
);
84 return Span
<const uint8_t>();
87 aRv
.ThrowTypeError("Input is not an ArrayBuffer nor an ArrayBufferView");
88 return Span
<const uint8_t>();
91 class TextDecoderStreamAlgorithms
: public TransformerAlgorithmsWrapper
{
92 NS_DECL_ISUPPORTS_INHERITED
93 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TextDecoderStreamAlgorithms
,
94 TransformerAlgorithmsBase
)
96 void SetDecoderStream(TextDecoderStream
& aStream
) {
97 mDecoderStream
= &aStream
;
100 // The common part of decode-and-enqueue and flush-and-enqueue.
101 // Note that the most of the decoding algorithm is implemented in
102 // mozilla::Decoder, and this is mainly about calling it properly.
103 // https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk
104 MOZ_CAN_RUN_SCRIPT
void DecodeSpanAndEnqueue(
105 JSContext
* aCx
, Span
<const uint8_t> aInput
, bool aFlush
,
106 TransformStreamDefaultController
& aController
, ErrorResult
& aRv
) {
107 CheckedInt
<nsAString::size_type
> needed
=
108 mDecoderStream
->Decoder()->MaxUTF16BufferLength(aInput
.Length());
109 if (!needed
.isValid()) {
110 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
114 nsString outDecodedString
;
115 auto output
= outDecodedString
.GetMutableData(needed
.value(), fallible
);
117 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
121 mDecoderStream
->DecodeNative(aInput
, !aFlush
, outDecodedString
, aRv
);
126 if (outDecodedString
.Length()) {
127 // Step 4.2. If outputChunk is non-empty, then enqueue outputChunk in
128 // decoder’s transform.
129 JS::Rooted
<JS::Value
> outputChunk(aCx
);
130 if (!xpc::NonVoidStringToJsval(aCx
, outDecodedString
, &outputChunk
)) {
131 JS_ClearPendingException(aCx
);
132 aRv
.Throw(NS_ERROR_UNEXPECTED
);
135 aController
.Enqueue(aCx
, outputChunk
, aRv
);
139 // https://encoding.spec.whatwg.org/#dom-textdecoderstream
140 MOZ_CAN_RUN_SCRIPT
void TransformCallbackImpl(
141 JS::Handle
<JS::Value
> aChunk
,
142 TransformStreamDefaultController
& aController
,
143 ErrorResult
& aRv
) override
{
144 // Step 7. Let transformAlgorithm be an algorithm which takes a chunk
145 // argument and runs the decode and enqueue a chunk algorithm with this and
148 // https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk
151 if (!jsapi
.Init(aController
.GetParentObject())) {
152 aRv
.ThrowUnknownError("Internal error");
155 JSContext
* cx
= jsapi
.cx();
157 // Step 1. Let bufferSource be the result of converting chunk to an
158 // [AllowShared] BufferSource.
159 // (But here we get a mozilla::Span instead)
160 Span
<const uint8_t> input
= ExtractSpanFromBufferSource(cx
, aChunk
, aRv
);
165 DecodeSpanAndEnqueue(cx
, input
, false, aController
, aRv
);
168 // https://encoding.spec.whatwg.org/#dom-textdecoderstream
169 MOZ_CAN_RUN_SCRIPT
void FlushCallbackImpl(
170 TransformStreamDefaultController
& aController
,
171 ErrorResult
& aRv
) override
{
172 // Step 8. Let flushAlgorithm be an algorithm which takes no arguments and
173 // runs the flush and enqueue algorithm with this.
176 if (!jsapi
.Init(aController
.GetParentObject())) {
177 aRv
.ThrowUnknownError("Internal error");
180 JSContext
* cx
= jsapi
.cx();
182 // https://encoding.spec.whatwg.org/#flush-and-enqueue
183 // (The flush and enqueue algorithm is basically a subset of decode and
184 // enqueue one, so let's reuse it)
185 DecodeSpanAndEnqueue(cx
, Span
<const uint8_t>(), true, aController
, aRv
);
189 ~TextDecoderStreamAlgorithms() override
= default;
191 RefPtr
<TextDecoderStream
> mDecoderStream
;
194 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextDecoderStreamAlgorithms
,
195 TransformerAlgorithmsBase
, mDecoderStream
)
196 NS_IMPL_ADDREF_INHERITED(TextDecoderStreamAlgorithms
, TransformerAlgorithmsBase
)
197 NS_IMPL_RELEASE_INHERITED(TextDecoderStreamAlgorithms
,
198 TransformerAlgorithmsBase
)
199 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextDecoderStreamAlgorithms
)
200 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase
)
202 // https://encoding.spec.whatwg.org/#dom-textdecoderstream
203 already_AddRefed
<TextDecoderStream
> TextDecoderStream::Constructor(
204 const GlobalObject
& aGlobal
, const nsAString
& aLabel
,
205 const TextDecoderOptions
& aOptions
, ErrorResult
& aRv
) {
206 // Step 1. Let encoding be the result of getting an encoding from label.
207 const Encoding
* encoding
= Encoding::ForLabelNoReplacement(aLabel
);
209 // Step 2. If encoding is failure or replacement, then throw a RangeError
211 NS_ConvertUTF16toUTF8
label(aLabel
);
212 label
.Trim(" \t\n\f\r");
213 aRv
.ThrowRangeError
<MSG_ENCODING_NOT_SUPPORTED
>(label
);
217 // Step 3-6. (Done in the constructor)
220 auto algorithms
= MakeRefPtr
<TextDecoderStreamAlgorithms
>();
223 RefPtr
<TransformStream
> transformStream
=
224 TransformStream::CreateGeneric(aGlobal
, *algorithms
, aRv
);
229 // Step 11. (Done in the constructor)
230 auto decoderStream
= MakeRefPtr
<TextDecoderStream
>(
231 aGlobal
.GetAsSupports(), *encoding
, aOptions
.mFatal
, aOptions
.mIgnoreBOM
,
233 algorithms
->SetDecoderStream(*decoderStream
);
234 return decoderStream
.forget();
237 ReadableStream
* TextDecoderStream::Readable() const {
238 return mStream
->Readable();
241 WritableStream
* TextDecoderStream::Writable() const {
242 return mStream
->Writable();
245 } // namespace mozilla::dom