/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
// IWYU pragma: private, include "nsString.h"

#ifndef nsTSubstring_h
#define nsTSubstring_h

#include <iterator>
#include <type_traits>

#include "mozilla/Casting.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/IntegerTypeTraits.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Span.h"
#include "mozilla/Unused.h"

#include "nsTStringRepr.h"

#ifndef MOZILLA_INTERNAL_API
#  error "Using XPCOM strings is limited to code linked into libxul."
#endif

// The max number of logically uninitialized code units to
// fill with a marker byte or to mark as unintialized for
// memory checking. (Limited to avoid quadratic behavior.)
const size_t kNsStringBufferMaxPoison = 16;

class nsStringBuffer;
template <typename T>
class nsTSubstringSplitter;
template <typename T>
class nsTString;
template <typename T>
class nsTSubstring;

namespace mozilla {

/**
 * This handle represents permission to perform low-level writes
 * the storage buffer of a string in a manner that's aware of the
 * actual capacity of the storage buffer allocation and that's
 * cache-friendly in the sense that the writing of zero terminator
 * for C compatibility can happen in linear memory access order
 * (i.e. the zero terminator write takes place after writing
 * new content to the string as opposed to the zero terminator
 * write happening first causing a non-linear memory write for
 * cache purposes).
 *
 * If you requested a prefix to be preserved when starting
 * or restarting the bulk write, the prefix is present at the
 * start of the buffer exposed by this handle as Span or
 * as a raw pointer, and it's your responsibility to start
 * writing after after the preserved prefix (which you
 * presumably wanted not to overwrite since you asked for
 * it to be preserved).
 *
 * In a success case, you must call Finish() with the new
 * length of the string. In failure cases, it's OK to return
 * early from the function whose local variable this handle is.
 * The destructor of this class takes care of putting the
 * string in a valid and mostly harmless state in that case
 * by setting the value of a non-empty string to a single
 * REPLACEMENT CHARACTER or in the case of nsACString that's
 * too short for a REPLACEMENT CHARACTER to fit, an ASCII
 * SUBSTITUTE.
 *
 * You must not allow this handle to outlive the string you
 * obtained it from.
 *
 * You must not access the string you obtained this handle
 * from in any way other than through this handle until
 * you call Finish() on the handle or the handle goes out
 * of scope.
 *
 * Once you've called Finish(), you must not call any
 * methods on this handle and must not use values previously
 * obtained.
 *
 * Once you call RestartBulkWrite(), you must not use
 * values previously obtained from this handle and must
 * reobtain the new corresponding values.
 */
template <typename T>
class BulkWriteHandle final {
  friend class nsTSubstring<T>;

 public:
  typedef typename mozilla::detail::nsTStringRepr<T> base_string_type;
  typedef typename base_string_type::size_type size_type;

  /**
   * Pointer to the start of the writable buffer. Never nullptr.
   *
   * This pointer is valid until whichever of these happens first:
   *  1) Finish() is called
   *  2) RestartBulkWrite() is called
   *  3) BulkWriteHandle goes out of scope
   */
  T* Elements() const {
    MOZ_ASSERT(mString);
    return mString->mData;
  }

  /**
   * How many code units can be written to the buffer.
   * (Note: This is not the same as the string's Length().)
   *
   * This value is valid until whichever of these happens first:
   *  1) Finish() is called
   *  2) RestartBulkWrite() is called
   *  3) BulkWriteHandle goes out of scope
   */
  size_type Length() const {
    MOZ_ASSERT(mString);
    return mCapacity;
  }

  /**
   * Pointer past the end of the buffer.
   *
   * This pointer is valid until whichever of these happens first:
   *  1) Finish() is called
   *  2) RestartBulkWrite() is called
   *  3) BulkWriteHandle goes out of scope
   */
  T* End() const { return Elements() + Length(); }

  /**
   * The writable buffer as Span.
   *
   * This Span is valid until whichever of these happens first:
   *  1) Finish() is called
   *  2) RestartBulkWrite() is called
   *  3) BulkWriteHandle goes out of scope
   */
  auto AsSpan() const { return mozilla::Span<T>{Elements(), Length()}; }

  /**
   * Autoconvert to the buffer as writable Span.
   *
   * This Span is valid until whichever of these happens first:
   *  1) Finish() is called
   *  2) RestartBulkWrite() is called
   *  3) BulkWriteHandle goes out of scope
   */
  operator mozilla::Span<T>() const { return AsSpan(); }

  /**
   * Restart the bulk write with a different capacity.
   *
   * This method invalidates previous return values
   * of the other methods above.
   *
   * Can fail if out of memory leaving the buffer
   * in the state before this call.
   *
   * @param aCapacity the new requested capacity
   * @param aPrefixToPreserve the number of code units at
   *                          the start of the string to
   *                          copy over to the new buffer
   * @param aAllowShrinking whether the string is
   *                        allowed to attempt to
   *                        allocate a smaller buffer
   *                        for its content and copy
   *                        the data over.
   */
  mozilla::Result<mozilla::Ok, nsresult> RestartBulkWrite(
      size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) {
    MOZ_ASSERT(mString);
    MOZ_TRY_VAR(mCapacity, mString->StartBulkWriteImpl(
                               aCapacity, aPrefixToPreserve, aAllowShrinking));
    return mozilla::Ok();
  }

  /**
   * Indicate that the bulk write finished successfully.
   *
   * @param aLength the number of code units written;
   *                must not exceed Length()
   * @param aAllowShrinking whether the string is
   *                        allowed to attempt to
   *                        allocate a smaller buffer
   *                        for its content and copy
   *                        the data over.
   */
  void Finish(size_type aLength, bool aAllowShrinking) {
    MOZ_ASSERT(mString);
    MOZ_ASSERT(aLength <= mCapacity);
    if (!aLength) {
      // Truncate is safe even when the string is in an invalid state
      mString->Truncate();
      mString = nullptr;
      return;
    }
    if (aAllowShrinking) {
      mozilla::Unused << mString->StartBulkWriteImpl(aLength, aLength, true);
    }
    mString->FinishBulkWriteImpl(aLength);
    mString = nullptr;
  }

  BulkWriteHandle(BulkWriteHandle&& aOther)
      : mString(aOther.Forget()), mCapacity(aOther.mCapacity) {}

  ~BulkWriteHandle() {
    if (!mString || !mCapacity) {
      return;
    }
    // The old zero terminator may be gone by now, so we need
    // to write a new one somewhere and make length match.
    // We can use a length between 1 and self.capacity.
    // The contents of the string can be partially uninitialized
    // or partially initialized in a way that would be dangerous
    // if parsed by some recipient. It's prudent to write something
    // same as the contents of the string. U+FFFD is the safest
    // placeholder, but when it doesn't fit, let's use ASCII
    // substitute. Merely truncating the string to a zero-length
    // string might be dangerous in some scenarios. See
    // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences
    // for closely related scenario.
    auto ptr = Elements();
    // Cast the pointer below to silence warnings
    if (sizeof(T) == 1) {
      unsigned char* charPtr = reinterpret_cast<unsigned char*>(ptr);
      if (mCapacity >= 3) {
        *charPtr++ = 0xEF;
        *charPtr++ = 0xBF;
        *charPtr++ = 0xBD;
        mString->mLength = 3;
      } else {
        *charPtr++ = 0x1A;
        mString->mLength = 1;
      }
      *charPtr = 0;
    } else if (sizeof(T) == 2) {
      char16_t* charPtr = reinterpret_cast<char16_t*>(ptr);
      *charPtr++ = 0xFFFD;
      *charPtr = 0;
      mString->mLength = 1;
    } else {
      MOZ_ASSERT_UNREACHABLE("Only 8-bit and 16-bit code units supported.");
    }
  }

  BulkWriteHandle() = delete;
  BulkWriteHandle(const BulkWriteHandle&) = delete;
  BulkWriteHandle& operator=(const BulkWriteHandle&) = delete;

 private:
  BulkWriteHandle(nsTSubstring<T>* aString, size_type aCapacity)
      : mString(aString), mCapacity(aCapacity) {}

  nsTSubstring<T>* Forget() {
    auto string = mString;
    mString = nullptr;
    return string;
  }

  nsTSubstring<T>* mString;  // nullptr upon finish
  size_type mCapacity;
};

}  // namespace mozilla

/**
 * nsTSubstring is an abstract string class. From an API perspective, this
 * class is the root of the string class hierarchy. It represents a single
 * contiguous array of characters, which may or may not be null-terminated.
 * This type is not instantiated directly. A sub-class is instantiated
 * instead. For example, see nsTString.
 *
 * NAMES:
 *   nsAString for wide characters
 *   nsACString for narrow characters
 *
 */
template <typename T>
class nsTSubstring : public mozilla::detail::nsTStringRepr<T> {
  friend class mozilla::BulkWriteHandle<T>;
  friend class nsStringBuffer;

 public:
  typedef nsTSubstring<T> self_type;

  typedef nsTString<T> string_type;

  typedef typename mozilla::detail::nsTStringRepr<T> base_string_type;
  typedef typename base_string_type::substring_type substring_type;

  typedef typename base_string_type::fallible_t fallible_t;

  typedef typename base_string_type::char_type char_type;
  typedef typename base_string_type::char_traits char_traits;
  typedef
      typename base_string_type::incompatible_char_type incompatible_char_type;

  typedef typename base_string_type::substring_tuple_type substring_tuple_type;

  typedef typename base_string_type::const_iterator const_iterator;
  typedef typename base_string_type::iterator iterator;

  typedef typename base_string_type::comparator_type comparator_type;

  typedef typename base_string_type::const_char_iterator const_char_iterator;

  typedef typename base_string_type::string_view string_view;

  typedef typename base_string_type::index_type index_type;
  typedef typename base_string_type::size_type size_type;

  // These are only for internal use within the string classes:
  typedef typename base_string_type::DataFlags DataFlags;
  typedef typename base_string_type::ClassFlags ClassFlags;
  typedef typename base_string_type::LengthStorage LengthStorage;

  // this acts like a virtual destructor
  ~nsTSubstring() { Finalize(); }

  /**
   * writing iterators
   *
   * BeginWriting() makes the string mutable (if it isn't
   * already) and returns (or writes into an outparam) a
   * pointer that provides write access to the string's buffer.
   *
   * Note: Consider if BulkWrite() suits your use case better
   * than BeginWriting() combined with SetLength().
   *
   * Note: Strings autoconvert into writable mozilla::Span,
   * which may suit your use case better than calling
   * BeginWriting() directly.
   *
   * When writing via the pointer obtained from BeginWriting(),
   * you are allowed to write at most the number of code units
   * indicated by Length() or, alternatively, write up to, but
   * not including, the position indicated by EndWriting().
   *
   * In particular, calling SetCapacity() does not affect what
   * the above paragraph says.
   */

  iterator BeginWriting() {
    if (!EnsureMutable()) {
      AllocFailed(base_string_type::mLength);
    }

    return base_string_type::mData;
  }

  iterator BeginWriting(const fallible_t&) {
    return EnsureMutable() ? base_string_type::mData : iterator(0);
  }

  iterator EndWriting() {
    if (!EnsureMutable()) {
      AllocFailed(base_string_type::mLength);
    }

    return base_string_type::mData + base_string_type::mLength;
  }

  iterator EndWriting(const fallible_t&) {
    return EnsureMutable()
               ? (base_string_type::mData + base_string_type::mLength)
               : iterator(0);
  }

  /**
   * Perform string to int conversion.
   * @param   aErrorCode will contain error if one occurs
   * @param   aRadix is the radix to use. Only 10 and 16 are supported.
   * @return  int rep of string value, and possible (out) error code
   */
  int32_t ToInteger(nsresult* aErrorCode, uint32_t aRadix = 10) const;

  /**
   * Perform string to 64-bit int conversion.
   * @param   aErrorCode will contain error if one occurs
   * @param   aRadix is the radix to use. Only 10 and 16 are supported.
   * @return  64-bit int rep of string value, and possible (out) error code
   */
  int64_t ToInteger64(nsresult* aErrorCode, uint32_t aRadix = 10) const;

  /**
   * assignment
   */

  void NS_FASTCALL Assign(char_type aChar);
  [[nodiscard]] bool NS_FASTCALL Assign(char_type aChar, const fallible_t&);

  void NS_FASTCALL Assign(const char_type* aData,
                          size_type aLength = size_type(-1));
  [[nodiscard]] bool NS_FASTCALL Assign(const char_type* aData,
                                        const fallible_t&);
  [[nodiscard]] bool NS_FASTCALL Assign(const char_type* aData,
                                        size_type aLength, const fallible_t&);

  void NS_FASTCALL Assign(const self_type&);
  [[nodiscard]] bool NS_FASTCALL Assign(const self_type&, const fallible_t&);

  void NS_FASTCALL Assign(self_type&&);
  [[nodiscard]] bool NS_FASTCALL Assign(self_type&&, const fallible_t&);

  void NS_FASTCALL Assign(const substring_tuple_type&);
  [[nodiscard]] bool NS_FASTCALL Assign(const substring_tuple_type&,
                                        const fallible_t&);

#if defined(MOZ_USE_CHAR16_WRAPPER)
  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  void Assign(char16ptr_t aData) {
    Assign(static_cast<const char16_t*>(aData));
  }

  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  void Assign(char16ptr_t aData, size_type aLength) {
    Assign(static_cast<const char16_t*>(aData), aLength);
  }

  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  [[nodiscard]] bool Assign(char16ptr_t aData, size_type aLength,
                            const fallible_t& aFallible) {
    return Assign(static_cast<const char16_t*>(aData), aLength, aFallible);
  }
#endif

  void NS_FASTCALL AssignASCII(const char* aData, size_type aLength);
  [[nodiscard]] bool NS_FASTCALL AssignASCII(const char* aData,
                                             size_type aLength,
                                             const fallible_t&);

  void NS_FASTCALL AssignASCII(const char* aData) {
    AssignASCII(aData, strlen(aData));
  }
  [[nodiscard]] bool NS_FASTCALL AssignASCII(const char* aData,
                                             const fallible_t& aFallible) {
    return AssignASCII(aData, strlen(aData), aFallible);
  }

  // AssignLiteral must ONLY be called with an actual literal string, or
  // a character array *constant* of static storage duration declared
  // without an explicit size and with an initializer that is a string
  // literal or is otherwise null-terminated.
  // Use Assign or AssignASCII for other character array variables.
  //
  // This method does not need a fallible version, because it uses the
  // POD buffer of the literal as the string's buffer without allocating.
  // The literal does not need to be ASCII. If this a 16-bit string, this
  // method takes a u"" literal. (The overload on 16-bit strings that takes
  // a "" literal takes only ASCII.)
  template <int N>
  void AssignLiteral(const char_type (&aStr)[N]) {
    AssignLiteral(aStr, N - 1);
  }

  // AssignLiteral must ONLY be called with an actual literal string, or
  // a char array *constant* declared without an explicit size and with an
  // initializer that is a string literal or is otherwise null-terminated.
  // Use AssignASCII for other char array variables.
  //
  // This method takes an 8-bit (ASCII-only!) string that is expanded
  // into a 16-bit string at run time causing a run-time allocation.
  // To avoid the run-time allocation (at the cost of the literal
  // taking twice the size in the binary), use the above overload that
  // takes a u"" string instead. Using the overload that takes a u""
  // literal is generally preferred when working with 16-bit strings.
  //
  // There is not a fallible version of this method because it only really
  // applies to small allocations that we wouldn't want to check anyway.
  template <int N, typename Q = T,
            typename EnableIfChar16 = typename mozilla::Char16OnlyT<Q>>
  void AssignLiteral(const incompatible_char_type (&aStr)[N]) {
    AssignASCII(aStr, N - 1);
  }

  self_type& operator=(char_type aChar) {
    Assign(aChar);
    return *this;
  }
  self_type& operator=(const char_type* aData) {
    Assign(aData);
    return *this;
  }
#if defined(MOZ_USE_CHAR16_WRAPPER)
  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  self_type& operator=(char16ptr_t aData) {
    Assign(aData);
    return *this;
  }
#endif
  self_type& operator=(const self_type& aStr) {
    Assign(aStr);
    return *this;
  }
  self_type& operator=(self_type&& aStr) {
    Assign(std::move(aStr));
    return *this;
  }
  self_type& operator=(const substring_tuple_type& aTuple) {
    Assign(aTuple);
    return *this;
  }

  // Adopt a heap-allocated char sequence for this string; is Voided if aData
  // is null. Useful for e.g. converting an strdup'd C string into an
  // nsCString. See also getter_Copies(), which is a useful wrapper.
  void NS_FASTCALL Adopt(char_type* aData, size_type aLength = size_type(-1));

  /**
   * buffer manipulation
   */

  void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength,
                           char_type aChar);
  [[nodiscard]] bool NS_FASTCALL Replace(index_type aCutStart,
                                         size_type aCutLength, char_type aChar,
                                         const fallible_t&);
  void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength,
                           const char_type* aData,
                           size_type aLength = size_type(-1));
  [[nodiscard]] bool NS_FASTCALL Replace(index_type aCutStart,
                                         size_type aCutLength,
                                         const char_type* aData,
                                         size_type aLength, const fallible_t&);
  void Replace(index_type aCutStart, size_type aCutLength,
               const self_type& aStr) {
    Replace(aCutStart, aCutLength, aStr.Data(), aStr.Length());
  }
  [[nodiscard]] bool Replace(index_type aCutStart, size_type aCutLength,
                             const self_type& aStr,
                             const fallible_t& aFallible) {
    return Replace(aCutStart, aCutLength, aStr.Data(), aStr.Length(),
                   aFallible);
  }
  void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength,
                           const substring_tuple_type& aTuple);

  // ReplaceLiteral must ONLY be called with an actual literal string, or
  // a character array *constant* of static storage duration declared
  // without an explicit size and with an initializer that is a string
  // literal or is otherwise null-terminated.
  // Use Replace for other character array variables.
  template <int N>
  void ReplaceLiteral(index_type aCutStart, size_type aCutLength,
                      const char_type (&aStr)[N]) {
    ReplaceLiteral(aCutStart, aCutLength, aStr, N - 1);
  }

  /**
   * |Left|, |Mid|, and |Right| are annoying signatures that seem better almost
   * any _other_ way than they are now.  Consider these alternatives
   *
   * // ...a member function that returns a |Substring|
   * aWritable = aReadable.Left(17);
   * // ...a global function that returns a |Substring|
   * aWritable = Left(aReadable, 17);
   * // ...a global function that does the assignment
   * Left(aReadable, 17, aWritable);
   *
   * as opposed to the current signature
   *
   * // ...a member function that does the assignment
   * aReadable.Left(aWritable, 17);
   *
   * or maybe just stamping them out in favor of |Substring|, they are just
   * duplicate functionality
   *
   * aWritable = Substring(aReadable, 0, 17);
   */
  size_type Mid(self_type& aResult, index_type aStartPos,
                size_type aCount) const;

  size_type Left(self_type& aResult, size_type aCount) const {
    return Mid(aResult, 0, aCount);
  }

  size_type Right(self_type& aResult, size_type aCount) const {
    aCount = XPCOM_MIN(this->Length(), aCount);
    return Mid(aResult, this->mLength - aCount, aCount);
  }

  /**
   *  This method strips whitespace throughout the string.
   */
  void StripWhitespace();
  bool StripWhitespace(const fallible_t&);

  /**
   *  This method is used to remove all occurrences of aChar from this
   * string.
   *
   *  @param  aChar -- char to be stripped
   */
  void StripChar(char_type aChar);

  /**
   *  This method is used to remove all occurrences of aChars from this
   * string.
   *
   *  @param  aChars -- chars to be stripped
   */
  void StripChars(const char_type* aChars);

  /**
   * This method is used to remove all occurrences of some characters this
   * from this string.  The characters removed have the corresponding
   * entries in the bool array set to true; we retain all characters
   * with code beyond 127.
   * THE CALLER IS RESPONSIBLE for making sure the complete boolean
   * array, 128 entries, is properly initialized.
   *
   * See also: ASCIIMask class.
   *
   *  @param  aToStrip -- Array where each entry is true if the
   *          corresponding ASCII character is to be stripped.  All
   *          characters beyond code 127 are retained.  Note that this
   *          parameter is of ASCIIMaskArray type, but we expand the typedef
   *          to avoid having to include nsASCIIMask.h in this include file
   *          as it brings other includes.
   */
  void StripTaggedASCII(const std::array<bool, 128>& aToStrip);

  /**
   * A shortcut to strip \r and \n.
   */
  void StripCRLF();

  /**
   * swaps occurence of 1 string for another
   */
  void ReplaceChar(char_type aOldChar, char_type aNewChar);
  void ReplaceChar(const string_view& aSet, char_type aNewChar);

  /**
   * Replace all occurrences of aTarget with aNewValue.
   * The complexity of this function is O(n+m), n being the length of the string
   * and m being the length of aNewValue.
   */
  void ReplaceSubstring(const self_type& aTarget, const self_type& aNewValue);
  void ReplaceSubstring(const char_type* aTarget, const char_type* aNewValue);
  [[nodiscard]] bool ReplaceSubstring(const self_type& aTarget,
                                      const self_type& aNewValue,
                                      const fallible_t&);
  [[nodiscard]] bool ReplaceSubstring(const char_type* aTarget,
                                      const char_type* aNewValue,
                                      const fallible_t&);

  /**
   *  This method trims characters found in aSet from either end of the
   *  underlying string.
   *
   *  @param   aSet -- contains chars to be trimmed from both ends
   *  @param   aTrimLeading
   *  @param   aTrimTrailing
   *  @param   aIgnoreQuotes -- if true, causes surrounding quotes to be ignored
   *  @return  this
   */
  void Trim(const std::string_view& aSet, bool aTrimLeading = true,
            bool aTrimTrailing = true, bool aIgnoreQuotes = false);

  /**
   *  This method strips whitespace from string.
   *  You can control whether whitespace is yanked from start and end of
   *  string as well.
   *
   *  @param   aTrimLeading controls stripping of leading ws
   *  @param   aTrimTrailing controls stripping of trailing ws
   */
  void CompressWhitespace(bool aTrimLeading = true, bool aTrimTrailing = true);

  void Append(char_type aChar);

  [[nodiscard]] bool Append(char_type aChar, const fallible_t& aFallible);

  void Append(const char_type* aData, size_type aLength = size_type(-1));

  [[nodiscard]] bool Append(const char_type* aData, size_type aLength,
                            const fallible_t& aFallible);

#if defined(MOZ_USE_CHAR16_WRAPPER)
  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  void Append(char16ptr_t aData, size_type aLength = size_type(-1)) {
    Append(static_cast<const char16_t*>(aData), aLength);
  }
#endif

  void Append(const self_type& aStr);

  [[nodiscard]] bool Append(const self_type& aStr, const fallible_t& aFallible);

  void Append(const substring_tuple_type& aTuple);

  [[nodiscard]] bool Append(const substring_tuple_type& aTuple,
                            const fallible_t& aFallible);

  void AppendASCII(const char* aData, size_type aLength = size_type(-1));

  [[nodiscard]] bool AppendASCII(const char* aData,
                                 const fallible_t& aFallible);

  [[nodiscard]] bool AppendASCII(const char* aData, size_type aLength,
                                 const fallible_t& aFallible);

  // Appends a literal string ("" literal in the 8-bit case and u"" literal
  // in the 16-bit case) to the string.
  //
  // AppendLiteral must ONLY be called with an actual literal string, or
  // a character array *constant* of static storage duration declared
  // without an explicit size and with an initializer that is a string
  // literal or is otherwise null-terminated.
  // Use Append or AppendASCII for other character array variables.
  template <int N>
  void AppendLiteral(const char_type (&aStr)[N]) {
    // The case where base_string_type::mLength is zero is intentionally
    // left unoptimized (could be optimized as call to AssignLiteral),
    // because it's rare/nonexistent. If you add that optimization,
    // please be sure to also check that
    // !(base_string_type::mDataFlags & DataFlags::REFCOUNTED)
    // to avoid undoing the effects of SetCapacity().
    Append(aStr, N - 1);
  }

  template <int N>
  void AppendLiteral(const char_type (&aStr)[N], const fallible_t& aFallible) {
    // The case where base_string_type::mLength is zero is intentionally
    // left unoptimized (could be optimized as call to AssignLiteral),
    // because it's rare/nonexistent. If you add that optimization,
    // please be sure to also check that
    // !(base_string_type::mDataFlags & DataFlags::REFCOUNTED)
    // to avoid undoing the effects of SetCapacity().
    return Append(aStr, N - 1, aFallible);
  }

  // Only enable for T = char16_t
  //
  // Appends an 8-bit literal string ("" literal) to a 16-bit string by
  // expanding it. The literal must only contain ASCII.
  //
  // Using u"" literals with 16-bit strings is generally preferred.
  template <int N, typename Q = T,
            typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  void AppendLiteral(const incompatible_char_type (&aStr)[N]) {
    AppendASCII(aStr, N - 1);
  }

  // Only enable for T = char16_t
  template <int N, typename Q = T,
            typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  [[nodiscard]] bool AppendLiteral(const incompatible_char_type (&aStr)[N],
                                   const fallible_t& aFallible) {
    return AppendASCII(aStr, N - 1, aFallible);
  }

  /**
   * Append a formatted string to the current string. Uses the
   * standard printf format codes.  This uses NSPR formatting, which will be
   * locale-aware for floating-point values.  You probably don't want to use
   * this with floating-point values as a result.
   */
  void AppendPrintf(const char* aFormat, ...) MOZ_FORMAT_PRINTF(2, 3);
  void AppendVprintf(const char* aFormat, va_list aAp) MOZ_FORMAT_PRINTF(2, 0);
  void AppendInt(int32_t aInteger) { AppendIntDec(aInteger); }
  void AppendInt(int32_t aInteger, int aRadix) {
    if (aRadix == 10) {
      AppendIntDec(aInteger);
    } else if (aRadix == 8) {
      AppendIntOct(static_cast<uint32_t>(aInteger));
    } else {
      AppendIntHex(static_cast<uint32_t>(aInteger));
    }
  }
  void AppendInt(uint32_t aInteger) { AppendIntDec(aInteger); }
  void AppendInt(uint32_t aInteger, int aRadix) {
    if (aRadix == 10) {
      AppendIntDec(aInteger);
    } else if (aRadix == 8) {
      AppendIntOct(aInteger);
    } else {
      AppendIntHex(aInteger);
    }
  }
  void AppendInt(int64_t aInteger) { AppendIntDec(aInteger); }
  void AppendInt(int64_t aInteger, int aRadix) {
    if (aRadix == 10) {
      AppendIntDec(aInteger);
    } else if (aRadix == 8) {
      AppendIntOct(static_cast<uint64_t>(aInteger));
    } else {
      AppendIntHex(static_cast<uint64_t>(aInteger));
    }
  }
  void AppendInt(uint64_t aInteger) { AppendIntDec(aInteger); }
  void AppendInt(uint64_t aInteger, int aRadix) {
    if (aRadix == 10) {
      AppendIntDec(aInteger);
    } else if (aRadix == 8) {
      AppendIntOct(aInteger);
    } else {
      AppendIntHex(aInteger);
    }
  }

 private:
  void AppendIntDec(int32_t);
  void AppendIntDec(uint32_t);
  void AppendIntOct(uint32_t);
  void AppendIntHex(uint32_t);
  void AppendIntDec(int64_t);
  void AppendIntDec(uint64_t);
  void AppendIntOct(uint64_t);
  void AppendIntHex(uint64_t);

 public:
  /**
   * Append the given float to this string
   */
  void NS_FASTCALL AppendFloat(float aFloat);
  void NS_FASTCALL AppendFloat(double aFloat);

  self_type& operator+=(char_type aChar) {
    Append(aChar);
    return *this;
  }
  self_type& operator+=(const char_type* aData) {
    Append(aData);
    return *this;
  }
#if defined(MOZ_USE_CHAR16_WRAPPER)
  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  self_type& operator+=(char16ptr_t aData) {
    Append(aData);
    return *this;
  }
#endif
  self_type& operator+=(const self_type& aStr) {
    Append(aStr);
    return *this;
  }
  self_type& operator+=(const substring_tuple_type& aTuple) {
    Append(aTuple);
    return *this;
  }

  void Insert(char_type aChar, index_type aPos) { Replace(aPos, 0, aChar); }
  void Insert(const char_type* aData, index_type aPos,
              size_type aLength = size_type(-1)) {
    Replace(aPos, 0, aData, aLength);
  }
#if defined(MOZ_USE_CHAR16_WRAPPER)
  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  void Insert(char16ptr_t aData, index_type aPos,
              size_type aLength = size_type(-1)) {
    Insert(static_cast<const char16_t*>(aData), aPos, aLength);
  }
#endif
  void Insert(const self_type& aStr, index_type aPos) {
    Replace(aPos, 0, aStr);
  }
  void Insert(const substring_tuple_type& aTuple, index_type aPos) {
    Replace(aPos, 0, aTuple);
  }

  // InsertLiteral must ONLY be called with an actual literal string, or
  // a character array *constant* of static storage duration declared
  // without an explicit size and with an initializer that is a string
  // literal or is otherwise null-terminated.
  // Use Insert for other character array variables.
  template <int N>
  void InsertLiteral(const char_type (&aStr)[N], index_type aPos) {
    ReplaceLiteral(aPos, 0, aStr, N - 1);
  }

  void Cut(index_type aCutStart, size_type aCutLength) {
    Replace(aCutStart, aCutLength, char_traits::sEmptyBuffer, 0);
  }

  nsTSubstringSplitter<T> Split(const char_type aChar) const;

  /**
   * buffer sizing
   */

  /**
   * Attempts to set the capacity to the given size in number of
   * code units without affecting the length of the string in
   * order to avoid reallocation during a subsequent sequence of
   * appends.
   *
   * This method is appropriate to use before a sequence of multiple
   * operations from the following list (without operations that are
   * not on the list between the SetCapacity() call and operations
   * from the list):
   *
   * Append()
   * AppendASCII()
   * AppendLiteral() (except if the string is empty: bug 1487606)
   * AppendPrintf()
   * AppendInt()
   * AppendFloat()
   * LossyAppendUTF16toASCII()
   * AppendASCIItoUTF16()
   *
   * DO NOT call SetCapacity() if the subsequent operations on the
   * string do not meet the criteria above. Operations that undo
   * the benefits of SetCapacity() include but are not limited to:
   *
   * SetLength()
   * Truncate()
   * Assign()
   * AssignLiteral()
   * Adopt()
   * CopyASCIItoUTF16()
   * LossyCopyUTF16toASCII()
   * AppendUTF16toUTF8()
   * AppendUTF8toUTF16()
   * CopyUTF16toUTF8()
   * CopyUTF8toUTF16()
   *
   * If your string is an nsAuto[C]String and you are calling
   * SetCapacity() with a constant N, please instead declare the
   * string as nsAuto[C]StringN<N+1> without calling SetCapacity().
   *
   * There is no need to include room for the null terminator: it is
   * the job of the string class.
   *
   * Note: Calling SetCapacity() does not give you permission to
   * use the pointer obtained from BeginWriting() to write
   * past the current length (as returned by Length()) of the
   * string. Please use either BulkWrite() or SetLength()
   * instead.
   *
   * Note: SetCapacity() won't make the string shorter if
   * called with an argument smaller than the length of the
   * string.
   *
   * Note: You must not use previously obtained iterators
   * or spans after calling SetCapacity().
   */
  void NS_FASTCALL SetCapacity(size_type aNewCapacity);
  [[nodiscard]] bool NS_FASTCALL SetCapacity(size_type aNewCapacity,
                                             const fallible_t&);

  /**
   * Changes the logical length of the string, potentially
   * allocating a differently-sized buffer for the string.
   *
   * When making the string shorter, this method never
   * reports allocation failure.
   *
   * Exposes uninitialized memory if the string got longer.
   *
   * If called with the argument 0, releases the
   * heap-allocated buffer, if any. (But the no-argument
   * overload of Truncate() is a more idiomatic and efficient
   * option than SetLength(0).)
   *
   * Note: You must not use previously obtained iterators
   * or spans after calling SetLength().
   */
  void NS_FASTCALL SetLength(size_type aNewLength);
  [[nodiscard]] bool NS_FASTCALL SetLength(size_type aNewLength,
                                           const fallible_t&);

  /**
   * Like SetLength() but asserts in that the string
   * doesn't become longer. Never fails, so doesn't need a
   * fallible variant.
   *
   * Note: You must not use previously obtained iterators
   * or spans after calling Truncate().
   */
  void Truncate(size_type aNewLength) {
    MOZ_RELEASE_ASSERT(aNewLength <= base_string_type::mLength,
                       "Truncate cannot make string longer");
    mozilla::DebugOnly<bool> success = SetLength(aNewLength, mozilla::fallible);
    MOZ_ASSERT(success);
  }

  /**
   * A more efficient overload for Truncate(0). Releases the
   * heap-allocated buffer if any.
   */
  void Truncate();

  /**
   * buffer access
   */

  /**
   * Get a const pointer to the string's internal buffer.  The caller
   * MUST NOT modify the characters at the returned address.
   *
   * @returns The length of the buffer in characters.
   */
  inline size_type GetData(const char_type** aData) const {
    *aData = base_string_type::mData;
    return base_string_type::mLength;
  }

  /**
   * Get a pointer to the string's internal buffer, optionally resizing
   * the buffer first.  If size_type(-1) is passed for newLen, then the
   * current length of the string is used.  The caller MAY modify the
   * characters at the returned address (up to but not exceeding the
   * length of the string).
   *
   * @returns The length of the buffer in characters or 0 if unable to
   * satisfy the request due to low-memory conditions.
   */
  size_type GetMutableData(char_type** aData,
                           size_type aNewLen = size_type(-1)) {
    if (!EnsureMutable(aNewLen)) {
      AllocFailed(aNewLen == size_type(-1) ? base_string_type::Length()
                                           : aNewLen);
    }

    *aData = base_string_type::mData;
    return base_string_type::Length();
  }

  size_type GetMutableData(char_type** aData, size_type aNewLen,
                           const fallible_t&) {
    if (!EnsureMutable(aNewLen)) {
      *aData = nullptr;
      return 0;
    }

    *aData = base_string_type::mData;
    return base_string_type::mLength;
  }

#if defined(MOZ_USE_CHAR16_WRAPPER)
  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  size_type GetMutableData(wchar_t** aData, size_type aNewLen = size_type(-1)) {
    return GetMutableData(reinterpret_cast<char16_t**>(aData), aNewLen);
  }

  template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
  size_type GetMutableData(wchar_t** aData, size_type aNewLen,
                           const fallible_t& aFallible) {
    return GetMutableData(reinterpret_cast<char16_t**>(aData), aNewLen,
                          aFallible);
  }
#endif

  mozilla::Span<char_type> GetMutableData(size_type aNewLen = size_type(-1)) {
    if (!EnsureMutable(aNewLen)) {
      AllocFailed(aNewLen == size_type(-1) ? base_string_type::Length()
                                           : aNewLen);
    }

    return mozilla::Span{base_string_type::mData, base_string_type::Length()};
  }

  mozilla::Maybe<mozilla::Span<char_type>> GetMutableData(size_type aNewLen,
                                                          const fallible_t&) {
    if (!EnsureMutable(aNewLen)) {
      return mozilla::Nothing();
    }
    return Some(
        mozilla::Span{base_string_type::mData, base_string_type::Length()});
  }

  /**
   * Span integration
   */

  operator mozilla::Span<const char_type>() const {
    return mozilla::Span{base_string_type::BeginReading(),
                         base_string_type::Length()};
  }

  void Append(mozilla::Span<const char_type> aSpan) {
    Append(aSpan.Elements(), aSpan.Length());
  }

  [[nodiscard]] bool Append(mozilla::Span<const char_type> aSpan,
                            const fallible_t& aFallible) {
    return Append(aSpan.Elements(), aSpan.Length(), aFallible);
  }

  void NS_FASTCALL AssignASCII(mozilla::Span<const char> aData) {
    AssignASCII(aData.Elements(), aData.Length());
  }
  [[nodiscard]] bool NS_FASTCALL AssignASCII(mozilla::Span<const char> aData,
                                             const fallible_t& aFallible) {
    return AssignASCII(aData.Elements(), aData.Length(), aFallible);
  }

  void AppendASCII(mozilla::Span<const char> aData) {
    AppendASCII(aData.Elements(), aData.Length());
  }

  template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>>
  operator mozilla::Span<const uint8_t>() const {
    return mozilla::Span{
        reinterpret_cast<const uint8_t*>(base_string_type::BeginReading()),
        base_string_type::Length()};
  }

  template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>>
  void Append(mozilla::Span<const uint8_t> aSpan) {
    Append(reinterpret_cast<const char*>(aSpan.Elements()), aSpan.Length());
  }

  template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>>
  [[nodiscard]] bool Append(mozilla::Span<const uint8_t> aSpan,
                            const fallible_t& aFallible) {
    return Append(reinterpret_cast<const char*>(aSpan.Elements()),
                  aSpan.Length(), aFallible);
  }

  void Insert(mozilla::Span<const char_type> aSpan, index_type aPos) {
    Insert(aSpan.Elements(), aPos, aSpan.Length());
  }

  /**
   * string data is never null, but can be marked void.  if true, the
   * string will be truncated.  @see nsTSubstring::IsVoid
   */

  void NS_FASTCALL SetIsVoid(bool);

  /**
   * If the string uses a shared buffer, this method
   * clears the pointer without releasing the buffer.
   */
  void ForgetSharedBuffer() {
    if (base_string_type::mDataFlags & DataFlags::REFCOUNTED) {
      SetToEmptyBuffer();
    }
  }

 protected:
  void AssertValid() {
    MOZ_DIAGNOSTIC_ASSERT(!(this->mClassFlags & ClassFlags::INVALID_MASK));
    MOZ_DIAGNOSTIC_ASSERT(!(this->mDataFlags & DataFlags::INVALID_MASK));
    MOZ_ASSERT(!(this->mClassFlags & ClassFlags::NULL_TERMINATED) ||
                   (this->mDataFlags & DataFlags::TERMINATED),
               "String classes whose static type guarantees a null-terminated "
               "buffer must not be assigned a non-null-terminated buffer.");
  }

 public:
  /**
   * this is public to support automatic conversion of tuple to string
   * base type, which helps avoid converting to nsTAString.
   */
  MOZ_IMPLICIT nsTSubstring(const substring_tuple_type& aTuple)
      : base_string_type(nullptr, 0, DataFlags(0), ClassFlags(0)) {
    AssertValid();
    Assign(aTuple);
  }

  size_t SizeOfExcludingThisIfUnshared(
      mozilla::MallocSizeOf aMallocSizeOf) const;
  size_t SizeOfIncludingThisIfUnshared(
      mozilla::MallocSizeOf aMallocSizeOf) const;

  /**
   * WARNING: Only use these functions if you really know what you are
   * doing, because they can easily lead to double-counting strings.  If
   * you do use them, please explain clearly in a comment why it's safe
   * and won't lead to double-counting.
   */
  size_t SizeOfExcludingThisEvenIfShared(
      mozilla::MallocSizeOf aMallocSizeOf) const;
  size_t SizeOfIncludingThisEvenIfShared(
      mozilla::MallocSizeOf aMallocSizeOf) const;

  template <class N>
  void NS_ABORT_OOM(T) {
    struct never {};  // a compiler-friendly way to do static_assert(false)
    static_assert(
        std::is_same_v<N, never>,
        "In string classes, use AllocFailed to account for sizeof(char_type). "
        "Use the global ::NS_ABORT_OOM if you really have a count of bytes.");
  }

  MOZ_ALWAYS_INLINE void AllocFailed(size_t aLength) {
    ::NS_ABORT_OOM(aLength * sizeof(char_type));
  }

 protected:
  // default initialization
  nsTSubstring()
      : base_string_type(char_traits::sEmptyBuffer, 0, DataFlags::TERMINATED,
                         ClassFlags(0)) {
    AssertValid();
  }

  // copy-constructor, constructs as dependent on given object
  // (NOTE: this is for internal use only)
  nsTSubstring(const self_type& aStr)
      : base_string_type(aStr.base_string_type::mData,
                         aStr.base_string_type::mLength,
                         aStr.base_string_type::mDataFlags &
                             (DataFlags::TERMINATED | DataFlags::VOIDED),
                         ClassFlags(0)) {
    AssertValid();
  }

  // initialization with ClassFlags
  explicit nsTSubstring(ClassFlags aClassFlags)
      : base_string_type(char_traits::sEmptyBuffer, 0, DataFlags::TERMINATED,
                         aClassFlags) {
    AssertValid();
  }

  /**
   * allows for direct initialization of a nsTSubstring object.
   */
  nsTSubstring(char_type* aData, size_type aLength, DataFlags aDataFlags,
               ClassFlags aClassFlags)
#if defined(NS_BUILD_REFCNT_LOGGING)
#  define XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE
      ;
#else
#  undef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE
      : base_string_type(aData, aLength, aDataFlags, aClassFlags) {
    AssertValid();
  }
#endif /* NS_BUILD_REFCNT_LOGGING */

  void SetToEmptyBuffer() {
    base_string_type::mData = char_traits::sEmptyBuffer;
    base_string_type::mLength = 0;
    base_string_type::mDataFlags = DataFlags::TERMINATED;
    AssertValid();
  }

  void SetData(char_type* aData, LengthStorage aLength, DataFlags aDataFlags) {
    base_string_type::mData = aData;
    base_string_type::mLength = aLength;
    base_string_type::mDataFlags = aDataFlags;
    AssertValid();
  }

  /**
   * this function releases mData and does not change the value of
   * any of its member variables.  in other words, this function acts
   * like a destructor.
   */
  void NS_FASTCALL Finalize();

 public:
  /**
   * Starts a low-level write transaction to the string.
   *
   * Prepares the string for mutation such that the capacity
   * of the string is at least aCapacity. The returned handle
   * exposes the actual, potentially larger, capacity.
   *
   * If meeting the capacity or mutability requirement requires
   * reallocation, aPrefixToPreserve code units are copied from the
   * start of the old buffer to the start of the new buffer.
   * aPrefixToPreserve must not be greater than the string's current
   * length or greater than aCapacity.
   *
   * aAllowShrinking indicates whether an allocation may be
   * performed when the string is already mutable and the requested
   * capacity is smaller than the current capacity.
   *
   * If this method returns successfully, you must not access
   * the string except through the returned BulkWriteHandle
   * until either the BulkWriteHandle goes out of scope or
   * you call Finish() on the BulkWriteHandle.
   *
   * Compared to SetLength() and BeginWriting(), this more
   * complex API accomplishes two things:
   *  1) It exposes the actual capacity which may be larger
   *     than the requested capacity, which is useful in some
   *     multi-step write operations that don't allocate for
   *     the worst case up front.
   *  2) It writes the zero terminator after the string
   *     content has been written, which results in a
   *     cache-friendly linear write pattern.
   */
  mozilla::Result<mozilla::BulkWriteHandle<T>, nsresult> NS_FASTCALL BulkWrite(
      size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking);

  /**
   * THIS IS NOT REALLY A PUBLIC METHOD! DO NOT CALL FROM OUTSIDE
   * THE STRING IMPLEMENTATION. (It's public only because friend
   * declarations don't allow extern or static and this needs to
   * be called from Rust FFI glue.)
   *
   * Prepares mData to be mutated such that the capacity of the string
   * (not counting the zero-terminator) is at least aCapacity.
   * Returns the actual capacity, which may be larger than what was
   * requested or Err(NS_ERROR_OUT_OF_MEMORY) on allocation failure.
   *
   * mLength is ignored by this method. If the buffer is reallocated,
   * aUnitsToPreserve specifies how many code units to copy over to
   * the new buffer. The old buffer is freed if applicable.
   *
   * Unless the return value is Err(NS_ERROR_OUT_OF_MEMORY) to signal
   * failure or 0 to signal that the string has been set to
   * the special empty state, this method leaves the string in an
   * invalid state! The caller is responsible for calling
   * FinishBulkWrite() (or in Rust calling
   * nsA[C]StringBulkWriteHandle::finish()), which put the string
   * into a valid state by setting mLength and zero-terminating.
   * This method sets the flag to claim that the string is
   * zero-terminated before it actually is.
   *
   * Once this method has been called and before FinishBulkWrite()
   * has been called, only accessing mData or calling this method
   * again are valid operations. Do not call any other methods or
   * access other fields between calling this method and
   * FinishBulkWrite().
   *
   * @param aCapacity The requested capacity. The return value
   *                  will be greater than or equal to this value.
   * @param aPrefixToPreserve The number of code units at the start
   *                          of the old buffer to copy into the
   *                          new buffer.
   * @parem aAllowShrinking If true, an allocation may be performed
   *                        if the requested capacity is smaller
   *                        than the current capacity.
   * @param aSuffixLength The length, in code units, of a suffix
   *                      to move.
   * @param aOldSuffixStart The old start index of the suffix to
   *                        move.
   * @param aNewSuffixStart The new start index of the suffix to
   *                        move.
   *
   */
  mozilla::Result<size_type, nsresult> NS_FASTCALL StartBulkWriteImpl(
      size_type aCapacity, size_type aPrefixToPreserve = 0,
      bool aAllowShrinking = true, size_type aSuffixLength = 0,
      size_type aOldSuffixStart = 0, size_type aNewSuffixStart = 0);

 private:
  void AssignOwned(self_type&& aStr);
  bool AssignNonDependent(const substring_tuple_type& aTuple,
                          size_type aTupleLength,
                          const mozilla::fallible_t& aFallible);

  /**
   * Do not call this except from within FinishBulkWriteImpl() and
   * SetCapacity().
   */
  MOZ_ALWAYS_INLINE void NS_FASTCALL
  FinishBulkWriteImplImpl(LengthStorage aLength) {
    base_string_type::mData[aLength] = char_type(0);
    base_string_type::mLength = aLength;
#ifdef DEBUG
    // ifdefed in order to avoid the call to Capacity() in non-debug
    // builds.
    //
    // Our string is mutable, so Capacity() doesn't return zero.
    // Capacity() doesn't include the space for the zero terminator,
    // but we want to unitialize that slot, too. Since we start
    // counting after the zero terminator the we just wrote above,
    // we end up overwriting the space for terminator not reflected
    // in the capacity number.
    char_traits::uninitialize(
        base_string_type::mData + aLength + 1,
        XPCOM_MIN(size_t(Capacity() - aLength), kNsStringBufferMaxPoison));
#endif
  }

 protected:
  /**
   * Restores the string to a valid state after a call to StartBulkWrite()
   * that returned a non-error result. The argument to this method
   * must be less than or equal to the value returned by the most recent
   * StartBulkWrite() call.
   */
  void NS_FASTCALL FinishBulkWriteImpl(size_type aLength);

  /**
   * this function prepares a section of mData to be modified.  if
   * necessary, this function will reallocate mData and possibly move
   * existing data to open up the specified section.
   *
   * @param aCutStart    specifies the starting offset of the section
   * @param aCutLength   specifies the length of the section to be replaced
   * @param aNewLength   specifies the length of the new section
   *
   * for example, suppose mData contains the string "abcdef" then
   *
   *   ReplacePrep(2, 3, 4);
   *
   * would cause mData to look like "ab____f" where the characters
   * indicated by '_' have an unspecified value and can be freely
   * modified.  this function will null-terminate mData upon return.
   *
   * this function returns false if is unable to allocate sufficient
   * memory.
   */
  [[nodiscard]] bool ReplacePrep(index_type aCutStart, size_type aCutLength,
                                 size_type aNewLength);

  [[nodiscard]] bool NS_FASTCALL ReplacePrepInternal(index_type aCutStart,
                                                     size_type aCutLength,
                                                     size_type aNewFragLength,
                                                     size_type aNewTotalLength);

  /**
   * returns the number of writable storage units starting at mData.
   * the value does not include space for the null-terminator character.
   *
   * NOTE: this function returns 0 if mData is immutable (or the buffer
   *       is 0-sized).
   */
  size_type NS_FASTCALL Capacity() const;

  /**
   * this helper function can be called prior to directly manipulating
   * the contents of mData.  see, for example, BeginWriting.
   */
  [[nodiscard]] bool NS_FASTCALL
  EnsureMutable(size_type aNewLen = size_type(-1));

  void NS_FASTCALL ReplaceLiteral(index_type aCutStart, size_type aCutLength,
                                  const char_type* aData, size_type aLength);

 public:
  // NOTE: this method is declared public _only_ for convenience for
  // callers who don't have access to the original nsLiteralString_CharT.
  void NS_FASTCALL AssignLiteral(const char_type* aData, size_type aLength);
};

extern template class nsTSubstring<char>;
extern template class nsTSubstring<char16_t>;

static_assert(sizeof(nsTSubstring<char>) ==
                  sizeof(mozilla::detail::nsTStringRepr<char>),
              "Don't add new data fields to nsTSubstring_CharT. "
              "Add to nsTStringRepr<T> instead.");

#include "nsCharSeparatedTokenizer.h"
#include "nsTDependentSubstring.h"

/**
 * Span integration
 */
namespace mozilla {
Span(const nsTSubstring<char>&)->Span<const char>;
Span(const nsTSubstring<char16_t>&)->Span<const char16_t>;

}  // namespace mozilla

#endif
