tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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