TextEncoderStream.cpp (8798B)
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 "mozilla/dom/TextEncoderStream.h" 8 9 #include "js/ArrayBuffer.h" 10 #include "js/experimental/TypedData.h" 11 #include "mozilla/Encoding.h" 12 #include "mozilla/dom/BindingUtils.h" 13 #include "mozilla/dom/TextEncoderStreamBinding.h" 14 #include "mozilla/dom/TransformStream.h" 15 #include "mozilla/dom/TransformerCallbackHelpers.h" 16 #include "nsIGlobalObject.h" 17 18 namespace mozilla::dom { 19 20 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TextEncoderStream, mGlobal, mStream) 21 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextEncoderStream) 22 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextEncoderStream) 23 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEncoderStream) 24 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 25 NS_INTERFACE_MAP_ENTRY(nsISupports) 26 NS_INTERFACE_MAP_END 27 28 TextEncoderStream::TextEncoderStream(nsISupports* aGlobal, 29 TransformStream& aStream) 30 : mGlobal(aGlobal), mStream(&aStream) { 31 // See the comment in EncodeNative() about why this uses a decoder instead of 32 // `UTF_8_ENCODING->NewEncoder()`. 33 // XXX: We have to consciously choose 16LE/BE because we ultimately have to read 34 // char16_t* as uint8_t*. See the same comment. 35 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 36 mDecoder = UTF_16LE_ENCODING->NewDecoder(); 37 #else 38 mDecoder = UTF_16BE_ENCODING->NewDecoder(); 39 #endif 40 } 41 42 TextEncoderStream::~TextEncoderStream() = default; 43 44 JSObject* TextEncoderStream::WrapObject(JSContext* aCx, 45 JS::Handle<JSObject*> aGivenProto) { 46 return TextEncoderStream_Binding::Wrap(aCx, this, aGivenProto); 47 } 48 49 // Note that the most of the encoding algorithm is implemented in 50 // mozilla::Decoder (see the comment in EncodeNative()), and this is mainly 51 // about calling it properly. 52 static void EncodeNative(JSContext* aCx, mozilla::Decoder* aDecoder, 53 Span<const char16_t> aInput, const bool aFlush, 54 JS::MutableHandle<JSObject*> aOutputArrayBufferView, 55 ErrorResult& aRv) { 56 // XXX: Adjust the length since Decoder always accepts uint8_t (whereas 57 // Encoder also accepts char16_t, see below). 58 if (aInput.Length() > SIZE_MAX / 2) { 59 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 60 return; 61 } 62 size_t lengthU8 = aInput.Length() * 2; 63 64 CheckedInt<nsAString::size_type> needed = 65 aDecoder->MaxUTF8BufferLength(lengthU8); 66 if (!needed.isValid()) { 67 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 68 return; 69 } 70 71 UniquePtr<uint8_t[], JS::FreePolicy> buffer( 72 static_cast<uint8_t*>(JS_malloc(aCx, needed.value()))); 73 if (!buffer) { 74 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 75 return; 76 } 77 78 mozilla::Span<uint8_t> input((uint8_t*)aInput.data(), lengthU8); 79 mozilla::Span<uint8_t> output(buffer, needed.value()); 80 81 // This originally wanted to use mozilla::Encoder::Encode() that accepts 82 // char16_t*, but it lacks the pending-high-surrogate feature to properly 83 // implement 84 // https://encoding.spec.whatwg.org/#convert-code-unit-to-scalar-value. 85 // See also https://github.com/hsivonen/encoding_rs/issues/82 about the 86 // reasoning. 87 // XXX: The code is more verbose here since we need to convert to 88 // uint8_t* which is the only type mozilla::Decoder accepts. 89 uint32_t result; 90 size_t read; 91 size_t written; 92 std::tie(result, read, written, std::ignore) = 93 aDecoder->DecodeToUTF8(input, output, aFlush); 94 MOZ_ASSERT(result == kInputEmpty); 95 MOZ_ASSERT(read == lengthU8); 96 MOZ_ASSERT(written <= needed.value()); 97 98 // https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk 99 // Step 4.2.2.1. Let chunk be a Uint8Array object wrapping an ArrayBuffer 100 // containing output. 101 JS::Rooted<JSObject*> arrayBuffer( 102 aCx, JS::NewArrayBufferWithContents(aCx, written, std::move(buffer))); 103 if (!arrayBuffer.get()) { 104 JS_ClearPendingException(aCx); 105 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 106 return; 107 } 108 aOutputArrayBufferView.set(JS_NewUint8ArrayWithBuffer( 109 aCx, arrayBuffer, 0, static_cast<int64_t>(written))); 110 if (!aOutputArrayBufferView.get()) { 111 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 112 return; 113 } 114 } 115 116 class TextEncoderStreamAlgorithms : public TransformerAlgorithmsWrapper { 117 NS_DECL_ISUPPORTS_INHERITED 118 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TextEncoderStreamAlgorithms, 119 TransformerAlgorithmsBase) 120 121 void SetEncoderStream(TextEncoderStream& aStream) { 122 mEncoderStream = &aStream; 123 } 124 125 // The common part of encode-and-enqueue and encode-and-flush. 126 // https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk 127 MOZ_CAN_RUN_SCRIPT void EncodeAndEnqueue( 128 JSContext* aCx, const nsAString& aInput, 129 TransformStreamDefaultController& aController, bool aFlush, 130 ErrorResult& aRv) { 131 JS::Rooted<JSObject*> outView(aCx); 132 // Passing a Decoder for a reason, see the comments in the method. 133 EncodeNative(aCx, mEncoderStream->Decoder(), aInput, aFlush, &outView, aRv); 134 135 if (JS_GetTypedArrayLength(outView) > 0) { 136 // Step 4.2.2.2. Enqueue chunk into encoder’s transform. 137 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*outView)); 138 aController.Enqueue(aCx, value, aRv); 139 } 140 } 141 142 // https://encoding.spec.whatwg.org/#dom-textencoderstream 143 MOZ_CAN_RUN_SCRIPT void TransformCallbackImpl( 144 JS::Handle<JS::Value> aChunk, 145 TransformStreamDefaultController& aController, 146 ErrorResult& aRv) override { 147 // Step 2. Let transformAlgorithm be an algorithm which takes a chunk 148 // argument and runs the encode and enqueue a chunk algorithm with this and 149 // chunk. 150 151 AutoJSAPI jsapi; 152 if (!jsapi.Init(aController.GetParentObject())) { 153 aRv.ThrowUnknownError("Internal error"); 154 return; 155 } 156 JSContext* cx = jsapi.cx(); 157 158 // https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk 159 160 // Step 1. Let input be the result of converting chunk to a DOMString. 161 // Step 2. Convert input to an I/O queue of code units. 162 nsString str; 163 if (!ConvertJSValueToString(cx, aChunk, eStringify, eStringify, str)) { 164 aRv.MightThrowJSException(); 165 aRv.StealExceptionFromJSContext(cx); 166 return; 167 } 168 169 EncodeAndEnqueue(cx, str, aController, false, aRv); 170 } 171 172 // https://encoding.spec.whatwg.org/#dom-textencoderstream 173 MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl( 174 TransformStreamDefaultController& aController, 175 ErrorResult& aRv) override { 176 // Step 3. Let flushAlgorithm be an algorithm which runs the encode and 177 // flush algorithm with this. 178 179 AutoJSAPI jsapi; 180 if (!jsapi.Init(aController.GetParentObject())) { 181 aRv.ThrowUnknownError("Internal error"); 182 return; 183 } 184 JSContext* cx = jsapi.cx(); 185 186 // The spec manually manages pending high surrogate here, but let's call the 187 // encoder as it's managed there. 188 EncodeAndEnqueue(cx, u""_ns, aController, true, aRv); 189 } 190 191 private: 192 ~TextEncoderStreamAlgorithms() override = default; 193 194 RefPtr<TextEncoderStream> mEncoderStream; 195 }; 196 197 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextEncoderStreamAlgorithms, 198 TransformerAlgorithmsBase, mEncoderStream) 199 NS_IMPL_ADDREF_INHERITED(TextEncoderStreamAlgorithms, TransformerAlgorithmsBase) 200 NS_IMPL_RELEASE_INHERITED(TextEncoderStreamAlgorithms, 201 TransformerAlgorithmsBase) 202 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEncoderStreamAlgorithms) 203 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) 204 205 // https://encoding.spec.whatwg.org/#dom-textencoderstream 206 already_AddRefed<TextEncoderStream> TextEncoderStream::Constructor( 207 const GlobalObject& aGlobal, ErrorResult& aRv) { 208 // Step 1. Set this’s encoder to an instance of the UTF-8 encoder. 209 210 // Step 2-3 211 auto algorithms = MakeRefPtr<TextEncoderStreamAlgorithms>(); 212 213 // Step 4-5 214 RefPtr<TransformStream> transformStream = 215 TransformStream::CreateGeneric(aGlobal, *algorithms, aRv); 216 if (aRv.Failed()) { 217 return nullptr; 218 } 219 220 // Step 6. Set this’s transform to transformStream. 221 // (Done in the constructor) 222 auto encoderStream = 223 MakeRefPtr<TextEncoderStream>(aGlobal.GetAsSupports(), *transformStream); 224 algorithms->SetEncoderStream(*encoderStream); 225 return encoderStream.forget(); 226 } 227 228 ReadableStream* TextEncoderStream::Readable() const { 229 return mStream->Readable(); 230 } 231 232 WritableStream* TextEncoderStream::Writable() const { 233 return mStream->Writable(); 234 } 235 236 void TextEncoderStream::GetEncoding(nsCString& aRetVal) const { 237 aRetVal.AssignLiteral("utf-8"); 238 } 239 240 } // namespace mozilla::dom