summaryrefslogtreecommitdiffstats
path: root/dom/encoding/TextEncoderStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/encoding/TextEncoderStream.cpp')
-rw-r--r--dom/encoding/TextEncoderStream.cpp240
1 files changed, 240 insertions, 0 deletions
diff --git a/dom/encoding/TextEncoderStream.cpp b/dom/encoding/TextEncoderStream.cpp
new file mode 100644
index 0000000000..06cd9d5a7e
--- /dev/null
+++ b/dom/encoding/TextEncoderStream.cpp
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TextEncoderStream.h"
+
+#include "js/ArrayBuffer.h"
+#include "js/experimental/TypedData.h"
+#include "nsIGlobalObject.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/TextEncoderStreamBinding.h"
+#include "mozilla/dom/TransformerCallbackHelpers.h"
+#include "mozilla/dom/TransformStream.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TextEncoderStream, mGlobal, mStream)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TextEncoderStream)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TextEncoderStream)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEncoderStream)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TextEncoderStream::TextEncoderStream(nsISupports* aGlobal,
+ TransformStream& aStream)
+ : mGlobal(aGlobal), mStream(&aStream) {
+// See the comment in EncodeNative() about why this uses a decoder instead of
+// `UTF_8_ENCODING->NewEncoder()`.
+// XXX: We have to consciously choose 16LE/BE because we ultimately have to read
+// char16_t* as uint8_t*. See the same comment.
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ mDecoder = UTF_16LE_ENCODING->NewDecoder();
+#else
+ mDecoder = UTF_16BE_ENCODING->NewDecoder();
+#endif
+}
+
+TextEncoderStream::~TextEncoderStream() = default;
+
+JSObject* TextEncoderStream::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TextEncoderStream_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// Note that the most of the encoding algorithm is implemented in
+// mozilla::Decoder (see the comment in EncodeNative()), and this is mainly
+// about calling it properly.
+static void EncodeNative(JSContext* aCx, mozilla::Decoder* aDecoder,
+ Span<const char16_t> aInput, const bool aFlush,
+ JS::MutableHandle<JSObject*> aOutputArrayBufferView,
+ ErrorResult& aRv) {
+ // XXX: Adjust the length since Decoder always accepts uint8_t (whereas
+ // Encoder also accepts char16_t, see below).
+ if (aInput.Length() > SIZE_MAX / 2) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ size_t lengthU8 = aInput.Length() * 2;
+
+ CheckedInt<nsAString::size_type> needed =
+ aDecoder->MaxUTF8BufferLength(lengthU8);
+ if (!needed.isValid()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ UniquePtr<uint8_t> buffer(
+ static_cast<uint8_t*>(JS_malloc(aCx, needed.value())));
+ if (!buffer) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ mozilla::Span<uint8_t> input((uint8_t*)aInput.data(), lengthU8);
+ mozilla::Span<uint8_t> output(buffer, needed.value());
+
+ // This originally wanted to use mozilla::Encoder::Encode() that accepts
+ // char16_t*, but it lacks the pending-high-surrogate feature to properly
+ // implement
+ // https://encoding.spec.whatwg.org/#convert-code-unit-to-scalar-value.
+ // See also https://github.com/hsivonen/encoding_rs/issues/82 about the
+ // reasoning.
+ // XXX: The code is more verbose here since we need to convert to
+ // uint8_t* which is the only type mozilla::Decoder accepts.
+ uint32_t result;
+ size_t read;
+ size_t written;
+ std::tie(result, read, written, std::ignore) =
+ aDecoder->DecodeToUTF8(input, output, aFlush);
+ MOZ_ASSERT(result == kInputEmpty);
+ MOZ_ASSERT(read == lengthU8);
+ MOZ_ASSERT(written <= needed.value());
+
+ // https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk
+ // Step 4.2.2.1. Let chunk be a Uint8Array object wrapping an ArrayBuffer
+ // containing output.
+ JS::Rooted<JSObject*> arrayBuffer(
+ aCx, JS::NewArrayBufferWithContents(aCx, written, buffer.release()));
+ if (!arrayBuffer.get()) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aOutputArrayBufferView.set(JS_NewUint8ArrayWithBuffer(
+ aCx, arrayBuffer, 0, static_cast<int64_t>(written)));
+ if (!aOutputArrayBufferView.get()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+class TextEncoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TextEncoderStreamAlgorithms,
+ TransformerAlgorithmsBase)
+
+ void SetEncoderStream(TextEncoderStream& aStream) {
+ mEncoderStream = &aStream;
+ }
+
+ // The common part of encode-and-enqueue and encode-and-flush.
+ // https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk
+ MOZ_CAN_RUN_SCRIPT void EncodeAndEnqueue(
+ JSContext* aCx, const nsAString& aInput,
+ TransformStreamDefaultController& aController, bool aFlush,
+ ErrorResult& aRv) {
+ JS::Rooted<JSObject*> outView(aCx);
+ // Passing a Decoder for a reason, see the comments in the method.
+ EncodeNative(aCx, mEncoderStream->Decoder(), aInput, aFlush, &outView, aRv);
+
+ if (JS_GetTypedArrayLength(outView) > 0) {
+ // Step 4.2.2.2. Enqueue chunk into encoder’s transform.
+ JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*outView));
+ aController.Enqueue(aCx, value, aRv);
+ }
+ }
+
+ // https://encoding.spec.whatwg.org/#dom-textencoderstream
+ MOZ_CAN_RUN_SCRIPT void TransformCallbackImpl(
+ JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController,
+ ErrorResult& aRv) override {
+ // Step 2. Let transformAlgorithm be an algorithm which takes a chunk
+ // argument and runs the encode and enqueue a chunk algorithm with this and
+ // chunk.
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aController.GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk
+
+ // Step 1. Let input be the result of converting chunk to a DOMString.
+ // Step 2. Convert input to an I/O queue of code units.
+ nsString str;
+ if (!ConvertJSValueToString(cx, aChunk, eStringify, eStringify, str)) {
+ aRv.MightThrowJSException();
+ aRv.StealExceptionFromJSContext(cx);
+ return;
+ }
+
+ EncodeAndEnqueue(cx, str, aController, false, aRv);
+ }
+
+ // https://encoding.spec.whatwg.org/#dom-textencoderstream
+ MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
+ TransformStreamDefaultController& aController,
+ ErrorResult& aRv) override {
+ // Step 3. Let flushAlgorithm be an algorithm which runs the encode and
+ // flush algorithm with this.
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aController.GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // The spec manually manages pending high surrogate here, but let's call the
+ // encoder as it's managed there.
+ EncodeAndEnqueue(cx, u""_ns, aController, true, aRv);
+ }
+
+ private:
+ ~TextEncoderStreamAlgorithms() override = default;
+
+ RefPtr<TextEncoderStream> mEncoderStream;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(TextEncoderStreamAlgorithms,
+ TransformerAlgorithmsBase, mEncoderStream)
+NS_IMPL_ADDREF_INHERITED(TextEncoderStreamAlgorithms, TransformerAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(TextEncoderStreamAlgorithms,
+ TransformerAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEncoderStreamAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
+
+// https://encoding.spec.whatwg.org/#dom-textencoderstream
+already_AddRefed<TextEncoderStream> TextEncoderStream::Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv) {
+ // Step 1. Set this’s encoder to an instance of the UTF-8 encoder.
+
+ // Step 2-3
+ auto algorithms = MakeRefPtr<TextEncoderStreamAlgorithms>();
+
+ // Step 4-5
+ RefPtr<TransformStream> transformStream =
+ TransformStream::CreateGeneric(aGlobal, *algorithms, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 6. Set this’s transform to transformStream.
+ // (Done in the constructor)
+ auto encoderStream =
+ MakeRefPtr<TextEncoderStream>(aGlobal.GetAsSupports(), *transformStream);
+ algorithms->SetEncoderStream(*encoderStream);
+ return encoderStream.forget();
+}
+
+ReadableStream* TextEncoderStream::Readable() const {
+ return mStream->Readable();
+}
+
+WritableStream* TextEncoderStream::Writable() const {
+ return mStream->Writable();
+}
+
+void TextEncoderStream::GetEncoding(nsCString& aRetVal) const {
+ aRetVal.AssignLiteral("utf-8");
+}
+
+} // namespace mozilla::dom