summaryrefslogtreecommitdiffstats
path: root/xpcom/string
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/string')
-rw-r--r--xpcom/string/README.html11
-rw-r--r--xpcom/string/RustRegex.h707
-rw-r--r--xpcom/string/RustStringAPI.cpp123
-rw-r--r--xpcom/string/crashtests/1113005-frame.html5
-rw-r--r--xpcom/string/crashtests/1113005.html2
-rw-r--r--xpcom/string/crashtests/394275-1.html9
-rw-r--r--xpcom/string/crashtests/395651-1.html30
-rw-r--r--xpcom/string/crashtests/crashtests.list3
-rw-r--r--xpcom/string/moz.build62
-rw-r--r--xpcom/string/nsASCIIMask.cpp38
-rw-r--r--xpcom/string/nsASCIIMask.h70
-rw-r--r--xpcom/string/nsAString.h38
-rw-r--r--xpcom/string/nsCharTraits.h486
-rw-r--r--xpcom/string/nsDependentString.h15
-rw-r--r--xpcom/string/nsDependentSubstring.h13
-rw-r--r--xpcom/string/nsLiteralString.h31
-rw-r--r--xpcom/string/nsPrintfCString.h64
-rw-r--r--xpcom/string/nsPromiseFlatString.h14
-rw-r--r--xpcom/string/nsReadableUtils.cpp630
-rw-r--r--xpcom/string/nsReadableUtils.h610
-rw-r--r--xpcom/string/nsString.h171
-rw-r--r--xpcom/string/nsStringBuffer.cpp162
-rw-r--r--xpcom/string/nsStringBuffer.h184
-rw-r--r--xpcom/string/nsStringFlags.h95
-rw-r--r--xpcom/string/nsStringFwd.h92
-rw-r--r--xpcom/string/nsStringIterator.h117
-rw-r--r--xpcom/string/nsStringStats.cpp66
-rw-r--r--xpcom/string/nsStringStats.h32
-rw-r--r--xpcom/string/nsTDependentString.cpp50
-rw-r--r--xpcom/string/nsTDependentString.h126
-rw-r--r--xpcom/string/nsTDependentSubstring.cpp106
-rw-r--r--xpcom/string/nsTDependentSubstring.h162
-rw-r--r--xpcom/string/nsTLiteralString.cpp10
-rw-r--r--xpcom/string/nsTLiteralString.h111
-rw-r--r--xpcom/string/nsTPromiseFlatString.cpp26
-rw-r--r--xpcom/string/nsTPromiseFlatString.h136
-rw-r--r--xpcom/string/nsTString.cpp42
-rw-r--r--xpcom/string/nsTString.h448
-rw-r--r--xpcom/string/nsTStringComparator.cpp91
-rw-r--r--xpcom/string/nsTStringHasher.h30
-rw-r--r--xpcom/string/nsTStringRepr.cpp273
-rw-r--r--xpcom/string/nsTStringRepr.h546
-rw-r--r--xpcom/string/nsTSubstring.cpp1694
-rw-r--r--xpcom/string/nsTSubstring.h1454
-rw-r--r--xpcom/string/nsTSubstringTuple.cpp92
-rw-r--r--xpcom/string/nsTSubstringTuple.h89
-rw-r--r--xpcom/string/nsTextFormatter.cpp899
-rw-r--r--xpcom/string/nsTextFormatter.h172
-rw-r--r--xpcom/string/nsUTF8Utils.h247
49 files changed, 10684 insertions, 0 deletions
diff --git a/xpcom/string/README.html b/xpcom/string/README.html
new file mode 100644
index 0000000000..4a0927c65c
--- /dev/null
+++ b/xpcom/string/README.html
@@ -0,0 +1,11 @@
+<html>
+<!-- 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/. -->
+<body>
+ <h1><span class="LXRSHORTDESC">managing sequences of characters</span></h1>
+<p>
+ <span class="LXRLONGDESC"></span>
+</p>
+</body>
+</html>
diff --git a/xpcom/string/RustRegex.h b/xpcom/string/RustRegex.h
new file mode 100644
index 0000000000..80b8140bb5
--- /dev/null
+++ b/xpcom/string/RustRegex.h
@@ -0,0 +1,707 @@
+/* -*- 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/. */
+
+#ifndef mozilla_RustRegex_h
+#define mozilla_RustRegex_h
+
+#include "nsPrintfCString.h"
+#include "nsTArray.h"
+#include "rure.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+// This header is a thin wrapper around the `rure.h` header file, which declares
+// the C API for interacting with the rust `regex` crate. This is intended to
+// make the type more ergonomic to use with mozilla types.
+
+class RustRegex;
+class RustRegexSet;
+class RustRegexOptions;
+class RustRegexCaptures;
+class RustRegexIter;
+class RustRegexIterCaptureNames;
+
+using RustRegexMatch = rure_match;
+
+/*
+ * RustRegexCaptures represents storage for sub-capture locations of a match.
+ *
+ * Computing the capture groups of a match can carry a significant performance
+ * penalty, so their use in the API is optional.
+ *
+ * A RustRegexCaptures value may outlive its corresponding RustRegex and can be
+ * freed independently.
+ *
+ * It is not safe to use from multiple threads simultaneously.
+ */
+class RustRegexCaptures final {
+ public:
+ RustRegexCaptures() = default;
+
+ // Check if the `RustRegexCaptures` object is valid.
+ bool IsValid() const { return mPtr != nullptr; }
+ explicit operator bool() const { return IsValid(); }
+
+ /*
+ * CaptureAt returns Some if and only if the capturing group at the
+ * index given was part of the match. If so, the returned RustRegexMatch
+ * object contains the start and end offsets (in bytes) of the match.
+ *
+ * If no capture group with the index aIdx exists, or the group was not part
+ * of the match, then Nothing is returned. (A capturing group exists if and
+ * only if aIdx is less than Length().)
+ *
+ * Note that index 0 corresponds to the full match.
+ */
+ Maybe<RustRegexMatch> CaptureAt(size_t aIdx) const {
+ RustRegexMatch match;
+ if (mPtr && rure_captures_at(mPtr.get(), aIdx, &match)) {
+ return Some(match);
+ }
+ return Nothing();
+ }
+ Maybe<RustRegexMatch> operator[](size_t aIdx) const {
+ return CaptureAt(aIdx);
+ }
+
+ /*
+ * Returns the number of capturing groups in this `RustRegexCaptures`.
+ */
+ size_t Length() const { return mPtr ? rure_captures_len(mPtr.get()) : 0; }
+
+ private:
+ friend class RustRegex;
+ friend class RustRegexIter;
+
+ explicit RustRegexCaptures(rure* aRe)
+ : mPtr(aRe ? rure_captures_new(aRe) : nullptr) {}
+
+ struct Deleter {
+ void operator()(rure_captures* ptr) const { rure_captures_free(ptr); }
+ };
+ UniquePtr<rure_captures, Deleter> mPtr;
+};
+
+/*
+ * RustRegexIterCaptureNames is an iterator over the list of capture group names
+ * in this particular RustRegex.
+ *
+ * A RustRegexIterCaptureNames value may not outlive its corresponding
+ * RustRegex, and should be destroyed before its corresponding RustRegex is
+ * destroyed.
+ *
+ * It is not safe to use from multiple threads simultaneously.
+ */
+class RustRegexIterCaptureNames {
+ public:
+ RustRegexIterCaptureNames() = delete;
+
+ // Check if the `RustRegexIterCaptureNames` object is valid.
+ bool IsValid() const { return mPtr != nullptr; }
+ explicit operator bool() const { return IsValid(); }
+
+ /*
+ * Advances the iterator and returns true if and only if another capture group
+ * name exists.
+ *
+ * The value of the capture group name is written to the provided pointer.
+ */
+ mozilla::Maybe<const char*> Next() {
+ char* next = nullptr;
+ if (mPtr && rure_iter_capture_names_next(mPtr.get(), &next)) {
+ return Some(next);
+ }
+ return Nothing();
+ }
+
+ private:
+ friend class RustRegex;
+
+ explicit RustRegexIterCaptureNames(rure* aRe)
+ : mPtr(aRe ? rure_iter_capture_names_new(aRe) : nullptr) {}
+
+ struct Deleter {
+ void operator()(rure_iter_capture_names* ptr) const {
+ rure_iter_capture_names_free(ptr);
+ }
+ };
+ UniquePtr<rure_iter_capture_names, Deleter> mPtr;
+};
+
+/*
+ * RustRegexIter is an iterator over successive non-overlapping matches in a
+ * particular haystack.
+ *
+ * A RustRegexIter value may not outlive its corresponding RustRegex and should
+ * be destroyed before its corresponding RustRegex is destroyed.
+ *
+ * It is not safe to use from multiple threads simultaneously.
+ */
+class RustRegexIter {
+ public:
+ RustRegexIter() = delete;
+
+ // Check if the `RustRegexIter` object is valid.
+ bool IsValid() const { return mPtr != nullptr; }
+ explicit operator bool() const { return IsValid(); }
+
+ /*
+ * Next() returns Some if and only if this regex matches anywhere in haystack.
+ * The returned RustRegexMatch object contains the start and end offsets (in
+ * bytes) of the match.
+ *
+ * If no match is found, then subsequent calls will return Nothing()
+ * indefinitely.
+ *
+ * Next() should be preferred to NextCaptures() since it may be faster.
+ *
+ * N.B. The performance of this search is not impacted by the presence of
+ * capturing groups in your regular expression.
+ */
+ mozilla::Maybe<RustRegexMatch> Next() {
+ RustRegexMatch match{};
+ if (mPtr &&
+ rure_iter_next(mPtr.get(), mHaystackPtr, mHaystackSize, &match)) {
+ return Some(match);
+ }
+ return Nothing();
+ }
+
+ /*
+ * NextCaptures returns a valid RustRegexCaptures if and only if this regex
+ * matches anywhere in haystack. If a match is found, then all of its capture
+ * locations are stored in the returned RustRegexCaptures object.
+ *
+ * If no match is found, then subsequent calls will return an invalid
+ * `RustRegexCaptures` indefinitely.
+ *
+ * Only use this function if you specifically need access to capture
+ * locations. It is not necessary to use this function just because your
+ * regular expression contains capturing groups.
+ *
+ * Capture locations can be accessed using the methods on RustRegexCaptures.
+ *
+ * N.B. The performance of this search can be impacted by the number of
+ * capturing groups. If you're using this function, it may be beneficial to
+ * use non-capturing groups (e.g., `(?:re)`) where possible.
+ */
+ RustRegexCaptures NextCaptures() {
+ RustRegexCaptures captures(mRe);
+ if (mPtr && rure_iter_next_captures(mPtr.get(), mHaystackPtr, mHaystackSize,
+ captures.mPtr.get())) {
+ return captures;
+ }
+ return {};
+ }
+
+ private:
+ friend class RustRegex;
+ RustRegexIter(rure* aRe, const std::string_view& aHaystack)
+ : mRe(aRe),
+ mHaystackPtr(reinterpret_cast<const uint8_t*>(aHaystack.data())),
+ mHaystackSize(aHaystack.size()),
+ mPtr(aRe ? rure_iter_new(aRe) : nullptr) {}
+
+ rure* MOZ_NON_OWNING_REF mRe;
+ const uint8_t* MOZ_NON_OWNING_REF mHaystackPtr;
+ size_t mHaystackSize;
+
+ struct Deleter {
+ void operator()(rure_iter* ptr) const { rure_iter_free(ptr); }
+ };
+ UniquePtr<rure_iter, Deleter> mPtr;
+};
+
+/*
+ * RustRegexOptions is the set of configuration options for compiling a regular
+ * expression.
+ *
+ * All flags on this type can be used to set default flags while compiling, and
+ * can be toggled in the expression itself using standard syntax, e.g. `(?i)`
+ * turns case-insensitive matching on, and `(?-i)` disables it.
+ *
+ * In addition, two non-flag options are available: setting the size limit of
+ * the compiled program and setting the size limit of the cache of states that
+ * the DFA uses while searching.
+ *
+ * For most uses, the default settings will work fine, and a default-constructed
+ * RustRegexOptions can be passed.
+ */
+class RustRegexOptions {
+ public:
+ RustRegexOptions() = default;
+
+ /*
+ * Set the value for the case insensitive (i) flag.
+ *
+ * When enabled, letters in the pattern will match both upper case and lower
+ * case variants.
+ */
+ RustRegexOptions& CaseInsensitive(bool aYes) {
+ return SetFlag(aYes, RURE_FLAG_CASEI);
+ }
+
+ /*
+ * Set the value for the multi-line matching (m) flag.
+ *
+ * When enabled, ^ matches the beginning of lines and $ matches the end of
+ * lines.
+ *
+ * By default, they match beginning/end of the input.
+ */
+ RustRegexOptions& MultiLine(bool aYes) {
+ return SetFlag(aYes, RURE_FLAG_MULTI);
+ }
+
+ /*
+ * Set the value for the any character (s) flag, where in . matches anything
+ * when s is set and matches anything except for new line when it is not set
+ * (the default).
+ *
+ * N.B. “matches anything” means “any byte” when Unicode is disabled and means
+ * “any valid UTF-8 encoding of any Unicode scalar value” when Unicode is
+ * enabled.
+ */
+ RustRegexOptions& DotMatchesNewLine(bool aYes) {
+ return SetFlag(aYes, RURE_FLAG_DOTNL);
+ }
+
+ /*
+ * Set the value for the greedy swap (U) flag.
+ *
+ * When enabled, a pattern like a* is lazy (tries to find shortest match) and
+ * a*? is greedy (tries to find longest match).
+ *
+ * By default, a* is greedy and a*? is lazy.
+ */
+ RustRegexOptions& SwapGreed(bool aYes) {
+ return SetFlag(aYes, RURE_FLAG_SWAP_GREED);
+ }
+
+ /*
+ * Set the value for the ignore whitespace (x) flag.
+ *
+ * When enabled, whitespace such as new lines and spaces will be ignored
+ * between expressions of the pattern, and # can be used to start a comment
+ * until the next new line.
+ */
+ RustRegexOptions& IgnoreWhitespace(bool aYes) {
+ return SetFlag(aYes, RURE_FLAG_SPACE);
+ }
+
+ /*
+ * Set the value for the Unicode (u) flag.
+ *
+ * Enabled by default. When disabled, character classes such as \w only match
+ * ASCII word characters instead of all Unicode word characters.
+ */
+ RustRegexOptions& Unicode(bool aYes) {
+ return SetFlag(aYes, RURE_FLAG_UNICODE);
+ }
+
+ /*
+ * SizeLimit sets the appoximate size limit of the compiled regular
+ * expression.
+ *
+ * This size limit roughly corresponds to the number of bytes occupied by
+ * a single compiled program. If the program would exceed this number,
+ * then an invalid RustRegex will be constructed.
+ */
+ RustRegexOptions& SizeLimit(size_t aLimit) {
+ mSizeLimit = Some(aLimit);
+ return *this;
+ }
+
+ /*
+ * DFASizeLimit sets the approximate size of the cache used by the DFA during
+ * search.
+ *
+ * This roughly corresponds to the number of bytes that the DFA will use while
+ * searching.
+ *
+ * Note that this is a *per thread* limit. There is no way to set a global
+ * limit. In particular, if a regular expression is used from multiple threads
+ * simultaneously, then each thread may use up to the number of bytes
+ * specified here.
+ */
+ RustRegexOptions& DFASizeLimit(size_t aLimit) {
+ mDFASizeLimit = Some(aLimit);
+ return *this;
+ }
+
+ private:
+ friend class RustRegex;
+ friend class RustRegexSet;
+
+ struct OptionsDeleter {
+ void operator()(rure_options* ptr) const { rure_options_free(ptr); }
+ };
+
+ UniquePtr<rure_options, OptionsDeleter> GetOptions() const {
+ UniquePtr<rure_options, OptionsDeleter> options;
+ if (mSizeLimit || mDFASizeLimit) {
+ options.reset(rure_options_new());
+ if (mSizeLimit) {
+ rure_options_size_limit(options.get(), *mSizeLimit);
+ }
+ if (mDFASizeLimit) {
+ rure_options_dfa_size_limit(options.get(), *mDFASizeLimit);
+ }
+ }
+ return options;
+ }
+
+ uint32_t GetFlags() const { return mFlags; }
+
+ RustRegexOptions& SetFlag(bool aYes, uint32_t aFlag) {
+ if (aYes) {
+ mFlags |= aFlag;
+ } else {
+ mFlags &= ~aFlag;
+ }
+ return *this;
+ }
+
+ uint32_t mFlags = RURE_DEFAULT_FLAGS;
+ Maybe<size_t> mSizeLimit;
+ Maybe<size_t> mDFASizeLimit;
+};
+
+/*
+ * RustRegex is the type of a compiled regular expression.
+ *
+ * A RustRegex can be safely used from multiple threads simultaneously.
+ *
+ * When calling the matching methods on this type, they will generally have the
+ * following parameters:
+ *
+ * aHaystack
+ * may contain arbitrary bytes, but ASCII compatible text is more useful.
+ * UTF-8 is even more useful. Other text encodings aren't supported.
+ *
+ * aStart
+ * the position in bytes at which to start searching. Note that setting the
+ * start position is distinct from using a substring for `aHaystack`, since
+ * the regex engine may look at bytes before the start position to determine
+ * match information. For example, if the start position is greater than 0,
+ * then the \A ("begin text") anchor can never match.
+ */
+class RustRegex final {
+ public:
+ // Create a new invalid RustRegex object
+ RustRegex() = default;
+
+ /*
+ * Compiles the given pattern into a regular expression. The pattern must be
+ * valid UTF-8 and the length corresponds to the number of bytes in the
+ * pattern.
+ *
+ * If an error occurs, the constructed RustRegex will be `!IsValid()`.
+ *
+ * The compiled expression returned may be used from multiple threads
+ * simultaneously.
+ */
+ explicit RustRegex(const std::string_view& aPattern,
+ const RustRegexOptions& aOptions = {}) {
+#ifdef DEBUG
+ rure_error* error = rure_error_new();
+#else
+ rure_error* error = nullptr;
+#endif
+ mPtr.reset(rure_compile(reinterpret_cast<const uint8_t*>(aPattern.data()),
+ aPattern.size(), aOptions.GetFlags(),
+ aOptions.GetOptions().get(), error));
+#ifdef DEBUG
+ if (!mPtr) {
+ NS_WARNING(nsPrintfCString("RustRegex compile failed: %s",
+ rure_error_message(error))
+ .get());
+ }
+ rure_error_free(error);
+#endif
+ }
+
+ // Check if the compiled `RustRegex` is valid.
+ bool IsValid() const { return mPtr != nullptr; }
+ explicit operator bool() const { return IsValid(); }
+
+ /*
+ * IsMatch returns true if and only if this regex matches anywhere in
+ * aHaystack.
+ *
+ * See the type-level comment for details on aHaystack and aStart.
+ *
+ * IsMatch() should be preferred to Find() since it may be faster.
+ *
+ * N.B. The performance of this search is not impacted by the presence of
+ * capturing groups in your regular expression.
+ */
+ bool IsMatch(const std::string_view& aHaystack, size_t aStart = 0) const {
+ return mPtr &&
+ rure_is_match(mPtr.get(),
+ reinterpret_cast<const uint8_t*>(aHaystack.data()),
+ aHaystack.size(), aStart);
+ }
+
+ /*
+ * Find returns Some if and only if this regex matches anywhere in
+ * haystack. The returned RustRegexMatch object contains the start and end
+ * offsets (in bytes) of the match.
+ *
+ * See the type-level comment for details on aHaystack and aStart.
+ *
+ * Find() should be preferred to FindCaptures() since it may be faster.
+ *
+ * N.B. The performance of this search is not impacted by the presence of
+ * capturing groups in your regular expression.
+ */
+ Maybe<RustRegexMatch> Find(const std::string_view& aHaystack,
+ size_t aStart = 0) const {
+ RustRegexMatch match{};
+ if (mPtr && rure_find(mPtr.get(),
+ reinterpret_cast<const uint8_t*>(aHaystack.data()),
+ aHaystack.size(), aStart, &match)) {
+ return Some(match);
+ }
+ return Nothing();
+ }
+
+ /*
+ * FindCaptures() returns a valid RustRegexCaptures if and only if this
+ * regex matches anywhere in haystack. If a match is found, then all of its
+ * capture locations are stored in the returned RustRegexCaptures object.
+ *
+ * See the type-level comment for details on aHaystack and aStart.
+ *
+ * Only use this function if you specifically need access to capture
+ * locations. It is not necessary to use this function just because your
+ * regular expression contains capturing groups.
+ *
+ * Capture locations can be accessed using the methods on RustRegexCaptures.
+ *
+ * N.B. The performance of this search can be impacted by the number of
+ * capturing groups. If you're using this function, it may be beneficial to
+ * use non-capturing groups (e.g., `(?:re)`) where possible.
+ */
+ RustRegexCaptures FindCaptures(const std::string_view& aHaystack,
+ size_t aStart = 0) const {
+ RustRegexCaptures captures(mPtr.get());
+ if (mPtr &&
+ rure_find_captures(mPtr.get(),
+ reinterpret_cast<const uint8_t*>(aHaystack.data()),
+ aHaystack.size(), aStart, captures.mPtr.get())) {
+ return captures;
+ }
+ return {};
+ }
+
+ /*
+ * ShortestMatch() returns Some if and only if this regex matches anywhere
+ * in haystack. If a match is found, then its end location is stored in the
+ * pointer given. The end location is the place at which the regex engine
+ * determined that a match exists, but may occur before the end of the
+ * proper leftmost-first match.
+ *
+ * See the type-level comment for details on aHaystack and aStart.
+ *
+ * ShortestMatch should be preferred to Find since it may be faster.
+ *
+ * N.B. The performance of this search is not impacted by the presence of
+ * capturing groups in your regular expression.
+ */
+ Maybe<size_t> ShortestMatch(const std::string_view& aHaystack,
+ size_t aStart = 0) const {
+ size_t end = 0;
+ if (mPtr &&
+ rure_shortest_match(mPtr.get(),
+ reinterpret_cast<const uint8_t*>(aHaystack.data()),
+ aHaystack.size(), aStart, &end)) {
+ return Some(end);
+ }
+ return Nothing();
+ }
+
+ /*
+ * Create an iterator over all successive non-overlapping matches of this
+ * regex in aHaystack.
+ *
+ * See the type-level comment for details on aHaystack.
+ *
+ * Both aHaystack and this regex must remain valid until the returned
+ * `RustRegexIter` is destroyed.
+ */
+ RustRegexIter IterMatches(const std::string_view& aHaystack) const {
+ return RustRegexIter(mPtr.get(), aHaystack);
+ }
+
+ /*
+ * Returns the capture index for the name given. If no such named capturing
+ * group exists in this regex, then -1 is returned.
+ *
+ * The capture index may be used with RustRegexCaptures::CaptureAt.
+ *
+ * This function never returns 0 since the first capture group always
+ * corresponds to the entire match and is always unnamed.
+ */
+ int32_t CaptureNameIndex(const char* aName) const {
+ return mPtr ? rure_capture_name_index(mPtr.get(), aName) : -1;
+ }
+
+ /*
+ * Create an iterator over the list of capture group names in this particular
+ * regex.
+ *
+ * This regex must remain valid until the returned `RustRegexIterCaptureNames`
+ * is destroyed.
+ */
+ RustRegexIterCaptureNames IterCaptureNames() const {
+ return RustRegexIterCaptureNames(mPtr.get());
+ }
+
+ /*
+ * Count the number of successive non-overlapping matches of this regex in
+ * aHaystack.
+ *
+ * See the type-level comment for details on aHaystack.
+ */
+ size_t CountMatches(const std::string_view& aHaystack) const {
+ size_t count = 0;
+ auto iter = IterMatches(aHaystack);
+ while (iter.Next()) {
+ count++;
+ }
+ return count;
+ }
+
+ private:
+ struct Deleter {
+ void operator()(rure* ptr) const { rure_free(ptr); }
+ };
+ UniquePtr<rure, Deleter> mPtr;
+};
+
+/*
+ * RustRegexSet is the type of a set of compiled regular expression.
+ *
+ * A RustRegexSet can be safely used from multiple threads simultaneously.
+ *
+ * When calling the matching methods on this type, they will generally have the
+ * following parameters:
+ *
+ * aHaystack
+ * may contain arbitrary bytes, but ASCII compatible text is more useful.
+ * UTF-8 is even more useful. Other text encodings aren't supported.
+ *
+ * aStart
+ * the position in bytes at which to start searching. Note that setting the
+ * start position is distinct from using a substring for `aHaystack`, since
+ * the regex engine may look at bytes before the start position to determine
+ * match information. For example, if the start position is greater than 0,
+ * then the \A ("begin text") anchor can never match.
+ */
+class RustRegexSet final {
+ public:
+ /*
+ * Compiles the given range of patterns into a single regular expression which
+ * can be matched in a linear-scan. Each pattern in aPatterns must be valid
+ * UTF-8, and implicitly coerce to `std::string_view`.
+ *
+ * If an error occurs, the constructed RustRegexSet will be `!IsValid()`.
+ *
+ * The compiled expression returned may be used from multiple threads
+ * simultaneously.
+ */
+ template <typename Patterns>
+ explicit RustRegexSet(Patterns&& aPatterns,
+ const RustRegexOptions& aOptions = {}) {
+#ifdef DEBUG
+ rure_error* error = rure_error_new();
+#else
+ rure_error* error = nullptr;
+#endif
+ AutoTArray<const uint8_t*, 4> patternPtrs;
+ AutoTArray<size_t, 4> patternSizes;
+ for (auto&& pattern : std::forward<Patterns>(aPatterns)) {
+ std::string_view view = pattern;
+ patternPtrs.AppendElement(reinterpret_cast<const uint8_t*>(view.data()));
+ patternSizes.AppendElement(view.size());
+ }
+ mPtr.reset(rure_compile_set(patternPtrs.Elements(), patternSizes.Elements(),
+ patternPtrs.Length(), aOptions.GetFlags(),
+ aOptions.GetOptions().get(), error));
+#ifdef DEBUG
+ if (!mPtr) {
+ NS_WARNING(nsPrintfCString("RustRegexSet compile failed: %s",
+ rure_error_message(error))
+ .get());
+ }
+ rure_error_free(error);
+#endif
+ }
+
+ // Check if the `RustRegexSet` object is valid.
+ bool IsValid() const { return mPtr != nullptr; }
+ explicit operator bool() const { return IsValid(); }
+
+ /*
+ * IsMatch returns true if and only if any regexes within the set
+ * match anywhere in the haystack. Once a match has been located, the
+ * matching engine will quit immediately.
+ *
+ * See the type-level comment for details on aHaystack and aStart.
+ */
+ bool IsMatch(const std::string_view& aHaystack, size_t aStart = 0) const {
+ return mPtr &&
+ rure_set_is_match(mPtr.get(),
+ reinterpret_cast<const uint8_t*>(aHaystack.data()),
+ aHaystack.size(), aStart);
+ }
+
+ struct SetMatches {
+ bool matchedAny = false;
+ nsTArray<bool> matches;
+ };
+
+ /*
+ * Matches() compares each regex in the set against the haystack and
+ * returns a list with the match result of each pattern. Match results are
+ * ordered in the same way as the regex set was compiled. For example, index 0
+ * of matches corresponds to the first pattern passed to the constructor.
+ *
+ * See the type-level comment for details on aHaystack and aStart.
+ *
+ * Only use this function if you specifically need to know which regexes
+ * matched within the set. To determine if any of the regexes matched without
+ * caring which, use IsMatch.
+ */
+ SetMatches Matches(const std::string_view& aHaystack,
+ size_t aStart = 0) const {
+ nsTArray<bool> matches;
+ matches.SetLength(Length());
+ bool any = mPtr && rure_set_matches(
+ mPtr.get(),
+ reinterpret_cast<const uint8_t*>(aHaystack.data()),
+ aHaystack.size(), aStart, matches.Elements());
+ return SetMatches{any, std::move(matches)};
+ }
+
+ /*
+ * Returns the number of patterns the regex set was compiled with.
+ */
+ size_t Length() const { return mPtr ? rure_set_len(mPtr.get()) : 0; }
+
+ private:
+ struct Deleter {
+ void operator()(rure_set* ptr) const { rure_set_free(ptr); }
+ };
+ UniquePtr<rure_set, Deleter> mPtr;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RustRegex_h
diff --git a/xpcom/string/RustStringAPI.cpp b/xpcom/string/RustStringAPI.cpp
new file mode 100644
index 0000000000..55ce6b9eeb
--- /dev/null
+++ b/xpcom/string/RustStringAPI.cpp
@@ -0,0 +1,123 @@
+/* -*- 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/. */
+
+#include "nsISupports.h"
+#include "nsString.h"
+
+// Extern "C" utilities used by the rust nsString bindings.
+
+// Provide rust bindings to the nsA[C]String types
+extern "C" {
+
+// This is a no-op on release, so we ifdef it out such that using it in release
+// results in a linker error.
+#ifdef DEBUG
+void Gecko_IncrementStringAdoptCount(void* aData) {
+ MOZ_LOG_CTOR(aData, "StringAdopt", 1);
+}
+#elif defined(MOZ_DEBUG_RUST)
+void Gecko_IncrementStringAdoptCount(void* aData) {}
+#endif
+
+void Gecko_FinalizeCString(nsACString* aThis) { aThis->~nsACString(); }
+
+void Gecko_AssignCString(nsACString* aThis, const nsACString* aOther) {
+ aThis->Assign(*aOther);
+}
+
+void Gecko_TakeFromCString(nsACString* aThis, nsACString* aOther) {
+ aThis->Assign(std::move(*aOther));
+}
+
+void Gecko_AppendCString(nsACString* aThis, const nsACString* aOther) {
+ aThis->Append(*aOther);
+}
+
+void Gecko_SetLengthCString(nsACString* aThis, uint32_t aLength) {
+ aThis->SetLength(aLength);
+}
+
+bool Gecko_FallibleAssignCString(nsACString* aThis, const nsACString* aOther) {
+ return aThis->Assign(*aOther, mozilla::fallible);
+}
+
+bool Gecko_FallibleTakeFromCString(nsACString* aThis, nsACString* aOther) {
+ return aThis->Assign(std::move(*aOther), mozilla::fallible);
+}
+
+bool Gecko_FallibleAppendCString(nsACString* aThis, const nsACString* aOther) {
+ return aThis->Append(*aOther, mozilla::fallible);
+}
+
+bool Gecko_FallibleSetLengthCString(nsACString* aThis, uint32_t aLength) {
+ return aThis->SetLength(aLength, mozilla::fallible);
+}
+
+char* Gecko_BeginWritingCString(nsACString* aThis) {
+ return aThis->BeginWriting();
+}
+
+char* Gecko_FallibleBeginWritingCString(nsACString* aThis) {
+ return aThis->BeginWriting(mozilla::fallible);
+}
+
+uint32_t Gecko_StartBulkWriteCString(nsACString* aThis, uint32_t aCapacity,
+ uint32_t aUnitsToPreserve,
+ bool aAllowShrinking) {
+ return aThis->StartBulkWriteImpl(aCapacity, aUnitsToPreserve, aAllowShrinking)
+ .unwrapOr(UINT32_MAX);
+}
+
+void Gecko_FinalizeString(nsAString* aThis) { aThis->~nsAString(); }
+
+void Gecko_AssignString(nsAString* aThis, const nsAString* aOther) {
+ aThis->Assign(*aOther);
+}
+
+void Gecko_TakeFromString(nsAString* aThis, nsAString* aOther) {
+ aThis->Assign(std::move(*aOther));
+}
+
+void Gecko_AppendString(nsAString* aThis, const nsAString* aOther) {
+ aThis->Append(*aOther);
+}
+
+void Gecko_SetLengthString(nsAString* aThis, uint32_t aLength) {
+ aThis->SetLength(aLength);
+}
+
+bool Gecko_FallibleAssignString(nsAString* aThis, const nsAString* aOther) {
+ return aThis->Assign(*aOther, mozilla::fallible);
+}
+
+bool Gecko_FallibleTakeFromString(nsAString* aThis, nsAString* aOther) {
+ return aThis->Assign(std::move(*aOther), mozilla::fallible);
+}
+
+bool Gecko_FallibleAppendString(nsAString* aThis, const nsAString* aOther) {
+ return aThis->Append(*aOther, mozilla::fallible);
+}
+
+bool Gecko_FallibleSetLengthString(nsAString* aThis, uint32_t aLength) {
+ return aThis->SetLength(aLength, mozilla::fallible);
+}
+
+char16_t* Gecko_BeginWritingString(nsAString* aThis) {
+ return aThis->BeginWriting();
+}
+
+char16_t* Gecko_FallibleBeginWritingString(nsAString* aThis) {
+ return aThis->BeginWriting(mozilla::fallible);
+}
+
+uint32_t Gecko_StartBulkWriteString(nsAString* aThis, uint32_t aCapacity,
+ uint32_t aUnitsToPreserve,
+ bool aAllowShrinking) {
+ return aThis->StartBulkWriteImpl(aCapacity, aUnitsToPreserve, aAllowShrinking)
+ .unwrapOr(UINT32_MAX);
+}
+
+} // extern "C"
diff --git a/xpcom/string/crashtests/1113005-frame.html b/xpcom/string/crashtests/1113005-frame.html
new file mode 100644
index 0000000000..505fc22f1e
--- /dev/null
+++ b/xpcom/string/crashtests/1113005-frame.html
@@ -0,0 +1,5 @@
+<form method=post enctype=multipart/form-data action="data:text/html,"><textarea name='file"; filename="filename.ext
+ '></textarea>
+<script>
+document.forms[0].submit();
+</script>
diff --git a/xpcom/string/crashtests/1113005.html b/xpcom/string/crashtests/1113005.html
new file mode 100644
index 0000000000..e377bb637f
--- /dev/null
+++ b/xpcom/string/crashtests/1113005.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe src="1113005-frame.html"></iframe>
diff --git a/xpcom/string/crashtests/394275-1.html b/xpcom/string/crashtests/394275-1.html
new file mode 100644
index 0000000000..b589c4d359
--- /dev/null
+++ b/xpcom/string/crashtests/394275-1.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+<script>
+style = document.createElement("style"); // eslint-disable-line no-undef
+document.documentElement.appendChild(style); // eslint-disable-line no-undef
+style.textContent = "tz\uDAB2 "; // eslint-disable-line no-undef
+</script>
+</body>
+</html>
diff --git a/xpcom/string/crashtests/395651-1.html b/xpcom/string/crashtests/395651-1.html
new file mode 100644
index 0000000000..bbed371fd6
--- /dev/null
+++ b/xpcom/string/crashtests/395651-1.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+<script>
+
+function X() { dump("X\n"); }
+function Y() { dump("Y\n"); }
+
+function boom() {
+ dump("Start9\n");
+
+ var div = document.getElementById("v");
+
+ var textNode = document.createTextNode(String.fromCharCode(0xDAAF)); // high surrogate
+ div.appendChild(textNode);
+
+ document.addEventListener("DOMCharacterDataModified", X, true);
+ textNode.data += "B";
+ document.removeEventListener("DOMCharacterDataModified", X, true);
+
+ document.addEventListener("DOMAttrModified", Y, true);
+ textNode.data += String.fromCharCode(0xDF53); // low surrogate
+ document.removeEventListener("DOMAttrModified", Y, true);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div id="v"></div></body>
+
+</html>
diff --git a/xpcom/string/crashtests/crashtests.list b/xpcom/string/crashtests/crashtests.list
new file mode 100644
index 0000000000..d464166e85
--- /dev/null
+++ b/xpcom/string/crashtests/crashtests.list
@@ -0,0 +1,3 @@
+load 394275-1.html
+load 395651-1.html
+skip-if(gtkWidget||winWidget) load 1113005.html # Bug 1683062
diff --git a/xpcom/string/moz.build b/xpcom/string/moz.build
new file mode 100644
index 0000000000..c0f8091b8f
--- /dev/null
+++ b/xpcom/string/moz.build
@@ -0,0 +1,62 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "String")
+
+EXPORTS += [
+ "nsASCIIMask.h",
+ "nsAString.h",
+ "nsCharTraits.h",
+ "nsDependentString.h",
+ "nsDependentSubstring.h",
+ "nsLiteralString.h",
+ "nsPrintfCString.h",
+ "nsPromiseFlatString.h",
+ "nsReadableUtils.h",
+ "nsString.h",
+ "nsStringBuffer.h",
+ "nsStringFlags.h",
+ "nsStringFwd.h",
+ "nsStringIterator.h",
+ "nsTDependentString.h",
+ "nsTDependentSubstring.h",
+ "nsTextFormatter.h",
+ "nsTLiteralString.h",
+ "nsTPromiseFlatString.h",
+ "nsTString.h",
+ "nsTStringHasher.h",
+ "nsTStringRepr.h",
+ "nsTSubstring.h",
+ "nsTSubstringTuple.h",
+ "nsUTF8Utils.h",
+]
+
+EXPORTS.mozilla += [
+ "RustRegex.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsASCIIMask.cpp",
+ "nsReadableUtils.cpp",
+ "nsStringBuffer.cpp",
+ "nsTDependentString.cpp",
+ "nsTDependentSubstring.cpp",
+ "nsTextFormatter.cpp",
+ "nsTLiteralString.cpp",
+ "nsTPromiseFlatString.cpp",
+ "nsTString.cpp",
+ "nsTStringComparator.cpp",
+ "nsTStringRepr.cpp",
+ "nsTSubstring.cpp",
+ "nsTSubstringTuple.cpp",
+ "RustStringAPI.cpp",
+]
+
+if CONFIG["MOZ_DEBUG"]:
+ UNIFIED_SOURCES += ["nsStringStats.cpp"]
+
+FINAL_LIBRARY = "xul"
diff --git a/xpcom/string/nsASCIIMask.cpp b/xpcom/string/nsASCIIMask.cpp
new file mode 100644
index 0000000000..abcff70306
--- /dev/null
+++ b/xpcom/string/nsASCIIMask.cpp
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+#include "nsASCIIMask.h"
+
+namespace mozilla {
+
+constexpr bool TestWhitespace(char c) {
+ return c == '\f' || c == '\t' || c == '\r' || c == '\n' || c == ' ';
+}
+constexpr ASCIIMaskArray sWhitespaceMask = CreateASCIIMask(TestWhitespace);
+
+constexpr bool TestCRLF(char c) { return c == '\r' || c == '\n'; }
+constexpr ASCIIMaskArray sCRLFMask = CreateASCIIMask(TestCRLF);
+
+constexpr bool TestCRLFTab(char c) {
+ return c == '\r' || c == '\n' || c == '\t';
+}
+constexpr ASCIIMaskArray sCRLFTabMask = CreateASCIIMask(TestCRLFTab);
+
+constexpr bool TestZeroToNine(char c) {
+ return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' ||
+ c == '6' || c == '7' || c == '8' || c == '9';
+}
+constexpr ASCIIMaskArray sZeroToNineMask = CreateASCIIMask(TestZeroToNine);
+
+const ASCIIMaskArray& ASCIIMask::MaskWhitespace() { return sWhitespaceMask; }
+
+const ASCIIMaskArray& ASCIIMask::MaskCRLF() { return sCRLFMask; }
+
+const ASCIIMaskArray& ASCIIMask::MaskCRLFTab() { return sCRLFTabMask; }
+
+const ASCIIMaskArray& ASCIIMask::Mask0to9() { return sZeroToNineMask; }
+
+} // namespace mozilla
diff --git a/xpcom/string/nsASCIIMask.h b/xpcom/string/nsASCIIMask.h
new file mode 100644
index 0000000000..54f51d8957
--- /dev/null
+++ b/xpcom/string/nsASCIIMask.h
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+
+#ifndef nsASCIIMask_h_
+#define nsASCIIMask_h_
+
+#include <array>
+#include <utility>
+
+#include "mozilla/Attributes.h"
+
+typedef std::array<bool, 128> ASCIIMaskArray;
+
+namespace mozilla {
+
+// Boolean arrays, fixed size and filled in at compile time, meant to
+// record something about each of the (standard) ASCII characters.
+// No extended ASCII for now, there has been no use case.
+// If you have loops that go through a string character by character
+// and test for equality to a certain set of characters before deciding
+// on a course of action, chances are building up one of these arrays
+// and using it is going to be faster, especially if the set of
+// characters is more than one long, and known at compile time.
+class ASCIIMask {
+ public:
+ // Preset masks for some common character groups
+ // When testing, you must check if the index is < 128 or use IsMasked()
+ //
+ // if (someChar < 128 && MaskCRLF()[someChar]) this is \r or \n
+
+ static const ASCIIMaskArray& MaskCRLF();
+ static const ASCIIMaskArray& Mask0to9();
+ static const ASCIIMaskArray& MaskCRLFTab();
+ static const ASCIIMaskArray& MaskWhitespace();
+
+ static MOZ_ALWAYS_INLINE bool IsMasked(const ASCIIMaskArray& aMask,
+ uint32_t aChar) {
+ return aChar < 128 && aMask[aChar];
+ }
+};
+
+// Outside of the preset ones, use these templates to create more masks.
+//
+// The example creation will look like this:
+//
+// constexpr bool TestABC(char c) { return c == 'A' || c == 'B' || c == 'C'; }
+// constexpr std::array<bool, 128> sABCMask = CreateASCIIMask(TestABC);
+// ...
+// if (someChar < 128 && sABCMask[someChar]) this is A or B or C
+
+namespace asciimask_details {
+template <typename F, size_t... Indices>
+constexpr std::array<bool, 128> CreateASCIIMask(
+ F fun, std::index_sequence<Indices...>) {
+ return {{fun(Indices)...}};
+}
+} // namespace asciimask_details
+
+template <typename F>
+constexpr std::array<bool, 128> CreateASCIIMask(F fun) {
+ return asciimask_details::CreateASCIIMask(fun,
+ std::make_index_sequence<128>{});
+}
+
+} // namespace mozilla
+
+#endif // nsASCIIMask_h_
diff --git a/xpcom/string/nsAString.h b/xpcom/string/nsAString.h
new file mode 100644
index 0000000000..3893ff8e37
--- /dev/null
+++ b/xpcom/string/nsAString.h
@@ -0,0 +1,38 @@
+/* -*- 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 nsAString_h___
+#define nsAString_h___
+
+#include "nsStringFwd.h"
+#include "nsStringIterator.h"
+#include "mozilla/TypedEnumBits.h"
+
+#include <string.h>
+#include <stdarg.h>
+
+#include "nsStringFlags.h"
+#include "nsTStringRepr.h"
+#include "nsTSubstring.h"
+#include "nsTSubstringTuple.h"
+
+/**
+ * ASCII case-insensitive comparator. (for Unicode case-insensitive
+ * comparision, see nsUnicharUtils.h)
+ */
+int nsCaseInsensitiveCStringComparator(const char*, const char*, size_t,
+ size_t);
+
+class nsCaseInsensitiveCStringArrayComparator {
+ public:
+ template <class A, class B>
+ bool Equals(const A& aStrA, const B& aStrB) const {
+ return aStrA.Equals(aStrB, nsCaseInsensitiveCStringComparator);
+ }
+};
+
+#endif // !defined(nsAString_h___)
diff --git a/xpcom/string/nsCharTraits.h b/xpcom/string/nsCharTraits.h
new file mode 100644
index 0000000000..c81c2f5b2d
--- /dev/null
+++ b/xpcom/string/nsCharTraits.h
@@ -0,0 +1,486 @@
+/* -*- 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/. */
+
+#ifndef nsCharTraits_h___
+#define nsCharTraits_h___
+
+#include <ctype.h> // for |EOF|, |WEOF|
+#include <stdint.h> // for |uint32_t|
+#include <string.h> // for |memcpy|, et al
+#include "mozilla/MemoryChecking.h"
+
+// This file may be used (through nsUTF8Utils.h) from non-XPCOM code, in
+// particular the standalone software updater. In that case stub out
+// the macros provided by nsDebug.h which are only usable when linking XPCOM
+
+#ifdef NS_NO_XPCOM
+# define NS_WARNING(msg)
+# define NS_ASSERTION(cond, msg)
+# define NS_ERROR(msg)
+#else
+# include "nsDebug.h" // for NS_ASSERTION
+#endif
+
+/*
+ * Some macros for converting char16_t (UTF-16) to and from Unicode scalar
+ * values.
+ *
+ * Note that UTF-16 represents all Unicode scalar values up to U+10FFFF by
+ * using "surrogate pairs". These consist of a high surrogate, i.e. a code
+ * point in the range U+D800 - U+DBFF, and a low surrogate, i.e. a code point
+ * in the range U+DC00 - U+DFFF, like this:
+ *
+ * U+D800 U+DC00 = U+10000
+ * U+D800 U+DC01 = U+10001
+ * ...
+ * U+DBFF U+DFFE = U+10FFFE
+ * U+DBFF U+DFFF = U+10FFFF
+ *
+ * These surrogate code points U+D800 - U+DFFF are not themselves valid Unicode
+ * scalar values and are not well-formed UTF-16 except as high-surrogate /
+ * low-surrogate pairs.
+ */
+
+#define PLANE1_BASE uint32_t(0x00010000)
+// High surrogates are in the range 0xD800 -- OxDBFF
+#define NS_IS_HIGH_SURROGATE(u) ((uint32_t(u) & 0xFFFFFC00) == 0xD800)
+// Low surrogates are in the range 0xDC00 -- 0xDFFF
+#define NS_IS_LOW_SURROGATE(u) ((uint32_t(u) & 0xFFFFFC00) == 0xDC00)
+// Easier to type than NS_IS_HIGH_SURROGATE && NS_IS_LOW_SURROGATE
+#define NS_IS_SURROGATE_PAIR(h, l) \
+ (NS_IS_HIGH_SURROGATE(h) && NS_IS_LOW_SURROGATE(l))
+// Faster than testing NS_IS_HIGH_SURROGATE || NS_IS_LOW_SURROGATE
+#define IS_SURROGATE(u) ((uint32_t(u) & 0xFFFFF800) == 0xD800)
+
+// Everything else is not a surrogate: 0x000 -- 0xD7FF, 0xE000 -- 0xFFFF
+
+// N = (H - 0xD800) * 0x400 + 0x10000 + (L - 0xDC00)
+// I wonder whether we could somehow assert that H is a high surrogate
+// and L is a low surrogate
+#define SURROGATE_TO_UCS4(h, l) \
+ (((uint32_t(h) & 0x03FF) << 10) + (uint32_t(l) & 0x03FF) + PLANE1_BASE)
+
+// Extract surrogates from a UCS4 char
+// Reference: the Unicode standard 4.0, section 3.9
+// Since (c - 0x10000) >> 10 == (c >> 10) - 0x0080 and
+// 0xD7C0 == 0xD800 - 0x0080,
+// ((c - 0x10000) >> 10) + 0xD800 can be simplified to
+#define H_SURROGATE(c) char16_t(char16_t(uint32_t(c) >> 10) + char16_t(0xD7C0))
+// where it's to be noted that 0xD7C0 is not bitwise-OR'd
+// but added.
+
+// Since 0x10000 & 0x03FF == 0,
+// (c - 0x10000) & 0x03FF == c & 0x03FF so that
+// ((c - 0x10000) & 0x03FF) | 0xDC00 is equivalent to
+#define L_SURROGATE(c) \
+ char16_t(char16_t(uint32_t(c) & uint32_t(0x03FF)) | char16_t(0xDC00))
+
+#define IS_IN_BMP(ucs) (uint32_t(ucs) < PLANE1_BASE)
+#define UCS2_REPLACEMENT_CHAR char16_t(0xFFFD)
+
+#define UCS_END uint32_t(0x00110000)
+#define IS_VALID_CHAR(c) ((uint32_t(c) < UCS_END) && !IS_SURROGATE(c))
+#define ENSURE_VALID_CHAR(c) (IS_VALID_CHAR(c) ? (c) : UCS2_REPLACEMENT_CHAR)
+
+template <class CharT>
+struct nsCharTraits {};
+
+template <>
+struct nsCharTraits<char16_t> {
+ typedef char16_t char_type;
+ typedef uint16_t unsigned_char_type;
+ typedef char incompatible_char_type;
+
+ static char_type* const sEmptyBuffer;
+
+ // integer representation of characters:
+ typedef int int_type;
+
+ static char_type to_char_type(int_type aChar) { return char_type(aChar); }
+
+ static int_type to_int_type(char_type aChar) {
+ return int_type(static_cast<unsigned_char_type>(aChar));
+ }
+
+ static bool eq_int_type(int_type aLhs, int_type aRhs) { return aLhs == aRhs; }
+
+ // |char_type| comparisons:
+
+ static bool eq(char_type aLhs, char_type aRhs) { return aLhs == aRhs; }
+
+ static bool lt(char_type aLhs, char_type aRhs) { return aLhs < aRhs; }
+
+ // operations on s[n] arrays:
+
+ static char_type* move(char_type* aStr1, const char_type* aStr2, size_t aN) {
+ return static_cast<char_type*>(
+ memmove(aStr1, aStr2, aN * sizeof(char_type)));
+ }
+
+ static char_type* copy(char_type* aStr1, const char_type* aStr2, size_t aN) {
+ return static_cast<char_type*>(
+ memcpy(aStr1, aStr2, aN * sizeof(char_type)));
+ }
+
+ static void uninitialize(char_type* aStr, size_t aN) {
+#ifdef DEBUG
+ memset(aStr, 0xE4, aN * sizeof(char_type));
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(aStr, aN * sizeof(char_type));
+ }
+
+ static char_type* copyASCII(char_type* aStr1, const char* aStr2, size_t aN) {
+ for (char_type* s = aStr1; aN--; ++s, ++aStr2) {
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ *s = static_cast<char_type>(*aStr2);
+ }
+ return aStr1;
+ }
+
+ static int compare(const char_type* aStr1, const char_type* aStr2,
+ size_t aN) {
+ for (; aN--; ++aStr1, ++aStr2) {
+ if (!eq(*aStr1, *aStr2)) {
+ return to_int_type(*aStr1) - to_int_type(*aStr2);
+ }
+ }
+
+ return 0;
+ }
+
+ static int compareASCII(const char_type* aStr1, const char* aStr2,
+ size_t aN) {
+ for (; aN--; ++aStr1, ++aStr2) {
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ if (!eq_int_type(to_int_type(*aStr1),
+ to_int_type(static_cast<char_type>(*aStr2)))) {
+ return to_int_type(*aStr1) -
+ to_int_type(static_cast<char_type>(*aStr2));
+ }
+ }
+
+ return 0;
+ }
+
+ static bool equalsLatin1(const char_type* aStr1, const char* aStr2,
+ const size_t aN) {
+ for (size_t i = aN; i > 0; --i, ++aStr1, ++aStr2) {
+ if (*aStr1 != static_cast<char_type>(*aStr2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // this version assumes that s2 is null-terminated and s1 has length n.
+ // if s1 is shorter than s2 then we return -1; if s1 is longer than s2,
+ // we return 1.
+ static int compareASCIINullTerminated(const char_type* aStr1, size_t aN,
+ const char* aStr2) {
+ for (; aN--; ++aStr1, ++aStr2) {
+ if (!*aStr2) {
+ return 1;
+ }
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ if (!eq_int_type(to_int_type(*aStr1),
+ to_int_type(static_cast<char_type>(*aStr2)))) {
+ return to_int_type(*aStr1) -
+ to_int_type(static_cast<char_type>(*aStr2));
+ }
+ }
+
+ if (*aStr2) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Convert c to its lower-case form, but only if c is in the ASCII
+ * range. Otherwise leave it alone.
+ */
+ static char_type ASCIIToLower(char_type aChar) {
+ if (aChar >= 'A' && aChar <= 'Z') {
+ return char_type(aChar + ('a' - 'A'));
+ }
+
+ return aChar;
+ }
+
+ static int compareLowerCaseToASCII(const char_type* aStr1, const char* aStr2,
+ size_t aN) {
+ for (; aN--; ++aStr1, ++aStr2) {
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'),
+ "Unexpected uppercase character");
+ char_type lower_s1 = ASCIIToLower(*aStr1);
+ if (lower_s1 != static_cast<char_type>(*aStr2)) {
+ return to_int_type(lower_s1) -
+ to_int_type(static_cast<char_type>(*aStr2));
+ }
+ }
+
+ return 0;
+ }
+
+ // this version assumes that s2 is null-terminated and s1 has length n.
+ // if s1 is shorter than s2 then we return -1; if s1 is longer than s2,
+ // we return 1.
+ static int compareLowerCaseToASCIINullTerminated(const char_type* aStr1,
+ size_t aN,
+ const char* aStr2) {
+ for (; aN--; ++aStr1, ++aStr2) {
+ if (!*aStr2) {
+ return 1;
+ }
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'),
+ "Unexpected uppercase character");
+ char_type lower_s1 = ASCIIToLower(*aStr1);
+ if (lower_s1 != static_cast<char_type>(*aStr2)) {
+ return to_int_type(lower_s1) -
+ to_int_type(static_cast<char_type>(*aStr2));
+ }
+ }
+
+ if (*aStr2) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ static size_t length(const char_type* aStr) {
+ size_t result = 0;
+ while (!eq(*aStr++, char_type(0))) {
+ ++result;
+ }
+ return result;
+ }
+
+ static const char_type* find(const char_type* aStr, size_t aN,
+ char_type aChar) {
+ while (aN--) {
+ if (eq(*aStr, aChar)) {
+ return aStr;
+ }
+ ++aStr;
+ }
+
+ return 0;
+ }
+};
+
+template <>
+struct nsCharTraits<char> {
+ typedef char char_type;
+ typedef unsigned char unsigned_char_type;
+ typedef char16_t incompatible_char_type;
+
+ static char_type* const sEmptyBuffer;
+
+ // integer representation of characters:
+
+ typedef int int_type;
+
+ static char_type to_char_type(int_type aChar) { return char_type(aChar); }
+
+ static int_type to_int_type(char_type aChar) {
+ return int_type(static_cast<unsigned_char_type>(aChar));
+ }
+
+ static bool eq_int_type(int_type aLhs, int_type aRhs) { return aLhs == aRhs; }
+
+ // |char_type| comparisons:
+
+ static bool eq(char_type aLhs, char_type aRhs) { return aLhs == aRhs; }
+
+ static bool lt(char_type aLhs, char_type aRhs) { return aLhs < aRhs; }
+
+ // operations on s[n] arrays:
+
+ static char_type* move(char_type* aStr1, const char_type* aStr2, size_t aN) {
+ return static_cast<char_type*>(
+ memmove(aStr1, aStr2, aN * sizeof(char_type)));
+ }
+
+ static char_type* copy(char_type* aStr1, const char_type* aStr2, size_t aN) {
+ return static_cast<char_type*>(
+ memcpy(aStr1, aStr2, aN * sizeof(char_type)));
+ }
+
+ static void uninitialize(char_type* aStr, size_t aN) {
+#ifdef DEBUG
+ memset(aStr, 0xE4, aN * sizeof(char_type));
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(aStr, aN * sizeof(char_type));
+ }
+
+ static char_type* copyASCII(char_type* aStr1, const char* aStr2, size_t aN) {
+ return copy(aStr1, aStr2, aN);
+ }
+
+ static int compare(const char_type* aStr1, const char_type* aStr2,
+ size_t aN) {
+ return memcmp(aStr1, aStr2, aN);
+ }
+
+ static int compareASCII(const char_type* aStr1, const char* aStr2,
+ size_t aN) {
+#ifdef DEBUG
+ for (size_t i = 0; i < aN; ++i) {
+ NS_ASSERTION(!(aStr2[i] & ~0x7F), "Unexpected non-ASCII character");
+ }
+#endif
+ return compare(aStr1, aStr2, aN);
+ }
+
+ static bool equalsLatin1(const char_type* aStr1, const char* aStr2,
+ size_t aN) {
+ return memcmp(aStr1, aStr2, aN) == 0;
+ }
+
+ // this version assumes that s2 is null-terminated and s1 has length n.
+ // if s1 is shorter than s2 then we return -1; if s1 is longer than s2,
+ // we return 1.
+ static int compareASCIINullTerminated(const char_type* aStr1, size_t aN,
+ const char* aStr2) {
+ // can't use strcmp here because we don't want to stop when aStr1
+ // contains a null
+ for (; aN--; ++aStr1, ++aStr2) {
+ if (!*aStr2) {
+ return 1;
+ }
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ if (*aStr1 != *aStr2) {
+ return to_int_type(*aStr1) - to_int_type(*aStr2);
+ }
+ }
+
+ if (*aStr2) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Convert c to its lower-case form, but only if c is ASCII.
+ */
+ static char_type ASCIIToLower(char_type aChar) {
+ if (aChar >= 'A' && aChar <= 'Z') {
+ return char_type(aChar + ('a' - 'A'));
+ }
+
+ return aChar;
+ }
+
+ static int compareLowerCaseToASCII(const char_type* aStr1, const char* aStr2,
+ size_t aN) {
+ for (; aN--; ++aStr1, ++aStr2) {
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'),
+ "Unexpected uppercase character");
+ char_type lower_s1 = ASCIIToLower(*aStr1);
+ if (lower_s1 != *aStr2) {
+ return to_int_type(lower_s1) - to_int_type(*aStr2);
+ }
+ }
+ return 0;
+ }
+
+ // this version assumes that s2 is null-terminated and s1 has length n.
+ // if s1 is shorter than s2 then we return -1; if s1 is longer than s2,
+ // we return 1.
+ static int compareLowerCaseToASCIINullTerminated(const char_type* aStr1,
+ size_t aN,
+ const char* aStr2) {
+ for (; aN--; ++aStr1, ++aStr2) {
+ if (!*aStr2) {
+ return 1;
+ }
+ NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character");
+ NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'),
+ "Unexpected uppercase character");
+ char_type lower_s1 = ASCIIToLower(*aStr1);
+ if (lower_s1 != *aStr2) {
+ return to_int_type(lower_s1) - to_int_type(*aStr2);
+ }
+ }
+
+ if (*aStr2) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ static size_t length(const char_type* aStr) { return strlen(aStr); }
+
+ static const char_type* find(const char_type* aStr, size_t aN,
+ char_type aChar) {
+ return reinterpret_cast<const char_type*>(
+ memchr(aStr, to_int_type(aChar), aN));
+ }
+};
+
+template <class InputIterator>
+struct nsCharSourceTraits {
+ typedef typename InputIterator::difference_type difference_type;
+
+ static difference_type readable_distance(const InputIterator& aFirst,
+ const InputIterator& aLast) {
+ // assumes single fragment
+ return aLast.get() - aFirst.get();
+ }
+
+ static const typename InputIterator::value_type* read(
+ const InputIterator& aIter) {
+ return aIter.get();
+ }
+
+ static void advance(InputIterator& aStr, difference_type aN) {
+ aStr.advance(aN);
+ }
+};
+
+template <class CharT>
+struct nsCharSourceTraits<CharT*> {
+ typedef ptrdiff_t difference_type;
+
+ static difference_type readable_distance(CharT* aStr) {
+ return nsCharTraits<CharT>::length(aStr);
+ }
+
+ static difference_type readable_distance(CharT* aFirst, CharT* aLast) {
+ return aLast - aFirst;
+ }
+
+ static const CharT* read(CharT* aStr) { return aStr; }
+
+ static void advance(CharT*& aStr, difference_type aN) { aStr += aN; }
+};
+
+template <class OutputIterator>
+struct nsCharSinkTraits {
+ static void write(OutputIterator& aIter,
+ const typename OutputIterator::value_type* aStr,
+ size_t aN) {
+ aIter.write(aStr, aN);
+ }
+};
+
+template <class CharT>
+struct nsCharSinkTraits<CharT*> {
+ static void write(CharT*& aIter, const CharT* aStr, size_t aN) {
+ nsCharTraits<CharT>::move(aIter, aStr, aN);
+ aIter += aN;
+ }
+};
+
+#endif // !defined(nsCharTraits_h___)
diff --git a/xpcom/string/nsDependentString.h b/xpcom/string/nsDependentString.h
new file mode 100644
index 0000000000..4896c8d086
--- /dev/null
+++ b/xpcom/string/nsDependentString.h
@@ -0,0 +1,15 @@
+/* -*- 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/. */
+
+#ifndef nsDependentString_h___
+#define nsDependentString_h___
+
+#include "nsString.h"
+#include "nsDebug.h"
+
+#include "nsTDependentString.h"
+
+#endif /* !defined(nsDependentString_h___) */
diff --git a/xpcom/string/nsDependentSubstring.h b/xpcom/string/nsDependentSubstring.h
new file mode 100644
index 0000000000..cb6cef5d77
--- /dev/null
+++ b/xpcom/string/nsDependentSubstring.h
@@ -0,0 +1,13 @@
+/* -*- 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/. */
+
+#ifndef nsDependentSubstring_h___
+#define nsDependentSubstring_h___
+
+#include "nsAString.h"
+#include "nsTDependentSubstring.h"
+
+#endif /* !defined(nsDependentSubstring_h___) */
diff --git a/xpcom/string/nsLiteralString.h b/xpcom/string/nsLiteralString.h
new file mode 100644
index 0000000000..f982724ce4
--- /dev/null
+++ b/xpcom/string/nsLiteralString.h
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+
+#ifndef nsLiteralString_h___
+#define nsLiteralString_h___
+
+#include "nscore.h"
+#include "nsString.h"
+
+#include "nsTLiteralString.h"
+
+#include "mozilla/Char16.h"
+
+#define NS_CSTRING_LITERAL_AS_STRING_LITERAL(s) u"" s
+
+#define NS_LITERAL_STRING_FROM_CSTRING(s) \
+ static_cast<const nsLiteralString&>( \
+ nsLiteralString(NS_CSTRING_LITERAL_AS_STRING_LITERAL(s)))
+
+constexpr auto operator""_ns(const char* aStr, std::size_t aLen) {
+ return nsLiteralCString{aStr, aLen};
+}
+
+constexpr auto operator""_ns(const char16_t* aStr, std::size_t aLen) {
+ return nsLiteralString{aStr, aLen};
+}
+
+#endif /* !defined(nsLiteralString_h___) */
diff --git a/xpcom/string/nsPrintfCString.h b/xpcom/string/nsPrintfCString.h
new file mode 100644
index 0000000000..f722888705
--- /dev/null
+++ b/xpcom/string/nsPrintfCString.h
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#ifndef nsPrintfCString_h___
+#define nsPrintfCString_h___
+
+#include "nsString.h"
+
+/**
+ * nsPrintfCString lets you create a nsCString using a printf-style format
+ * string. For example:
+ *
+ * NS_WARNING(nsPrintfCString("Unexpected value: %f", 13.917).get());
+ *
+ * nsPrintfCString has a small built-in auto-buffer. For larger strings, it
+ * will allocate on the heap.
+ *
+ * See also nsCString::AppendPrintf().
+ */
+class nsPrintfCString : public nsAutoCStringN<16> {
+ typedef nsCString string_type;
+
+ public:
+ explicit nsPrintfCString(const char_type* aFormat, ...)
+ MOZ_FORMAT_PRINTF(2, 3) {
+ va_list ap;
+ va_start(ap, aFormat);
+ AppendVprintf(aFormat, ap);
+ va_end(ap);
+ }
+};
+
+/**
+ *
+ *
+ * nsVPrintfCString is like nsPrinfCString but is created using vprintf style
+ * args. This is useful for functions that have already received variadic
+ * arguments and want to create a nsPrintfCString. For example:
+ *
+ * void LogToSeveralLocations(const char* aFormat,...) {
+ * va_list ap;
+ * va_start(ap, aFormat);
+ * nsPrintfCString logString(aFormat, ap);
+ * va_end(ap);
+ * // Use logString
+ * }
+ *
+ * See also nsCString::AppendVprintf().
+ */
+
+class nsVprintfCString : public nsAutoCStringN<16> {
+ typedef nsCString string_type;
+
+ public:
+ nsVprintfCString(const char_type* aFormat, va_list aArgs)
+ MOZ_FORMAT_PRINTF(2, 0) {
+ AppendVprintf(aFormat, aArgs);
+ }
+};
+
+#endif // !defined(nsPrintfCString_h___)
diff --git a/xpcom/string/nsPromiseFlatString.h b/xpcom/string/nsPromiseFlatString.h
new file mode 100644
index 0000000000..98541ceb4a
--- /dev/null
+++ b/xpcom/string/nsPromiseFlatString.h
@@ -0,0 +1,14 @@
+/* -*- 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/. */
+
+#ifndef nsPromiseFlatString_h___
+#define nsPromiseFlatString_h___
+
+#include "nsString.h"
+
+#include "nsTPromiseFlatString.h"
+
+#endif /* !defined(nsPromiseFlatString_h___) */
diff --git a/xpcom/string/nsReadableUtils.cpp b/xpcom/string/nsReadableUtils.cpp
new file mode 100644
index 0000000000..fa4c4bc69b
--- /dev/null
+++ b/xpcom/string/nsReadableUtils.cpp
@@ -0,0 +1,630 @@
+/* -*- 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/. */
+
+#include "nsReadableUtils.h"
+
+#include <algorithm>
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Utf8.h"
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsUTF8Utils.h"
+
+using mozilla::Span;
+
+/**
+ * A helper function that allocates a buffer of the desired character type big
+ * enough to hold a copy of the supplied string (plus a zero terminator).
+ *
+ * @param aSource an string you will eventually be making a copy of
+ * @return a new buffer which you must free with |free|.
+ *
+ */
+template <class FromStringT, class CharT>
+inline CharT* AllocateStringCopy(const FromStringT& aSource, CharT*) {
+ return static_cast<CharT*>(
+ malloc((size_t(aSource.Length()) + 1) * sizeof(CharT)));
+}
+
+char* ToNewCString(const nsAString& aSource) {
+ char* str = ToNewCString(aSource, mozilla::fallible);
+ if (!str) {
+ MOZ_CRASH("Unable to allocate memory");
+ }
+ return str;
+}
+
+char* ToNewCString(const nsAString& aSource,
+ const mozilla::fallible_t& aFallible) {
+ char* dest = AllocateStringCopy(aSource, (char*)nullptr);
+ if (!dest) {
+ return nullptr;
+ }
+
+ auto len = aSource.Length();
+ LossyConvertUtf16toLatin1(aSource, Span(dest, len));
+ dest[len] = 0;
+ return dest;
+}
+
+char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count,
+ const mozilla::fallible_t& aFallible) {
+ auto len = aSource.Length();
+ // The uses of this function seem temporary enough that it's not
+ // worthwhile to be fancy about the allocation size. Let's just use
+ // the worst case.
+ // Times 3 plus 1, because ConvertUTF16toUTF8 requires times 3 and
+ // then we have the terminator.
+ // Using CheckedInt<uint32_t>, because aUTF8Count is uint32_t* for
+ // historical reasons.
+ mozilla::CheckedInt<uint32_t> destLen(len);
+ destLen *= 3;
+ destLen += 1;
+ if (!destLen.isValid()) {
+ return nullptr;
+ }
+ size_t destLenVal = destLen.value();
+ char* dest = static_cast<char*>(malloc(destLenVal));
+ if (!dest) {
+ return nullptr;
+ }
+
+ size_t written = ConvertUtf16toUtf8(aSource, Span(dest, destLenVal));
+ dest[written] = 0;
+
+ if (aUTF8Count) {
+ *aUTF8Count = written;
+ }
+
+ return dest;
+}
+
+char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count) {
+ char* str = ToNewUTF8String(aSource, aUTF8Count, mozilla::fallible);
+ if (!str) {
+ MOZ_CRASH("Unable to allocate memory");
+ }
+ return str;
+}
+
+char* ToNewCString(const nsACString& aSource) {
+ char* str = ToNewCString(aSource, mozilla::fallible);
+ if (!str) {
+ MOZ_CRASH("Unable to allocate memory");
+ }
+ return str;
+}
+
+char* ToNewCString(const nsACString& aSource,
+ const mozilla::fallible_t& aFallible) {
+ // no conversion needed, just allocate a buffer of the correct length and copy
+ // into it
+
+ char* dest = AllocateStringCopy(aSource, (char*)nullptr);
+ if (!dest) {
+ return nullptr;
+ }
+
+ auto len = aSource.Length();
+ memcpy(dest, aSource.BeginReading(), len * sizeof(char));
+ dest[len] = 0;
+ return dest;
+}
+
+char16_t* ToNewUnicode(const nsAString& aSource) {
+ char16_t* str = ToNewUnicode(aSource, mozilla::fallible);
+ if (!str) {
+ MOZ_CRASH("Unable to allocate memory");
+ }
+ return str;
+}
+
+char16_t* ToNewUnicode(const nsAString& aSource,
+ const mozilla::fallible_t& aFallible) {
+ // no conversion needed, just allocate a buffer of the correct length and copy
+ // into it
+
+ char16_t* dest = AllocateStringCopy(aSource, (char16_t*)nullptr);
+ if (!dest) {
+ return nullptr;
+ }
+
+ auto len = aSource.Length();
+ memcpy(dest, aSource.BeginReading(), len * sizeof(char16_t));
+ dest[len] = 0;
+ return dest;
+}
+
+char16_t* ToNewUnicode(const nsACString& aSource) {
+ char16_t* str = ToNewUnicode(aSource, mozilla::fallible);
+ if (!str) {
+ MOZ_CRASH("Unable to allocate memory");
+ }
+ return str;
+}
+
+char16_t* ToNewUnicode(const nsACString& aSource,
+ const mozilla::fallible_t& aFallible) {
+ char16_t* dest = AllocateStringCopy(aSource, (char16_t*)nullptr);
+ if (!dest) {
+ return nullptr;
+ }
+
+ auto len = aSource.Length();
+ ConvertLatin1toUtf16(aSource, Span(dest, len));
+ dest[len] = 0;
+ return dest;
+}
+
+char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count,
+ const mozilla::fallible_t& aFallible) {
+ // Compute length plus one as required by ConvertUTF8toUTF16
+ uint32_t lengthPlusOne = aSource.Length() + 1; // Can't overflow
+
+ mozilla::CheckedInt<size_t> allocLength(lengthPlusOne);
+ // Add space for zero-termination
+ allocLength += 1;
+ // We need UTF-16 units
+ allocLength *= sizeof(char16_t);
+
+ if (!allocLength.isValid()) {
+ return nullptr;
+ }
+
+ char16_t* dest = (char16_t*)malloc(allocLength.value());
+ if (!dest) {
+ return nullptr;
+ }
+
+ size_t written = ConvertUtf8toUtf16(aSource, Span(dest, lengthPlusOne));
+ dest[written] = 0;
+
+ if (aUTF16Count) {
+ *aUTF16Count = written;
+ }
+
+ return dest;
+}
+
+char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count) {
+ char16_t* str = UTF8ToNewUnicode(aSource, aUTF16Count, mozilla::fallible);
+ if (!str) {
+ MOZ_CRASH("Unable to allocate memory");
+ }
+ return str;
+}
+
+char16_t* CopyUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset,
+ char16_t* aDest, uint32_t aLength) {
+ MOZ_ASSERT(aSrcOffset + aLength <= aSource.Length());
+ memcpy(aDest, aSource.BeginReading() + aSrcOffset,
+ size_t(aLength) * sizeof(char16_t));
+ return aDest;
+}
+
+void ToUpperCase(nsACString& aCString) {
+ char* cp = aCString.BeginWriting();
+ char* end = cp + aCString.Length();
+ while (cp != end) {
+ char ch = *cp;
+ if (ch >= 'a' && ch <= 'z') {
+ *cp = ch - ('a' - 'A');
+ }
+ ++cp;
+ }
+}
+
+void ToUpperCase(const nsACString& aSource, nsACString& aDest) {
+ aDest.SetLength(aSource.Length());
+ const char* src = aSource.BeginReading();
+ const char* end = src + aSource.Length();
+ char* dst = aDest.BeginWriting();
+ while (src != end) {
+ char ch = *src;
+ if (ch >= 'a' && ch <= 'z') {
+ *dst = ch - ('a' - 'A');
+ } else {
+ *dst = ch;
+ }
+ ++src;
+ ++dst;
+ }
+}
+
+void ToLowerCase(nsACString& aCString) {
+ char* cp = aCString.BeginWriting();
+ char* end = cp + aCString.Length();
+ while (cp != end) {
+ char ch = *cp;
+ if (ch >= 'A' && ch <= 'Z') {
+ *cp = ch + ('a' - 'A');
+ }
+ ++cp;
+ }
+}
+
+void ToLowerCase(const nsACString& aSource, nsACString& aDest) {
+ aDest.SetLength(aSource.Length());
+ const char* src = aSource.BeginReading();
+ const char* end = src + aSource.Length();
+ char* dst = aDest.BeginWriting();
+ while (src != end) {
+ char ch = *src;
+ if (ch >= 'A' && ch <= 'Z') {
+ *dst = ch + ('a' - 'A');
+ } else {
+ *dst = ch;
+ }
+ ++src;
+ ++dst;
+ }
+}
+
+void ParseString(const nsACString& aSource, char aDelimiter,
+ nsTArray<nsCString>& aArray) {
+ nsACString::const_iterator start, end;
+ aSource.BeginReading(start);
+ aSource.EndReading(end);
+
+ for (;;) {
+ nsACString::const_iterator delimiter = start;
+ FindCharInReadable(aDelimiter, delimiter, end);
+
+ if (delimiter != start) {
+ aArray.AppendElement(Substring(start, delimiter));
+ }
+
+ if (delimiter == end) {
+ break;
+ }
+ start = ++delimiter;
+ if (start == end) {
+ break;
+ }
+ }
+}
+
+template <class StringT, class IteratorT>
+bool FindInReadable_Impl(
+ const StringT& aPattern, IteratorT& aSearchStart, IteratorT& aSearchEnd,
+ nsTStringComparator<typename StringT::char_type> aCompare) {
+ bool found_it = false;
+
+ // only bother searching at all if we're given a non-empty range to search
+ if (aSearchStart != aSearchEnd) {
+ IteratorT aPatternStart, aPatternEnd;
+ aPattern.BeginReading(aPatternStart);
+ aPattern.EndReading(aPatternEnd);
+
+ // outer loop keeps searching till we find it or run out of string to search
+ while (!found_it) {
+ // fast inner loop (that's what it's called, not what it is) looks for a
+ // potential match
+ while (aSearchStart != aSearchEnd &&
+ aCompare(aPatternStart.get(), aSearchStart.get(), 1, 1)) {
+ ++aSearchStart;
+ }
+
+ // if we broke out of the `fast' loop because we're out of string ...
+ // we're done: no match
+ if (aSearchStart == aSearchEnd) {
+ break;
+ }
+
+ // otherwise, we're at a potential match, let's see if we really hit one
+ IteratorT testPattern(aPatternStart);
+ IteratorT testSearch(aSearchStart);
+
+ // slow inner loop verifies the potential match (found by the `fast' loop)
+ // at the current position
+ for (;;) {
+ // we already compared the first character in the outer loop,
+ // so we'll advance before the next comparison
+ ++testPattern;
+ ++testSearch;
+
+ // if we verified all the way to the end of the pattern, then we found
+ // it!
+ if (testPattern == aPatternEnd) {
+ found_it = true;
+ aSearchEnd = testSearch; // return the exact found range through the
+ // parameters
+ break;
+ }
+
+ // if we got to end of the string we're searching before we hit the end
+ // of the
+ // pattern, we'll never find what we're looking for
+ if (testSearch == aSearchEnd) {
+ aSearchStart = aSearchEnd;
+ break;
+ }
+
+ // else if we mismatched ... it's time to advance to the next search
+ // position
+ // and get back into the `fast' loop
+ if (aCompare(testPattern.get(), testSearch.get(), 1, 1)) {
+ ++aSearchStart;
+ break;
+ }
+ }
+ }
+ }
+
+ return found_it;
+}
+
+/**
+ * This searches the entire string from right to left, and returns the first
+ * match found, if any.
+ */
+template <class StringT, class IteratorT>
+bool RFindInReadable_Impl(
+ const StringT& aPattern, IteratorT& aSearchStart, IteratorT& aSearchEnd,
+ nsTStringComparator<typename StringT::char_type> aCompare) {
+ IteratorT patternStart, patternEnd, searchEnd = aSearchEnd;
+ aPattern.BeginReading(patternStart);
+ aPattern.EndReading(patternEnd);
+
+ // Point to the last character in the pattern
+ --patternEnd;
+ // outer loop keeps searching till we run out of string to search
+ while (aSearchStart != searchEnd) {
+ // Point to the end position of the next possible match
+ --searchEnd;
+
+ // Check last character, if a match, explore further from here
+ if (aCompare(patternEnd.get(), searchEnd.get(), 1, 1) == 0) {
+ // We're at a potential match, let's see if we really hit one
+ IteratorT testPattern(patternEnd);
+ IteratorT testSearch(searchEnd);
+
+ // inner loop verifies the potential match at the current position
+ do {
+ // if we verified all the way to the end of the pattern, then we found
+ // it!
+ if (testPattern == patternStart) {
+ aSearchStart = testSearch; // point to start of match
+ aSearchEnd = ++searchEnd; // point to end of match
+ return true;
+ }
+
+ // if we got to end of the string we're searching before we hit the end
+ // of the
+ // pattern, we'll never find what we're looking for
+ if (testSearch == aSearchStart) {
+ aSearchStart = aSearchEnd;
+ return false;
+ }
+
+ // test previous character for a match
+ --testPattern;
+ --testSearch;
+ } while (aCompare(testPattern.get(), testSearch.get(), 1, 1) == 0);
+ }
+ }
+
+ aSearchStart = aSearchEnd;
+ return false;
+}
+
+bool FindInReadable(const nsAString& aPattern,
+ nsAString::const_iterator& aSearchStart,
+ nsAString::const_iterator& aSearchEnd,
+ nsStringComparator aComparator) {
+ return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator);
+}
+
+bool FindInReadable(const nsACString& aPattern,
+ nsACString::const_iterator& aSearchStart,
+ nsACString::const_iterator& aSearchEnd,
+ nsCStringComparator aComparator) {
+ return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator);
+}
+
+bool CaseInsensitiveFindInReadable(const nsACString& aPattern,
+ nsACString::const_iterator& aSearchStart,
+ nsACString::const_iterator& aSearchEnd) {
+ return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd,
+ nsCaseInsensitiveCStringComparator);
+}
+
+bool RFindInReadable(const nsAString& aPattern,
+ nsAString::const_iterator& aSearchStart,
+ nsAString::const_iterator& aSearchEnd,
+ const nsStringComparator aComparator) {
+ return RFindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator);
+}
+
+bool RFindInReadable(const nsACString& aPattern,
+ nsACString::const_iterator& aSearchStart,
+ nsACString::const_iterator& aSearchEnd,
+ const nsCStringComparator aComparator) {
+ return RFindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator);
+}
+
+bool FindCharInReadable(char16_t aChar, nsAString::const_iterator& aSearchStart,
+ const nsAString::const_iterator& aSearchEnd) {
+ ptrdiff_t fragmentLength = aSearchEnd.get() - aSearchStart.get();
+
+ const char16_t* charFoundAt =
+ nsCharTraits<char16_t>::find(aSearchStart.get(), fragmentLength, aChar);
+ if (charFoundAt) {
+ aSearchStart.advance(charFoundAt - aSearchStart.get());
+ return true;
+ }
+
+ aSearchStart.advance(fragmentLength);
+ return false;
+}
+
+bool FindCharInReadable(char aChar, nsACString::const_iterator& aSearchStart,
+ const nsACString::const_iterator& aSearchEnd) {
+ ptrdiff_t fragmentLength = aSearchEnd.get() - aSearchStart.get();
+
+ const char* charFoundAt =
+ nsCharTraits<char>::find(aSearchStart.get(), fragmentLength, aChar);
+ if (charFoundAt) {
+ aSearchStart.advance(charFoundAt - aSearchStart.get());
+ return true;
+ }
+
+ aSearchStart.advance(fragmentLength);
+ return false;
+}
+
+bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring) {
+ nsAString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, 0, sub_len).Equals(aSubstring);
+}
+
+bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring,
+ nsStringComparator aComparator) {
+ nsAString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, 0, sub_len).Equals(aSubstring, aComparator);
+}
+
+bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring) {
+ nsACString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, 0, sub_len).Equals(aSubstring);
+}
+
+bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring,
+ nsCStringComparator aComparator) {
+ nsACString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, 0, sub_len).Equals(aSubstring, aComparator);
+}
+
+bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring) {
+ nsAString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, src_len - sub_len, sub_len).Equals(aSubstring);
+}
+
+bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring,
+ nsStringComparator aComparator) {
+ nsAString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, src_len - sub_len, sub_len)
+ .Equals(aSubstring, aComparator);
+}
+
+bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring) {
+ nsACString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, src_len - sub_len, sub_len).Equals(aSubstring);
+}
+
+bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring,
+ nsCStringComparator aComparator) {
+ nsACString::size_type src_len = aSource.Length(),
+ sub_len = aSubstring.Length();
+ if (sub_len > src_len) {
+ return false;
+ }
+ return Substring(aSource, src_len - sub_len, sub_len)
+ .Equals(aSubstring, aComparator);
+}
+
+static const char16_t empty_buffer[1] = {'\0'};
+
+const nsString& EmptyString() {
+ static const nsDependentString sEmpty(empty_buffer);
+
+ return sEmpty;
+}
+
+const nsCString& EmptyCString() {
+ static const nsDependentCString sEmpty((const char*)empty_buffer);
+
+ return sEmpty;
+}
+
+const nsString& VoidString() {
+ static const nsString sNull(mozilla::detail::StringDataFlags::VOIDED);
+
+ return sNull;
+}
+
+const nsCString& VoidCString() {
+ static const nsCString sNull(mozilla::detail::StringDataFlags::VOIDED);
+
+ return sNull;
+}
+
+int32_t CompareUTF8toUTF16(const nsACString& aUTF8String,
+ const nsAString& aUTF16String, bool* aErr) {
+ const char* u8;
+ const char* u8end;
+ aUTF8String.BeginReading(u8);
+ aUTF8String.EndReading(u8end);
+
+ const char16_t* u16;
+ const char16_t* u16end;
+ aUTF16String.BeginReading(u16);
+ aUTF16String.EndReading(u16end);
+
+ for (;;) {
+ if (u8 == u8end) {
+ if (u16 == u16end) {
+ return 0;
+ }
+ return -1;
+ }
+ if (u16 == u16end) {
+ return 1;
+ }
+ // No need for ASCII optimization, since both NextChar()
+ // calls get inlined.
+ uint32_t scalar8 = UTF8CharEnumerator::NextChar(&u8, u8end, aErr);
+ uint32_t scalar16 = UTF16CharEnumerator::NextChar(&u16, u16end, aErr);
+ if (scalar16 == scalar8) {
+ continue;
+ }
+ if (scalar8 < scalar16) {
+ return -1;
+ }
+ return 1;
+ }
+}
+
+void AppendUCS4ToUTF16(const uint32_t aSource, nsAString& aDest) {
+ NS_ASSERTION(IS_VALID_CHAR(aSource), "Invalid UCS4 char");
+ if (IS_IN_BMP(aSource)) {
+ aDest.Append(char16_t(aSource));
+ } else {
+ aDest.Append(H_SURROGATE(aSource));
+ aDest.Append(L_SURROGATE(aSource));
+ }
+}
diff --git a/xpcom/string/nsReadableUtils.h b/xpcom/string/nsReadableUtils.h
new file mode 100644
index 0000000000..803c6b5d2f
--- /dev/null
+++ b/xpcom/string/nsReadableUtils.h
@@ -0,0 +1,610 @@
+/* -*- 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 nsReadableUtils_h___
+#define nsReadableUtils_h___
+
+/**
+ * I guess all the routines in this file are all mis-named.
+ * According to our conventions, they should be |NS_xxx|.
+ */
+
+#include "mozilla/Assertions.h"
+#include "nsAString.h"
+#include "mozilla/TextUtils.h"
+
+#include "nsTArrayForwardDeclare.h"
+
+// From the nsstring crate
+extern "C" {
+bool nsstring_fallible_append_utf8_impl(nsAString* aThis, const char* aOther,
+ size_t aOtherLen, size_t aOldLen);
+
+bool nsstring_fallible_append_latin1_impl(nsAString* aThis, const char* aOther,
+ size_t aOtherLen, size_t aOldLen,
+ bool aAllowShrinking);
+
+bool nscstring_fallible_append_utf16_to_utf8_impl(nsACString* aThis,
+ const char16_t*,
+ size_t aOtherLen,
+ size_t aOldLen);
+
+bool nscstring_fallible_append_utf16_to_latin1_lossy_impl(nsACString* aThis,
+ const char16_t*,
+ size_t aOtherLen,
+ size_t aOldLen,
+ bool aAllowShrinking);
+
+bool nscstring_fallible_append_utf8_to_latin1_lossy_check(
+ nsACString* aThis, const nsACString* aOther, size_t aOldLen);
+
+bool nscstring_fallible_append_latin1_to_utf8_check(nsACString* aThis,
+ const nsACString* aOther,
+ size_t aOldLen);
+}
+
+inline size_t Distance(const nsReadingIterator<char16_t>& aStart,
+ const nsReadingIterator<char16_t>& aEnd) {
+ MOZ_ASSERT(aStart.get() <= aEnd.get());
+ return static_cast<size_t>(aEnd.get() - aStart.get());
+}
+
+inline size_t Distance(const nsReadingIterator<char>& aStart,
+ const nsReadingIterator<char>& aEnd) {
+ MOZ_ASSERT(aStart.get() <= aEnd.get());
+ return static_cast<size_t>(aEnd.get() - aStart.get());
+}
+
+// NOTE: Operations that don't need an operand to be an XPCOM string
+// are in mozilla/TextUtils.h and mozilla/Utf8.h.
+
+// UTF-8 to UTF-16
+// Invalid UTF-8 byte sequences are replaced with the REPLACEMENT CHARACTER.
+
+[[nodiscard]] inline bool CopyUTF8toUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest,
+ const mozilla::fallible_t&) {
+ return nsstring_fallible_append_utf8_impl(&aDest, aSource.Elements(),
+ aSource.Length(), 0);
+}
+
+inline void CopyUTF8toUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest) {
+ if (MOZ_UNLIKELY(!CopyUTF8toUTF16(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aSource.Length());
+ }
+}
+
+[[nodiscard]] inline bool AppendUTF8toUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest,
+ const mozilla::fallible_t&) {
+ return nsstring_fallible_append_utf8_impl(&aDest, aSource.Elements(),
+ aSource.Length(), aDest.Length());
+}
+
+inline void AppendUTF8toUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest) {
+ if (MOZ_UNLIKELY(!AppendUTF8toUTF16(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aDest.Length() + aSource.Length());
+ }
+}
+
+// Latin1 to UTF-16
+// Interpret each incoming unsigned byte value as a Unicode scalar value (not
+// windows-1252!). The function names say "ASCII" instead of "Latin1" for
+// legacy reasons.
+
+[[nodiscard]] inline bool CopyASCIItoUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest,
+ const mozilla::fallible_t&) {
+ return nsstring_fallible_append_latin1_impl(&aDest, aSource.Elements(),
+ aSource.Length(), 0, true);
+}
+
+inline void CopyASCIItoUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest) {
+ if (MOZ_UNLIKELY(!CopyASCIItoUTF16(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aSource.Length());
+ }
+}
+
+[[nodiscard]] inline bool AppendASCIItoUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest,
+ const mozilla::fallible_t&) {
+ return nsstring_fallible_append_latin1_impl(
+ &aDest, aSource.Elements(), aSource.Length(), aDest.Length(), false);
+}
+
+inline void AppendASCIItoUTF16(mozilla::Span<const char> aSource,
+ nsAString& aDest) {
+ if (MOZ_UNLIKELY(!AppendASCIItoUTF16(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aDest.Length() + aSource.Length());
+ }
+}
+
+// UTF-16 to UTF-8
+// Unpaired surrogates are replaced with the REPLACEMENT CHARACTER.
+
+[[nodiscard]] inline bool CopyUTF16toUTF8(mozilla::Span<const char16_t> aSource,
+ nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_utf16_to_utf8_impl(
+ &aDest, aSource.Elements(), aSource.Length(), 0);
+}
+
+inline void CopyUTF16toUTF8(mozilla::Span<const char16_t> aSource,
+ nsACString& aDest) {
+ if (MOZ_UNLIKELY(!CopyUTF16toUTF8(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aSource.Length());
+ }
+}
+
+[[nodiscard]] inline bool AppendUTF16toUTF8(
+ mozilla::Span<const char16_t> aSource, nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_utf16_to_utf8_impl(
+ &aDest, aSource.Elements(), aSource.Length(), aDest.Length());
+}
+
+inline void AppendUTF16toUTF8(mozilla::Span<const char16_t> aSource,
+ nsACString& aDest) {
+ if (MOZ_UNLIKELY(!AppendUTF16toUTF8(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aDest.Length() + aSource.Length());
+ }
+}
+
+// UTF-16 to Latin1
+// If all code points in the input are below U+0100, represents each scalar
+// value as an unsigned byte. (This is not windows-1252!) If there are code
+// points above U+00FF, memory-safely produces garbage and will likely start
+// asserting in future debug builds. The nature of the garbage may differ
+// based on CPU architecture and must not be relied upon. The names say
+// "ASCII" instead of "Latin1" for legacy reasons.
+
+[[nodiscard]] inline bool LossyCopyUTF16toASCII(
+ mozilla::Span<const char16_t> aSource, nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_utf16_to_latin1_lossy_impl(
+ &aDest, aSource.Elements(), aSource.Length(), 0, true);
+}
+
+inline void LossyCopyUTF16toASCII(mozilla::Span<const char16_t> aSource,
+ nsACString& aDest) {
+ if (MOZ_UNLIKELY(!LossyCopyUTF16toASCII(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aSource.Length());
+ }
+}
+
+[[nodiscard]] inline bool LossyAppendUTF16toASCII(
+ mozilla::Span<const char16_t> aSource, nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_utf16_to_latin1_lossy_impl(
+ &aDest, aSource.Elements(), aSource.Length(), aDest.Length(), false);
+}
+
+inline void LossyAppendUTF16toASCII(mozilla::Span<const char16_t> aSource,
+ nsACString& aDest) {
+ if (MOZ_UNLIKELY(
+ !LossyAppendUTF16toASCII(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aDest.Length() + aSource.Length());
+ }
+}
+
+// Latin1 to UTF-8
+// Interpret each incoming unsigned byte value as a Unicode scalar value (not
+// windows-1252!).
+// If the input is ASCII, the heap-allocated nsStringBuffer is shared if
+// possible.
+
+[[nodiscard]] inline bool CopyLatin1toUTF8(const nsACString& aSource,
+ nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_latin1_to_utf8_check(&aDest, &aSource, 0);
+}
+
+inline void CopyLatin1toUTF8(const nsACString& aSource, nsACString& aDest) {
+ if (MOZ_UNLIKELY(!CopyLatin1toUTF8(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aSource.Length());
+ }
+}
+
+[[nodiscard]] inline bool AppendLatin1toUTF8(const nsACString& aSource,
+ nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_latin1_to_utf8_check(&aDest, &aSource,
+ aDest.Length());
+}
+
+inline void AppendLatin1toUTF8(const nsACString& aSource, nsACString& aDest) {
+ if (MOZ_UNLIKELY(!AppendLatin1toUTF8(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aDest.Length() + aSource.Length());
+ }
+}
+
+// UTF-8 to Latin1
+// If all code points in the input are below U+0100, represents each scalar
+// value as an unsigned byte. (This is not windows-1252!) If there are code
+// points above U+00FF, memory-safely produces garbage in release builds and
+// asserts in debug builds. The nature of the garbage may differ
+// based on CPU architecture and must not be relied upon.
+// If the input is ASCII, the heap-allocated nsStringBuffer is shared if
+// possible.
+
+[[nodiscard]] inline bool LossyCopyUTF8toLatin1(const nsACString& aSource,
+ nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_utf8_to_latin1_lossy_check(&aDest, &aSource,
+ 0);
+}
+
+inline void LossyCopyUTF8toLatin1(const nsACString& aSource,
+ nsACString& aDest) {
+ if (MOZ_UNLIKELY(!LossyCopyUTF8toLatin1(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aSource.Length());
+ }
+}
+
+[[nodiscard]] inline bool LossyAppendUTF8toLatin1(const nsACString& aSource,
+ nsACString& aDest,
+ const mozilla::fallible_t&) {
+ return nscstring_fallible_append_utf8_to_latin1_lossy_check(&aDest, &aSource,
+ aDest.Length());
+}
+
+inline void LossyAppendUTF8toLatin1(const nsACString& aSource,
+ nsACString& aDest) {
+ if (MOZ_UNLIKELY(
+ !LossyAppendUTF8toLatin1(aSource, aDest, mozilla::fallible))) {
+ aDest.AllocFailed(aDest.Length() + aSource.Length());
+ }
+}
+
+/**
+ * Returns a new |char| buffer containing a zero-terminated copy of |aSource|.
+ *
+ * Infallibly allocates and returns a new |char| buffer which you must
+ * free with |free|.
+ * Performs a conversion with LossyConvertUTF16toLatin1() writing into the
+ * newly-allocated buffer.
+ *
+ * The new buffer is zero-terminated, but that may not help you if |aSource|
+ * contains embedded nulls.
+ *
+ * @param aSource a 16-bit wide string
+ * @return a new |char| buffer you must free with |free|.
+ */
+char* ToNewCString(const nsAString& aSource);
+
+/* A fallible version of ToNewCString. Returns nullptr on failure. */
+char* ToNewCString(const nsAString& aSource,
+ const mozilla::fallible_t& aFallible);
+
+/**
+ * Returns a new |char| buffer containing a zero-terminated copy of |aSource|.
+ *
+ * Infallibly allocates and returns a new |char| buffer which you must
+ * free with |free|.
+ *
+ * The new buffer is zero-terminated, but that may not help you if |aSource|
+ * contains embedded nulls.
+ *
+ * @param aSource an 8-bit wide string
+ * @return a new |char| buffer you must free with |free|.
+ */
+char* ToNewCString(const nsACString& aSource);
+
+/* A fallible version of ToNewCString. Returns nullptr on failure. */
+char* ToNewCString(const nsACString& aSource,
+ const mozilla::fallible_t& aFallible);
+
+/**
+ * Returns a new |char| buffer containing a zero-terminated copy of |aSource|.
+ *
+ * Infallibly allocates and returns a new |char| buffer which you must
+ * free with |free|.
+ * Performs an encoding conversion from a UTF-16 string to a UTF-8 string with
+ * unpaired surrogates replaced with the REPLACEMENT CHARACTER copying
+ * |aSource| to your new buffer.
+ *
+ * The new buffer is zero-terminated, but that may not help you if |aSource|
+ * contains embedded nulls.
+ *
+ * @param aSource a UTF-16 string (made of char16_t's)
+ * @param aUTF8Count the number of 8-bit units that was returned
+ * @return a new |char| buffer you must free with |free|.
+ */
+char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count = nullptr);
+
+/* A fallible version of ToNewUTF8String. Returns nullptr on failure. */
+char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count,
+ const mozilla::fallible_t& aFallible);
+
+/**
+ * Returns a new |char16_t| buffer containing a zero-terminated copy
+ * of |aSource|.
+ *
+ * Infallibly allocates and returns a new |char16_t| buffer which you must
+ * free with |free|.
+ *
+ * The new buffer is zero-terminated, but that may not help you if |aSource|
+ * contains embedded nulls.
+ *
+ * @param aSource a UTF-16 string
+ * @return a new |char16_t| buffer you must free with |free|.
+ */
+char16_t* ToNewUnicode(const nsAString& aSource);
+
+/* A fallible version of ToNewUnicode. Returns nullptr on failure. */
+char16_t* ToNewUnicode(const nsAString& aSource,
+ const mozilla::fallible_t& aFallible);
+
+/**
+ * Returns a new |char16_t| buffer containing a zero-terminated copy
+ * of |aSource|.
+ *
+ * Infallibly allocates and returns a new |char16_t| buffer which you must
+ * free with|free|.
+ *
+ * Performs an encoding conversion by 0-padding 8-bit wide characters up to
+ * 16-bits wide (i.e. Latin1 to UTF-16 conversion) while copying |aSource|
+ * to your new buffer.
+ *
+ * The new buffer is zero-terminated, but that may not help you if |aSource|
+ * contains embedded nulls.
+ *
+ * @param aSource a Latin1 string
+ * @return a new |char16_t| buffer you must free with |free|.
+ */
+char16_t* ToNewUnicode(const nsACString& aSource);
+
+/* A fallible version of ToNewUnicode. Returns nullptr on failure. */
+char16_t* ToNewUnicode(const nsACString& aSource,
+ const mozilla::fallible_t& aFallible);
+
+/**
+ * Returns a new |char16_t| buffer containing a zero-terminated copy
+ * of |aSource|.
+ *
+ * Infallibly allocates and returns a new |char| buffer which you must
+ * free with |free|. Performs an encoding conversion from UTF-8 to UTF-16
+ * while copying |aSource| to your new buffer. Malformed byte sequences
+ * are replaced with the REPLACEMENT CHARACTER.
+ *
+ * The new buffer is zero-terminated, but that may not help you if |aSource|
+ * contains embedded nulls.
+ *
+ * @param aSource an 8-bit wide string, UTF-8 encoded
+ * @param aUTF16Count the number of 16-bit units that was returned
+ * @return a new |char16_t| buffer you must free with |free|.
+ * (UTF-16 encoded)
+ */
+char16_t* UTF8ToNewUnicode(const nsACString& aSource,
+ uint32_t* aUTF16Count = nullptr);
+
+/* A fallible version of UTF8ToNewUnicode. Returns nullptr on failure. */
+char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count,
+ const mozilla::fallible_t& aFallible);
+
+/**
+ * Copies |aLength| 16-bit code units from the start of |aSource| to the
+ * |char16_t| buffer |aDest|.
+ *
+ * After this operation |aDest| is not null terminated.
+ *
+ * @param aSource a UTF-16 string
+ * @param aSrcOffset start offset in the source string
+ * @param aDest a |char16_t| buffer
+ * @param aLength the number of 16-bit code units to copy
+ * @return pointer to destination buffer - identical to |aDest|
+ */
+char16_t* CopyUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset,
+ char16_t* aDest, uint32_t aLength);
+
+/**
+ * Replaces unpaired surrogates with U+FFFD in the argument.
+ *
+ * Copies a shared string buffer or an otherwise read-only
+ * buffer only if there are unpaired surrogates.
+ */
+[[nodiscard]] inline bool EnsureUTF16Validity(nsAString& aString) {
+ size_t upTo = mozilla::Utf16ValidUpTo(aString);
+ size_t len = aString.Length();
+ if (upTo == len) {
+ return true;
+ }
+ char16_t* ptr = aString.BeginWriting(mozilla::fallible);
+ if (!ptr) {
+ return false;
+ }
+ auto span = mozilla::Span(ptr, len);
+ span[upTo] = 0xFFFD;
+ mozilla::EnsureUtf16ValiditySpan(span.From(upTo + 1));
+ return true;
+}
+
+void ParseString(const nsACString& aSource, char aDelimiter,
+ nsTArray<nsCString>& aArray);
+
+namespace mozilla::detail {
+
+constexpr auto kStringJoinAppendDefault =
+ [](auto& aResult, const auto& aValue) { aResult.Append(aValue); };
+
+} // namespace mozilla::detail
+
+/**
+ * Join a sequence of items, each optionally transformed to a string, with a
+ * given separator, appending to a given string.
+ *
+ * \tparam CharType char or char16_t
+ * \tparam InputRange a range usable with range-based for
+ * \tparam Func optionally, a functor accepting a nsTSubstring<CharType>& and
+ * an item of InputRange which appends the latter to the former
+ */
+template <
+ typename CharType, typename InputRange,
+ typename Func = const decltype(mozilla::detail::kStringJoinAppendDefault)&>
+void StringJoinAppend(
+ nsTSubstring<CharType>& aOutput,
+ const nsTLiteralString<CharType>& aSeparator, const InputRange& aInputRange,
+ Func&& aFunc = mozilla::detail::kStringJoinAppendDefault) {
+ bool first = true;
+ for (const auto& item : aInputRange) {
+ if (first) {
+ first = false;
+ } else {
+ aOutput.Append(aSeparator);
+ }
+
+ aFunc(aOutput, item);
+ }
+}
+
+/**
+ * Join a sequence of items, each optionally transformed to a string, with a
+ * given separator, returning a new string.
+ *
+ * \tparam CharType char or char16_t
+ * \tparam InputRange a range usable with range-based for
+ * \tparam Func optionally, a functor accepting a nsTSubstring<CharType>& and
+ * an item of InputRange which appends the latter to the former
+
+ */
+template <
+ typename CharType, typename InputRange,
+ typename Func = const decltype(mozilla::detail::kStringJoinAppendDefault)&>
+auto StringJoin(const nsTLiteralString<CharType>& aSeparator,
+ const InputRange& aInputRange,
+ Func&& aFunc = mozilla::detail::kStringJoinAppendDefault) {
+ nsTAutoString<CharType> res;
+ StringJoinAppend(res, aSeparator, aInputRange, std::forward<Func>(aFunc));
+ return res;
+}
+
+/**
+ * Converts case in place in the argument string.
+ */
+void ToUpperCase(nsACString&);
+
+void ToLowerCase(nsACString&);
+
+void ToUpperCase(nsACString&);
+
+void ToLowerCase(nsACString&);
+
+/**
+ * Converts case from string aSource to aDest.
+ */
+void ToUpperCase(const nsACString& aSource, nsACString& aDest);
+
+void ToLowerCase(const nsACString& aSource, nsACString& aDest);
+
+/**
+ * Finds the leftmost occurrence of |aPattern|, if any in the range
+ * |aSearchStart|..|aSearchEnd|.
+ *
+ * Returns |true| if a match was found, and adjusts |aSearchStart| and
+ * |aSearchEnd| to point to the match. If no match was found, returns |false|
+ * and makes |aSearchStart == aSearchEnd|.
+ *
+ * Currently, this is equivalent to the O(m*n) implementation previously on
+ * |ns[C]String|.
+ *
+ * If we need something faster, then we can implement that later.
+ */
+
+bool FindInReadable(const nsAString& aPattern, nsAString::const_iterator&,
+ nsAString::const_iterator&,
+ nsStringComparator = nsTDefaultStringComparator);
+bool FindInReadable(const nsACString& aPattern, nsACString::const_iterator&,
+ nsACString::const_iterator&,
+ nsCStringComparator = nsTDefaultStringComparator);
+
+/* sometimes we don't care about where the string was, just that we
+ * found it or not */
+inline bool FindInReadable(
+ const nsAString& aPattern, const nsAString& aSource,
+ nsStringComparator aCompare = nsTDefaultStringComparator) {
+ nsAString::const_iterator start, end;
+ aSource.BeginReading(start);
+ aSource.EndReading(end);
+ return FindInReadable(aPattern, start, end, aCompare);
+}
+
+inline bool FindInReadable(
+ const nsACString& aPattern, const nsACString& aSource,
+ nsCStringComparator aCompare = nsTDefaultStringComparator) {
+ nsACString::const_iterator start, end;
+ aSource.BeginReading(start);
+ aSource.EndReading(end);
+ return FindInReadable(aPattern, start, end, aCompare);
+}
+
+bool CaseInsensitiveFindInReadable(const nsACString& aPattern,
+ nsACString::const_iterator&,
+ nsACString::const_iterator&);
+
+/**
+ * Finds the rightmost occurrence of |aPattern|
+ * Returns |true| if a match was found, and adjusts |aSearchStart| and
+ * |aSearchEnd| to point to the match. If no match was found, returns |false|
+ * and makes |aSearchStart == aSearchEnd|.
+ */
+bool RFindInReadable(const nsAString& aPattern, nsAString::const_iterator&,
+ nsAString::const_iterator&,
+ nsStringComparator = nsTDefaultStringComparator);
+bool RFindInReadable(const nsACString& aPattern, nsACString::const_iterator&,
+ nsACString::const_iterator&,
+ nsCStringComparator = nsTDefaultStringComparator);
+
+/**
+ * Finds the leftmost occurrence of |aChar|, if any in the range
+ * |aSearchStart|..|aSearchEnd|.
+ *
+ * Returns |true| if a match was found, and adjusts |aSearchStart| to
+ * point to the match. If no match was found, returns |false| and
+ * makes |aSearchStart == aSearchEnd|.
+ */
+bool FindCharInReadable(char16_t aChar, nsAString::const_iterator& aSearchStart,
+ const nsAString::const_iterator& aSearchEnd);
+bool FindCharInReadable(char aChar, nsACString::const_iterator& aSearchStart,
+ const nsACString::const_iterator& aSearchEnd);
+
+bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring);
+bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring,
+ nsStringComparator);
+bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring);
+bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring,
+ nsCStringComparator);
+bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring);
+bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring,
+ nsStringComparator);
+bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring);
+bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring,
+ nsCStringComparator);
+
+const nsString& EmptyString();
+const nsCString& EmptyCString();
+
+const nsString& VoidString();
+const nsCString& VoidCString();
+
+/**
+ * Compare a UTF-8 string to an UTF-16 string.
+ *
+ * Returns 0 if the strings are equal, -1 if aUTF8String is less
+ * than aUTF16Count, and 1 in the reverse case. Errors are replaced
+ * with U+FFFD and then the U+FFFD is compared as if it had occurred
+ * in the input. If aErr is not nullptr, *aErr is set to true if
+ * either string had malformed sequences.
+ */
+int32_t CompareUTF8toUTF16(const nsACString& aUTF8String,
+ const nsAString& aUTF16String, bool* aErr = nullptr);
+
+void AppendUCS4ToUTF16(const uint32_t aSource, nsAString& aDest);
+
+#endif // !defined(nsReadableUtils_h___)
diff --git a/xpcom/string/nsString.h b/xpcom/string/nsString.h
new file mode 100644
index 0000000000..e86ea594ac
--- /dev/null
+++ b/xpcom/string/nsString.h
@@ -0,0 +1,171 @@
+/* -*- 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/. */
+
+#ifndef nsString_h___
+#define nsString_h___
+
+#include <ostream>
+
+#include "mozilla/Attributes.h"
+
+#include "nsStringFwd.h"
+
+#include "nsAString.h"
+#include "nsDependentSubstring.h"
+#include "nsReadableUtils.h"
+
+#include "nsTString.h"
+
+static_assert(sizeof(char16_t) == 2, "size of char16_t must be 2");
+static_assert(sizeof(nsString::char_type) == 2,
+ "size of nsString::char_type must be 2");
+static_assert(nsString::char_type(-1) > nsString::char_type(0),
+ "nsString::char_type must be unsigned");
+static_assert(sizeof(nsCString::char_type) == 1,
+ "size of nsCString::char_type must be 1");
+
+static_assert(sizeof(nsTLiteralString<char>) == sizeof(nsTString<char>),
+ "nsLiteralCString can masquerade as nsCString, "
+ "so they must have identical layout");
+
+static_assert(sizeof(nsTLiteralString<char16_t>) == sizeof(nsTString<char16_t>),
+ "nsTLiteralString can masquerade as nsString, "
+ "so they must have identical layout");
+
+/**
+ * A helper class that converts a UTF-16 string to ASCII in a lossy manner
+ */
+class NS_LossyConvertUTF16toASCII : public nsAutoCString {
+ public:
+ explicit NS_LossyConvertUTF16toASCII(const char16ptr_t aString) {
+ LossyAppendUTF16toASCII(mozilla::MakeStringSpan(aString), *this);
+ }
+
+ NS_LossyConvertUTF16toASCII(const char16ptr_t aString, size_t aLength) {
+ LossyAppendUTF16toASCII(
+ Substring(static_cast<const char16_t*>(aString), aLength), *this);
+ }
+
+ explicit NS_LossyConvertUTF16toASCII(const nsAString& aString) {
+ LossyAppendUTF16toASCII(aString, *this);
+ }
+
+ private:
+ // NOT TO BE IMPLEMENTED
+ NS_LossyConvertUTF16toASCII(char) = delete;
+};
+
+class NS_ConvertASCIItoUTF16 : public nsAutoString {
+ public:
+ explicit NS_ConvertASCIItoUTF16(const char* aCString) {
+ AppendASCIItoUTF16(mozilla::MakeStringSpan(aCString), *this);
+ }
+
+ NS_ConvertASCIItoUTF16(const char* aCString, size_t aLength) {
+ AppendASCIItoUTF16(Substring(aCString, aLength), *this);
+ }
+
+ explicit NS_ConvertASCIItoUTF16(const nsACString& aCString) {
+ AppendASCIItoUTF16(aCString, *this);
+ }
+
+ explicit NS_ConvertASCIItoUTF16(mozilla::Span<const char> aCString) {
+ AppendASCIItoUTF16(aCString, *this);
+ }
+
+ private:
+ // NOT TO BE IMPLEMENTED
+ NS_ConvertASCIItoUTF16(char16_t) = delete;
+};
+
+/**
+ * A helper class that converts a UTF-16 string to UTF-8
+ */
+class NS_ConvertUTF16toUTF8 : public nsAutoCString {
+ public:
+ explicit NS_ConvertUTF16toUTF8(const char16ptr_t aString) {
+ AppendUTF16toUTF8(mozilla::MakeStringSpan(aString), *this);
+ }
+
+ NS_ConvertUTF16toUTF8(const char16ptr_t aString, size_t aLength) {
+ AppendUTF16toUTF8(Substring(static_cast<const char16_t*>(aString), aLength),
+ *this);
+ }
+
+ explicit NS_ConvertUTF16toUTF8(const nsAString& aString) {
+ AppendUTF16toUTF8(aString, *this);
+ }
+
+ explicit NS_ConvertUTF16toUTF8(mozilla::Span<const char16_t> aString) {
+ AppendUTF16toUTF8(aString, *this);
+ }
+
+ private:
+ // NOT TO BE IMPLEMENTED
+ NS_ConvertUTF16toUTF8(char) = delete;
+};
+
+class NS_ConvertUTF8toUTF16 : public nsAutoString {
+ public:
+ explicit NS_ConvertUTF8toUTF16(const char* aCString) {
+ AppendUTF8toUTF16(mozilla::MakeStringSpan(aCString), *this);
+ }
+
+ NS_ConvertUTF8toUTF16(const char* aCString, size_t aLength) {
+ AppendUTF8toUTF16(Substring(aCString, aLength), *this);
+ }
+
+ explicit NS_ConvertUTF8toUTF16(const nsACString& aCString) {
+ AppendUTF8toUTF16(aCString, *this);
+ }
+
+ explicit NS_ConvertUTF8toUTF16(mozilla::Span<const char> aCString) {
+ AppendUTF8toUTF16(aCString, *this);
+ }
+
+ private:
+ // NOT TO BE IMPLEMENTED
+ NS_ConvertUTF8toUTF16(char16_t) = delete;
+};
+
+/**
+ * Converts an integer (signed/unsigned, 32/64bit) to its decimal string
+ * representation and returns it as an nsAutoCString/nsAutoString.
+ */
+template <typename T, typename U>
+nsTAutoString<T> IntToTString(const U aInt, const int aRadix = 10) {
+ nsTAutoString<T> string;
+ string.AppendInt(aInt, aRadix);
+ return string;
+}
+
+template <typename U>
+nsAutoCString IntToCString(const U aInt, const int aRadix = 10) {
+ return IntToTString<char>(aInt, aRadix);
+}
+
+template <typename U>
+nsAutoString IntToString(const U aInt, const int aRadix = 10) {
+ return IntToTString<char16_t>(aInt, aRadix);
+}
+
+// MOZ_DBG support
+
+inline std::ostream& operator<<(std::ostream& aOut, const nsACString& aString) {
+ aOut.write(aString.Data(), aString.Length());
+ return aOut;
+}
+
+inline std::ostream& operator<<(std::ostream& aOut, const nsAString& aString) {
+ return aOut << NS_ConvertUTF16toUTF8(aString);
+}
+
+// the following are included/declared for backwards compatibility
+#include "nsDependentString.h"
+#include "nsLiteralString.h"
+#include "nsPromiseFlatString.h"
+
+#endif // !defined(nsString_h___)
diff --git a/xpcom/string/nsStringBuffer.cpp b/xpcom/string/nsStringBuffer.cpp
new file mode 100644
index 0000000000..f32b51d3a6
--- /dev/null
+++ b/xpcom/string/nsStringBuffer.cpp
@@ -0,0 +1,162 @@
+/* -*- 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/. */
+
+#include "nsStringBuffer.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+
+#ifdef DEBUG
+# include "nsStringStats.h"
+#else
+# define STRING_STAT_INCREMENT(_s)
+#endif
+
+void nsStringBuffer::AddRef() {
+ // Memory synchronization is not required when incrementing a
+ // reference count. The first increment of a reference count on a
+ // thread is not important, since the first use of the object on a
+ // thread can happen before it. What is important is the transfer
+ // of the pointer to that thread, which may happen prior to the
+ // first increment on that thread. The necessary memory
+ // synchronization is done by the mechanism that transfers the
+ // pointer between threads.
+#ifdef NS_BUILD_REFCNT_LOGGING
+ uint32_t count =
+#endif
+ mRefCount.fetch_add(1, std::memory_order_relaxed)
+#ifdef NS_BUILD_REFCNT_LOGGING
+ + 1
+#endif
+ ;
+ STRING_STAT_INCREMENT(Share);
+ NS_LOG_ADDREF(this, count, "nsStringBuffer", sizeof(*this));
+}
+
+void nsStringBuffer::Release() {
+ // Since this may be the last release on this thread, we need
+ // release semantics so that prior writes on this thread are visible
+ // to the thread that destroys the object when it reads mValue with
+ // acquire semantics.
+ uint32_t count = mRefCount.fetch_sub(1, std::memory_order_release) - 1;
+ NS_LOG_RELEASE(this, count, "nsStringBuffer");
+ if (count == 0) {
+ // We're going to destroy the object on this thread, so we need
+ // acquire semantics to synchronize with the memory released by
+ // the last release on other threads, that is, to ensure that
+ // writes prior to that release are now visible on this thread.
+ count = mRefCount.load(std::memory_order_acquire);
+
+ STRING_STAT_INCREMENT(Free);
+ free(this); // we were allocated with |malloc|
+ }
+}
+
+/**
+ * Alloc returns a pointer to a new string header with set capacity.
+ */
+already_AddRefed<nsStringBuffer> nsStringBuffer::Alloc(size_t aSize) {
+ NS_ASSERTION(aSize != 0, "zero capacity allocation not allowed");
+ NS_ASSERTION(sizeof(nsStringBuffer) + aSize <= size_t(uint32_t(-1)) &&
+ sizeof(nsStringBuffer) + aSize > aSize,
+ "mStorageSize will truncate");
+
+ nsStringBuffer* hdr = (nsStringBuffer*)malloc(sizeof(nsStringBuffer) + aSize);
+ if (hdr) {
+ STRING_STAT_INCREMENT(Alloc);
+
+ hdr->mRefCount = 1;
+ hdr->mStorageSize = aSize;
+ NS_LOG_ADDREF(hdr, 1, "nsStringBuffer", sizeof(*hdr));
+ }
+ return already_AddRefed(hdr);
+}
+
+nsStringBuffer* nsStringBuffer::Realloc(nsStringBuffer* aHdr, size_t aSize) {
+ STRING_STAT_INCREMENT(Realloc);
+
+ NS_ASSERTION(aSize != 0, "zero capacity allocation not allowed");
+ NS_ASSERTION(sizeof(nsStringBuffer) + aSize <= size_t(uint32_t(-1)) &&
+ sizeof(nsStringBuffer) + aSize > aSize,
+ "mStorageSize will truncate");
+
+ // no point in trying to save ourselves if we hit this assertion
+ NS_ASSERTION(!aHdr->IsReadonly(), "|Realloc| attempted on readonly string");
+
+ // Treat this as a release and addref for refcounting purposes, since we
+ // just asserted that the refcount is 1. If we don't do that, refcount
+ // logging will claim we've leaked all sorts of stuff.
+ NS_LOG_RELEASE(aHdr, 0, "nsStringBuffer");
+
+ aHdr = (nsStringBuffer*)realloc(aHdr, sizeof(nsStringBuffer) + aSize);
+ if (aHdr) {
+ NS_LOG_ADDREF(aHdr, 1, "nsStringBuffer", sizeof(*aHdr));
+ aHdr->mStorageSize = aSize;
+ }
+
+ return aHdr;
+}
+
+nsStringBuffer* nsStringBuffer::FromString(const nsAString& aStr) {
+ if (!(aStr.mDataFlags & nsAString::DataFlags::REFCOUNTED)) {
+ return nullptr;
+ }
+
+ return FromData(aStr.mData);
+}
+
+nsStringBuffer* nsStringBuffer::FromString(const nsACString& aStr) {
+ if (!(aStr.mDataFlags & nsACString::DataFlags::REFCOUNTED)) {
+ return nullptr;
+ }
+
+ return FromData(aStr.mData);
+}
+
+void nsStringBuffer::ToString(uint32_t aLen, nsAString& aStr,
+ bool aMoveOwnership) {
+ char16_t* data = static_cast<char16_t*>(Data());
+
+ MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char16_t(0),
+ "data should be null terminated");
+
+ nsAString::DataFlags flags =
+ nsAString::DataFlags::REFCOUNTED | nsAString::DataFlags::TERMINATED;
+
+ if (!aMoveOwnership) {
+ AddRef();
+ }
+ aStr.Finalize();
+ aStr.SetData(data, aLen, flags);
+}
+
+void nsStringBuffer::ToString(uint32_t aLen, nsACString& aStr,
+ bool aMoveOwnership) {
+ char* data = static_cast<char*>(Data());
+
+ MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char(0),
+ "data should be null terminated");
+
+ nsACString::DataFlags flags =
+ nsACString::DataFlags::REFCOUNTED | nsACString::DataFlags::TERMINATED;
+
+ if (!aMoveOwnership) {
+ AddRef();
+ }
+ aStr.Finalize();
+ aStr.SetData(data, aLen, flags);
+}
+
+size_t nsStringBuffer::SizeOfIncludingThisIfUnshared(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return IsReadonly() ? 0 : aMallocSizeOf(this);
+}
+
+size_t nsStringBuffer::SizeOfIncludingThisEvenIfShared(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
diff --git a/xpcom/string/nsStringBuffer.h b/xpcom/string/nsStringBuffer.h
new file mode 100644
index 0000000000..3c92959932
--- /dev/null
+++ b/xpcom/string/nsStringBuffer.h
@@ -0,0 +1,184 @@
+/* -*- 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/. */
+
+#ifndef nsStringBuffer_h__
+#define nsStringBuffer_h__
+
+#include <atomic>
+#include "mozilla/MemoryReporting.h"
+#include "nsStringFwd.h"
+
+template <class T>
+struct already_AddRefed;
+
+/**
+ * This structure precedes the string buffers "we" allocate. It may be the
+ * case that nsTAString::mData does not point to one of these special
+ * buffers. The mDataFlags member variable distinguishes the buffer type.
+ *
+ * When this header is in use, it enables reference counting, and capacity
+ * tracking. NOTE: A string buffer can be modified only if its reference
+ * count is 1.
+ */
+class nsStringBuffer {
+ private:
+ friend class CheckStaticAtomSizes;
+
+ std::atomic<uint32_t> mRefCount;
+ uint32_t mStorageSize;
+
+ public:
+ /**
+ * Allocates a new string buffer, with given size in bytes and a
+ * reference count of one. When the string buffer is no longer needed,
+ * it should be released via Release.
+ *
+ * It is up to the caller to set the bytes corresponding to the string
+ * buffer by calling the Data method to fetch the raw data pointer. Care
+ * must be taken to properly null terminate the character array. The
+ * storage size can be greater than the length of the actual string
+ * (i.e., it is not required that the null terminator appear in the last
+ * storage unit of the string buffer's data).
+ *
+ * @return new string buffer or null if out of memory.
+ */
+ static already_AddRefed<nsStringBuffer> Alloc(size_t aStorageSize);
+
+ /**
+ * Resizes the given string buffer to the specified storage size. This
+ * method must not be called on a readonly string buffer. Use this API
+ * carefully!!
+ *
+ * This method behaves like the ANSI-C realloc function. (i.e., If the
+ * allocation fails, null will be returned and the given string buffer
+ * will remain unmodified.)
+ *
+ * @see IsReadonly
+ */
+ static nsStringBuffer* Realloc(nsStringBuffer* aBuf, size_t aStorageSize);
+
+ /**
+ * Increment the reference count on this string buffer.
+ */
+ void NS_FASTCALL AddRef();
+
+ /**
+ * Decrement the reference count on this string buffer. The string
+ * buffer will be destroyed when its reference count reaches zero.
+ */
+ void NS_FASTCALL Release();
+
+ /**
+ * This method returns the string buffer corresponding to the given data
+ * pointer. The data pointer must have been returned previously by a
+ * call to the nsStringBuffer::Data method.
+ */
+ static nsStringBuffer* FromData(void* aData) {
+ return reinterpret_cast<nsStringBuffer*>(aData) - 1;
+ }
+
+ /**
+ * This method returns the data pointer for this string buffer.
+ */
+ void* Data() const {
+ return const_cast<char*>(reinterpret_cast<const char*>(this + 1));
+ }
+
+ /**
+ * This function returns the storage size of a string buffer in bytes.
+ * This value is the same value that was originally passed to Alloc (or
+ * Realloc).
+ */
+ uint32_t StorageSize() const { return mStorageSize; }
+
+ /**
+ * If this method returns false, then the caller can be sure that their
+ * reference to the string buffer is the only reference to the string
+ * buffer, and therefore it has exclusive access to the string buffer and
+ * associated data. However, if this function returns true, then other
+ * consumers may rely on the data in this buffer being immutable and
+ * other threads may access this buffer simultaneously.
+ */
+ bool IsReadonly() const {
+ // This doesn't lead to the destruction of the buffer, so we don't
+ // need to perform acquire memory synchronization for the normal
+ // reason that a reference count needs acquire synchronization
+ // (ensuring that all writes to the object made on other threads are
+ // visible to the thread destroying the object).
+ //
+ // We then need to consider the possibility that there were prior
+ // writes to the buffer on a different thread: one that has either
+ // since released its reference count, or one that also has access
+ // to this buffer through the same reference. There are two ways
+ // for that to happen: either the buffer pointer or a data structure
+ // (e.g., string object) pointing to the buffer was transferred from
+ // one thread to another, or the data structure pointing to the
+ // buffer was already visible on both threads. In the first case
+ // (transfer), the transfer of data from one thread to another would
+ // have handled the memory synchronization. In the latter case
+ // (data structure visible on both threads), the caller needed some
+ // sort of higher level memory synchronization to protect against
+ // the string object being mutated at the same time on multiple
+ // threads.
+
+ // See bug 1603504. TSan might complain about a race when using
+ // memory_order_relaxed, so use memory_order_acquire for making TSan
+ // happy.
+#if defined(MOZ_TSAN)
+ return mRefCount.load(std::memory_order_acquire) > 1;
+#else
+ return mRefCount.load(std::memory_order_relaxed) > 1;
+#endif
+ }
+
+ /**
+ * The FromString methods return a string buffer for the given string
+ * object or null if the string object does not have a string buffer.
+ * The reference count of the string buffer is NOT incremented by these
+ * methods. If the caller wishes to hold onto the returned value, then
+ * the returned string buffer must have its reference count incremented
+ * via a call to the AddRef method.
+ */
+ static nsStringBuffer* FromString(const nsAString& aStr);
+ static nsStringBuffer* FromString(const nsACString& aStr);
+
+ /**
+ * The ToString methods assign this string buffer to a given string
+ * object. If the string object does not support sharable string
+ * buffers, then its value will be set to a copy of the given string
+ * buffer. Otherwise, these methods increment the reference count of the
+ * given string buffer. It is important to specify the length (in
+ * storage units) of the string contained in the string buffer since the
+ * length of the string may be less than its storage size. The string
+ * must have a null terminator at the offset specified by |len|.
+ *
+ * NOTE: storage size is measured in bytes even for wide strings;
+ * however, string length is always measured in storage units
+ * (2-byte units for wide strings).
+ */
+ void ToString(uint32_t aLen, nsAString& aStr, bool aMoveOwnership = false);
+ void ToString(uint32_t aLen, nsACString& aStr, bool aMoveOwnership = false);
+
+ /**
+ * This measures the size only if the StringBuffer is unshared.
+ */
+ size_t SizeOfIncludingThisIfUnshared(
+ mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ /**
+ * This measures the size regardless of whether the StringBuffer is
+ * unshared.
+ *
+ * WARNING: Only use this if you really know what you are doing, because
+ * it 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 SizeOfIncludingThisEvenIfShared(
+ mozilla::MallocSizeOf aMallocSizeOf) const;
+};
+
+#endif /* !defined(nsStringBuffer_h__ */
diff --git a/xpcom/string/nsStringFlags.h b/xpcom/string/nsStringFlags.h
new file mode 100644
index 0000000000..d0ba05c8db
--- /dev/null
+++ b/xpcom/string/nsStringFlags.h
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+#ifndef nsStringFlags_h
+#define nsStringFlags_h
+
+#include <stdint.h>
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+namespace detail {
+// NOTE: these flags are declared public _only_ for convenience inside
+// the string implementation. And they are outside of the string
+// class so that the type is the same for both narrow and wide
+// strings.
+
+// bits for mDataFlags
+enum class StringDataFlags : uint16_t {
+ // Some terminology:
+ //
+ // "dependent buffer" A dependent buffer is one that the string class
+ // does not own. The string class relies on some
+ // external code to ensure the lifetime of the
+ // dependent buffer.
+ //
+ // "refcounted buffer" A refcounted buffer is one that the string class
+ // allocates. When it allocates a refcounted string
+ // buffer, it allocates some additional space at
+ // the beginning of the buffer for additional
+ // fields, including a reference count and a
+ // buffer length. See nsStringHeader.
+ //
+ // "adopted buffer" An adopted buffer is a raw string buffer
+ // allocated on the heap (using moz_xmalloc)
+ // of which the string class subsumes ownership.
+ //
+ // Some comments about the string data flags:
+ //
+ // REFCOUNTED, OWNED, and INLINE are all mutually exlusive. They
+ // indicate the allocation type of mData. If none of these flags
+ // are set, then the string buffer is dependent.
+ //
+ // REFCOUNTED, OWNED, or INLINE imply TERMINATED. This is because
+ // the string classes always allocate null-terminated buffers, and
+ // non-terminated substrings are always dependent.
+ //
+ // VOIDED implies TERMINATED, and moreover it implies that mData
+ // points to char_traits::sEmptyBuffer. Therefore, VOIDED is
+ // mutually exclusive with REFCOUNTED, OWNED, and INLINE.
+ //
+ // INLINE requires StringClassFlags::INLINE to be set on the type.
+
+ // IsTerminated returns true
+ TERMINATED = 1 << 0,
+
+ // IsVoid returns true
+ VOIDED = 1 << 1,
+
+ // mData points to a heap-allocated, shareable, refcounted buffer
+ REFCOUNTED = 1 << 2,
+
+ // mData points to a heap-allocated, raw buffer
+ OWNED = 1 << 3,
+
+ // mData points to a writable, inline buffer
+ INLINE = 1 << 4,
+
+ // mData points to a string literal; DataFlags::TERMINATED will also be set
+ LITERAL = 1 << 5,
+
+ // used to check for invalid flags -- all bits above the last item
+ INVALID_MASK = (uint16_t) ~((LITERAL << 1) - 1)
+};
+
+// bits for mClassFlags
+enum class StringClassFlags : uint16_t {
+ // |this|'s buffer is inline, and requires the type to be binary-compatible
+ // with nsTAutoStringN
+ INLINE = 1 << 0,
+ // |this| requires its buffer is null-terminated
+ NULL_TERMINATED = 1 << 1,
+ // used to check for invalid flags -- all bits above the last item
+ INVALID_MASK = (uint16_t) ~((NULL_TERMINATED << 1) - 1)
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StringDataFlags)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StringClassFlags)
+
+} // namespace detail
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/string/nsStringFwd.h b/xpcom/string/nsStringFwd.h
new file mode 100644
index 0000000000..f737545163
--- /dev/null
+++ b/xpcom/string/nsStringFwd.h
@@ -0,0 +1,92 @@
+/* -*- 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/. */
+
+/* nsStringFwd.h --- forward declarations for string classes */
+
+#ifndef nsStringFwd_h
+#define nsStringFwd_h
+
+#include "nscore.h"
+
+static constexpr int32_t kNotFound = -1;
+
+namespace mozilla {
+namespace detail {
+
+template <typename T>
+class nsTStringRepr;
+
+using nsStringRepr = nsTStringRepr<char16_t>;
+using nsCStringRepr = nsTStringRepr<char>;
+
+} // namespace detail
+} // namespace mozilla
+
+static const size_t AutoStringDefaultStorageSize = 64;
+
+template <typename T>
+class nsTSubstring;
+template <typename T>
+class nsTSubstringTuple;
+template <typename T>
+class nsTString;
+template <typename T, size_t N>
+class nsTAutoStringN;
+template <typename T>
+class nsTDependentString;
+template <typename T>
+class nsTDependentSubstring;
+template <typename T>
+class nsTPromiseFlatString;
+template <typename T>
+class nsTLiteralString;
+template <typename T>
+class nsTSubstringSplitter;
+
+template <typename T>
+using nsTStringComparator = int (*)(const T*, const T*, size_t, size_t);
+
+// The default string comparator (case-sensitive comparision)
+template <typename T>
+int nsTDefaultStringComparator(const T*, const T*, size_t, size_t);
+
+// We define this version without a size param instead of providing a
+// default value for N so that so there is a default typename that doesn't
+// require angle brackets.
+template <typename T>
+using nsTAutoString = nsTAutoStringN<T, AutoStringDefaultStorageSize>;
+
+// Double-byte (char16_t) string types.
+
+using nsAString = nsTSubstring<char16_t>;
+using nsSubstringTuple = nsTSubstringTuple<char16_t>;
+using nsString = nsTString<char16_t>;
+using nsAutoString = nsTAutoString<char16_t>;
+template <size_t N>
+using nsAutoStringN = nsTAutoStringN<char16_t, N>;
+using nsDependentString = nsTDependentString<char16_t>;
+using nsDependentSubstring = nsTDependentSubstring<char16_t>;
+using nsPromiseFlatString = nsTPromiseFlatString<char16_t>;
+using nsStringComparator = nsTStringComparator<char16_t>;
+using nsLiteralString = nsTLiteralString<char16_t>;
+using nsSubstringSplitter = nsTSubstringSplitter<char16_t>;
+
+// Single-byte (char) string types.
+
+using nsACString = nsTSubstring<char>;
+using nsCSubstringTuple = nsTSubstringTuple<char>;
+using nsCString = nsTString<char>;
+using nsAutoCString = nsTAutoString<char>;
+template <size_t N>
+using nsAutoCStringN = nsTAutoStringN<char, N>;
+using nsDependentCString = nsTDependentString<char>;
+using nsDependentCSubstring = nsTDependentSubstring<char>;
+using nsPromiseFlatCString = nsTPromiseFlatString<char>;
+using nsCStringComparator = nsTStringComparator<char>;
+using nsLiteralCString = nsTLiteralString<char>;
+using nsCSubstringSplitter = nsTSubstringSplitter<char>;
+
+#endif /* !defined(nsStringFwd_h) */
diff --git a/xpcom/string/nsStringIterator.h b/xpcom/string/nsStringIterator.h
new file mode 100644
index 0000000000..db14efdaca
--- /dev/null
+++ b/xpcom/string/nsStringIterator.h
@@ -0,0 +1,117 @@
+/* -*- 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/. */
+
+#ifndef nsStringIterator_h___
+#define nsStringIterator_h___
+
+#include "nsCharTraits.h"
+#include "nsAlgorithm.h"
+#include "nsDebug.h"
+
+/**
+ * @see nsTAString
+ */
+
+template <class CharT>
+class nsReadingIterator {
+ public:
+ typedef nsReadingIterator<CharT> self_type;
+ typedef ptrdiff_t difference_type;
+ typedef size_t size_type;
+ typedef CharT value_type;
+ typedef const CharT* pointer;
+ typedef const CharT& reference;
+
+ private:
+ friend class mozilla::detail::nsTStringRepr<CharT>;
+
+ // unfortunately, the API for nsReadingIterator requires that the
+ // iterator know its start and end positions. this was needed when
+ // we supported multi-fragment strings, but now it is really just
+ // extra baggage. we should remove mStart and mEnd at some point.
+
+ const CharT* mStart;
+ const CharT* mEnd;
+ const CharT* mPosition;
+
+ public:
+ nsReadingIterator() : mStart(nullptr), mEnd(nullptr), mPosition(nullptr) {}
+ // clang-format off
+ // nsReadingIterator( const nsReadingIterator<CharT>& ); // auto-generated copy-constructor OK
+ // nsReadingIterator<CharT>& operator=( const nsReadingIterator<CharT>& ); // auto-generated copy-assignment operator OK
+ // clang-format on
+
+ pointer get() const { return mPosition; }
+
+ CharT operator*() const { return *get(); }
+
+ self_type& operator++() {
+ ++mPosition;
+ return *this;
+ }
+
+ self_type operator++(int) {
+ self_type result(*this);
+ ++mPosition;
+ return result;
+ }
+
+ self_type& operator--() {
+ --mPosition;
+ return *this;
+ }
+
+ self_type operator--(int) {
+ self_type result(*this);
+ --mPosition;
+ return result;
+ }
+
+ self_type& advance(difference_type aN) {
+ if (aN > 0) {
+ difference_type step = XPCOM_MIN(aN, mEnd - mPosition);
+
+ NS_ASSERTION(
+ step > 0,
+ "can't advance a reading iterator beyond the end of a string");
+
+ mPosition += step;
+ } else if (aN < 0) {
+ difference_type step = XPCOM_MAX(aN, -(mPosition - mStart));
+
+ NS_ASSERTION(step < 0,
+ "can't advance (backward) a reading iterator beyond the end "
+ "of a string");
+
+ mPosition += step;
+ }
+ return *this;
+ }
+
+ // We return an unsigned type here (with corresponding assert) rather than
+ // the more usual difference_type because we want to make this class go
+ // away in favor of mozilla::RangedPtr. Since RangedPtr has the same
+ // requirement we are enforcing here, the transition ought to be much
+ // smoother.
+ size_type operator-(const self_type& aOther) const {
+ MOZ_ASSERT(mPosition >= aOther.mPosition);
+ return mPosition - aOther.mPosition;
+ }
+};
+
+template <class CharT>
+inline bool operator==(const nsReadingIterator<CharT>& aLhs,
+ const nsReadingIterator<CharT>& aRhs) {
+ return aLhs.get() == aRhs.get();
+}
+
+template <class CharT>
+inline bool operator!=(const nsReadingIterator<CharT>& aLhs,
+ const nsReadingIterator<CharT>& aRhs) {
+ return aLhs.get() != aRhs.get();
+}
+
+#endif /* !defined(nsStringIterator_h___) */
diff --git a/xpcom/string/nsStringStats.cpp b/xpcom/string/nsStringStats.cpp
new file mode 100644
index 0000000000..7fc3d82ad5
--- /dev/null
+++ b/xpcom/string/nsStringStats.cpp
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#include "nsStringStats.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsString.h"
+
+#include <stdint.h>
+#include <stdio.h>
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <process.h>
+#else
+# include <unistd.h>
+# include <pthread.h>
+#endif
+
+nsStringStats gStringStats;
+
+nsStringStats::~nsStringStats() {
+ // this is a hack to suppress duplicate string stats printing
+ // in seamonkey as a result of the string code being linked
+ // into seamonkey and libxpcom! :-(
+ if (!mAllocCount && !mAdoptCount) {
+ return;
+ }
+
+ // Only print the stats if we detect a leak.
+ if (mAllocCount <= mFreeCount && mAdoptCount <= mAdoptFreeCount) {
+ return;
+ }
+
+ printf("nsStringStats\n");
+ printf(" => mAllocCount: % 10d\n", int(mAllocCount));
+ printf(" => mReallocCount: % 10d\n", int(mReallocCount));
+ printf(" => mFreeCount: % 10d", int(mFreeCount));
+ if (mAllocCount > mFreeCount) {
+ printf(" -- LEAKED %d !!!\n", mAllocCount - mFreeCount);
+ } else {
+ printf("\n");
+ }
+ printf(" => mShareCount: % 10d\n", int(mShareCount));
+ printf(" => mAdoptCount: % 10d\n", int(mAdoptCount));
+ printf(" => mAdoptFreeCount: % 10d", int(mAdoptFreeCount));
+ if (mAdoptCount > mAdoptFreeCount) {
+ printf(" -- LEAKED %d !!!\n", mAdoptCount - mAdoptFreeCount);
+ } else {
+ printf("\n");
+ }
+
+#ifdef XP_WIN
+ auto pid = uintptr_t(_getpid());
+ auto tid = uintptr_t(GetCurrentThreadId());
+#else
+ auto pid = uintptr_t(getpid());
+ auto tid = uintptr_t(pthread_self());
+#endif
+
+ printf(" => Process ID: %" PRIuPTR ", Thread ID: %" PRIuPTR "\n", pid, tid);
+}
diff --git a/xpcom/string/nsStringStats.h b/xpcom/string/nsStringStats.h
new file mode 100644
index 0000000000..a38304c2b7
--- /dev/null
+++ b/xpcom/string/nsStringStats.h
@@ -0,0 +1,32 @@
+/* -*- 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/. */
+
+#ifndef nsStringStats_h
+#define nsStringStats_h
+
+#include "mozilla/Atomics.h"
+
+class nsStringStats {
+ public:
+ nsStringStats() = default;
+
+ ~nsStringStats();
+
+ using AtomicInt = mozilla::Atomic<int32_t, mozilla::SequentiallyConsistent>;
+
+ AtomicInt mAllocCount{0};
+ AtomicInt mReallocCount{0};
+ AtomicInt mFreeCount{0};
+ AtomicInt mShareCount{0};
+ AtomicInt mAdoptCount{0};
+ AtomicInt mAdoptFreeCount{0};
+};
+
+extern nsStringStats gStringStats;
+
+#define STRING_STAT_INCREMENT(_s) (gStringStats.m##_s##Count)++
+
+#endif // nsStringStats_h
diff --git a/xpcom/string/nsTDependentString.cpp b/xpcom/string/nsTDependentString.cpp
new file mode 100644
index 0000000000..83cfa39687
--- /dev/null
+++ b/xpcom/string/nsTDependentString.cpp
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#include "nsTDependentString.h"
+
+template <typename T>
+nsTDependentString<T>::nsTDependentString(const char_type* aStart,
+ const char_type* aEnd)
+ : string_type(const_cast<char_type*>(aStart), aEnd - aStart,
+ DataFlags::TERMINATED, ClassFlags(0)) {
+ MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!");
+ this->AssertValidDependentString();
+}
+
+template <typename T>
+void nsTDependentString<T>::Rebind(const string_type& str,
+ index_type startPos) {
+ MOZ_ASSERT(str.GetDataFlags() & DataFlags::TERMINATED,
+ "Unterminated flat string");
+
+ // If we currently own a buffer, release it.
+ this->Finalize();
+
+ size_type strLength = str.Length();
+
+ if (startPos > strLength) {
+ startPos = strLength;
+ }
+
+ char_type* newData =
+ const_cast<char_type*>(static_cast<const char_type*>(str.Data())) +
+ startPos;
+ size_type newLen = strLength - startPos;
+ DataFlags newDataFlags =
+ str.GetDataFlags() & (DataFlags::TERMINATED | DataFlags::LITERAL);
+ this->SetData(newData, newLen, newDataFlags);
+}
+
+template <typename T>
+void nsTDependentString<T>::Rebind(const char_type* aStart,
+ const char_type* aEnd) {
+ MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!");
+ this->Rebind(aStart, aEnd - aStart);
+}
+
+template class nsTDependentString<char>;
+template class nsTDependentString<char16_t>;
diff --git a/xpcom/string/nsTDependentString.h b/xpcom/string/nsTDependentString.h
new file mode 100644
index 0000000000..c7194a677f
--- /dev/null
+++ b/xpcom/string/nsTDependentString.h
@@ -0,0 +1,126 @@
+/* -*- 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/. */
+
+#ifndef nsTDependentString_h
+#define nsTDependentString_h
+
+#include "nsTString.h"
+
+/**
+ * nsTDependentString
+ *
+ * Stores a null-terminated, immutable sequence of characters.
+ *
+ * Subclass of nsTString that restricts string value to an immutable
+ * character sequence. This class does not own its data, so the creator
+ * of objects of this type must take care to ensure that a
+ * nsTDependentString continues to reference valid memory for the
+ * duration of its use.
+ */
+template <typename T>
+class nsTDependentString : public nsTString<T> {
+ public:
+ typedef nsTDependentString<T> self_type;
+ typedef nsTString<T> base_string_type;
+ typedef typename base_string_type::string_type string_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;
+
+ public:
+ /**
+ * constructors
+ */
+
+ nsTDependentString(const char_type* aStart, const char_type* aEnd);
+
+ nsTDependentString(const char_type* aData, size_type aLength)
+ : string_type(const_cast<char_type*>(aData), aLength,
+ DataFlags::TERMINATED, ClassFlags(0)) {
+ this->AssertValidDependentString();
+ }
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ nsTDependentString(char16ptr_t aData, size_type aLength)
+ : nsTDependentString(static_cast<const char16_t*>(aData), aLength) {}
+#endif
+
+ explicit nsTDependentString(const char_type* aData)
+ : string_type(const_cast<char_type*>(aData), char_traits::length(aData),
+ DataFlags::TERMINATED, ClassFlags(0)) {
+ string_type::AssertValidDependentString();
+ }
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ explicit nsTDependentString(char16ptr_t aData)
+ : nsTDependentString(static_cast<const char16_t*>(aData)) {}
+#endif
+
+ nsTDependentString(const string_type& aStr, index_type aStartPos)
+ : string_type() {
+ Rebind(aStr, aStartPos);
+ }
+
+ // Create a nsTDependentSubstring to be bound later
+ nsTDependentString() : string_type() {}
+
+ // auto-generated destructor OK
+
+ nsTDependentString(self_type&& aStr) : string_type() {
+ Rebind(aStr, /* aStartPos = */ 0);
+ aStr.SetToEmptyBuffer();
+ }
+
+ explicit nsTDependentString(const self_type& aStr) : string_type() {
+ Rebind(aStr, /* aStartPos = */ 0);
+ }
+
+ /**
+ * allow this class to be bound to a different string...
+ */
+
+ using nsTString<T>::Rebind;
+ void Rebind(const char_type* aData) {
+ Rebind(aData, char_traits::length(aData));
+ }
+
+ void Rebind(const char_type* aStart, const char_type* aEnd);
+ void Rebind(const string_type&, index_type aStartPos);
+
+ private:
+ // NOT USED
+ nsTDependentString(const substring_tuple_type&) = delete;
+ self_type& operator=(const self_type& aStr) = delete;
+};
+
+extern template class nsTDependentString<char>;
+extern template class nsTDependentString<char16_t>;
+
+#endif
diff --git a/xpcom/string/nsTDependentSubstring.cpp b/xpcom/string/nsTDependentSubstring.cpp
new file mode 100644
index 0000000000..ba1620f98b
--- /dev/null
+++ b/xpcom/string/nsTDependentSubstring.cpp
@@ -0,0 +1,106 @@
+/* -*- 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/. */
+
+// FIXME: Due to an include cycle, we need to include `nsTSubstring` first.
+#include "nsTSubstring.h"
+#include "nsTDependentSubstring.h"
+
+template <typename T>
+void nsTDependentSubstring<T>::Rebind(const substring_type& str,
+ size_type startPos, size_type length) {
+ // If we currently own a buffer, release it.
+ this->Finalize();
+
+ size_type strLength = str.Length();
+
+ if (startPos > strLength) {
+ startPos = strLength;
+ }
+
+ char_type* newData =
+ const_cast<char_type*>(static_cast<const char_type*>(str.Data())) +
+ startPos;
+ size_type newLength = XPCOM_MIN(length, strLength - startPos);
+ DataFlags newDataFlags = DataFlags(0);
+ this->SetData(newData, newLength, newDataFlags);
+}
+
+template <typename T>
+void nsTDependentSubstring<T>::Rebind(const char_type* data, size_type length) {
+ NS_ASSERTION(data, "nsTDependentSubstring must wrap a non-NULL buffer");
+
+ // If we currently own a buffer, release it.
+ this->Finalize();
+
+ char_type* newData =
+ const_cast<char_type*>(static_cast<const char_type*>(data));
+ size_type newLength = length;
+ DataFlags newDataFlags = DataFlags(0);
+ this->SetData(newData, newLength, newDataFlags);
+}
+
+template <typename T>
+void nsTDependentSubstring<T>::Rebind(const char_type* aStart,
+ const char_type* aEnd) {
+ MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!");
+ this->Rebind(aStart, size_type(aEnd - aStart));
+}
+
+template <typename T>
+nsTDependentSubstring<T>::nsTDependentSubstring(const char_type* aStart,
+ const char_type* aEnd)
+ : substring_type(const_cast<char_type*>(aStart), aEnd - aStart,
+ DataFlags(0), ClassFlags(0)) {
+ MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!");
+}
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+template <typename T>
+template <typename Q, typename EnableIfChar16>
+nsTDependentSubstring<T>::nsTDependentSubstring(char16ptr_t aStart,
+ char16ptr_t aEnd)
+ : substring_type(static_cast<const char16_t*>(aStart),
+ static_cast<const char16_t*>(aEnd)) {
+ MOZ_RELEASE_ASSERT(static_cast<const char16_t*>(aStart) <=
+ static_cast<const char16_t*>(aEnd),
+ "Overflow!");
+}
+#endif
+
+template <typename T>
+nsTDependentSubstring<T>::nsTDependentSubstring(const const_iterator& aStart,
+ const const_iterator& aEnd)
+ : substring_type(const_cast<char_type*>(aStart.get()),
+ aEnd.get() - aStart.get(), DataFlags(0), ClassFlags(0)) {
+ MOZ_RELEASE_ASSERT(aStart.get() <= aEnd.get(), "Overflow!");
+}
+
+template <typename T>
+const nsTDependentSubstring<T> Substring(const T* aStart, const T* aEnd) {
+ MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!");
+ return nsTDependentSubstring<T>(aStart, aEnd);
+}
+
+template nsTDependentSubstring<char> const Substring<char>(char const*,
+ char const*);
+template nsTDependentSubstring<char16_t> const Substring<char16_t>(
+ char16_t const*, char16_t const*);
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+const nsTDependentSubstring<char16_t> Substring(char16ptr_t aData,
+ size_t aLength) {
+ return nsTDependentSubstring<char16_t>(aData, aLength);
+}
+
+const nsTDependentSubstring<char16_t> Substring(char16ptr_t aStart,
+ char16ptr_t aEnd) {
+ return Substring(static_cast<const char16_t*>(aStart),
+ static_cast<const char16_t*>(aEnd));
+}
+#endif
+
+template class nsTDependentSubstring<char>;
+template class nsTDependentSubstring<char16_t>;
diff --git a/xpcom/string/nsTDependentSubstring.h b/xpcom/string/nsTDependentSubstring.h
new file mode 100644
index 0000000000..b5198ff2b5
--- /dev/null
+++ b/xpcom/string/nsTDependentSubstring.h
@@ -0,0 +1,162 @@
+/* -*- 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 nsTDependentSubstring_h
+#define nsTDependentSubstring_h
+
+#include "nsTSubstring.h"
+#include "nsTLiteralString.h"
+#include "mozilla/Span.h"
+
+/**
+ * nsTDependentSubstring_CharT
+ *
+ * A string class which wraps an external array of string characters. It
+ * is the client code's responsibility to ensure that the external buffer
+ * remains valid for a long as the string is alive.
+ *
+ * NAMES:
+ * nsDependentSubstring for wide characters
+ * nsDependentCSubstring for narrow characters
+ */
+template <typename T>
+class nsTDependentSubstring : public nsTSubstring<T> {
+ public:
+ typedef nsTDependentSubstring<T> self_type;
+ typedef nsTSubstring<T> substring_type;
+ typedef typename substring_type::fallible_t fallible_t;
+
+ typedef typename substring_type::char_type char_type;
+ typedef typename substring_type::char_traits char_traits;
+ typedef
+ typename substring_type::incompatible_char_type incompatible_char_type;
+
+ typedef typename substring_type::substring_tuple_type substring_tuple_type;
+
+ typedef typename substring_type::const_iterator const_iterator;
+ typedef typename substring_type::iterator iterator;
+
+ typedef typename substring_type::comparator_type comparator_type;
+
+ typedef typename substring_type::const_char_iterator const_char_iterator;
+
+ typedef typename substring_type::string_view string_view;
+
+ typedef typename substring_type::index_type index_type;
+ typedef typename substring_type::size_type size_type;
+
+ // These are only for internal use within the string classes:
+ typedef typename substring_type::DataFlags DataFlags;
+ typedef typename substring_type::ClassFlags ClassFlags;
+
+ public:
+ void Rebind(const substring_type&, size_type aStartPos,
+ size_type aLength = size_type(-1));
+
+ void Rebind(const char_type* aData, size_type aLength);
+
+ void Rebind(const char_type* aStart, const char_type* aEnd);
+
+ nsTDependentSubstring(const substring_type& aStr, size_type aStartPos,
+ size_type aLength = size_type(-1))
+ : substring_type() {
+ Rebind(aStr, aStartPos, aLength);
+ }
+
+ nsTDependentSubstring(const char_type* aData, size_type aLength)
+ : substring_type(const_cast<char_type*>(aData), aLength, DataFlags(0),
+ ClassFlags(0)) {}
+
+ explicit nsTDependentSubstring(mozilla::Span<const char_type> aData)
+ : nsTDependentSubstring(aData.Elements(), aData.Length()) {}
+
+ nsTDependentSubstring(const char_type* aStart, const char_type* aEnd);
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ nsTDependentSubstring(char16ptr_t aData, size_type aLength)
+ : nsTDependentSubstring(static_cast<const char16_t*>(aData), aLength) {}
+
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ nsTDependentSubstring(char16ptr_t aStart, char16ptr_t aEnd);
+#endif
+
+ nsTDependentSubstring(const const_iterator& aStart,
+ const const_iterator& aEnd);
+
+ // Create a nsTDependentSubstring to be bound later
+ nsTDependentSubstring() : substring_type() {}
+
+ // auto-generated copy-constructor OK (XXX really?? what about base class
+ // copy-ctor?)
+ nsTDependentSubstring(const nsTDependentSubstring&) = default;
+
+ private:
+ // NOT USED
+ void operator=(const self_type&) =
+ delete; // we're immutable, you can't assign into a substring
+};
+
+extern template class nsTDependentSubstring<char>;
+extern template class nsTDependentSubstring<char16_t>;
+
+template <typename T>
+inline const nsTDependentSubstring<T> Substring(const nsTSubstring<T>& aStr,
+ size_t aStartPos,
+ size_t aLength = size_t(-1)) {
+ return nsTDependentSubstring<T>(aStr, aStartPos, aLength);
+}
+
+template <typename T>
+inline const nsTDependentSubstring<T> Substring(const nsTLiteralString<T>& aStr,
+ size_t aStartPos,
+ size_t aLength = size_t(-1)) {
+ return nsTDependentSubstring<T>(aStr, aStartPos, aLength);
+}
+
+template <typename T>
+inline const nsTDependentSubstring<T> Substring(
+ const nsReadingIterator<T>& aStart, const nsReadingIterator<T>& aEnd) {
+ return nsTDependentSubstring<T>(aStart.get(), aEnd.get());
+}
+
+template <typename T>
+inline const nsTDependentSubstring<T> Substring(const T* aData,
+ size_t aLength) {
+ return nsTDependentSubstring<T>(aData, aLength);
+}
+
+template <typename T>
+const nsTDependentSubstring<T> Substring(const T* aStart, const T* aEnd);
+
+extern template const nsTDependentSubstring<char> Substring(const char* aStart,
+ const char* aEnd);
+
+extern template const nsTDependentSubstring<char16_t> Substring(
+ const char16_t* aStart, const char16_t* aEnd);
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+inline const nsTDependentSubstring<char16_t> Substring(char16ptr_t aData,
+ size_t aLength);
+
+const nsTDependentSubstring<char16_t> Substring(char16ptr_t aStart,
+ char16ptr_t aEnd);
+#endif
+
+template <typename T>
+inline const nsTDependentSubstring<T> StringHead(const nsTSubstring<T>& aStr,
+ size_t aCount) {
+ return nsTDependentSubstring<T>(aStr, 0, aCount);
+}
+
+template <typename T>
+inline const nsTDependentSubstring<T> StringTail(const nsTSubstring<T>& aStr,
+ size_t aCount) {
+ return nsTDependentSubstring<T>(aStr, aStr.Length() - aCount, aCount);
+}
+
+#endif
diff --git a/xpcom/string/nsTLiteralString.cpp b/xpcom/string/nsTLiteralString.cpp
new file mode 100644
index 0000000000..79454f7783
--- /dev/null
+++ b/xpcom/string/nsTLiteralString.cpp
@@ -0,0 +1,10 @@
+/* -*- 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/. */
+
+#include "nsTLiteralString.h"
+
+template class nsTLiteralString<char>;
+template class nsTLiteralString<char16_t>;
diff --git a/xpcom/string/nsTLiteralString.h b/xpcom/string/nsTLiteralString.h
new file mode 100644
index 0000000000..d7c9213a5c
--- /dev/null
+++ b/xpcom/string/nsTLiteralString.h
@@ -0,0 +1,111 @@
+/* -*- 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/. */
+
+#ifndef nsTLiteralString_h
+#define nsTLiteralString_h
+
+#include "nsTStringRepr.h"
+
+/**
+ * nsTLiteralString_CharT
+ *
+ * Stores a null-terminated, immutable sequence of characters.
+ *
+ * nsTString-lookalike that restricts its string value to a literal character
+ * sequence. Can be implicitly cast to const nsTString& (the const is
+ * essential, since this class's data are not writable). The data are assumed
+ * to be static (permanent) and therefore, as an optimization, this class
+ * does not have a destructor.
+ */
+template <typename T>
+class nsTLiteralString : public mozilla::detail::nsTStringRepr<T> {
+ public:
+ typedef nsTLiteralString<T> self_type;
+
+#ifdef __clang__
+ // bindgen w/ clang 3.9 at least chokes on a typedef, but using is okay.
+ using typename mozilla::detail::nsTStringRepr<T>::base_string_type;
+#else
+ // On the other hand msvc chokes on the using statement. It seems others
+ // don't care either way so we lump them in here.
+ typedef typename mozilla::detail::nsTStringRepr<T>::base_string_type
+ base_string_type;
+#endif
+
+ typedef typename base_string_type::char_type char_type;
+ typedef typename base_string_type::size_type size_type;
+ typedef typename base_string_type::DataFlags DataFlags;
+ typedef typename base_string_type::ClassFlags ClassFlags;
+
+ public:
+ /**
+ * constructor
+ */
+
+ template <size_type N>
+ explicit constexpr nsTLiteralString(const char_type (&aStr)[N])
+ : nsTLiteralString(aStr, N - 1) {}
+
+ /**
+ * For compatibility with existing code that requires const ns[C]String*.
+ * Use sparingly. If possible, rewrite code to use const ns[C]String&
+ * and the implicit cast will just work.
+ */
+ MOZ_LIFETIME_BOUND const nsTString<T>& AsString() const {
+ return *reinterpret_cast<const nsTString<T>*>(this);
+ }
+
+ MOZ_LIFETIME_BOUND operator const nsTString<T>&() const { return AsString(); }
+
+ template <typename N, typename Dummy>
+ struct raw_type {
+ typedef N* type;
+ };
+
+#ifdef MOZ_USE_CHAR16_WRAPPER
+ template <typename Dummy>
+ struct raw_type<char16_t, Dummy> {
+ typedef char16ptr_t type;
+ };
+#endif
+
+ /**
+ * Prohibit get() on temporaries as in "x"_ns.get().
+ * These should be written as just "x", using a string literal directly.
+ */
+ const typename raw_type<T, int>::type get() const&& = delete;
+ const typename raw_type<T, int>::type get() const& { return this->mData; }
+
+// At least older gcc versions do not accept these friend declarations,
+// complaining about an "invalid argument list" here, but not where the actual
+// operators are defined or used. We make the supposed-to-be-private constructor
+// public when building with gcc, relying on the default clang builds to fail if
+// any non-private use of that constructor would get into the codebase.
+#if defined(__clang__)
+ private:
+ friend constexpr auto operator"" _ns(const char* aStr, std::size_t aLen);
+ friend constexpr auto operator"" _ns(const char16_t* aStr, std::size_t aLen);
+#else
+ public:
+#endif
+ // Only for use by operator""
+ constexpr nsTLiteralString(const char_type* aStr, size_t aLen)
+ : base_string_type(const_cast<char_type*>(aStr), aLen,
+ DataFlags::TERMINATED | DataFlags::LITERAL,
+ ClassFlags::NULL_TERMINATED) {}
+
+ public:
+ // NOT TO BE IMPLEMENTED
+ template <size_type N>
+ nsTLiteralString(char_type (&aStr)[N]) = delete;
+
+ self_type& operator=(const self_type&) = delete;
+};
+
+extern template class nsTLiteralString<char>;
+extern template class nsTLiteralString<char16_t>;
+
+#endif
diff --git a/xpcom/string/nsTPromiseFlatString.cpp b/xpcom/string/nsTPromiseFlatString.cpp
new file mode 100644
index 0000000000..1243300033
--- /dev/null
+++ b/xpcom/string/nsTPromiseFlatString.cpp
@@ -0,0 +1,26 @@
+/* -*- 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/. */
+
+#include "nsTPromiseFlatString.h"
+
+template <typename T>
+void nsTPromiseFlatString<T>::Init(const substring_type& str) {
+ if (str.IsTerminated()) {
+ char_type* newData =
+ const_cast<char_type*>(static_cast<const char_type*>(str.Data()));
+ size_type newLength = str.Length();
+ DataFlags newDataFlags =
+ str.GetDataFlags() & (DataFlags::TERMINATED | DataFlags::LITERAL);
+ // does not promote DataFlags::VOIDED
+
+ this->SetData(newData, newLength, newDataFlags);
+ } else {
+ this->Assign(str);
+ }
+}
+
+template class nsTPromiseFlatString<char>;
+template class nsTPromiseFlatString<char16_t>;
diff --git a/xpcom/string/nsTPromiseFlatString.h b/xpcom/string/nsTPromiseFlatString.h
new file mode 100644
index 0000000000..126362ec9c
--- /dev/null
+++ b/xpcom/string/nsTPromiseFlatString.h
@@ -0,0 +1,136 @@
+/* -*- 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/. */
+
+#ifndef nsTPromiseFlatString_h
+#define nsTPromiseFlatString_h
+
+#include "nsTString.h"
+
+/**
+ * NOTE:
+ *
+ * Try to avoid flat strings. |PromiseFlat[C]String| will help you as a last
+ * resort, and this may be necessary when dealing with legacy or OS calls,
+ * but in general, requiring a null-terminated array of characters kills many
+ * of the performance wins the string classes offer. Write your own code to
+ * use |nsA[C]String&|s for parameters. Write your string proccessing
+ * algorithms to exploit iterators. If you do this, you will benefit from
+ * being able to chain operations without copying or allocating and your code
+ * will be significantly more efficient. Remember, a function that takes an
+ * |const nsA[C]String&| can always be passed a raw character pointer by
+ * wrapping it (for free) in a |nsDependent[C]String|. But a function that
+ * takes a character pointer always has the potential to force allocation and
+ * copying.
+ *
+ *
+ * How to use it:
+ *
+ * A |nsPromiseFlat[C]String| doesn't necessarily own the characters it
+ * promises. You must never use it to promise characters out of a string
+ * with a shorter lifespan. The typical use will be something like this:
+ *
+ * SomeOSFunction( PromiseFlatCString(aCSubstring).get() ); // GOOD
+ *
+ * Here's a BAD use:
+ *
+ * const char* buffer = PromiseFlatCString(aCSubstring).get();
+ * SomeOSFunction(buffer); // BAD!! |buffer| is a dangling pointer
+ *
+ * The only way to make one is with the function |PromiseFlat[C]String|,
+ * which produce a |const| instance. ``What if I need to keep a promise
+ * around for a little while?'' you might ask. In that case, you can keep a
+ * reference, like so:
+ *
+ * const nsCString& flat = PromiseFlatString(aCSubstring);
+ * // Temporaries usually die after the full expression containing the
+ * // expression that created the temporary is evaluated. But when a
+ * // temporary is assigned to a local reference, the temporary's lifetime
+ * // is extended to the reference's lifetime (C++11 [class.temporary]p5).
+ * //
+ * // This reference holds the anonymous temporary alive. But remember: it
+ * // must _still_ have a lifetime shorter than that of |aCSubstring|, and
+ * // |aCSubstring| must not be changed while the PromiseFlatString lives.
+ *
+ * SomeOSFunction(flat.get());
+ * SomeOtherOSFunction(flat.get());
+ *
+ *
+ * How does it work?
+ *
+ * A |nsPromiseFlat[C]String| is just a wrapper for another string. If you
+ * apply it to a string that happens to be flat, your promise is just a
+ * dependent reference to the string's data. If you apply it to a non-flat
+ * string, then a temporary flat string is created for you, by allocating and
+ * copying. In the event that you end up assigning the result into a sharing
+ * string (e.g., |nsTString|), the right thing happens.
+ */
+
+template <typename T>
+class nsTPromiseFlatString : public nsTString<T> {
+ public:
+ typedef nsTPromiseFlatString<T> self_type;
+ typedef nsTString<T> base_string_type;
+ typedef typename base_string_type::substring_type substring_type;
+ typedef typename base_string_type::string_type string_type;
+ typedef typename base_string_type::substring_tuple_type substring_tuple_type;
+ typedef typename base_string_type::char_type char_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;
+
+ private:
+ void Init(const substring_type&);
+
+ // NOT TO BE IMPLEMENTED
+ void operator=(const self_type&) = delete;
+
+ // NOT TO BE IMPLEMENTED
+ nsTPromiseFlatString() = delete;
+
+ // NOT TO BE IMPLEMENTED
+ nsTPromiseFlatString(const string_type& aStr) = delete;
+
+ public:
+ explicit nsTPromiseFlatString(const substring_type& aStr) : string_type() {
+ Init(aStr);
+ }
+
+ explicit nsTPromiseFlatString(const substring_tuple_type& aTuple)
+ : string_type() {
+ // nothing else to do here except assign the value of the tuple
+ // into ourselves.
+ this->Assign(aTuple);
+ }
+};
+
+extern template class nsTPromiseFlatString<char>;
+extern template class nsTPromiseFlatString<char16_t>;
+
+// We template this so that the constructor is chosen based on the type of the
+// parameter. This allows us to reject attempts to promise a flat flat string.
+template <class T>
+const nsTPromiseFlatString<T> TPromiseFlatString(
+ const typename nsTPromiseFlatString<T>::substring_type& aString) {
+ return nsTPromiseFlatString<T>(aString);
+}
+
+template <class T>
+const nsTPromiseFlatString<T> TPromiseFlatString(
+ const typename nsTPromiseFlatString<T>::substring_tuple_type& aString) {
+ return nsTPromiseFlatString<T>(aString);
+}
+
+#ifndef PromiseFlatCString
+# define PromiseFlatCString TPromiseFlatString<char>
+#endif
+
+#ifndef PromiseFlatString
+# define PromiseFlatString TPromiseFlatString<char16_t>
+#endif
+
+#endif
diff --git a/xpcom/string/nsTString.cpp b/xpcom/string/nsTString.cpp
new file mode 100644
index 0000000000..4e845f62df
--- /dev/null
+++ b/xpcom/string/nsTString.cpp
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+#include "nsTString.h"
+#include "nsString.h"
+#include "prdtoa.h"
+
+/**
+ * nsTString::SetCharAt
+ */
+
+template <typename T>
+bool nsTString<T>::SetCharAt(char16_t aChar, index_type aIndex) {
+ if (aIndex >= this->mLength) {
+ return false;
+ }
+
+ if (!this->EnsureMutable()) {
+ this->AllocFailed(this->mLength);
+ }
+
+ this->mData[aIndex] = char_type(aChar);
+ return true;
+}
+
+template <typename T>
+void nsTString<T>::Rebind(const char_type* data, size_type length) {
+ // If we currently own a buffer, release it.
+ this->Finalize();
+
+ this->SetData(const_cast<char_type*>(data), length, DataFlags::TERMINATED);
+ this->AssertValidDependentString();
+}
+
+template class nsTString<char>;
+template class nsTString<char16_t>;
+
+template class nsTAutoStringN<char, 64>;
+template class nsTAutoStringN<char16_t, 64>;
diff --git a/xpcom/string/nsTString.h b/xpcom/string/nsTString.h
new file mode 100644
index 0000000000..3f7e6435fc
--- /dev/null
+++ b/xpcom/string/nsTString.h
@@ -0,0 +1,448 @@
+/* -*- 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 nsTString_h
+#define nsTString_h
+
+#include "nsTSubstring.h"
+
+/**
+ * This is the canonical null-terminated string class. All subclasses
+ * promise null-terminated storage. Instances of this class allocate
+ * strings on the heap.
+ *
+ * NAMES:
+ * nsString for wide characters
+ * nsCString for narrow characters
+ *
+ * This class is also known as nsAFlat[C]String, where "flat" is used
+ * to denote a null-terminated string.
+ */
+template <typename T>
+class nsTString : public nsTSubstring<T> {
+ public:
+ typedef nsTString<T> self_type;
+
+ using repr_type = mozilla::detail::nsTStringRepr<T>;
+
+#ifdef __clang__
+ // bindgen w/ clang 3.9 at least chokes on a typedef, but using is okay.
+ using typename nsTSubstring<T>::substring_type;
+#else
+ // On the other hand msvc chokes on the using statement. It seems others
+ // don't care either way so we lump them in here.
+ typedef typename nsTSubstring<T>::substring_type substring_type;
+#endif
+
+ typedef typename substring_type::fallible_t fallible_t;
+
+ typedef typename substring_type::char_type char_type;
+ typedef typename substring_type::char_traits char_traits;
+ typedef
+ typename substring_type::incompatible_char_type incompatible_char_type;
+
+ typedef typename substring_type::substring_tuple_type substring_tuple_type;
+
+ typedef typename substring_type::const_iterator const_iterator;
+ typedef typename substring_type::iterator iterator;
+
+ typedef typename substring_type::comparator_type comparator_type;
+
+ typedef typename substring_type::const_char_iterator const_char_iterator;
+
+ typedef typename substring_type::string_view string_view;
+
+ typedef typename substring_type::index_type index_type;
+ typedef typename substring_type::size_type size_type;
+
+ // These are only for internal use within the string classes:
+ typedef typename substring_type::DataFlags DataFlags;
+ typedef typename substring_type::ClassFlags ClassFlags;
+
+ public:
+ /**
+ * constructors
+ */
+
+ nsTString() : substring_type(ClassFlags::NULL_TERMINATED) {}
+
+ explicit nsTString(const char_type* aData, size_type aLength = size_type(-1))
+ : substring_type(ClassFlags::NULL_TERMINATED) {
+ this->Assign(aData, aLength);
+ }
+
+ explicit nsTString(mozilla::Span<const char_type> aData)
+ : nsTString(aData.Elements(), aData.Length()) {}
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ explicit nsTString(char16ptr_t aStr, size_type aLength = size_type(-1))
+ : substring_type(ClassFlags::NULL_TERMINATED) {
+ this->Assign(static_cast<const char16_t*>(aStr), aLength);
+ }
+#endif
+
+ nsTString(const self_type& aStr)
+ : substring_type(ClassFlags::NULL_TERMINATED) {
+ this->Assign(aStr);
+ }
+
+ nsTString(self_type&& aStr) : substring_type(ClassFlags::NULL_TERMINATED) {
+ this->Assign(std::move(aStr));
+ }
+
+ MOZ_IMPLICIT nsTString(const substring_tuple_type& aTuple)
+ : substring_type(ClassFlags::NULL_TERMINATED) {
+ this->Assign(aTuple);
+ }
+
+ explicit nsTString(const substring_type& aReadable)
+ : substring_type(ClassFlags::NULL_TERMINATED) {
+ this->Assign(aReadable);
+ }
+
+ explicit nsTString(substring_type&& aReadable)
+ : substring_type(ClassFlags::NULL_TERMINATED) {
+ this->Assign(std::move(aReadable));
+ }
+
+ // |operator=| does not inherit, so we must define our own
+ self_type& operator=(char_type aChar) {
+ this->Assign(aChar);
+ return *this;
+ }
+ self_type& operator=(const char_type* aData) {
+ this->Assign(aData);
+ return *this;
+ }
+ self_type& operator=(const self_type& aStr) {
+ this->Assign(aStr);
+ return *this;
+ }
+ self_type& operator=(self_type&& aStr) {
+ this->Assign(std::move(aStr));
+ return *this;
+ }
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ self_type& operator=(const char16ptr_t aStr) {
+ this->Assign(static_cast<const char16_t*>(aStr));
+ return *this;
+ }
+#endif
+ self_type& operator=(const substring_type& aStr) {
+ this->Assign(aStr);
+ return *this;
+ }
+ self_type& operator=(substring_type&& aStr) {
+ this->Assign(std::move(aStr));
+ return *this;
+ }
+ self_type& operator=(const substring_tuple_type& aTuple) {
+ this->Assign(aTuple);
+ return *this;
+ }
+
+ /**
+ * returns the null-terminated string
+ */
+
+ template <typename U, typename Dummy>
+ struct raw_type {
+ typedef const U* type;
+ };
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Dummy>
+ struct raw_type<char16_t, Dummy> {
+ typedef char16ptr_t type;
+ };
+#endif
+
+ MOZ_NO_DANGLING_ON_TEMPORARIES typename raw_type<T, int>::type get() const {
+ return this->mData;
+ }
+
+ /**
+ * returns character at specified index.
+ *
+ * NOTE: unlike nsTSubstring::CharAt, this function allows you to index
+ * the null terminator character.
+ */
+
+ char_type CharAt(index_type aIndex) const {
+ MOZ_ASSERT(aIndex <= this->Length(), "index exceeds allowable range");
+ return this->mData[aIndex];
+ }
+
+ char_type operator[](index_type aIndex) const { return CharAt(aIndex); }
+
+ /**
+ * Set a char inside this string at given index
+ *
+ * @param aChar is the char you want to write into this string
+ * @param anIndex is the ofs where you want to write the given char
+ * @return TRUE if successful
+ */
+ bool SetCharAt(char16_t aChar, index_type aIndex);
+
+ /**
+ * Allow this string to be bound to a character buffer
+ * until the string is rebound or mutated; the caller
+ * must ensure that the buffer outlives the string.
+ */
+ void Rebind(const char_type* aData, size_type aLength);
+
+ /**
+ * verify restrictions for dependent strings
+ */
+ void AssertValidDependentString() {
+ MOZ_ASSERT(this->mData, "nsTDependentString must wrap a non-NULL buffer");
+ MOZ_DIAGNOSTIC_ASSERT(this->mData[substring_type::mLength] == 0,
+ "nsTDependentString must wrap only null-terminated "
+ "strings. You are probably looking for "
+ "nsTDependentSubstring.");
+ }
+
+ protected:
+ // allow subclasses to initialize fields directly
+ nsTString(char_type* aData, size_type aLength, DataFlags aDataFlags,
+ ClassFlags aClassFlags)
+ : substring_type(aData, aLength, aDataFlags,
+ aClassFlags | ClassFlags::NULL_TERMINATED) {}
+
+ friend const nsTString<char>& VoidCString();
+ friend const nsTString<char16_t>& VoidString();
+
+ // Used by Null[C]String.
+ explicit nsTString(DataFlags aDataFlags)
+ : substring_type(char_traits::sEmptyBuffer, 0,
+ aDataFlags | DataFlags::TERMINATED,
+ ClassFlags::NULL_TERMINATED) {}
+};
+
+extern template class nsTString<char>;
+extern template class nsTString<char16_t>;
+
+/**
+ * nsTAutoStringN
+ *
+ * Subclass of nsTString that adds support for stack-based string
+ * allocation. It is normally not a good idea to use this class on the
+ * heap, because it will allocate space which may be wasted if the string
+ * it contains is significantly smaller or any larger than 64 characters.
+ *
+ * NAMES:
+ * nsAutoStringN / nsTAutoString for wide characters
+ * nsAutoCStringN / nsTAutoCString for narrow characters
+ */
+template <typename T, size_t N>
+class MOZ_NON_MEMMOVABLE nsTAutoStringN : public nsTString<T> {
+ public:
+ typedef nsTAutoStringN<T, N> self_type;
+
+ typedef nsTString<T> base_string_type;
+ typedef typename base_string_type::string_type string_type;
+ typedef typename base_string_type::char_type char_type;
+ typedef typename base_string_type::char_traits char_traits;
+ typedef typename base_string_type::substring_type substring_type;
+ typedef typename base_string_type::size_type size_type;
+ typedef typename base_string_type::substring_tuple_type substring_tuple_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;
+
+ public:
+ /**
+ * constructors
+ */
+
+ nsTAutoStringN()
+ : string_type(mStorage, 0, DataFlags::TERMINATED | DataFlags::INLINE,
+ ClassFlags::INLINE),
+ mInlineCapacity(N - 1) {
+ // null-terminate
+ mStorage[0] = char_type(0);
+ }
+
+ explicit nsTAutoStringN(char_type aChar) : self_type() {
+ this->Assign(aChar);
+ }
+
+ explicit nsTAutoStringN(const char_type* aData,
+ size_type aLength = size_type(-1))
+ : self_type() {
+ this->Assign(aData, aLength);
+ }
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ explicit nsTAutoStringN(char16ptr_t aData, size_type aLength = size_type(-1))
+ : self_type(static_cast<const char16_t*>(aData), aLength) {}
+#endif
+
+ nsTAutoStringN(const self_type& aStr) : self_type() { this->Assign(aStr); }
+
+ nsTAutoStringN(self_type&& aStr) : self_type() {
+ this->Assign(std::move(aStr));
+ }
+
+ explicit nsTAutoStringN(const substring_type& aStr) : self_type() {
+ this->Assign(aStr);
+ }
+
+ explicit nsTAutoStringN(substring_type&& aStr) : self_type() {
+ this->Assign(std::move(aStr));
+ }
+
+ MOZ_IMPLICIT nsTAutoStringN(const substring_tuple_type& aTuple)
+ : self_type() {
+ this->Assign(aTuple);
+ }
+
+ // |operator=| does not inherit, so we must define our own
+ self_type& operator=(char_type aChar) {
+ this->Assign(aChar);
+ return *this;
+ }
+ self_type& operator=(const char_type* aData) {
+ this->Assign(aData);
+ return *this;
+ }
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ self_type& operator=(char16ptr_t aStr) {
+ this->Assign(aStr);
+ return *this;
+ }
+#endif
+ self_type& operator=(const self_type& aStr) {
+ this->Assign(aStr);
+ return *this;
+ }
+ self_type& operator=(self_type&& aStr) {
+ this->Assign(std::move(aStr));
+ return *this;
+ }
+ self_type& operator=(const substring_type& aStr) {
+ this->Assign(aStr);
+ return *this;
+ }
+ self_type& operator=(substring_type&& aStr) {
+ this->Assign(std::move(aStr));
+ return *this;
+ }
+ self_type& operator=(const substring_tuple_type& aTuple) {
+ this->Assign(aTuple);
+ return *this;
+ }
+
+ static const size_t kStorageSize = N;
+
+ protected:
+ friend class nsTSubstring<T>;
+
+ const LengthStorage mInlineCapacity;
+
+ private:
+ char_type mStorage[N];
+};
+
+// Externs for the most common nsTAutoStringN variations.
+extern template class nsTAutoStringN<char, 64>;
+extern template class nsTAutoStringN<char16_t, 64>;
+
+//
+// nsAutoString stores pointers into itself which are invalidated when an
+// nsTArray is resized, so nsTArray must not be instantiated with nsAutoString
+// elements!
+//
+template <class E>
+class nsTArrayElementTraits;
+template <typename T>
+class nsTArrayElementTraits<nsTAutoString<T>> {
+ public:
+ template <class A>
+ struct Dont_Instantiate_nsTArray_of;
+ template <class A>
+ struct Instead_Use_nsTArray_of;
+
+ static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Construct(
+ Instead_Use_nsTArray_of<nsTString<T>>* aE) {
+ return 0;
+ }
+ template <class A>
+ static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Construct(
+ Instead_Use_nsTArray_of<nsTString<T>>* aE, const A& aArg) {
+ return 0;
+ }
+ template <class... Args>
+ static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Construct(
+ Instead_Use_nsTArray_of<nsTString<T>>* aE, Args&&... aArgs) {
+ return 0;
+ }
+ static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Destruct(
+ Instead_Use_nsTArray_of<nsTString<T>>* aE) {
+ return 0;
+ }
+};
+
+/**
+ * getter_Copies support for adopting raw string out params that are
+ * heap-allocated, e.g.:
+ *
+ * char* gStr;
+ * void GetBlah(char** aStr)
+ * {
+ * *aStr = strdup(gStr);
+ * }
+ *
+ * // This works, but is clumsy.
+ * void Inelegant()
+ * {
+ * char* buf;
+ * GetBlah(&buf);
+ * nsCString str;
+ * str.Adopt(buf);
+ * // ...
+ * }
+ *
+ * // This is nicer.
+ * void Elegant()
+ * {
+ * nsCString str;
+ * GetBlah(getter_Copies(str));
+ * // ...
+ * }
+ */
+template <typename T>
+class MOZ_STACK_CLASS nsTGetterCopies {
+ public:
+ typedef T char_type;
+
+ explicit nsTGetterCopies(nsTSubstring<T>& aStr)
+ : mString(aStr), mData(nullptr) {}
+
+ ~nsTGetterCopies() {
+ mString.Adopt(mData); // OK if mData is null
+ }
+
+ operator char_type**() { return &mData; }
+
+ private:
+ nsTSubstring<T>& mString;
+ char_type* mData;
+};
+
+// See the comment above nsTGetterCopies_CharT for how to use this.
+template <typename T>
+inline nsTGetterCopies<T> getter_Copies(nsTSubstring<T>& aString) {
+ return nsTGetterCopies<T>(aString);
+}
+
+#endif
diff --git a/xpcom/string/nsTStringComparator.cpp b/xpcom/string/nsTStringComparator.cpp
new file mode 100644
index 0000000000..801a3623b9
--- /dev/null
+++ b/xpcom/string/nsTStringComparator.cpp
@@ -0,0 +1,91 @@
+/* -*- 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/. */
+
+#include "nsString.h"
+#include "plstr.h"
+
+template <typename T>
+int NS_FASTCALL Compare(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs,
+ const nsTStringComparator<T> comp) {
+ typedef typename nsTSubstring<T>::size_type size_type;
+ typedef typename nsTSubstring<T>::const_iterator const_iterator;
+
+ if (&aLhs == &aRhs) {
+ return 0;
+ }
+
+ const_iterator leftIter, rightIter;
+ aLhs.BeginReading(leftIter);
+ aRhs.BeginReading(rightIter);
+
+ size_type lLength = aLhs.Length();
+ size_type rLength = aRhs.Length();
+ size_type lengthToCompare = XPCOM_MIN(lLength, rLength);
+
+ int result;
+ if ((result = comp(leftIter.get(), rightIter.get(), lengthToCompare,
+ lengthToCompare)) == 0) {
+ if (lLength < rLength) {
+ result = -1;
+ } else if (rLength < lLength) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ }
+
+ return result;
+}
+
+template int NS_FASTCALL Compare<char>(
+ mozilla::detail::nsTStringRepr<char> const&,
+ mozilla::detail::nsTStringRepr<char> const&, nsTStringComparator<char>);
+
+template int NS_FASTCALL
+Compare<char16_t>(mozilla::detail::nsTStringRepr<char16_t> const&,
+ mozilla::detail::nsTStringRepr<char16_t> const&,
+ nsTStringComparator<char16_t>);
+
+template <typename T>
+int nsTDefaultStringComparator(const T* aLhs, const T* aRhs, size_t aLLength,
+ size_t aRLength) {
+ return aLLength == aRLength ? nsCharTraits<T>::compare(aLhs, aRhs, aLLength)
+ : (aLLength > aRLength) ? 1
+ : -1;
+}
+
+template int nsTDefaultStringComparator(const char*, const char*, size_t,
+ size_t);
+template int nsTDefaultStringComparator(const char16_t*, const char16_t*,
+ size_t, size_t);
+
+int nsCaseInsensitiveCStringComparator(const char* aLhs, const char* aRhs,
+ size_t aLhsLength, size_t aRhsLength) {
+#if defined(LIBFUZZER) && defined(LINUX)
+ // Make sure libFuzzer can see this string compare by calling the POSIX
+ // native function which is intercepted. We also call this if the lengths
+ // don't match so libFuzzer can at least see a partial string, but we throw
+ // away the result afterwards again.
+ int32_t result =
+ int32_t(strncasecmp(aLhs, aRhs, std::min(aLhsLength, aRhsLength)));
+
+ if (aLhsLength != aRhsLength) {
+ return (aLhsLength > aRhsLength) ? 1 : -1;
+ }
+#else
+ if (aLhsLength != aRhsLength) {
+ return (aLhsLength > aRhsLength) ? 1 : -1;
+ }
+ int32_t result = int32_t(PL_strncasecmp(aLhs, aRhs, aLhsLength));
+#endif
+ // Egads. PL_strncasecmp is returning *very* negative numbers.
+ // Some folks expect -1,0,1, so let's temper its enthusiasm.
+ if (result < 0) {
+ result = -1;
+ }
+ return result;
+}
diff --git a/xpcom/string/nsTStringHasher.h b/xpcom/string/nsTStringHasher.h
new file mode 100644
index 0000000000..7b3f42ba58
--- /dev/null
+++ b/xpcom/string/nsTStringHasher.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef nsTStringHasher_h___
+#define nsTStringHasher_h___
+
+#include "mozilla/HashTable.h" // mozilla::{DefaultHasher, HashNumber, HashString}
+
+namespace mozilla {
+
+template <typename T>
+struct DefaultHasher<nsTString<T>> {
+ using Key = nsTString<T>;
+ using Lookup = nsTString<T>;
+
+ static mozilla::HashNumber hash(const Lookup& aLookup) {
+ return mozilla::HashString(aLookup.get());
+ }
+
+ static bool match(const Key& aKey, const Lookup& aLookup) {
+ return aKey.Equals(aLookup);
+ }
+};
+
+} // namespace mozilla
+
+#endif // !defined(nsTStringHasher_h___)
diff --git a/xpcom/string/nsTStringRepr.cpp b/xpcom/string/nsTStringRepr.cpp
new file mode 100644
index 0000000000..0dd8eafb34
--- /dev/null
+++ b/xpcom/string/nsTStringRepr.cpp
@@ -0,0 +1,273 @@
+/* -*- 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/. */
+
+#include "nsTStringRepr.h"
+
+#include "double-conversion/string-to-double.h"
+#include "mozilla/FloatingPoint.h"
+#include "nsError.h"
+#include "nsString.h"
+
+namespace mozilla::detail {
+
+template <typename T>
+typename nsTStringRepr<T>::char_type nsTStringRepr<T>::First() const {
+ MOZ_RELEASE_ASSERT(this->mLength > 0, "|First()| called on an empty string");
+ return this->mData[0];
+}
+
+template <typename T>
+typename nsTStringRepr<T>::char_type nsTStringRepr<T>::Last() const {
+ MOZ_RELEASE_ASSERT(this->mLength > 0, "|Last()| called on an empty string");
+ return this->mData[this->mLength - 1];
+}
+
+template <typename T>
+bool nsTStringRepr<T>::Equals(const self_type& aStr) const {
+ return this->mLength == aStr.mLength &&
+ char_traits::compare(this->mData, aStr.mData, this->mLength) == 0;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::Equals(const self_type& aStr,
+ comparator_type aComp) const {
+ return this->mLength == aStr.mLength &&
+ aComp(this->mData, aStr.mData, this->mLength, aStr.mLength) == 0;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::Equals(const substring_tuple_type& aTuple) const {
+ return Equals(substring_type(aTuple));
+}
+
+template <typename T>
+bool nsTStringRepr<T>::Equals(const substring_tuple_type& aTuple,
+ comparator_type aComp) const {
+ return Equals(substring_type(aTuple), aComp);
+}
+
+template <typename T>
+bool nsTStringRepr<T>::Equals(const char_type* aData) const {
+ // unfortunately, some callers pass null :-(
+ if (!aData) {
+ MOZ_ASSERT_UNREACHABLE("null data pointer");
+ return this->mLength == 0;
+ }
+
+ // XXX avoid length calculation?
+ size_type length = char_traits::length(aData);
+ return this->mLength == length &&
+ char_traits::compare(this->mData, aData, this->mLength) == 0;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::Equals(const char_type* aData,
+ comparator_type aComp) const {
+ // unfortunately, some callers pass null :-(
+ if (!aData) {
+ MOZ_ASSERT_UNREACHABLE("null data pointer");
+ return this->mLength == 0;
+ }
+
+ // XXX avoid length calculation?
+ size_type length = char_traits::length(aData);
+ return this->mLength == length &&
+ aComp(this->mData, aData, this->mLength, length) == 0;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::EqualsASCII(const char* aData, size_type aLen) const {
+ return this->mLength == aLen &&
+ char_traits::compareASCII(this->mData, aData, aLen) == 0;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::EqualsASCII(const char* aData) const {
+ return char_traits::compareASCIINullTerminated(this->mData, this->mLength,
+ aData) == 0;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::EqualsLatin1(const char* aData,
+ const size_type aLength) const {
+ return (this->mLength == aLength) &&
+ char_traits::equalsLatin1(this->mData, aData, aLength);
+}
+
+template <typename T>
+bool nsTStringRepr<T>::LowerCaseEqualsASCII(const char* aData,
+ size_type aLen) const {
+ return this->mLength == aLen &&
+ char_traits::compareLowerCaseToASCII(this->mData, aData, aLen) == 0;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::LowerCaseEqualsASCII(const char* aData) const {
+ return char_traits::compareLowerCaseToASCIINullTerminated(
+ this->mData, this->mLength, aData) == 0;
+}
+
+template <typename T>
+int32_t nsTStringRepr<T>::Find(const string_view& aString,
+ index_type aOffset) const {
+ auto idx = View().find(aString, aOffset);
+ return idx == string_view::npos ? kNotFound : idx;
+}
+
+template <typename T>
+int32_t nsTStringRepr<T>::LowerCaseFindASCII(const std::string_view& aString,
+ index_type aOffset) const {
+ if (aOffset > Length()) {
+ return kNotFound;
+ }
+ auto begin = BeginReading();
+ auto end = EndReading();
+ auto it =
+ std::search(begin + aOffset, end, aString.begin(), aString.end(),
+ [](char_type l, char r) {
+ MOZ_ASSERT(!(r & ~0x7F), "Unexpected non-ASCII character");
+ MOZ_ASSERT(char_traits::ASCIIToLower(r) == char_type(r),
+ "Search string must be ASCII lowercase");
+ return char_traits::ASCIIToLower(l) == char_type(r);
+ });
+ return it == end ? kNotFound : std::distance(begin, it);
+}
+
+template <typename T>
+int32_t nsTStringRepr<T>::RFind(const string_view& aString) const {
+ auto idx = View().rfind(aString);
+ return idx == string_view::npos ? kNotFound : idx;
+}
+
+template <typename T>
+typename nsTStringRepr<T>::size_type nsTStringRepr<T>::CountChar(
+ char_type aChar) const {
+ return std::count(BeginReading(), EndReading(), aChar);
+}
+
+template <typename T>
+int32_t nsTStringRepr<T>::FindChar(char_type aChar, index_type aOffset) const {
+ auto idx = View().find(aChar, aOffset);
+ return idx == string_view::npos ? kNotFound : idx;
+}
+
+template <typename T>
+int32_t nsTStringRepr<T>::RFindChar(char_type aChar, int32_t aOffset) const {
+ auto idx = View().rfind(aChar, aOffset != -1 ? aOffset : string_view::npos);
+ return idx == string_view::npos ? kNotFound : idx;
+}
+
+template <typename T>
+int32_t nsTStringRepr<T>::FindCharInSet(const string_view& aSet,
+ index_type aOffset) const {
+ auto idx = View().find_first_of(aSet, aOffset);
+ return idx == string_view::npos ? kNotFound : idx;
+}
+
+template <typename T>
+int32_t nsTStringRepr<T>::RFindCharInSet(const string_view& aSet,
+ int32_t aOffset) const {
+ auto idx =
+ View().find_last_of(aSet, aOffset != -1 ? aOffset : string_view::npos);
+ return idx == string_view::npos ? kNotFound : idx;
+}
+
+template <typename T>
+bool nsTStringRepr<T>::EqualsIgnoreCase(const std::string_view& aString) const {
+ return std::equal(BeginReading(), EndReading(), aString.begin(),
+ aString.end(), [](char_type l, char r) {
+ return char_traits::ASCIIToLower(l) ==
+ char_traits::ASCIIToLower(char_type(r));
+ });
+}
+
+// We can't use the method `StringToDoubleConverter::ToDouble` due to linking
+// issues on Windows as it's in mozglue. Instead, implement the selection logic
+// using an overload set.
+//
+// StringToFloat is used instead of StringToDouble for floats due to differences
+// in rounding behaviour.
+static void StringToFP(
+ const double_conversion::StringToDoubleConverter& aConverter,
+ const char* aData, size_t aLength, int* aProcessed, float* aResult) {
+ *aResult = aConverter.StringToFloat(aData, aLength, aProcessed);
+}
+
+static void StringToFP(
+ const double_conversion::StringToDoubleConverter& aConverter,
+ const char* aData, size_t aLength, int* aProcessed, double* aResult) {
+ *aResult = aConverter.StringToDouble(aData, aLength, aProcessed);
+}
+
+static void StringToFP(
+ const double_conversion::StringToDoubleConverter& aConverter,
+ const char16_t* aData, size_t aLength, int* aProcessed, float* aResult) {
+ *aResult = aConverter.StringToFloat(reinterpret_cast<const uc16*>(aData),
+ aLength, aProcessed);
+}
+
+static void StringToFP(
+ const double_conversion::StringToDoubleConverter& aConverter,
+ const char16_t* aData, size_t aLength, int* aProcessed, double* aResult) {
+ *aResult = aConverter.StringToDouble(reinterpret_cast<const uc16*>(aData),
+ aLength, aProcessed);
+}
+
+template <typename FloatT, typename CharT>
+static FloatT ParseFloatingPoint(const nsTStringRepr<CharT>& aString,
+ bool aAllowTrailingChars,
+ nsresult* aErrorCode) {
+ // Performs conversion to double following the "rules for parsing
+ // floating-point number values" from the HTML standard.
+ // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values
+ //
+ // This behaviour allows for leading spaces, and will not generate infinity or
+ // NaN values except in error conditions.
+ int flags = double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES;
+ if (aAllowTrailingChars) {
+ flags |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK;
+ }
+ double_conversion::StringToDoubleConverter converter(
+ flags, mozilla::UnspecifiedNaN<double>(),
+ mozilla::UnspecifiedNaN<double>(), nullptr, nullptr);
+
+ FloatT result;
+ int processed;
+ StringToFP(converter, aString.Data(), aString.Length(), &processed, &result);
+
+ *aErrorCode = mozilla::IsFinite(result) ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
+ return result;
+}
+
+template <typename T>
+double nsTStringRepr<T>::ToDouble(nsresult* aErrorCode) const {
+ return ParseFloatingPoint<double, T>(*this, /* aAllowTrailingChars */ false,
+ aErrorCode);
+}
+
+template <typename T>
+double nsTStringRepr<T>::ToDoubleAllowTrailingChars(
+ nsresult* aErrorCode) const {
+ return ParseFloatingPoint<double, T>(*this, /* aAllowTrailingChars */ true,
+ aErrorCode);
+}
+
+template <typename T>
+float nsTStringRepr<T>::ToFloat(nsresult* aErrorCode) const {
+ return ParseFloatingPoint<float, T>(*this, /* aAllowTrailingChars */ false,
+ aErrorCode);
+}
+
+template <typename T>
+float nsTStringRepr<T>::ToFloatAllowTrailingChars(nsresult* aErrorCode) const {
+ return ParseFloatingPoint<float, T>(*this, /* aAllowTrailingChars */ true,
+ aErrorCode);
+}
+
+} // namespace mozilla::detail
+
+template class mozilla::detail::nsTStringRepr<char>;
+template class mozilla::detail::nsTStringRepr<char16_t>;
diff --git a/xpcom/string/nsTStringRepr.h b/xpcom/string/nsTStringRepr.h
new file mode 100644
index 0000000000..7e7fa53384
--- /dev/null
+++ b/xpcom/string/nsTStringRepr.h
@@ -0,0 +1,546 @@
+/* -*- 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/. */
+
+#ifndef nsTStringRepr_h
+#define nsTStringRepr_h
+
+#include <limits>
+#include <string_view>
+#include <type_traits> // std::enable_if
+
+#include "mozilla/Char16.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/fallible.h"
+#include "nsStringBuffer.h"
+#include "nsStringFlags.h"
+#include "nsStringFwd.h"
+#include "nsStringIterator.h"
+#include "nsCharTraits.h"
+
+template <typename T>
+class nsTSubstringTuple;
+
+namespace mozilla {
+
+// This is mainly intended to be used in the context of nsTStrings where
+// we want to enable a specific function only for a given character class. In
+// order for this technique to work the member function needs to be templated
+// on something other than `T`. We keep this in the `mozilla` namespace rather
+// than `nsTStringRepr` as it's intentionally not dependent on `T`.
+//
+// The 'T' at the end of `Char[16]OnlyT` is refering to the `::type` portion
+// which will only be defined if the character class is correct. This is similar
+// to `std::enable_if_t` which is available in C++14, but not C++11.
+//
+// `CharType` is generally going to be a shadowed type of `T`.
+//
+// Example usage of a function that will only be defined if `T` == `char`:
+//
+// template <typename T>
+// class nsTSubstring : public nsTStringRepr<T> {
+// template <typename Q = T, typename EnableForChar = typename CharOnlyT<Q>>
+// int Foo() { return 42; }
+// };
+//
+// Please note that we had to use a separate type `Q` for this to work. You
+// will get a semi-decent compiler error if you use `T` directly.
+
+template <typename CharType>
+using CharOnlyT =
+ typename std::enable_if<std::is_same<char, CharType>::value>::type;
+
+template <typename CharType>
+using Char16OnlyT =
+ typename std::enable_if<std::is_same<char16_t, CharType>::value>::type;
+
+namespace detail {
+
+// nsTStringLengthStorage is a helper class which holds the string's length and
+// provides getters and setters for converting to and from `size_t`. This is
+// done to allow the length to be stored in a `uint32_t` using assertions.
+template <typename T>
+class nsTStringLengthStorage {
+ public:
+ // The maximum byte capacity for a `nsTString` must fit within an `int32_t`,
+ // with enough room for a trailing null, as consumers often cast `Length()`
+ // and `Capacity()` to smaller types like `int32_t`.
+ static constexpr size_t kMax =
+ size_t{std::numeric_limits<int32_t>::max()} / sizeof(T) - 1;
+ static_assert(
+ (kMax + 1) * sizeof(T) <= std::numeric_limits<int32_t>::max(),
+ "nsTString's maximum length, including the trailing null, must fit "
+ "within `int32_t`, as callers will cast to `int32_t` occasionally");
+ static_assert(((CheckedInt<uint32_t>{kMax} + 1) * sizeof(T) +
+ sizeof(nsStringBuffer))
+ .isValid(),
+ "Math required to allocate a nsStringBuffer for a "
+ "maximum-capacity string must not overflow uint32_t");
+
+ // Implicit conversion and assignment from `size_t` which assert that the
+ // value is in-range.
+ MOZ_IMPLICIT constexpr nsTStringLengthStorage(size_t aLength)
+ : mLength(static_cast<uint32_t>(aLength)) {
+ MOZ_RELEASE_ASSERT(aLength <= kMax, "string is too large");
+ }
+ constexpr nsTStringLengthStorage& operator=(size_t aLength) {
+ MOZ_RELEASE_ASSERT(aLength <= kMax, "string is too large");
+ mLength = static_cast<uint32_t>(aLength);
+ return *this;
+ }
+ MOZ_IMPLICIT constexpr operator size_t() const { return mLength; }
+
+ private:
+ uint32_t mLength = 0;
+};
+
+// nsTStringRepr defines a string's memory layout and some accessor methods.
+// This class exists so that nsTLiteralString can avoid inheriting
+// nsTSubstring's destructor. All methods on this class must be const because
+// literal strings are not writable.
+//
+// This class is an implementation detail and should not be instantiated
+// directly, nor used in any way outside of the string code itself. It is
+// buried in a namespace to discourage its use in function parameters.
+// If you need to take a parameter, use [const] ns[C]Substring&.
+// If you need to instantiate a string, use ns[C]String or descendents.
+//
+// NAMES:
+// nsStringRepr for wide characters
+// nsCStringRepr for narrow characters
+template <typename T>
+class nsTStringRepr {
+ public:
+ typedef mozilla::fallible_t fallible_t;
+
+ typedef T char_type;
+
+ typedef nsCharTraits<char_type> char_traits;
+ typedef typename char_traits::incompatible_char_type incompatible_char_type;
+
+ typedef nsTStringRepr<T> self_type;
+ typedef self_type base_string_type;
+
+ typedef nsTSubstring<T> substring_type;
+ typedef nsTSubstringTuple<T> substring_tuple_type;
+
+ typedef nsReadingIterator<char_type> const_iterator;
+ typedef char_type* iterator;
+
+ typedef nsTStringComparator<char_type> comparator_type;
+
+ typedef const char_type* const_char_iterator;
+
+ typedef std::basic_string_view<char_type> string_view;
+
+ typedef size_t index_type;
+ typedef size_t size_type;
+
+ // These are only for internal use within the string classes:
+ typedef StringDataFlags DataFlags;
+ typedef StringClassFlags ClassFlags;
+ typedef nsTStringLengthStorage<T> LengthStorage;
+
+ // Reading iterators.
+ constexpr const_char_iterator BeginReading() const { return mData; }
+ constexpr const_char_iterator EndReading() const { return mData + mLength; }
+
+ // Deprecated reading iterators.
+ const_iterator& BeginReading(const_iterator& aIter) const {
+ aIter.mStart = mData;
+ aIter.mEnd = mData + mLength;
+ aIter.mPosition = aIter.mStart;
+ return aIter;
+ }
+
+ const_iterator& EndReading(const_iterator& aIter) const {
+ aIter.mStart = mData;
+ aIter.mEnd = mData + mLength;
+ aIter.mPosition = aIter.mEnd;
+ return aIter;
+ }
+
+ const_char_iterator& BeginReading(const_char_iterator& aIter) const {
+ return aIter = mData;
+ }
+
+ const_char_iterator& EndReading(const_char_iterator& aIter) const {
+ return aIter = mData + mLength;
+ }
+
+ // Accessors.
+ template <typename U, typename Dummy>
+ struct raw_type {
+ typedef const U* type;
+ };
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Dummy>
+ struct raw_type<char16_t, Dummy> {
+ typedef char16ptr_t type;
+ };
+#endif
+
+ // Returns pointer to string data (not necessarily null-terminated)
+ constexpr typename raw_type<T, int>::type Data() const { return mData; }
+
+ constexpr size_type Length() const { return static_cast<size_type>(mLength); }
+
+ constexpr string_view View() const { return string_view(Data(), Length()); }
+
+ constexpr operator string_view() const { return View(); }
+
+ constexpr DataFlags GetDataFlags() const { return mDataFlags; }
+
+ constexpr bool IsEmpty() const { return mLength == 0; }
+
+ constexpr bool IsLiteral() const {
+ return !!(mDataFlags & DataFlags::LITERAL);
+ }
+
+ constexpr bool IsVoid() const { return !!(mDataFlags & DataFlags::VOIDED); }
+
+ constexpr bool IsTerminated() const {
+ return !!(mDataFlags & DataFlags::TERMINATED);
+ }
+
+ constexpr char_type CharAt(index_type aIndex) const {
+ NS_ASSERTION(aIndex < Length(), "index exceeds allowable range");
+ return mData[aIndex];
+ }
+
+ constexpr char_type operator[](index_type aIndex) const {
+ return CharAt(aIndex);
+ }
+
+ char_type First() const;
+
+ char_type Last() const;
+
+ // Equality.
+ bool NS_FASTCALL Equals(const self_type&) const;
+ bool NS_FASTCALL Equals(const self_type&, comparator_type) const;
+
+ bool NS_FASTCALL Equals(const substring_tuple_type& aTuple) const;
+ bool NS_FASTCALL Equals(const substring_tuple_type& aTuple,
+ comparator_type) const;
+
+ bool NS_FASTCALL Equals(const char_type* aData) const;
+ bool NS_FASTCALL Equals(const char_type* aData, comparator_type) const;
+
+ /**
+ * Compare this string and another ASCII-case-insensitively.
+ *
+ * This method is similar to `LowerCaseEqualsASCII` however both strings are
+ * lowercased, meaning that `aString` need not be all lowercase.
+ *
+ * @param aString is the string to check
+ * @return boolean
+ */
+ bool EqualsIgnoreCase(const std::string_view& aString) const;
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ template <typename Q = T, typename EnableIfChar16 = Char16OnlyT<Q>>
+ bool NS_FASTCALL Equals(char16ptr_t aData) const {
+ return Equals(static_cast<const char16_t*>(aData));
+ }
+ template <typename Q = T, typename EnableIfChar16 = Char16OnlyT<Q>>
+ bool NS_FASTCALL Equals(char16ptr_t aData, comparator_type aComp) const {
+ return Equals(static_cast<const char16_t*>(aData), aComp);
+ }
+#endif
+
+ // An efficient comparison with ASCII that can be used even
+ // for wide strings. Call this version when you know the
+ // length of 'data'.
+ bool NS_FASTCALL EqualsASCII(const char* aData, size_type aLen) const;
+ // An efficient comparison with ASCII that can be used even
+ // for wide strings. Call this version when 'data' is
+ // null-terminated.
+ bool NS_FASTCALL EqualsASCII(const char* aData) const;
+
+ // An efficient comparison with Latin1 characters that can be used even for
+ // wide strings.
+ bool EqualsLatin1(const char* aData, size_type aLength) const;
+
+ // EqualsLiteral 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 EqualsASCII for other char array variables.
+ // (Although this method may happen to produce expected results for other
+ // char arrays that have bound one greater than the sequence of interest,
+ // such use is discouraged for reasons of readability and maintainability.)
+ // The template trick to acquire the array bound at compile time without
+ // using a macro is due to Corey Kosak, with much thanks.
+ template <int N>
+ inline bool EqualsLiteral(const char (&aStr)[N]) const {
+ return EqualsASCII(aStr, N - 1);
+ }
+
+ // EqualsLiteral 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 EqualsASCII for other char array variables.
+ // (Although this method may happen to produce expected results for other
+ // char arrays that have bound one greater than the sequence of interest,
+ // such use is discouraged for reasons of readability and maintainability.)
+ // The template trick to acquire the array bound at compile time without
+ // using a macro is due to Corey Kosak, with much thanks.
+ template <size_t N, typename = std::enable_if_t<!std::is_same_v<
+ const char (&)[N], const char_type (&)[N]>>>
+ inline bool EqualsLiteral(const char_type (&aStr)[N]) const {
+ return *this == nsTLiteralString<char_type>(aStr);
+ }
+
+ // The LowerCaseEquals methods compare the ASCII-lowercase version of
+ // this string (lowercasing only ASCII uppercase characters) to some
+ // ASCII/Literal string. The ASCII string is *not* lowercased for
+ // you. If you compare to an ASCII or literal string that contains an
+ // uppercase character, it is guaranteed to return false. We will
+ // throw assertions too.
+ bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData,
+ size_type aLen) const;
+ bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData) const;
+
+ // LowerCaseEqualsLiteral 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 LowerCaseEqualsASCII for other char array variables.
+ // (Although this method may happen to produce expected results for other
+ // char arrays that have bound one greater than the sequence of interest,
+ // such use is discouraged for reasons of readability and maintainability.)
+ template <int N>
+ bool LowerCaseEqualsLiteral(const char (&aStr)[N]) const {
+ return LowerCaseEqualsASCII(aStr, N - 1);
+ }
+
+ // Returns true if this string overlaps with the given string fragment.
+ bool IsDependentOn(const char_type* aStart, const char_type* aEnd) const {
+ // If it _isn't_ the case that one fragment starts after the other ends,
+ // or ends before the other starts, then, they conflict:
+ //
+ // !(f2.begin >= f1.aEnd || f2.aEnd <= f1.begin)
+ //
+ // Simplified, that gives us (To avoid relying on Undefined Behavior
+ // from comparing pointers from different allocations (which in
+ // principle gives the optimizer the permission to assume elsewhere
+ // that the pointers are from the same allocation), the comparisons
+ // are done on integers, which merely relies on implementation-defined
+ // behavior of converting pointers to integers. std::less and
+ // std::greater implementations don't actually provide the guarantees
+ // that they should.):
+ return (reinterpret_cast<uintptr_t>(aStart) <
+ reinterpret_cast<uintptr_t>(mData + mLength) &&
+ reinterpret_cast<uintptr_t>(aEnd) >
+ reinterpret_cast<uintptr_t>(mData));
+ }
+
+ /**
+ * Search for the given substring within this string.
+ *
+ * @param aString is substring to be sought in this
+ * @param aOffset tells us where in this string to start searching
+ * @return offset in string, or kNotFound
+ */
+ int32_t Find(const string_view& aString, index_type aOffset = 0) const;
+
+ // Previously there was an overload of `Find()` which took a bool second
+ // argument. Avoid issues by explicitly preventing that overload.
+ // TODO: Remove this at some point.
+ template <typename I,
+ typename = std::enable_if_t<!std::is_same_v<I, index_type> &&
+ std::is_convertible_v<I, index_type>>>
+ int32_t Find(const string_view& aString, I aOffset) const {
+ static_assert(!std::is_same_v<I, bool>, "offset must not be `bool`");
+ return Find(aString, static_cast<index_type>(aOffset));
+ }
+
+ /**
+ * Search for the given ASCII substring within this string, ignoring case.
+ *
+ * @param aString is substring to be sought in this
+ * @param aOffset tells us where in this string to start searching
+ * @return offset in string, or kNotFound
+ */
+ int32_t LowerCaseFindASCII(const std::string_view& aString,
+ index_type aOffset = 0) const;
+
+ /**
+ * Scan the string backwards, looking for the given substring.
+ *
+ * @param aString is substring to be sought in this
+ * @return offset in string, or kNotFound
+ */
+ int32_t RFind(const string_view& aString) const;
+
+ size_type CountChar(char_type) const;
+
+ bool Contains(char_type aChar) const { return FindChar(aChar) != kNotFound; }
+
+ /**
+ * Search for the first instance of a given char within this string
+ *
+ * @param aChar is the character to search for
+ * @param aOffset tells us where in this string to start searching
+ * @return offset in string, or kNotFound
+ */
+ int32_t FindChar(char_type aChar, index_type aOffset = 0) const;
+
+ /**
+ * Search for the last instance of a given char within this string
+ *
+ * @param aChar is the character to search for
+ * @param aOffset tells us where in this string to start searching
+ * @return offset in string, or kNotFound
+ */
+ int32_t RFindChar(char_type aChar, int32_t aOffset = -1) const;
+
+ /**
+ * This method searches this string for the first character found in
+ * the given string.
+ *
+ * @param aSet contains set of chars to be found
+ * @param aOffset tells us where in this string to start searching
+ * (counting from left)
+ * @return offset in string, or kNotFound
+ */
+
+ int32_t FindCharInSet(const string_view& aSet, index_type aOffset = 0) const;
+
+ /**
+ * This method searches this string for the last character found in
+ * the given string.
+ *
+ * @param aSet contains set of chars to be found
+ * @param aOffset tells us where in this string to start searching
+ * (counting from left)
+ * @return offset in string, or kNotFound
+ */
+
+ int32_t RFindCharInSet(const string_view& aSet, int32_t aOffset = -1) const;
+
+ /**
+ * Perform locale-independent string to double-precision float conversion.
+ *
+ * Leading spaces in the string will be ignored. The returned value will be
+ * finite unless aErrorCode is set to a failed status.
+ *
+ * @param aErrorCode will contain error if one occurs
+ * @return double-precision float rep of string value
+ */
+ double ToDouble(nsresult* aErrorCode) const;
+
+ /**
+ * Perform locale-independent string to single-precision float conversion.
+ *
+ * Leading spaces in the string will be ignored. The returned value will be
+ * finite unless aErrorCode is set to a failed status.
+ *
+ * @param aErrorCode will contain error if one occurs
+ * @return single-precision float rep of string value
+ */
+ float ToFloat(nsresult* aErrorCode) const;
+
+ /**
+ * Similar to above ToDouble and ToFloat but allows trailing characters that
+ * are not converted.
+ */
+ double ToDoubleAllowTrailingChars(nsresult* aErrorCode) const;
+ float ToFloatAllowTrailingChars(nsresult* aErrorCode) const;
+
+ protected:
+ nsTStringRepr() = delete; // Never instantiate directly
+
+ constexpr nsTStringRepr(char_type* aData, size_type aLength,
+ DataFlags aDataFlags, ClassFlags aClassFlags)
+ : mData(aData),
+ mLength(aLength),
+ mDataFlags(aDataFlags),
+ mClassFlags(aClassFlags) {}
+
+ static constexpr size_type kMaxCapacity = LengthStorage::kMax;
+
+ /**
+ * Checks if the given capacity is valid for this string type.
+ */
+ [[nodiscard]] static constexpr bool CheckCapacity(size_type aCapacity) {
+ return aCapacity <= kMaxCapacity;
+ }
+
+ char_type* mData;
+ LengthStorage mLength;
+ DataFlags mDataFlags;
+ ClassFlags const mClassFlags;
+};
+
+extern template class nsTStringRepr<char>;
+extern template class nsTStringRepr<char16_t>;
+
+} // namespace detail
+} // namespace mozilla
+
+template <typename T>
+int NS_FASTCALL Compare(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs,
+ nsTStringComparator<T> = nsTDefaultStringComparator<T>);
+
+extern template int NS_FASTCALL Compare<char>(
+ const mozilla::detail::nsTStringRepr<char>&,
+ const mozilla::detail::nsTStringRepr<char>&, nsTStringComparator<char>);
+
+extern template int NS_FASTCALL
+Compare<char16_t>(const mozilla::detail::nsTStringRepr<char16_t>&,
+ const mozilla::detail::nsTStringRepr<char16_t>&,
+ nsTStringComparator<char16_t>);
+
+template <typename T>
+inline constexpr bool operator!=(
+ const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs) {
+ return !aLhs.Equals(aRhs);
+}
+
+template <typename T>
+inline constexpr bool operator!=(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const T* aRhs) {
+ return !aLhs.Equals(aRhs);
+}
+
+template <typename T>
+inline bool operator<(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs) {
+ return Compare(aLhs, aRhs) < 0;
+}
+
+template <typename T>
+inline bool operator<=(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs) {
+ return Compare(aLhs, aRhs) <= 0;
+}
+
+template <typename T>
+inline bool operator==(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs) {
+ return aLhs.Equals(aRhs);
+}
+
+template <typename T>
+inline bool operator==(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const T* aRhs) {
+ return aLhs.Equals(aRhs);
+}
+
+template <typename T>
+inline bool operator>=(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs) {
+ return Compare(aLhs, aRhs) >= 0;
+}
+
+template <typename T>
+inline bool operator>(const mozilla::detail::nsTStringRepr<T>& aLhs,
+ const mozilla::detail::nsTStringRepr<T>& aRhs) {
+ return Compare(aLhs, aRhs) > 0;
+}
+
+#endif
diff --git a/xpcom/string/nsTSubstring.cpp b/xpcom/string/nsTSubstring.cpp
new file mode 100644
index 0000000000..6dcf933629
--- /dev/null
+++ b/xpcom/string/nsTSubstring.cpp
@@ -0,0 +1,1694 @@
+/* -*- 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/. */
+
+#include "double-conversion/double-conversion.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Printf.h"
+#include "mozilla/ResultExtensions.h"
+
+#include "nsASCIIMask.h"
+#include "nsCharTraits.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#ifdef DEBUG
+# include "nsStringStats.h"
+#else
+# define STRING_STAT_INCREMENT(_s)
+#endif
+
+// It's not worthwhile to reallocate the buffer and memcpy the
+// contents over when the size difference isn't large. With
+// power-of-two allocation buckets and 64 as the typical inline
+// capacity, considering that above 1000 there performance aspects
+// of realloc and memcpy seem to be absorbed, relative to the old
+// code, by the performance benefits of the new code being exact,
+// we need to choose which transitions of 256 to 128, 512 to 256
+// and 1024 to 512 to allow. As a guess, let's pick the middle
+// one as the the largest potential transition that we forgo. So
+// we'll shrink from 1024 bucket to 512 bucket but not from 512
+// bucket to 256 bucket. We'll decide by comparing the difference
+// of capacities. As bucket differences, the differences are 256
+// and 512. Since the capacities have various overheads, we
+// can't compare with 256 or 512 exactly but it's easier to
+// compare to some number that's between the two, so it's
+// far away from either to ignore the overheads.
+const uint32_t kNsStringBufferShrinkingThreshold = 384;
+
+using double_conversion::DoubleToStringConverter;
+
+// ---------------------------------------------------------------------------
+
+static const char16_t gNullChar = 0;
+
+char* const nsCharTraits<char>::sEmptyBuffer =
+ (char*)const_cast<char16_t*>(&gNullChar);
+char16_t* const nsCharTraits<char16_t>::sEmptyBuffer =
+ const_cast<char16_t*>(&gNullChar);
+
+// ---------------------------------------------------------------------------
+
+static void ReleaseData(void* aData, nsAString::DataFlags aFlags) {
+ if (aFlags & nsAString::DataFlags::REFCOUNTED) {
+ nsStringBuffer::FromData(aData)->Release();
+ } else if (aFlags & nsAString::DataFlags::OWNED) {
+ free(aData);
+ STRING_STAT_INCREMENT(AdoptFree);
+ // Treat this as destruction of a "StringAdopt" object for leak
+ // tracking purposes.
+ MOZ_LOG_DTOR(aData, "StringAdopt", 1);
+ }
+ // otherwise, nothing to do.
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE
+template <typename T>
+nsTSubstring<T>::nsTSubstring(char_type* aData, size_type aLength,
+ DataFlags aDataFlags, ClassFlags aClassFlags)
+ : ::mozilla::detail::nsTStringRepr<T>(aData, aLength, aDataFlags,
+ aClassFlags) {
+ AssertValid();
+
+ if (aDataFlags & DataFlags::OWNED) {
+ STRING_STAT_INCREMENT(Adopt);
+ MOZ_LOG_CTOR(this->mData, "StringAdopt", 1);
+ }
+}
+#endif /* XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE */
+
+/**
+ * helper function for down-casting a nsTSubstring to an nsTAutoString.
+ */
+template <typename T>
+inline const nsTAutoString<T>* AsAutoString(const nsTSubstring<T>* aStr) {
+ return static_cast<const nsTAutoString<T>*>(aStr);
+}
+
+template <typename T>
+mozilla::Result<mozilla::BulkWriteHandle<T>, nsresult>
+nsTSubstring<T>::BulkWrite(size_type aCapacity, size_type aPrefixToPreserve,
+ bool aAllowShrinking) {
+ auto r = StartBulkWriteImpl(aCapacity, aPrefixToPreserve, aAllowShrinking);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return r.propagateErr();
+ }
+ return mozilla::BulkWriteHandle<T>(this, r.unwrap());
+}
+
+template <typename T>
+auto nsTSubstring<T>::StartBulkWriteImpl(size_type aCapacity,
+ size_type aPrefixToPreserve,
+ bool aAllowShrinking,
+ size_type aSuffixLength,
+ size_type aOldSuffixStart,
+ size_type aNewSuffixStart)
+ -> mozilla::Result<size_type, nsresult> {
+ // Note! Capacity does not include room for the terminating null char.
+
+ MOZ_ASSERT(aPrefixToPreserve <= aCapacity,
+ "Requested preservation of an overlong prefix.");
+ MOZ_ASSERT(aNewSuffixStart + aSuffixLength <= aCapacity,
+ "Requesed move of suffix to out-of-bounds location.");
+ // Can't assert aOldSuffixStart, because mLength may not be valid anymore,
+ // since this method allows itself to be called more than once.
+
+ // If zero capacity is requested, set the string to the special empty
+ // string.
+ if (MOZ_UNLIKELY(!aCapacity)) {
+ ReleaseData(this->mData, this->mDataFlags);
+ SetToEmptyBuffer();
+ return 0;
+ }
+
+ // Note! Capacity() returns 0 when the string is immutable.
+ const size_type curCapacity = Capacity();
+
+ bool shrinking = false;
+
+ // We've established that aCapacity > 0.
+ // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we
+ // need to allocate a new buffer. We cannot use the existing buffer even
+ // though it might be large enough.
+
+ if (aCapacity <= curCapacity) {
+ if (aAllowShrinking) {
+ shrinking = true;
+ } else {
+ char_traits::move(this->mData + aNewSuffixStart,
+ this->mData + aOldSuffixStart, aSuffixLength);
+ if (aSuffixLength) {
+ char_traits::uninitialize(this->mData + aPrefixToPreserve,
+ XPCOM_MIN(aNewSuffixStart - aPrefixToPreserve,
+ kNsStringBufferMaxPoison));
+ char_traits::uninitialize(
+ this->mData + aNewSuffixStart + aSuffixLength,
+ XPCOM_MIN(curCapacity + 1 - aNewSuffixStart - aSuffixLength,
+ kNsStringBufferMaxPoison));
+ } else {
+ char_traits::uninitialize(this->mData + aPrefixToPreserve,
+ XPCOM_MIN(curCapacity + 1 - aPrefixToPreserve,
+ kNsStringBufferMaxPoison));
+ }
+ return curCapacity;
+ }
+ }
+
+ char_type* oldData = this->mData;
+ DataFlags oldFlags = this->mDataFlags;
+
+ char_type* newData;
+ DataFlags newDataFlags;
+ size_type newCapacity;
+
+ // If this is an nsTAutoStringN, it's possible that we can use the inline
+ // buffer.
+ if ((this->mClassFlags & ClassFlags::INLINE) &&
+ (aCapacity <= AsAutoString(this)->mInlineCapacity)) {
+ newCapacity = AsAutoString(this)->mInlineCapacity;
+ newData = (char_type*)AsAutoString(this)->mStorage;
+ newDataFlags = DataFlags::TERMINATED | DataFlags::INLINE;
+ } else {
+ // If |aCapacity > kMaxCapacity|, then our doubling algorithm may not be
+ // able to allocate it. Just bail out in cases like that. We don't want
+ // to be allocating 2GB+ strings anyway.
+ static_assert((sizeof(nsStringBuffer) & 0x1) == 0,
+ "bad size for nsStringBuffer");
+ if (MOZ_UNLIKELY(!this->CheckCapacity(aCapacity))) {
+ return mozilla::Err(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // We increase our capacity so that the allocated buffer grows
+ // exponentially, which gives us amortized O(1) appending. Below the
+ // threshold, we use powers-of-two. Above the threshold, we grow by at
+ // least 1.125, rounding up to the nearest MiB.
+ const size_type slowGrowthThreshold = 8 * 1024 * 1024;
+
+ // nsStringBuffer allocates sizeof(nsStringBuffer) + passed size, and
+ // storageSize below wants extra 1 * sizeof(char_type).
+ const size_type neededExtraSpace =
+ sizeof(nsStringBuffer) / sizeof(char_type) + 1;
+
+ size_type temp;
+ if (aCapacity >= slowGrowthThreshold) {
+ size_type minNewCapacity =
+ curCapacity + (curCapacity >> 3); // multiply by 1.125
+ temp = XPCOM_MAX(aCapacity, minNewCapacity) + neededExtraSpace;
+
+ // Round up to the next multiple of MiB, but ensure the expected
+ // capacity doesn't include the extra space required by nsStringBuffer
+ // and null-termination.
+ const size_t MiB = 1 << 20;
+ temp = (MiB * ((temp + MiB - 1) / MiB)) - neededExtraSpace;
+ } else {
+ // Round up to the next power of two.
+ temp =
+ mozilla::RoundUpPow2(aCapacity + neededExtraSpace) - neededExtraSpace;
+ }
+
+ newCapacity = XPCOM_MIN(temp, base_string_type::kMaxCapacity);
+ MOZ_ASSERT(newCapacity >= aCapacity,
+ "should have hit the early return at the top");
+ // Avoid shrinking if the new buffer size is close to the old. Note that
+ // unsigned underflow is defined behavior.
+ if ((curCapacity - newCapacity) <= kNsStringBufferShrinkingThreshold &&
+ (this->mDataFlags & DataFlags::REFCOUNTED)) {
+ MOZ_ASSERT(aAllowShrinking, "How come we didn't return earlier?");
+ // We're already close enough to the right size.
+ newData = oldData;
+ newCapacity = curCapacity;
+ } else {
+ size_type storageSize = (newCapacity + 1) * sizeof(char_type);
+ // Since we allocate only by powers of 2 we always fit into a full
+ // mozjemalloc bucket, it's not useful to use realloc, which may spend
+ // time uselessly copying too much.
+ nsStringBuffer* newHdr = nsStringBuffer::Alloc(storageSize).take();
+ if (newHdr) {
+ newData = (char_type*)newHdr->Data();
+ } else if (shrinking) {
+ // We're still in a consistent state.
+ //
+ // Since shrinking is just a memory footprint optimization, we
+ // don't propagate OOM if we tried to shrink in order to avoid
+ // OOM crashes from infallible callers. If we're lucky, soon enough
+ // a fallible caller reaches OOM and is able to deal or we end up
+ // disposing of this string before reaching OOM again.
+ newData = oldData;
+ newCapacity = curCapacity;
+ } else {
+ return mozilla::Err(NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ newDataFlags = DataFlags::TERMINATED | DataFlags::REFCOUNTED;
+ }
+
+ this->mData = newData;
+ this->mDataFlags = newDataFlags;
+
+ if (oldData == newData) {
+ char_traits::move(newData + aNewSuffixStart, oldData + aOldSuffixStart,
+ aSuffixLength);
+ if (aSuffixLength) {
+ char_traits::uninitialize(this->mData + aPrefixToPreserve,
+ XPCOM_MIN(aNewSuffixStart - aPrefixToPreserve,
+ kNsStringBufferMaxPoison));
+ char_traits::uninitialize(
+ this->mData + aNewSuffixStart + aSuffixLength,
+ XPCOM_MIN(newCapacity + 1 - aNewSuffixStart - aSuffixLength,
+ kNsStringBufferMaxPoison));
+ } else {
+ char_traits::uninitialize(this->mData + aPrefixToPreserve,
+ XPCOM_MIN(newCapacity + 1 - aPrefixToPreserve,
+ kNsStringBufferMaxPoison));
+ }
+ } else {
+ char_traits::copy(newData, oldData, aPrefixToPreserve);
+ char_traits::copy(newData + aNewSuffixStart, oldData + aOldSuffixStart,
+ aSuffixLength);
+ ReleaseData(oldData, oldFlags);
+ }
+
+ return newCapacity;
+}
+
+template <typename T>
+void nsTSubstring<T>::FinishBulkWriteImpl(size_type aLength) {
+ if (aLength) {
+ FinishBulkWriteImplImpl(aLength);
+ } else {
+ ReleaseData(this->mData, this->mDataFlags);
+ SetToEmptyBuffer();
+ }
+ AssertValid();
+}
+
+template <typename T>
+void nsTSubstring<T>::Finalize() {
+ ReleaseData(this->mData, this->mDataFlags);
+ // this->mData, this->mLength, and this->mDataFlags are purposefully left
+ // dangling
+}
+
+template <typename T>
+bool nsTSubstring<T>::ReplacePrep(index_type aCutStart, size_type aCutLength,
+ size_type aNewLength) {
+ aCutLength = XPCOM_MIN(aCutLength, this->mLength - aCutStart);
+
+ mozilla::CheckedInt<size_type> newTotalLen = this->Length();
+ newTotalLen += aNewLength;
+ newTotalLen -= aCutLength;
+ if (!newTotalLen.isValid()) {
+ return false;
+ }
+
+ if (aCutStart == this->mLength && Capacity() > newTotalLen.value()) {
+ this->mDataFlags &= ~DataFlags::VOIDED;
+ this->mData[newTotalLen.value()] = char_type(0);
+ this->mLength = newTotalLen.value();
+ return true;
+ }
+
+ return ReplacePrepInternal(aCutStart, aCutLength, aNewLength,
+ newTotalLen.value());
+}
+
+template <typename T>
+bool nsTSubstring<T>::ReplacePrepInternal(index_type aCutStart,
+ size_type aCutLen, size_type aFragLen,
+ size_type aNewLen) {
+ size_type newSuffixStart = aCutStart + aFragLen;
+ size_type oldSuffixStart = aCutStart + aCutLen;
+ size_type suffixLength = this->mLength - oldSuffixStart;
+
+ mozilla::Result<size_type, nsresult> r = StartBulkWriteImpl(
+ aNewLen, aCutStart, false, suffixLength, oldSuffixStart, newSuffixStart);
+ if (r.isErr()) {
+ return false;
+ }
+ FinishBulkWriteImpl(aNewLen);
+ return true;
+}
+
+template <typename T>
+typename nsTSubstring<T>::size_type nsTSubstring<T>::Capacity() const {
+ // return 0 to indicate an immutable or 0-sized buffer
+
+ size_type capacity;
+ if (this->mDataFlags & DataFlags::REFCOUNTED) {
+ // if the string is readonly, then we pretend that it has no capacity.
+ nsStringBuffer* hdr = nsStringBuffer::FromData(this->mData);
+ if (hdr->IsReadonly()) {
+ capacity = 0;
+ } else {
+ capacity = (size_t(hdr->StorageSize()) / sizeof(char_type)) - 1;
+ }
+ } else if (this->mDataFlags & DataFlags::INLINE) {
+ MOZ_ASSERT(this->mClassFlags & ClassFlags::INLINE);
+ capacity = AsAutoString(this)->mInlineCapacity;
+ } else if (this->mDataFlags & DataFlags::OWNED) {
+ // we don't store the capacity of an adopted buffer because that would
+ // require an additional member field. the best we can do is base the
+ // capacity on our length. remains to be seen if this is the right
+ // trade-off.
+ capacity = this->mLength;
+ } else {
+ capacity = 0;
+ }
+
+ return capacity;
+}
+
+template <typename T>
+bool nsTSubstring<T>::EnsureMutable(size_type aNewLen) {
+ if (aNewLen == size_type(-1) || aNewLen == this->mLength) {
+ if (this->mDataFlags & (DataFlags::INLINE | DataFlags::OWNED)) {
+ return true;
+ }
+ if ((this->mDataFlags & DataFlags::REFCOUNTED) &&
+ !nsStringBuffer::FromData(this->mData)->IsReadonly()) {
+ return true;
+ }
+
+ aNewLen = this->mLength;
+ }
+ return SetLength(aNewLen, mozilla::fallible);
+}
+
+// ---------------------------------------------------------------------------
+
+// This version of Assign is optimized for single-character assignment.
+template <typename T>
+void nsTSubstring<T>::Assign(char_type aChar) {
+ if (MOZ_UNLIKELY(!Assign(aChar, mozilla::fallible))) {
+ AllocFailed(1);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Assign(char_type aChar, const fallible_t&) {
+ auto r = StartBulkWriteImpl(1, 0, true);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return false;
+ }
+ *this->mData = aChar;
+ FinishBulkWriteImpl(1);
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::Assign(const char_type* aData, size_type aLength) {
+ if (MOZ_UNLIKELY(!Assign(aData, aLength, mozilla::fallible))) {
+ AllocFailed(aLength == size_type(-1) ? char_traits::length(aData)
+ : aLength);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Assign(const char_type* aData,
+ const fallible_t& aFallible) {
+ return Assign(aData, size_type(-1), aFallible);
+}
+
+template <typename T>
+bool nsTSubstring<T>::Assign(const char_type* aData, size_type aLength,
+ const fallible_t& aFallible) {
+ if (!aData || aLength == 0) {
+ Truncate();
+ return true;
+ }
+
+ if (MOZ_UNLIKELY(aLength == size_type(-1))) {
+ aLength = char_traits::length(aData);
+ }
+
+ if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) {
+ return Assign(string_type(aData, aLength), aFallible);
+ }
+
+ auto r = StartBulkWriteImpl(aLength, 0, true);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return false;
+ }
+ char_traits::copy(this->mData, aData, aLength);
+ FinishBulkWriteImpl(aLength);
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::AssignASCII(const char* aData, size_type aLength) {
+ if (MOZ_UNLIKELY(!AssignASCII(aData, aLength, mozilla::fallible))) {
+ AllocFailed(aLength);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::AssignASCII(const char* aData, size_type aLength,
+ const fallible_t& aFallible) {
+ MOZ_ASSERT(aLength != size_type(-1));
+
+ // A Unicode string can't depend on an ASCII string buffer,
+ // so this dependence check only applies to CStrings.
+ if constexpr (std::is_same_v<T, char>) {
+ if (this->IsDependentOn(aData, aData + aLength)) {
+ return Assign(string_type(aData, aLength), aFallible);
+ }
+ }
+
+ auto r = StartBulkWriteImpl(aLength, 0, true);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return false;
+ }
+ char_traits::copyASCII(this->mData, aData, aLength);
+ FinishBulkWriteImpl(aLength);
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::AssignLiteral(const char_type* aData, size_type aLength) {
+ ReleaseData(this->mData, this->mDataFlags);
+ SetData(const_cast<char_type*>(aData), aLength,
+ DataFlags::TERMINATED | DataFlags::LITERAL);
+}
+
+template <typename T>
+void nsTSubstring<T>::Assign(const self_type& aStr) {
+ if (!Assign(aStr, mozilla::fallible)) {
+ AllocFailed(aStr.Length());
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Assign(const self_type& aStr,
+ const fallible_t& aFallible) {
+ // |aStr| could be sharable. We need to check its flags to know how to
+ // deal with it.
+
+ if (&aStr == this) {
+ return true;
+ }
+
+ if (!aStr.mLength) {
+ Truncate();
+ this->mDataFlags |= aStr.mDataFlags & DataFlags::VOIDED;
+ return true;
+ }
+
+ if (aStr.mDataFlags & DataFlags::REFCOUNTED) {
+ // nice! we can avoid a string copy :-)
+
+ // |aStr| should be null-terminated
+ NS_ASSERTION(aStr.mDataFlags & DataFlags::TERMINATED,
+ "shared, but not terminated");
+
+ ReleaseData(this->mData, this->mDataFlags);
+
+ SetData(aStr.mData, aStr.mLength,
+ DataFlags::TERMINATED | DataFlags::REFCOUNTED);
+
+ // get an owning reference to the this->mData
+ nsStringBuffer::FromData(this->mData)->AddRef();
+ return true;
+ } else if (aStr.mDataFlags & DataFlags::LITERAL) {
+ MOZ_ASSERT(aStr.mDataFlags & DataFlags::TERMINATED, "Unterminated literal");
+
+ AssignLiteral(aStr.mData, aStr.mLength);
+ return true;
+ }
+
+ // else, treat this like an ordinary assignment.
+ return Assign(aStr.Data(), aStr.Length(), aFallible);
+}
+
+template <typename T>
+void nsTSubstring<T>::Assign(self_type&& aStr) {
+ if (!Assign(std::move(aStr), mozilla::fallible)) {
+ AllocFailed(aStr.Length());
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AssignOwned(self_type&& aStr) {
+ MOZ_ASSERT(aStr.mDataFlags & (DataFlags::REFCOUNTED | DataFlags::OWNED),
+ "neither shared nor owned");
+
+ // If they have a REFCOUNTED or OWNED buffer, we can avoid a copy - so steal
+ // their buffer and reset them to the empty string.
+
+ // |aStr| should be null-terminated
+ MOZ_ASSERT(aStr.mDataFlags & DataFlags::TERMINATED,
+ "shared or owned, but not terminated");
+
+ ReleaseData(this->mData, this->mDataFlags);
+
+ SetData(aStr.mData, aStr.mLength, aStr.mDataFlags);
+ aStr.SetToEmptyBuffer();
+}
+
+template <typename T>
+bool nsTSubstring<T>::Assign(self_type&& aStr, const fallible_t& aFallible) {
+ // We're moving |aStr| in this method, so we need to try to steal the data,
+ // and in the fallback perform a copy-assignment followed by a truncation of
+ // the original string.
+
+ if (&aStr == this) {
+ NS_WARNING("Move assigning a string to itself?");
+ return true;
+ }
+
+ if (aStr.mDataFlags & (DataFlags::REFCOUNTED | DataFlags::OWNED)) {
+ AssignOwned(std::move(aStr));
+ return true;
+ }
+
+ // Otherwise treat this as a normal assignment, and truncate the moved string.
+ // We don't truncate the source string if the allocation failed.
+ if (!Assign(aStr, aFallible)) {
+ return false;
+ }
+ aStr.Truncate();
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::Assign(const substring_tuple_type& aTuple) {
+ if (!Assign(aTuple, mozilla::fallible)) {
+ AllocFailed(aTuple.Length());
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::AssignNonDependent(const substring_tuple_type& aTuple,
+ size_type aTupleLength,
+ const mozilla::fallible_t& aFallible) {
+ NS_ASSERTION(aTuple.Length() == aTupleLength, "wrong length passed");
+
+ auto r = StartBulkWriteImpl(aTupleLength);
+ if (r.isErr()) {
+ return false;
+ }
+
+ aTuple.WriteTo(this->mData, aTupleLength);
+
+ FinishBulkWriteImpl(aTupleLength);
+ return true;
+}
+
+template <typename T>
+bool nsTSubstring<T>::Assign(const substring_tuple_type& aTuple,
+ const fallible_t& aFallible) {
+ const auto [isDependentOnThis, tupleLength] =
+ aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength);
+ if (isDependentOnThis) {
+ string_type temp;
+ self_type& tempSubstring = temp;
+ if (!tempSubstring.AssignNonDependent(aTuple, tupleLength, aFallible)) {
+ return false;
+ }
+ AssignOwned(std::move(temp));
+ return true;
+ }
+
+ return AssignNonDependent(aTuple, tupleLength, aFallible);
+}
+
+template <typename T>
+void nsTSubstring<T>::Adopt(char_type* aData, size_type aLength) {
+ if (aData) {
+ ReleaseData(this->mData, this->mDataFlags);
+
+ if (aLength == size_type(-1)) {
+ aLength = char_traits::length(aData);
+ }
+
+ SetData(aData, aLength, DataFlags::TERMINATED | DataFlags::OWNED);
+
+ STRING_STAT_INCREMENT(Adopt);
+ // Treat this as construction of a "StringAdopt" object for leak
+ // tracking purposes.
+ MOZ_LOG_CTOR(this->mData, "StringAdopt", 1);
+ } else {
+ SetIsVoid(true);
+ }
+}
+
+// This version of Replace is optimized for single-character replacement.
+template <typename T>
+void nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength,
+ char_type aChar) {
+ aCutStart = XPCOM_MIN(aCutStart, this->Length());
+
+ if (ReplacePrep(aCutStart, aCutLength, 1)) {
+ this->mData[aCutStart] = aChar;
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength,
+ char_type aChar, const fallible_t&) {
+ aCutStart = XPCOM_MIN(aCutStart, this->Length());
+
+ if (!ReplacePrep(aCutStart, aCutLength, 1)) {
+ return false;
+ }
+
+ this->mData[aCutStart] = aChar;
+
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength,
+ const char_type* aData, size_type aLength) {
+ if (!Replace(aCutStart, aCutLength, aData, aLength, mozilla::fallible)) {
+ AllocFailed(this->Length() - aCutLength + 1);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength,
+ const char_type* aData, size_type aLength,
+ const fallible_t& aFallible) {
+ // unfortunately, some callers pass null :-(
+ if (!aData) {
+ aLength = 0;
+ } else {
+ if (aLength == size_type(-1)) {
+ aLength = char_traits::length(aData);
+ }
+
+ if (this->IsDependentOn(aData, aData + aLength)) {
+ nsTAutoString<T> temp(aData, aLength);
+ return Replace(aCutStart, aCutLength, temp, aFallible);
+ }
+ }
+
+ aCutStart = XPCOM_MIN(aCutStart, this->Length());
+
+ bool ok = ReplacePrep(aCutStart, aCutLength, aLength);
+ if (!ok) {
+ return false;
+ }
+
+ if (aLength > 0) {
+ char_traits::copy(this->mData + aCutStart, aData, aLength);
+ }
+
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength,
+ const substring_tuple_type& aTuple) {
+ const auto [isDependentOnThis, tupleLength] =
+ aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength);
+
+ if (isDependentOnThis) {
+ nsTAutoString<T> temp;
+ if (!temp.AssignNonDependent(aTuple, tupleLength, mozilla::fallible)) {
+ AllocFailed(tupleLength);
+ }
+ Replace(aCutStart, aCutLength, temp);
+ return;
+ }
+
+ aCutStart = XPCOM_MIN(aCutStart, this->Length());
+
+ if (ReplacePrep(aCutStart, aCutLength, tupleLength) && tupleLength > 0) {
+ aTuple.WriteTo(this->mData + aCutStart, tupleLength);
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::ReplaceLiteral(index_type aCutStart, size_type aCutLength,
+ const char_type* aData,
+ size_type aLength) {
+ aCutStart = XPCOM_MIN(aCutStart, this->Length());
+
+ if (!aCutStart && aCutLength == this->Length() &&
+ !(this->mDataFlags & DataFlags::REFCOUNTED)) {
+ // Check for REFCOUNTED above to avoid undoing the effect of
+ // SetCapacity().
+ AssignLiteral(aData, aLength);
+ } else if (ReplacePrep(aCutStart, aCutLength, aLength) && aLength > 0) {
+ char_traits::copy(this->mData + aCutStart, aData, aLength);
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::Append(char_type aChar) {
+ if (MOZ_UNLIKELY(!Append(aChar, mozilla::fallible))) {
+ AllocFailed(this->mLength + 1);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Append(char_type aChar, const fallible_t& aFallible) {
+ size_type oldLen = this->mLength;
+ size_type newLen = oldLen + 1; // Can't overflow
+ auto r = StartBulkWriteImpl(newLen, oldLen, false);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return false;
+ }
+ this->mData[oldLen] = aChar;
+ FinishBulkWriteImpl(newLen);
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::Append(const char_type* aData, size_type aLength) {
+ if (MOZ_UNLIKELY(!Append(aData, aLength, mozilla::fallible))) {
+ AllocFailed(this->mLength + (aLength == size_type(-1)
+ ? char_traits::length(aData)
+ : aLength));
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Append(const char_type* aData, size_type aLength,
+ const fallible_t& aFallible) {
+ if (MOZ_UNLIKELY(aLength == size_type(-1))) {
+ aLength = char_traits::length(aData);
+ }
+
+ if (MOZ_UNLIKELY(!aLength)) {
+ // Avoid undoing the effect of SetCapacity() if both
+ // mLength and aLength are zero.
+ return true;
+ }
+
+ if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) {
+ return Append(string_type(aData, aLength), mozilla::fallible);
+ }
+ size_type oldLen = this->mLength;
+ mozilla::CheckedInt<size_type> newLen(oldLen);
+ newLen += aLength;
+ if (MOZ_UNLIKELY(!newLen.isValid())) {
+ return false;
+ }
+ auto r = StartBulkWriteImpl(newLen.value(), oldLen, false);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return false;
+ }
+ char_traits::copy(this->mData + oldLen, aData, aLength);
+ FinishBulkWriteImpl(newLen.value());
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendASCII(const char* aData, size_type aLength) {
+ if (MOZ_UNLIKELY(!AppendASCII(aData, aLength, mozilla::fallible))) {
+ AllocFailed(this->mLength +
+ (aLength == size_type(-1) ? strlen(aData) : aLength));
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::AppendASCII(const char* aData,
+ const fallible_t& aFallible) {
+ return AppendASCII(aData, size_type(-1), aFallible);
+}
+
+template <typename T>
+bool nsTSubstring<T>::AppendASCII(const char* aData, size_type aLength,
+ const fallible_t& aFallible) {
+ if (MOZ_UNLIKELY(aLength == size_type(-1))) {
+ aLength = strlen(aData);
+ }
+
+ if (MOZ_UNLIKELY(!aLength)) {
+ // Avoid undoing the effect of SetCapacity() if both
+ // mLength and aLength are zero.
+ return true;
+ }
+
+ if constexpr (std::is_same_v<T, char>) {
+ // 16-bit string can't depend on an 8-bit buffer
+ if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) {
+ return Append(string_type(aData, aLength), mozilla::fallible);
+ }
+ }
+
+ size_type oldLen = this->mLength;
+ mozilla::CheckedInt<size_type> newLen(oldLen);
+ newLen += aLength;
+ if (MOZ_UNLIKELY(!newLen.isValid())) {
+ return false;
+ }
+ auto r = StartBulkWriteImpl(newLen.value(), oldLen, false);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return false;
+ }
+ char_traits::copyASCII(this->mData + oldLen, aData, aLength);
+ FinishBulkWriteImpl(newLen.value());
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::Append(const self_type& aStr) {
+ if (MOZ_UNLIKELY(!Append(aStr, mozilla::fallible))) {
+ AllocFailed(this->mLength + aStr.Length());
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Append(const self_type& aStr,
+ const fallible_t& aFallible) {
+ // Check refcounted to avoid undoing the effects of SetCapacity().
+ if (MOZ_UNLIKELY(!this->mLength &&
+ !(this->mDataFlags & DataFlags::REFCOUNTED))) {
+ return Assign(aStr, mozilla::fallible);
+ }
+ return Append(aStr.BeginReading(), aStr.Length(), mozilla::fallible);
+}
+
+template <typename T>
+void nsTSubstring<T>::Append(const substring_tuple_type& aTuple) {
+ if (MOZ_UNLIKELY(!Append(aTuple, mozilla::fallible))) {
+ AllocFailed(this->mLength + aTuple.Length());
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::Append(const substring_tuple_type& aTuple,
+ const fallible_t& aFallible) {
+ const auto [isDependentOnThis, tupleLength] =
+ aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength);
+
+ if (MOZ_UNLIKELY(!tupleLength)) {
+ // Avoid undoing the effect of SetCapacity() if both
+ // mLength and tupleLength are zero.
+ return true;
+ }
+
+ if (MOZ_UNLIKELY(isDependentOnThis)) {
+ return Append(string_type(aTuple), aFallible);
+ }
+
+ size_type oldLen = this->mLength;
+ mozilla::CheckedInt<size_type> newLen(oldLen);
+ newLen += tupleLength;
+ if (MOZ_UNLIKELY(!newLen.isValid())) {
+ return false;
+ }
+ auto r = StartBulkWriteImpl(newLen.value(), oldLen, false);
+ if (MOZ_UNLIKELY(r.isErr())) {
+ return false;
+ }
+ aTuple.WriteTo(this->mData + oldLen, tupleLength);
+ FinishBulkWriteImpl(newLen.value());
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::SetCapacity(size_type aCapacity) {
+ if (!SetCapacity(aCapacity, mozilla::fallible)) {
+ AllocFailed(aCapacity);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::SetCapacity(size_type aCapacity, const fallible_t&) {
+ size_type length = this->mLength;
+ // This method can no longer be used to shorten the
+ // logical length.
+ size_type capacity = XPCOM_MAX(aCapacity, length);
+
+ auto r = StartBulkWriteImpl(capacity, length, true);
+ if (r.isErr()) {
+ return false;
+ }
+
+ if (MOZ_UNLIKELY(!capacity)) {
+ // Zero capacity was requested on a zero-length
+ // string. In this special case, we are pointing
+ // to the special empty buffer, which is already
+ // zero-terminated and not writable, so we must
+ // not attempt to zero-terminate it.
+ AssertValid();
+ return true;
+ }
+
+ // FinishBulkWriteImpl with argument zero releases
+ // the heap-allocated buffer. However, SetCapacity()
+ // is a special case that allows mLength to be zero
+ // while a heap-allocated buffer exists.
+ // By calling FinishBulkWriteImplImpl, we skip the
+ // zero case handling that's inappropriate in the
+ // SetCapacity() case.
+ FinishBulkWriteImplImpl(length);
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::SetLength(size_type aLength) {
+ if (!SetLength(aLength, mozilla::fallible)) {
+ AllocFailed(aLength);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::SetLength(size_type aLength,
+ const fallible_t& aFallible) {
+ size_type preserve = XPCOM_MIN(aLength, this->Length());
+ auto r = StartBulkWriteImpl(aLength, preserve, true);
+ if (r.isErr()) {
+ return false;
+ }
+
+ FinishBulkWriteImpl(aLength);
+
+ return true;
+}
+
+template <typename T>
+void nsTSubstring<T>::Truncate() {
+ ReleaseData(this->mData, this->mDataFlags);
+ SetToEmptyBuffer();
+ AssertValid();
+}
+
+template <typename T>
+void nsTSubstring<T>::SetIsVoid(bool aVal) {
+ if (aVal) {
+ Truncate();
+ this->mDataFlags |= DataFlags::VOIDED;
+ } else {
+ this->mDataFlags &= ~DataFlags::VOIDED;
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::StripChar(char_type aChar) {
+ if (this->mLength == 0) {
+ return;
+ }
+
+ if (!EnsureMutable()) { // XXX do this lazily?
+ AllocFailed(this->mLength);
+ }
+
+ // XXX(darin): this code should defer writing until necessary.
+
+ char_type* to = this->mData;
+ char_type* from = this->mData;
+ char_type* end = this->mData + this->mLength;
+
+ while (from < end) {
+ char_type theChar = *from++;
+ if (aChar != theChar) {
+ *to++ = theChar;
+ }
+ }
+ *to = char_type(0); // add the null
+ this->mLength = to - this->mData;
+}
+
+template <typename T>
+void nsTSubstring<T>::StripChars(const char_type* aChars) {
+ if (this->mLength == 0) {
+ return;
+ }
+
+ if (!EnsureMutable()) { // XXX do this lazily?
+ AllocFailed(this->mLength);
+ }
+
+ // XXX(darin): this code should defer writing until necessary.
+
+ char_type* to = this->mData;
+ char_type* from = this->mData;
+ char_type* end = this->mData + this->mLength;
+
+ while (from < end) {
+ char_type theChar = *from++;
+ const char_type* test = aChars;
+
+ for (; *test && *test != theChar; ++test)
+ ;
+
+ if (!*test) {
+ // Not stripped, copy this char.
+ *to++ = theChar;
+ }
+ }
+ *to = char_type(0); // add the null
+ this->mLength = to - this->mData;
+}
+
+template <typename T>
+void nsTSubstring<T>::StripTaggedASCII(const ASCIIMaskArray& aToStrip) {
+ if (this->mLength == 0) {
+ return;
+ }
+
+ if (!EnsureMutable()) {
+ AllocFailed(this->mLength);
+ }
+
+ char_type* to = this->mData;
+ char_type* from = this->mData;
+ char_type* end = this->mData + this->mLength;
+
+ while (from < end) {
+ uint32_t theChar = (uint32_t)*from++;
+ // Replacing this with a call to ASCIIMask::IsMasked
+ // regresses performance somewhat, so leaving it inlined.
+ if (!mozilla::ASCIIMask::IsMasked(aToStrip, theChar)) {
+ // Not stripped, copy this char.
+ *to++ = (char_type)theChar;
+ }
+ }
+ *to = char_type(0); // add the null
+ this->mLength = to - this->mData;
+}
+
+template <typename T>
+void nsTSubstring<T>::StripCRLF() {
+ // Expanding this call to copy the code from StripTaggedASCII
+ // instead of just calling it does somewhat help with performance
+ // but it is not worth it given the duplicated code.
+ StripTaggedASCII(mozilla::ASCIIMask::MaskCRLF());
+}
+
+template <typename T>
+struct MOZ_STACK_CLASS PrintfAppend : public mozilla::PrintfTarget {
+ explicit PrintfAppend(nsTSubstring<T>* aString) : mString(aString) {}
+
+ bool append(const char* aStr, size_t aLen) override {
+ if (aLen == 0) {
+ return true;
+ }
+
+ mString->AppendASCII(aStr, aLen);
+ return true;
+ }
+
+ private:
+ nsTSubstring<T>* mString;
+};
+
+template <typename T>
+void nsTSubstring<T>::AppendPrintf(const char* aFormat, ...) {
+ PrintfAppend<T> appender(this);
+ va_list ap;
+ va_start(ap, aFormat);
+ bool r = appender.vprint(aFormat, ap);
+ if (!r) {
+ MOZ_CRASH("Allocation or other failure in PrintfTarget::print");
+ }
+ va_end(ap);
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendVprintf(const char* aFormat, va_list aAp) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.vprint(aFormat, aAp);
+ if (!r) {
+ MOZ_CRASH("Allocation or other failure in PrintfTarget::print");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntDec(int32_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntDec(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntDec(uint32_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntDec(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntOct(uint32_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntOct(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntHex(uint32_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntHex(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntDec(int64_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntDec(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntDec(uint64_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntDec(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntOct(uint64_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntOct(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendIntHex(uint64_t aInteger) {
+ PrintfAppend<T> appender(this);
+ bool r = appender.appendIntHex(aInteger);
+ if (MOZ_UNLIKELY(!r)) {
+ MOZ_CRASH("Allocation or other failure while appending integers");
+ }
+}
+
+// Returns the length of the formatted aDouble in aBuf.
+static int FormatWithoutTrailingZeros(char (&aBuf)[40], double aDouble,
+ int aPrecision) {
+ static const DoubleToStringConverter converter(
+ DoubleToStringConverter::UNIQUE_ZERO |
+ DoubleToStringConverter::NO_TRAILING_ZERO |
+ DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN,
+ "Infinity", "NaN", 'e', -6, 21, 6, 1);
+ double_conversion::StringBuilder builder(aBuf, sizeof(aBuf));
+ converter.ToPrecision(aDouble, aPrecision, &builder);
+ int length = builder.position();
+ builder.Finalize();
+ return length;
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendFloat(float aFloat) {
+ char buf[40];
+ int length = FormatWithoutTrailingZeros(buf, aFloat, 6);
+ AppendASCII(buf, length);
+}
+
+template <typename T>
+void nsTSubstring<T>::AppendFloat(double aFloat) {
+ char buf[40];
+ int length = FormatWithoutTrailingZeros(buf, aFloat, 15);
+ AppendASCII(buf, length);
+}
+
+template <typename T>
+size_t nsTSubstring<T>::SizeOfExcludingThisIfUnshared(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ if (this->mDataFlags & DataFlags::REFCOUNTED) {
+ return nsStringBuffer::FromData(this->mData)
+ ->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
+ }
+ if (this->mDataFlags & DataFlags::OWNED) {
+ return aMallocSizeOf(this->mData);
+ }
+
+ // If we reach here, exactly one of the following must be true:
+ // - DataFlags::VOIDED is set, and this->mData points to sEmptyBuffer;
+ // - DataFlags::INLINE is set, and this->mData points to a buffer within a
+ // string object (e.g. nsAutoString);
+ // - None of DataFlags::REFCOUNTED, DataFlags::OWNED, DataFlags::INLINE is
+ // set, and this->mData points to a buffer owned by something else.
+ //
+ // In all three cases, we don't measure it.
+ return 0;
+}
+
+template <typename T>
+size_t nsTSubstring<T>::SizeOfExcludingThisEvenIfShared(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ // This is identical to SizeOfExcludingThisIfUnshared except for the
+ // DataFlags::REFCOUNTED case.
+ if (this->mDataFlags & DataFlags::REFCOUNTED) {
+ return nsStringBuffer::FromData(this->mData)
+ ->SizeOfIncludingThisEvenIfShared(aMallocSizeOf);
+ }
+ if (this->mDataFlags & DataFlags::OWNED) {
+ return aMallocSizeOf(this->mData);
+ }
+ return 0;
+}
+
+template <typename T>
+size_t nsTSubstring<T>::SizeOfIncludingThisIfUnshared(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+template <typename T>
+size_t nsTSubstring<T>::SizeOfIncludingThisEvenIfShared(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThisEvenIfShared(aMallocSizeOf);
+}
+
+template <typename T>
+nsTSubstringSplitter<T> nsTSubstring<T>::Split(const char_type aChar) const {
+ return nsTSubstringSplitter<T>(
+ nsTCharSeparatedTokenizerTemplate<
+ NS_TokenizerIgnoreNothing, T,
+ nsTokenizerFlags::IncludeEmptyTokenAtEnd>(*this, aChar));
+}
+
+// Common logic for nsTSubstring<T>::ToInteger and nsTSubstring<T>::ToInteger64.
+template <typename T, typename int_type>
+int_type ToIntegerCommon(const nsTSubstring<T>& aSrc, nsresult* aErrorCode,
+ uint32_t aRadix) {
+ MOZ_ASSERT(aRadix == 10 || aRadix == 16);
+
+ // Initial value, override if we find an integer.
+ *aErrorCode = NS_ERROR_ILLEGAL_VALUE;
+
+ // Begin by skipping over leading chars that shouldn't be part of the number.
+ auto cp = aSrc.BeginReading();
+ auto endcp = aSrc.EndReading();
+ bool negate = false;
+ bool done = false;
+
+ // NB: For backwards compatibility I'm not going to change this logic but
+ // it seems really odd. Previously there was logic to auto-detect the
+ // radix if kAutoDetect was passed in. In practice this value was never
+ // used, so it pretended to auto detect and skipped some preceding
+ // letters (excluding valid hex digits) but never used the result.
+ //
+ // For example if you pass in "Get the number: 10", aRadix = 10 we'd
+ // skip the 'G', and then fail to parse "et the number: 10". If aRadix =
+ // 16 we'd skip the 'G', and parse just 'e' returning 14.
+ while ((cp < endcp) && (!done)) {
+ switch (*cp++) {
+ // clang-format off
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ done = true;
+ break;
+ // clang-format on
+ case '-':
+ negate = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!done) {
+ // No base 16 or base 10 digits were found.
+ return 0;
+ }
+
+ // Step back.
+ cp--;
+
+ mozilla::CheckedInt<int_type> result;
+
+ // Now iterate the numeric chars and build our result.
+ while (cp < endcp) {
+ auto theChar = *cp++;
+ if (('0' <= theChar) && (theChar <= '9')) {
+ result = (aRadix * result) + (theChar - '0');
+ } else if ((theChar >= 'A') && (theChar <= 'F')) {
+ if (10 == aRadix) {
+ // Invalid base 10 digit, error out.
+ return 0;
+ } else {
+ result = (aRadix * result) + ((theChar - 'A') + 10);
+ }
+ } else if ((theChar >= 'a') && (theChar <= 'f')) {
+ if (10 == aRadix) {
+ // Invalid base 10 digit, error out.
+ return 0;
+ } else {
+ result = (aRadix * result) + ((theChar - 'a') + 10);
+ }
+ } else if ((('X' == theChar) || ('x' == theChar)) && result == 0) {
+ // For some reason we support a leading 'x' regardless of radix. For
+ // example: "000000x500", aRadix = 10 would be parsed as 500 rather
+ // than 0.
+ continue;
+ } else {
+ // We've encountered a char that's not a legal number or sign and we can
+ // terminate processing.
+ break;
+ }
+
+ if (!result.isValid()) {
+ // Overflow!
+ return 0;
+ }
+ }
+
+ // Integer found.
+ *aErrorCode = NS_OK;
+
+ if (negate) {
+ result = -result;
+ }
+
+ return result.value();
+}
+
+template <typename T>
+int32_t nsTSubstring<T>::ToInteger(nsresult* aErrorCode,
+ uint32_t aRadix) const {
+ return ToIntegerCommon<T, int32_t>(*this, aErrorCode, aRadix);
+}
+
+/**
+ * nsTSubstring::ToInteger64
+ */
+template <typename T>
+int64_t nsTSubstring<T>::ToInteger64(nsresult* aErrorCode,
+ uint32_t aRadix) const {
+ return ToIntegerCommon<T, int64_t>(*this, aErrorCode, aRadix);
+}
+
+/**
+ * nsTSubstring::Mid
+ */
+template <typename T>
+typename nsTSubstring<T>::size_type nsTSubstring<T>::Mid(
+ self_type& aResult, index_type aStartPos, size_type aLengthToCopy) const {
+ if (aStartPos == 0 && aLengthToCopy >= this->mLength) {
+ aResult = *this;
+ } else {
+ aResult = Substring(*this, aStartPos, aLengthToCopy);
+ }
+
+ return aResult.mLength;
+}
+
+/**
+ * nsTSubstring::StripWhitespace
+ */
+
+template <typename T>
+void nsTSubstring<T>::StripWhitespace() {
+ if (!StripWhitespace(mozilla::fallible)) {
+ this->AllocFailed(this->mLength);
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::StripWhitespace(const fallible_t&) {
+ if (!this->EnsureMutable()) {
+ return false;
+ }
+
+ this->StripTaggedASCII(mozilla::ASCIIMask::MaskWhitespace());
+ return true;
+}
+
+/**
+ * nsTSubstring::ReplaceChar,ReplaceSubstring
+ */
+
+template <typename T>
+void nsTSubstring<T>::ReplaceChar(char_type aOldChar, char_type aNewChar) {
+ int32_t i = this->FindChar(aOldChar);
+ if (i == kNotFound) {
+ return;
+ }
+
+ if (!this->EnsureMutable()) {
+ this->AllocFailed(this->mLength);
+ }
+ for (; i != kNotFound; i = this->FindChar(aOldChar, i + 1)) {
+ this->mData[i] = aNewChar;
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::ReplaceChar(const string_view& aSet, char_type aNewChar) {
+ int32_t i = this->FindCharInSet(aSet);
+ if (i == kNotFound) {
+ return;
+ }
+
+ if (!this->EnsureMutable()) {
+ this->AllocFailed(this->mLength);
+ }
+ for (; i != kNotFound; i = this->FindCharInSet(aSet, i + 1)) {
+ this->mData[i] = aNewChar;
+ }
+}
+
+template <typename T>
+void nsTSubstring<T>::ReplaceSubstring(const char_type* aTarget,
+ const char_type* aNewValue) {
+ ReplaceSubstring(nsTDependentString<T>(aTarget),
+ nsTDependentString<T>(aNewValue));
+}
+
+template <typename T>
+bool nsTSubstring<T>::ReplaceSubstring(const char_type* aTarget,
+ const char_type* aNewValue,
+ const fallible_t& aFallible) {
+ return ReplaceSubstring(nsTDependentString<T>(aTarget),
+ nsTDependentString<T>(aNewValue), aFallible);
+}
+
+template <typename T>
+void nsTSubstring<T>::ReplaceSubstring(const self_type& aTarget,
+ const self_type& aNewValue) {
+ if (!ReplaceSubstring(aTarget, aNewValue, mozilla::fallible)) {
+ // Note that this may wildly underestimate the allocation that failed, as
+ // we could have been replacing multiple copies of aTarget.
+ this->AllocFailed(this->mLength + (aNewValue.Length() - aTarget.Length()));
+ }
+}
+
+template <typename T>
+bool nsTSubstring<T>::ReplaceSubstring(const self_type& aTarget,
+ const self_type& aNewValue,
+ const fallible_t&) {
+ struct Segment {
+ uint32_t mBegin, mLength;
+ Segment(uint32_t aBegin, uint32_t aLength)
+ : mBegin(aBegin), mLength(aLength) {}
+ };
+
+ if (aTarget.Length() == 0) {
+ return true;
+ }
+
+ // Remember all of the non-matching parts.
+ AutoTArray<Segment, 16> nonMatching;
+ uint32_t i = 0;
+ mozilla::CheckedUint32 newLength;
+ while (true) {
+ int32_t r = this->Find(aTarget, i);
+ int32_t until = (r == kNotFound) ? this->Length() - i : r - i;
+ nonMatching.AppendElement(Segment(i, until));
+ newLength += until;
+ if (r == kNotFound) {
+ break;
+ }
+
+ newLength += aNewValue.Length();
+ i = r + aTarget.Length();
+ if (i >= this->Length()) {
+ // Add an auxiliary entry at the end of the list to help as an edge case
+ // for the algorithms below.
+ nonMatching.AppendElement(Segment(this->Length(), 0));
+ break;
+ }
+ }
+
+ if (!newLength.isValid()) {
+ return false;
+ }
+
+ // If there's only one non-matching segment, then the target string was not
+ // found, and there's nothing to do.
+ if (nonMatching.Length() == 1) {
+ MOZ_ASSERT(
+ nonMatching[0].mBegin == 0 && nonMatching[0].mLength == this->Length(),
+ "We should have the correct non-matching segment.");
+ return true;
+ }
+
+ // Make sure that we can mutate our buffer.
+ // Note that we always allocate at least an this->mLength sized buffer,
+ // because the rest of the algorithm relies on having access to all of the
+ // original string. In other words, we over-allocate in the shrinking case.
+ uint32_t oldLen = this->Length();
+ auto r =
+ this->StartBulkWriteImpl(XPCOM_MAX(oldLen, newLength.value()), oldLen);
+ if (r.isErr()) {
+ return false;
+ }
+
+ if (aTarget.Length() >= aNewValue.Length()) {
+ // In the shrinking case, start filling the buffer from the beginning.
+ const uint32_t delta = (aTarget.Length() - aNewValue.Length());
+ for (i = 1; i < nonMatching.Length(); ++i) {
+ // When we move the i'th non-matching segment into position, we need to
+ // account for the characters deleted by the previous |i| replacements by
+ // subtracting |i * delta|.
+ const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin;
+ char_type* destinationSegmentPtr =
+ this->mData + nonMatching[i].mBegin - i * delta;
+ // Write the i'th replacement immediately before the new i'th non-matching
+ // segment.
+ char_traits::copy(destinationSegmentPtr - aNewValue.Length(),
+ aNewValue.Data(), aNewValue.Length());
+ char_traits::move(destinationSegmentPtr, sourceSegmentPtr,
+ nonMatching[i].mLength);
+ }
+ } else {
+ // In the growing case, start filling the buffer from the end.
+ const uint32_t delta = (aNewValue.Length() - aTarget.Length());
+ for (i = nonMatching.Length() - 1; i > 0; --i) {
+ // When we move the i'th non-matching segment into position, we need to
+ // account for the characters added by the previous |i| replacements by
+ // adding |i * delta|.
+ const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin;
+ char_type* destinationSegmentPtr =
+ this->mData + nonMatching[i].mBegin + i * delta;
+ char_traits::move(destinationSegmentPtr, sourceSegmentPtr,
+ nonMatching[i].mLength);
+ // Write the i'th replacement immediately before the new i'th non-matching
+ // segment.
+ char_traits::copy(destinationSegmentPtr - aNewValue.Length(),
+ aNewValue.Data(), aNewValue.Length());
+ }
+ }
+
+ // Adjust the length and make sure the string is null terminated.
+ this->FinishBulkWriteImpl(newLength.value());
+
+ return true;
+}
+
+/**
+ * nsTSubstring::Trim
+ */
+
+template <typename T>
+void nsTSubstring<T>::Trim(const std::string_view& aSet, bool aTrimLeading,
+ bool aTrimTrailing, bool aIgnoreQuotes) {
+ char_type* start = this->mData;
+ char_type* end = this->mData + this->mLength;
+
+ // skip over quotes if requested
+ if (aIgnoreQuotes && this->mLength > 2 &&
+ this->mData[0] == this->mData[this->mLength - 1] &&
+ (this->mData[0] == '\'' || this->mData[0] == '"')) {
+ ++start;
+ --end;
+ }
+
+ if (aTrimLeading) {
+ uint32_t cutStart = start - this->mData;
+ uint32_t cutLength = 0;
+
+ // walk forward from start to end
+ for (; start != end; ++start, ++cutLength) {
+ if ((*start & ~0x7F) || // non-ascii
+ aSet.find(char(*start)) == std::string_view::npos) {
+ break;
+ }
+ }
+
+ if (cutLength) {
+ this->Cut(cutStart, cutLength);
+
+ // reset iterators
+ start = this->mData + cutStart;
+ end = this->mData + this->mLength - cutStart;
+ }
+ }
+
+ if (aTrimTrailing) {
+ uint32_t cutEnd = end - this->mData;
+ uint32_t cutLength = 0;
+
+ // walk backward from end to start
+ --end;
+ for (; end >= start; --end, ++cutLength) {
+ if ((*end & ~0x7F) || // non-ascii
+ aSet.find(char(*end)) == std::string_view::npos) {
+ break;
+ }
+ }
+
+ if (cutLength) {
+ this->Cut(cutEnd - cutLength, cutLength);
+ }
+ }
+}
+
+/**
+ * nsTSubstring::CompressWhitespace.
+ */
+
+template <typename T>
+void nsTSubstring<T>::CompressWhitespace(bool aTrimLeading,
+ bool aTrimTrailing) {
+ // Quick exit
+ if (this->mLength == 0) {
+ return;
+ }
+
+ if (!this->EnsureMutable()) {
+ this->AllocFailed(this->mLength);
+ }
+
+ const ASCIIMaskArray& mask = mozilla::ASCIIMask::MaskWhitespace();
+
+ char_type* to = this->mData;
+ char_type* from = this->mData;
+ char_type* end = this->mData + this->mLength;
+
+ // Compresses runs of whitespace down to a normal space ' ' and convert
+ // any whitespace to a normal space. This assumes that whitespace is
+ // all standard 7-bit ASCII.
+ bool skipWS = aTrimLeading;
+ while (from < end) {
+ uint32_t theChar = *from++;
+ if (mozilla::ASCIIMask::IsMasked(mask, theChar)) {
+ if (!skipWS) {
+ *to++ = ' ';
+ skipWS = true;
+ }
+ } else {
+ *to++ = theChar;
+ skipWS = false;
+ }
+ }
+
+ // If we need to trim the trailing whitespace, back up one character.
+ if (aTrimTrailing && skipWS && to > this->mData) {
+ to--;
+ }
+
+ *to = char_type(0); // add the null
+ this->mLength = to - this->mData;
+}
+
+template class nsTSubstring<char>;
+template class nsTSubstring<char16_t>;
diff --git a/xpcom/string/nsTSubstring.h b/xpcom/string/nsTSubstring.h
new file mode 100644
index 0000000000..d54dcafd7b
--- /dev/null
+++ b/xpcom/string/nsTSubstring.h
@@ -0,0 +1,1454 @@
+/* -*- 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
diff --git a/xpcom/string/nsTSubstringTuple.cpp b/xpcom/string/nsTSubstringTuple.cpp
new file mode 100644
index 0000000000..3219cb19fa
--- /dev/null
+++ b/xpcom/string/nsTSubstringTuple.cpp
@@ -0,0 +1,92 @@
+/* -*- 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/. */
+
+#include "nsTSubstringTuple.h"
+#include "mozilla/CheckedInt.h"
+
+/**
+ * computes the aggregate string length
+ */
+
+template <typename T>
+typename nsTSubstringTuple<T>::size_type nsTSubstringTuple<T>::Length() const {
+ mozilla::CheckedInt<size_type> len;
+ if (mHead) {
+ len = mHead->Length();
+ } else {
+ len = mFragA->Length();
+ }
+
+ len += mFragB->Length();
+ MOZ_RELEASE_ASSERT(len.isValid(), "Substring tuple length is invalid");
+ return len.value();
+}
+
+/**
+ * writes the aggregate string to the given buffer. aBufLen is assumed
+ * to be equal to or greater than the value returned by the Length()
+ * method. the string written to |aBuf| is not null-terminated.
+ */
+
+template <typename T>
+void nsTSubstringTuple<T>::WriteTo(char_type* aBuf, size_type aBufLen) const {
+ MOZ_RELEASE_ASSERT(aBufLen >= mFragB->Length(), "buffer too small");
+ size_type headLen = aBufLen - mFragB->Length();
+ if (mHead) {
+ mHead->WriteTo(aBuf, headLen);
+ } else {
+ MOZ_RELEASE_ASSERT(mFragA->Length() == headLen, "buffer incorrectly sized");
+ char_traits::copy(aBuf, mFragA->Data(), mFragA->Length());
+ }
+
+ char_traits::copy(aBuf + headLen, mFragB->Data(), mFragB->Length());
+}
+
+/**
+ * returns true if this tuple is dependent on (i.e., overlapping with)
+ * the given char sequence.
+ */
+
+template <typename T>
+bool nsTSubstringTuple<T>::IsDependentOn(const char_type* aStart,
+ const char_type* aEnd) const {
+ // we start with the right-most fragment since it is faster to check.
+
+ if (mFragB->IsDependentOn(aStart, aEnd)) {
+ return true;
+ }
+
+ if (mHead) {
+ return mHead->IsDependentOn(aStart, aEnd);
+ }
+
+ return mFragA->IsDependentOn(aStart, aEnd);
+}
+
+template <typename T>
+auto nsTSubstringTuple<T>::IsDependentOnWithLength(const char_type* aStart,
+ const char_type* aEnd) const
+ -> std::pair<bool, size_type> {
+ // we start with the right-most fragment since it is faster to check for
+ // dependency.
+ const bool rightDependentOn = mFragB->IsDependentOn(aStart, aEnd);
+
+ if (rightDependentOn) {
+ return {true, Length()};
+ }
+
+ const auto [leftDependentOn, leftLen] =
+ mHead ? mHead->IsDependentOnWithLength(aStart, aEnd)
+ : std::pair{mFragA->IsDependentOn(aStart, aEnd), mFragA->Length()};
+
+ const auto checkedLen =
+ mozilla::CheckedInt<size_type>{leftLen} + mFragB->Length();
+ MOZ_RELEASE_ASSERT(checkedLen.isValid(), "Substring tuple length is invalid");
+ return {leftDependentOn, checkedLen.value()};
+}
+
+template class nsTSubstringTuple<char>;
+template class nsTSubstringTuple<char16_t>;
diff --git a/xpcom/string/nsTSubstringTuple.h b/xpcom/string/nsTSubstringTuple.h
new file mode 100644
index 0000000000..dff8dedca9
--- /dev/null
+++ b/xpcom/string/nsTSubstringTuple.h
@@ -0,0 +1,89 @@
+/* -*- 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 nsTSubstringTuple_h
+#define nsTSubstringTuple_h
+
+#include "mozilla/Attributes.h"
+#include "nsTStringRepr.h"
+
+/**
+ * nsTSubstringTuple
+ *
+ * Represents a tuple of string fragments. Built as a recursive binary tree.
+ * It is used to implement the concatenation of two or more string objects.
+ *
+ * NOTE: This class is a private implementation detail and should never be
+ * referenced outside the string code.
+ */
+template <typename T>
+class MOZ_TEMPORARY_CLASS nsTSubstringTuple {
+ public:
+ typedef T char_type;
+ typedef nsCharTraits<char_type> char_traits;
+
+ typedef nsTSubstringTuple<T> self_type;
+ typedef mozilla::detail::nsTStringRepr<char_type> base_string_type;
+ typedef size_t size_type;
+
+ public:
+ nsTSubstringTuple(const base_string_type* aStrA,
+ const base_string_type* aStrB)
+ : mHead(nullptr), mFragA(aStrA), mFragB(aStrB) {}
+
+ nsTSubstringTuple(const self_type& aHead, const base_string_type* aStrB)
+ : mHead(&aHead),
+ mFragA(nullptr), // this fragment is ignored when aHead != nullptr
+ mFragB(aStrB) {}
+
+ /**
+ * computes the aggregate string length
+ */
+ size_type Length() const;
+
+ /**
+ * writes the aggregate string to the given buffer. bufLen is assumed
+ * to be equal to or greater than the value returned by the Length()
+ * method. the string written to |buf| is not null-terminated.
+ */
+ void WriteTo(char_type* aBuf, size_type aBufLen) const;
+
+ /**
+ * returns true if this tuple is dependent on (i.e., overlapping with)
+ * the given char sequence.
+ */
+ bool IsDependentOn(const char_type* aStart, const char_type* aEnd) const;
+
+ /**
+ * returns a pair of the results of IsDependentOn() and Length(). This is more
+ * efficient than calling both functions subsequently, as this traverses the
+ * tree only once.
+ */
+ std::pair<bool, size_type> IsDependentOnWithLength(
+ const char_type* aStart, const char_type* aEnd) const;
+
+ private:
+ const self_type* const mHead;
+ const base_string_type* const mFragA;
+ const base_string_type* const mFragB;
+};
+
+template <typename T>
+inline const nsTSubstringTuple<T> operator+(
+ const mozilla::detail::nsTStringRepr<T>& aStrA,
+ const mozilla::detail::nsTStringRepr<T>& aStrB) {
+ return nsTSubstringTuple<T>(&aStrA, &aStrB);
+}
+
+template <typename T>
+inline const nsTSubstringTuple<T> operator+(
+ const nsTSubstringTuple<T>& aHead,
+ const mozilla::detail::nsTStringRepr<T>& aStrB) {
+ return nsTSubstringTuple<T>(aHead, &aStrB);
+}
+
+#endif
diff --git a/xpcom/string/nsTextFormatter.cpp b/xpcom/string/nsTextFormatter.cpp
new file mode 100644
index 0000000000..076b9c12fc
--- /dev/null
+++ b/xpcom/string/nsTextFormatter.cpp
@@ -0,0 +1,899 @@
+/* -*- 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/. */
+
+/*
+ * Portable safe sprintf code.
+ *
+ * Code based on mozilla/nsprpub/src/io/prprf.c rev 3.7
+ *
+ * Contributor(s):
+ * Kipp E.B. Hickman <kipp@netscape.com> (original author)
+ * Frank Yung-Fong Tang <ftang@netscape.com>
+ * Daniele Nicolodi <daniele@grinta.net>
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include "prdtoa.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Sprintf.h"
+#include "nsCRTGlue.h"
+#include "nsTextFormatter.h"
+
+struct nsTextFormatter::SprintfStateStr {
+ int (*stuff)(SprintfStateStr* aState, const char16_t* aStr, uint32_t aLen);
+
+ char16_t* base;
+ char16_t* cur;
+ uint32_t maxlen;
+
+ void* stuffclosure;
+};
+
+#define _LEFT 0x1
+#define _SIGNED 0x2
+#define _SPACED 0x4
+#define _ZEROS 0x8
+#define _NEG 0x10
+#define _UNSIGNED 0x20
+
+#define ELEMENTS_OF(array_) (sizeof(array_) / sizeof(array_[0]))
+
+/*
+** Fill into the buffer using the data in src
+*/
+int nsTextFormatter::fill2(SprintfStateStr* aState, const char16_t* aSrc,
+ int aSrcLen, int aWidth, int aFlags) {
+ char16_t space = ' ';
+ int rv;
+
+ aWidth -= aSrcLen;
+ /* Right adjusting */
+ if ((aWidth > 0) && ((aFlags & _LEFT) == 0)) {
+ if (aFlags & _ZEROS) {
+ space = '0';
+ }
+ while (--aWidth >= 0) {
+ rv = (*aState->stuff)(aState, &space, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ }
+
+ /* Copy out the source data */
+ rv = (*aState->stuff)(aState, aSrc, aSrcLen);
+ if (rv < 0) {
+ return rv;
+ }
+
+ /* Left adjusting */
+ if ((aWidth > 0) && ((aFlags & _LEFT) != 0)) {
+ while (--aWidth >= 0) {
+ rv = (*aState->stuff)(aState, &space, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Fill a number. The order is: optional-sign zero-filling conversion-digits
+*/
+int nsTextFormatter::fill_n(nsTextFormatter::SprintfStateStr* aState,
+ const char16_t* aSrc, int aSrcLen, int aWidth,
+ int aPrec, int aFlags) {
+ int zerowidth = 0;
+ int precwidth = 0;
+ int signwidth = 0;
+ int leftspaces = 0;
+ int rightspaces = 0;
+ int cvtwidth;
+ int rv;
+ char16_t sign;
+ char16_t space = ' ';
+ char16_t zero = '0';
+
+ if ((aFlags & _UNSIGNED) == 0) {
+ if (aFlags & _NEG) {
+ sign = '-';
+ signwidth = 1;
+ } else if (aFlags & _SIGNED) {
+ sign = '+';
+ signwidth = 1;
+ } else if (aFlags & _SPACED) {
+ sign = ' ';
+ signwidth = 1;
+ }
+ }
+ cvtwidth = signwidth + aSrcLen;
+
+ if (aPrec > 0) {
+ if (aPrec > aSrcLen) {
+ /* Need zero filling */
+ precwidth = aPrec - aSrcLen;
+ cvtwidth += precwidth;
+ }
+ }
+
+ if ((aFlags & _ZEROS) && (aPrec < 0)) {
+ if (aWidth > cvtwidth) {
+ /* Zero filling */
+ zerowidth = aWidth - cvtwidth;
+ cvtwidth += zerowidth;
+ }
+ }
+
+ if (aFlags & _LEFT) {
+ if (aWidth > cvtwidth) {
+ /* Space filling on the right (i.e. left adjusting) */
+ rightspaces = aWidth - cvtwidth;
+ }
+ } else {
+ if (aWidth > cvtwidth) {
+ /* Space filling on the left (i.e. right adjusting) */
+ leftspaces = aWidth - cvtwidth;
+ }
+ }
+ while (--leftspaces >= 0) {
+ rv = (*aState->stuff)(aState, &space, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ if (signwidth) {
+ rv = (*aState->stuff)(aState, &sign, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ while (--precwidth >= 0) {
+ rv = (*aState->stuff)(aState, &space, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ while (--zerowidth >= 0) {
+ rv = (*aState->stuff)(aState, &zero, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ rv = (*aState->stuff)(aState, aSrc, aSrcLen);
+ if (rv < 0) {
+ return rv;
+ }
+ while (--rightspaces >= 0) {
+ rv = (*aState->stuff)(aState, &space, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ return 0;
+}
+
+/*
+** Convert a 64-bit integer into its printable form
+*/
+int nsTextFormatter::cvt_ll(SprintfStateStr* aState, uint64_t aNum, int aWidth,
+ int aPrec, int aRadix, int aFlags,
+ const char16_t* aHexStr) {
+ char16_t cvtbuf[100];
+ char16_t* cvt;
+ int digits;
+
+ /* according to the man page this needs to happen */
+ if (aPrec == 0 && aNum == 0) {
+ return 0;
+ }
+
+ /*
+ ** Converting decimal is a little tricky. In the unsigned case we
+ ** need to stop when we hit 10 digits. In the signed case, we can
+ ** stop when the number is zero.
+ */
+ cvt = &cvtbuf[0] + ELEMENTS_OF(cvtbuf);
+ digits = 0;
+ while (aNum != 0) {
+ uint64_t quot = aNum / aRadix;
+ uint64_t rem = aNum % aRadix;
+ *--cvt = aHexStr[rem & 0xf];
+ digits++;
+ aNum = quot;
+ }
+ if (digits == 0) {
+ *--cvt = '0';
+ digits++;
+ }
+
+ /*
+ ** Now that we have the number converted without its sign, deal with
+ ** the sign and zero padding.
+ */
+ return fill_n(aState, cvt, digits, aWidth, aPrec, aFlags);
+}
+
+/*
+** Convert a double precision floating point number into its printable
+** form.
+*/
+int nsTextFormatter::cvt_f(SprintfStateStr* aState, double aDouble, int aWidth,
+ int aPrec, const char16_t aType, int aFlags) {
+ int mode = 2;
+ int decpt;
+ int sign;
+ char buf[256];
+ char* bufp = buf;
+ int bufsz = 256;
+ char num[256];
+ char* nump;
+ char* endnum;
+ int numdigits = 0;
+ char exp = 'e';
+
+ if (aPrec == -1) {
+ aPrec = 6;
+ } else if (aPrec > 50) {
+ // limit precision to avoid PR_dtoa bug 108335
+ // and to prevent buffers overflows
+ aPrec = 50;
+ }
+
+ switch (aType) {
+ case 'f':
+ numdigits = aPrec;
+ mode = 3;
+ break;
+ case 'E':
+ exp = 'E';
+ [[fallthrough]];
+ case 'e':
+ numdigits = aPrec + 1;
+ mode = 2;
+ break;
+ case 'G':
+ exp = 'E';
+ [[fallthrough]];
+ case 'g':
+ if (aPrec == 0) {
+ aPrec = 1;
+ }
+ numdigits = aPrec;
+ mode = 2;
+ break;
+ default:
+ NS_ERROR("invalid aType passed to cvt_f");
+ }
+
+ if (PR_dtoa(aDouble, mode, numdigits, &decpt, &sign, &endnum, num, bufsz) ==
+ PR_FAILURE) {
+ buf[0] = '\0';
+ return -1;
+ }
+ numdigits = endnum - num;
+ nump = num;
+
+ if (sign) {
+ *bufp++ = '-';
+ } else if (aFlags & _SIGNED) {
+ *bufp++ = '+';
+ }
+
+ if (decpt == 9999) {
+ while ((*bufp++ = *nump++)) {
+ }
+ } else {
+ switch (aType) {
+ case 'E':
+ case 'e':
+
+ *bufp++ = *nump++;
+ if (aPrec > 0) {
+ *bufp++ = '.';
+ while (*nump) {
+ *bufp++ = *nump++;
+ aPrec--;
+ }
+ while (aPrec-- > 0) {
+ *bufp++ = '0';
+ }
+ }
+ *bufp++ = exp;
+
+ ::snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1);
+ break;
+
+ case 'f':
+
+ if (decpt < 1) {
+ *bufp++ = '0';
+ if (aPrec > 0) {
+ *bufp++ = '.';
+ while (decpt++ && aPrec-- > 0) {
+ *bufp++ = '0';
+ }
+ while (*nump && aPrec-- > 0) {
+ *bufp++ = *nump++;
+ }
+ while (aPrec-- > 0) {
+ *bufp++ = '0';
+ }
+ }
+ } else {
+ while (*nump && decpt-- > 0) {
+ *bufp++ = *nump++;
+ }
+ while (decpt-- > 0) {
+ *bufp++ = '0';
+ }
+ if (aPrec > 0) {
+ *bufp++ = '.';
+ while (*nump && aPrec-- > 0) {
+ *bufp++ = *nump++;
+ }
+ while (aPrec-- > 0) {
+ *bufp++ = '0';
+ }
+ }
+ }
+ *bufp = '\0';
+ break;
+
+ case 'G':
+ case 'g':
+
+ if ((decpt < -3) || ((decpt - 1) >= aPrec)) {
+ *bufp++ = *nump++;
+ numdigits--;
+ if (numdigits > 0) {
+ *bufp++ = '.';
+ while (*nump) {
+ *bufp++ = *nump++;
+ }
+ }
+ *bufp++ = exp;
+ ::snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1);
+ } else {
+ if (decpt < 1) {
+ *bufp++ = '0';
+ if (aPrec > 0) {
+ *bufp++ = '.';
+ while (decpt++) {
+ *bufp++ = '0';
+ }
+ while (*nump) {
+ *bufp++ = *nump++;
+ }
+ }
+ } else {
+ while (*nump && decpt-- > 0) {
+ *bufp++ = *nump++;
+ numdigits--;
+ }
+ while (decpt-- > 0) {
+ *bufp++ = '0';
+ }
+ if (numdigits > 0) {
+ *bufp++ = '.';
+ while (*nump) {
+ *bufp++ = *nump++;
+ }
+ }
+ }
+ *bufp = '\0';
+ }
+ }
+ }
+
+ char16_t rbuf[256];
+ char16_t* rbufp = rbuf;
+ bufp = buf;
+ // cast to char16_t
+ while ((*rbufp++ = *bufp++)) {
+ }
+ *rbufp = '\0';
+
+ return fill2(aState, rbuf, NS_strlen(rbuf), aWidth, aFlags);
+}
+
+/*
+** Convert a string into its printable form. |aWidth| is the output
+** width. |aPrec| is the maximum number of characters of |aStr| to output,
+** where -1 means until NUL.
+*/
+int nsTextFormatter::cvt_S(SprintfStateStr* aState, const char16_t* aStr,
+ int aWidth, int aPrec, int aFlags) {
+ int slen;
+
+ if (aPrec == 0) {
+ return 0;
+ }
+
+ /* Limit string length by precision value */
+ slen = aStr ? NS_strlen(aStr) : 6;
+ if (aPrec > 0) {
+ if (aPrec < slen) {
+ slen = aPrec;
+ }
+ }
+
+ /* and away we go */
+ return fill2(aState, aStr ? aStr : u"(null)", slen, aWidth, aFlags);
+}
+
+/*
+** Convert a string into its printable form. |aWidth| is the output
+** width. |aPrec| is the maximum number of characters of |aStr| to output,
+** where -1 means until NUL.
+*/
+int nsTextFormatter::cvt_s(nsTextFormatter::SprintfStateStr* aState,
+ const char* aStr, int aWidth, int aPrec,
+ int aFlags) {
+ // Be sure to handle null the same way as %S.
+ if (aStr == nullptr) {
+ return cvt_S(aState, nullptr, aWidth, aPrec, aFlags);
+ }
+ NS_ConvertUTF8toUTF16 utf16Val(aStr);
+ return cvt_S(aState, utf16Val.get(), aWidth, aPrec, aFlags);
+}
+
+/*
+** The workhorse sprintf code.
+*/
+int nsTextFormatter::dosprintf(SprintfStateStr* aState, const char16_t* aFmt,
+ mozilla::Span<BoxedValue> aValues) {
+ static const char16_t space = ' ';
+ static const char16_t hex[] = u"0123456789abcdef";
+ static const char16_t HEX[] = u"0123456789ABCDEF";
+ static const BoxedValue emptyString(u"");
+
+ char16_t c;
+ int flags, width, prec, radix;
+
+ const char16_t* hexp;
+
+ // Next argument for non-numbered arguments.
+ size_t nextNaturalArg = 0;
+ // True if we ever saw a numbered argument.
+ bool sawNumberedArg = false;
+
+ while ((c = *aFmt++) != 0) {
+ int rv;
+
+ if (c != '%') {
+ rv = (*aState->stuff)(aState, aFmt - 1, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ continue;
+ }
+
+ // Save the location of the "%" in case we decide it isn't a
+ // format and want to just emit the text from the format string.
+ const char16_t* percentPointer = aFmt - 1;
+
+ /*
+ ** Gobble up the % format string. Hopefully we have handled all
+ ** of the strange cases!
+ */
+ flags = 0;
+ c = *aFmt++;
+ if (c == '%') {
+ /* quoting a % with %% */
+ rv = (*aState->stuff)(aState, aFmt - 1, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ continue;
+ }
+
+ // Check for a numbered argument.
+ bool sawWidth = false;
+ const BoxedValue* thisArg = nullptr;
+ if (c >= '0' && c <= '9') {
+ size_t argNumber = 0;
+ while (c && c >= '0' && c <= '9') {
+ argNumber = (argNumber * 10) + (c - '0');
+ c = *aFmt++;
+ }
+
+ if (c == '$') {
+ // Mixing numbered arguments and implicit arguments is
+ // disallowed.
+ if (nextNaturalArg > 0) {
+ return -1;
+ }
+
+ c = *aFmt++;
+
+ // Numbered arguments start at 1.
+ --argNumber;
+ if (argNumber >= aValues.Length()) {
+ // A correctness issue but not a safety issue.
+ MOZ_ASSERT(false);
+ thisArg = &emptyString;
+ } else {
+ thisArg = &aValues[argNumber];
+ }
+ sawNumberedArg = true;
+ } else {
+ width = argNumber;
+ sawWidth = true;
+ }
+ }
+
+ if (!sawWidth) {
+ /*
+ * Examine optional flags. Note that we do not implement the
+ * '#' flag of sprintf(). The ANSI C spec. of the '#' flag is
+ * somewhat ambiguous and not ideal, which is perhaps why
+ * the various sprintf() implementations are inconsistent
+ * on this feature.
+ */
+ while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) {
+ if (c == '-') {
+ flags |= _LEFT;
+ }
+ if (c == '+') {
+ flags |= _SIGNED;
+ }
+ if (c == ' ') {
+ flags |= _SPACED;
+ }
+ if (c == '0') {
+ flags |= _ZEROS;
+ }
+ c = *aFmt++;
+ }
+ if (flags & _SIGNED) {
+ flags &= ~_SPACED;
+ }
+ if (flags & _LEFT) {
+ flags &= ~_ZEROS;
+ }
+
+ /* width */
+ if (c == '*') {
+ // Not supported with numbered arguments.
+ if (sawNumberedArg) {
+ return -1;
+ }
+
+ if (nextNaturalArg >= aValues.Length() ||
+ !aValues[nextNaturalArg].IntCompatible()) {
+ // A correctness issue but not a safety issue.
+ MOZ_ASSERT(false);
+ width = 0;
+ } else {
+ width = aValues[nextNaturalArg++].mValue.mInt;
+ }
+ c = *aFmt++;
+ } else {
+ width = 0;
+ while ((c >= '0') && (c <= '9')) {
+ width = (width * 10) + (c - '0');
+ c = *aFmt++;
+ }
+ }
+ }
+
+ /* precision */
+ prec = -1;
+ if (c == '.') {
+ c = *aFmt++;
+ if (c == '*') {
+ // Not supported with numbered arguments.
+ if (sawNumberedArg) {
+ return -1;
+ }
+
+ if (nextNaturalArg >= aValues.Length() ||
+ !aValues[nextNaturalArg].IntCompatible()) {
+ // A correctness issue but not a safety issue.
+ MOZ_ASSERT(false);
+ } else {
+ prec = aValues[nextNaturalArg++].mValue.mInt;
+ }
+ c = *aFmt++;
+ } else {
+ prec = 0;
+ while ((c >= '0') && (c <= '9')) {
+ prec = (prec * 10) + (c - '0');
+ c = *aFmt++;
+ }
+ }
+ }
+
+ // If the argument isn't known yet, find it now. This is done
+ // after the width and precision code, in case '*' was used.
+ if (thisArg == nullptr) {
+ // Mixing numbered arguments and implicit arguments is
+ // disallowed.
+ if (sawNumberedArg) {
+ return -1;
+ }
+
+ if (nextNaturalArg >= aValues.Length()) {
+ // A correctness issue but not a safety issue.
+ MOZ_ASSERT(false);
+ thisArg = &emptyString;
+ } else {
+ thisArg = &aValues[nextNaturalArg++];
+ }
+ }
+
+ /* Size. Defaults to 32 bits. */
+ uint64_t mask = UINT32_MAX;
+ if (c == 'h') {
+ c = *aFmt++;
+ mask = UINT16_MAX;
+ } else if (c == 'L') {
+ c = *aFmt++;
+ mask = UINT64_MAX;
+ } else if (c == 'l') {
+ c = *aFmt++;
+ if (c == 'l') {
+ c = *aFmt++;
+ mask = UINT64_MAX;
+ } else {
+ mask = UINT32_MAX;
+ }
+ }
+
+ /* format */
+ hexp = hex;
+ radix = 10;
+ // Several `MOZ_ASSERT`s below check for argument compatibility
+ // with the format specifier. These are only debug assertions,
+ // not release assertions, and exist to catch problems in C++
+ // callers of `nsTextFormatter`, as we do not have compile-time
+ // checking of format strings. In release mode, these assertions
+ // will be no-ops, and we will fall through to printing the
+ // argument based on the known type of the argument.
+ switch (c) {
+ case 'd':
+ case 'i': /* decimal/integer */
+ MOZ_ASSERT(thisArg->IntCompatible());
+ break;
+
+ case 'o': /* octal */
+ MOZ_ASSERT(thisArg->IntCompatible());
+ radix = 8;
+ flags |= _UNSIGNED;
+ break;
+
+ case 'u': /* unsigned decimal */
+ MOZ_ASSERT(thisArg->IntCompatible());
+ radix = 10;
+ flags |= _UNSIGNED;
+ break;
+
+ case 'x': /* unsigned hex */
+ MOZ_ASSERT(thisArg->IntCompatible());
+ radix = 16;
+ flags |= _UNSIGNED;
+ break;
+
+ case 'X': /* unsigned HEX */
+ MOZ_ASSERT(thisArg->IntCompatible());
+ radix = 16;
+ hexp = HEX;
+ flags |= _UNSIGNED;
+ break;
+
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ MOZ_ASSERT(thisArg->mKind == DOUBLE);
+ // Type-based printing below.
+ break;
+
+ case 'S':
+ MOZ_ASSERT(thisArg->mKind == STRING16);
+ // Type-based printing below.
+ break;
+
+ case 's':
+ MOZ_ASSERT(thisArg->mKind == STRING);
+ // Type-based printing below.
+ break;
+
+ case 'c': {
+ if (!thisArg->IntCompatible()) {
+ MOZ_ASSERT(false);
+ // Type-based printing below.
+ break;
+ }
+
+ if ((flags & _LEFT) == 0) {
+ while (width-- > 1) {
+ rv = (*aState->stuff)(aState, &space, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ }
+ char16_t ch = thisArg->mValue.mInt;
+ rv = (*aState->stuff)(aState, &ch, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ if (flags & _LEFT) {
+ while (width-- > 1) {
+ rv = (*aState->stuff)(aState, &space, 1);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ }
+ }
+ continue;
+
+ case 'p':
+ if (!thisArg->PointerCompatible()) {
+ MOZ_ASSERT(false);
+ break;
+ }
+ static_assert(sizeof(uint64_t) >= sizeof(void*),
+ "pointers are larger than 64 bits");
+ rv = cvt_ll(aState, uintptr_t(thisArg->mValue.mPtr), width, prec, 16,
+ flags | _UNSIGNED, hexp);
+ if (rv < 0) {
+ return rv;
+ }
+ continue;
+
+ case 'n':
+ if (thisArg->mKind != INTPOINTER) {
+ return -1;
+ }
+
+ if (thisArg->mValue.mIntPtr != nullptr) {
+ *thisArg->mValue.mIntPtr = aState->cur - aState->base;
+ }
+ continue;
+
+ default:
+ /* Not a % token after all... skip it */
+ rv = (*aState->stuff)(aState, percentPointer, aFmt - percentPointer);
+ if (rv < 0) {
+ return rv;
+ }
+ continue;
+ }
+
+ // If we get here, we want to handle the argument according to its
+ // actual type; modified by the flags as appropriate.
+ switch (thisArg->mKind) {
+ case INT:
+ case UINT: {
+ int64_t val = thisArg->mValue.mInt;
+ if ((flags & _UNSIGNED) == 0 && val < 0) {
+ val = -val;
+ flags |= _NEG;
+ }
+ rv = cvt_ll(aState, uint64_t(val) & mask, width, prec, radix, flags,
+ hexp);
+ } break;
+ case INTPOINTER:
+ case POINTER:
+ // Always treat these as unsigned hex, no matter the format.
+ static_assert(sizeof(uint64_t) >= sizeof(void*),
+ "pointers are larger than 64 bits");
+ rv = cvt_ll(aState, uintptr_t(thisArg->mValue.mPtr), width, prec, 16,
+ flags | _UNSIGNED, hexp);
+ break;
+ case DOUBLE:
+ if (c != 'f' && c != 'E' && c != 'e' && c != 'G' && c != 'g') {
+ // Pick some default.
+ c = 'g';
+ }
+ rv = cvt_f(aState, thisArg->mValue.mDouble, width, prec, c, flags);
+ break;
+ case STRING:
+ rv = cvt_s(aState, thisArg->mValue.mString, width, prec, flags);
+ break;
+ case STRING16:
+ rv = cvt_S(aState, thisArg->mValue.mString16, width, prec, flags);
+ break;
+ default:
+ // Can't happen.
+ MOZ_ASSERT(0);
+ }
+
+ if (rv < 0) {
+ return rv;
+ }
+ }
+
+ return 0;
+}
+
+/************************************************************************/
+
+int nsTextFormatter::StringStuff(nsTextFormatter::SprintfStateStr* aState,
+ const char16_t* aStr, uint32_t aLen) {
+ ptrdiff_t off = aState->cur - aState->base;
+
+ nsAString* str = static_cast<nsAString*>(aState->stuffclosure);
+ str->Append(aStr, aLen);
+
+ aState->base = str->BeginWriting();
+ aState->cur = aState->base + off;
+
+ return 0;
+}
+
+void nsTextFormatter::vssprintf(nsAString& aOut, const char16_t* aFmt,
+ mozilla::Span<BoxedValue> aValues) {
+ SprintfStateStr ss;
+ ss.stuff = StringStuff;
+ ss.base = 0;
+ ss.cur = 0;
+ ss.maxlen = 0;
+ ss.stuffclosure = &aOut;
+
+ aOut.Truncate();
+ dosprintf(&ss, aFmt, aValues);
+}
+
+/*
+** Stuff routine that discards overflow data
+*/
+int nsTextFormatter::LimitStuff(SprintfStateStr* aState, const char16_t* aStr,
+ uint32_t aLen) {
+ uint32_t limit = aState->maxlen - (aState->cur - aState->base);
+
+ if (aLen > limit) {
+ aLen = limit;
+ }
+ while (aLen) {
+ --aLen;
+ *aState->cur++ = *aStr++;
+ }
+ return 0;
+}
+
+uint32_t nsTextFormatter::vsnprintf(char16_t* aOut, uint32_t aOutLen,
+ const char16_t* aFmt,
+ mozilla::Span<BoxedValue> aValues) {
+ SprintfStateStr ss;
+
+ MOZ_ASSERT((int32_t)aOutLen > 0);
+ if ((int32_t)aOutLen <= 0) {
+ return 0;
+ }
+
+ ss.stuff = LimitStuff;
+ ss.base = aOut;
+ ss.cur = aOut;
+ ss.maxlen = aOutLen;
+ int result = dosprintf(&ss, aFmt, aValues);
+
+ if (ss.cur == ss.base) {
+ return 0;
+ }
+
+ // Append a NUL. However, be sure not to count it in the returned
+ // length.
+ if (ss.cur - ss.base >= ptrdiff_t(ss.maxlen)) {
+ --ss.cur;
+ }
+ *ss.cur = '\0';
+
+ // Check the result now, so that an unterminated string can't
+ // possibly escape.
+ if (result < 0) {
+ return -1;
+ }
+
+ return ss.cur - ss.base;
+}
diff --git a/xpcom/string/nsTextFormatter.h b/xpcom/string/nsTextFormatter.h
new file mode 100644
index 0000000000..f571043da2
--- /dev/null
+++ b/xpcom/string/nsTextFormatter.h
@@ -0,0 +1,172 @@
+/* -*- 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/. */
+
+/*
+ * This code was copied from xpcom/ds/nsTextFormatter r1.3
+ * Memory model and Frozen linkage changes only.
+ * -- Prasad <prasad@medhas.org>
+ */
+
+#ifndef nsTextFormatter_h___
+#define nsTextFormatter_h___
+
+/*
+ ** API for PR printf like routines. Supports the following formats
+ ** %d - decimal
+ ** %u - unsigned decimal
+ ** %x - unsigned hex
+ ** %X - unsigned uppercase hex
+ ** %o - unsigned octal
+ ** %hd, %hu, %hx, %hX, %ho - 16-bit versions of above
+ ** %ld, %lu, %lx, %lX, %lo - 32-bit versions of above
+ ** %lld, %llu, %llx, %llX, %llo - 64 bit versions of above
+ ** %s - utf8 string
+ ** %S - char16_t string
+ ** %c - character
+ ** %p - pointer (deals with machine dependent pointer size)
+ ** %f - float
+ ** %g - float
+ */
+#include <stdio.h>
+#include <stdarg.h>
+#include "nscore.h"
+#include "nsString.h"
+#include "mozilla/Span.h"
+
+#ifdef XPCOM_GLUE
+# error \
+ "nsTextFormatter is not available in the standalone glue due to NSPR dependencies."
+#endif
+
+class nsTextFormatter {
+ public:
+ /*
+ * sprintf into a fixed size buffer. Guarantees that the buffer is null
+ * terminated. Returns the length of the written output, NOT including the
+ * null terminator, or (uint32_t)-1 if an error occurs.
+ */
+ template <typename... T>
+ static uint32_t snprintf(char16_t* aOut, uint32_t aOutLen,
+ const char16_t* aFmt, T... aArgs) {
+ BoxedValue values[] = {BoxedValue(aArgs)...};
+ return vsnprintf(aOut, aOutLen, aFmt,
+ mozilla::Span(values, sizeof...(aArgs)));
+ }
+
+ /*
+ * sprintf into an existing nsAString, overwriting any contents it already
+ * has. Infallible.
+ */
+ template <typename... T>
+ static void ssprintf(nsAString& aOut, const char16_t* aFmt, T... aArgs) {
+ BoxedValue values[] = {BoxedValue(aArgs)...};
+ vssprintf(aOut, aFmt, mozilla::Span(values, sizeof...(aArgs)));
+ }
+
+ private:
+ enum ArgumentKind {
+ INT,
+ UINT,
+ POINTER,
+ DOUBLE,
+ STRING,
+ STRING16,
+ INTPOINTER,
+ };
+
+ union ValueUnion {
+ int64_t mInt;
+ uint64_t mUInt;
+ void const* mPtr;
+ double mDouble;
+ char const* mString;
+ char16_t const* mString16;
+ int* mIntPtr;
+ };
+
+ struct BoxedValue {
+ ArgumentKind mKind;
+ ValueUnion mValue;
+
+ explicit BoxedValue(int aArg) : mKind(INT) { mValue.mInt = aArg; }
+
+ explicit BoxedValue(unsigned int aArg) : mKind(UINT) {
+ mValue.mUInt = aArg;
+ }
+
+ explicit BoxedValue(long aArg) : mKind(INT) { mValue.mInt = aArg; }
+
+ explicit BoxedValue(unsigned long aArg) : mKind(UINT) {
+ mValue.mUInt = aArg;
+ }
+
+ explicit BoxedValue(long long aArg) : mKind(INT) { mValue.mInt = aArg; }
+
+ explicit BoxedValue(unsigned long long aArg) : mKind(UINT) {
+ mValue.mUInt = aArg;
+ }
+
+ explicit BoxedValue(const void* aArg) : mKind(POINTER) {
+ mValue.mPtr = aArg;
+ }
+
+ explicit BoxedValue(double aArg) : mKind(DOUBLE) { mValue.mDouble = aArg; }
+
+ explicit BoxedValue(const char* aArg) : mKind(STRING) {
+ mValue.mString = aArg;
+ }
+
+ explicit BoxedValue(const char16_t* aArg) : mKind(STRING16) {
+ mValue.mString16 = aArg;
+ }
+
+#if defined(MOZ_USE_CHAR16_WRAPPER)
+ explicit BoxedValue(const char16ptr_t aArg) : mKind(STRING16) {
+ mValue.mString16 = aArg;
+ }
+
+#endif
+
+ explicit BoxedValue(int* aArg) : mKind(INTPOINTER) {
+ mValue.mIntPtr = aArg;
+ }
+
+ bool IntCompatible() const { return mKind == INT || mKind == UINT; }
+
+ bool PointerCompatible() const {
+ return mKind == POINTER || mKind == STRING || mKind == STRING16 ||
+ mKind == INTPOINTER;
+ }
+ };
+
+ struct SprintfStateStr;
+
+ static int fill2(SprintfStateStr* aState, const char16_t* aSrc, int aSrcLen,
+ int aWidth, int aFlags);
+ static int fill_n(SprintfStateStr* aState, const char16_t* aSrc, int aSrcLen,
+ int aWidth, int aPrec, int aFlags);
+ static int cvt_ll(SprintfStateStr* aState, uint64_t aNum, int aWidth,
+ int aPrec, int aRadix, int aFlags, const char16_t* aHexStr);
+ static int cvt_f(SprintfStateStr* aState, double aDouble, int aWidth,
+ int aPrec, const char16_t aType, int aFlags);
+ static int cvt_S(SprintfStateStr* aState, const char16_t* aStr, int aWidth,
+ int aPrec, int aFlags);
+ static int cvt_s(SprintfStateStr* aState, const char* aStr, int aWidth,
+ int aPrec, int aFlags);
+ static int dosprintf(SprintfStateStr* aState, const char16_t* aFmt,
+ mozilla::Span<BoxedValue> aValues);
+ static int StringStuff(SprintfStateStr* aState, const char16_t* aStr,
+ uint32_t aLen);
+ static int LimitStuff(SprintfStateStr* aState, const char16_t* aStr,
+ uint32_t aLen);
+ static uint32_t vsnprintf(char16_t* aOut, uint32_t aOutLen,
+ const char16_t* aFmt,
+ mozilla::Span<BoxedValue> aValues);
+ static void vssprintf(nsAString& aOut, const char16_t* aFmt,
+ mozilla::Span<BoxedValue> aValues);
+};
+
+#endif /* nsTextFormatter_h___ */
diff --git a/xpcom/string/nsUTF8Utils.h b/xpcom/string/nsUTF8Utils.h
new file mode 100644
index 0000000000..0145011ec1
--- /dev/null
+++ b/xpcom/string/nsUTF8Utils.h
@@ -0,0 +1,247 @@
+/* -*- 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/. */
+#ifndef nsUTF8Utils_h_
+#define nsUTF8Utils_h_
+
+// NB: This code may be used from non-XPCOM code, in particular, the
+// standalone updater executable. That is, this file may be used in
+// two ways: if MOZILLA_INTERNAL_API is defined, this file will
+// provide signatures for the Mozilla abstract string types. It will
+// use XPCOM assertion/debugging macros, etc.
+
+#include <type_traits>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/EndianUtils.h"
+
+#include "nsCharTraits.h"
+
+#ifdef MOZILLA_INTERNAL_API
+# define UTF8UTILS_WARNING(msg) NS_WARNING(msg)
+#else
+# define UTF8UTILS_WARNING(msg)
+#endif
+
+class UTF8traits {
+ public:
+ static bool isASCII(char aChar) { return (aChar & 0x80) == 0x00; }
+ static bool isInSeq(char aChar) { return (aChar & 0xC0) == 0x80; }
+ static bool is2byte(char aChar) { return (aChar & 0xE0) == 0xC0; }
+ static bool is3byte(char aChar) { return (aChar & 0xF0) == 0xE0; }
+ static bool is4byte(char aChar) { return (aChar & 0xF8) == 0xF0; }
+ static bool is5byte(char aChar) { return (aChar & 0xFC) == 0xF8; }
+ static bool is6byte(char aChar) { return (aChar & 0xFE) == 0xFC; }
+ // return the number of bytes in a sequence beginning with aChar
+ static int bytes(char aChar) {
+ if (isASCII(aChar)) {
+ return 1;
+ }
+ if (is2byte(aChar)) {
+ return 2;
+ }
+ if (is3byte(aChar)) {
+ return 3;
+ }
+ if (is4byte(aChar)) {
+ return 4;
+ }
+ MOZ_ASSERT_UNREACHABLE("should not be used for in-sequence characters");
+ return 1;
+ }
+};
+
+/**
+ * Extract the next Unicode scalar value from the buffer and return it. The
+ * pointer passed in is advanced to the start of the next character in the
+ * buffer. Upon error, the return value is 0xFFFD, *aBuffer is advanced
+ * over the maximal valid prefix and *aErr is set to true (if aErr is not
+ * null).
+ *
+ * Note: This method never sets *aErr to false to allow error accumulation
+ * across multiple calls.
+ *
+ * Precondition: *aBuffer < aEnd
+ */
+class UTF8CharEnumerator {
+ public:
+ static inline char32_t NextChar(const char** aBuffer, const char* aEnd,
+ bool* aErr = nullptr) {
+ MOZ_ASSERT(aBuffer, "null buffer pointer pointer");
+ MOZ_ASSERT(aEnd, "null end pointer");
+
+ const unsigned char* p = reinterpret_cast<const unsigned char*>(*aBuffer);
+ const unsigned char* end = reinterpret_cast<const unsigned char*>(aEnd);
+
+ MOZ_ASSERT(p, "null buffer");
+ MOZ_ASSERT(p < end, "Bogus range");
+
+ unsigned char first = *p;
+ ++p;
+
+ if (MOZ_LIKELY(first < 0x80U)) {
+ *aBuffer = reinterpret_cast<const char*>(p);
+ return first;
+ }
+
+ // Unsigned underflow is defined behavior
+ if (MOZ_UNLIKELY((p == end) || ((first - 0xC2U) >= (0xF5U - 0xC2U)))) {
+ *aBuffer = reinterpret_cast<const char*>(p);
+ if (aErr) {
+ *aErr = true;
+ }
+ return 0xFFFDU;
+ }
+
+ unsigned char second = *p;
+
+ if (first < 0xE0U) {
+ // Two-byte
+ if (MOZ_LIKELY((second & 0xC0U) == 0x80U)) {
+ ++p;
+ *aBuffer = reinterpret_cast<const char*>(p);
+ return ((uint32_t(first) & 0x1FU) << 6) | (uint32_t(second) & 0x3FU);
+ }
+ *aBuffer = reinterpret_cast<const char*>(p);
+ if (aErr) {
+ *aErr = true;
+ }
+ return 0xFFFDU;
+ }
+
+ if (MOZ_LIKELY(first < 0xF0U)) {
+ // Three-byte
+ unsigned char lower = 0x80U;
+ unsigned char upper = 0xBFU;
+ if (first == 0xE0U) {
+ lower = 0xA0U;
+ } else if (first == 0xEDU) {
+ upper = 0x9FU;
+ }
+ if (MOZ_LIKELY(second >= lower && second <= upper)) {
+ ++p;
+ if (MOZ_LIKELY(p != end)) {
+ unsigned char third = *p;
+ if (MOZ_LIKELY((third & 0xC0U) == 0x80U)) {
+ ++p;
+ *aBuffer = reinterpret_cast<const char*>(p);
+ return ((uint32_t(first) & 0xFU) << 12) |
+ ((uint32_t(second) & 0x3FU) << 6) |
+ (uint32_t(third) & 0x3FU);
+ }
+ }
+ }
+ *aBuffer = reinterpret_cast<const char*>(p);
+ if (aErr) {
+ *aErr = true;
+ }
+ return 0xFFFDU;
+ }
+
+ // Four-byte
+ unsigned char lower = 0x80U;
+ unsigned char upper = 0xBFU;
+ if (first == 0xF0U) {
+ lower = 0x90U;
+ } else if (first == 0xF4U) {
+ upper = 0x8FU;
+ }
+ if (MOZ_LIKELY(second >= lower && second <= upper)) {
+ ++p;
+ if (MOZ_LIKELY(p != end)) {
+ unsigned char third = *p;
+ if (MOZ_LIKELY((third & 0xC0U) == 0x80U)) {
+ ++p;
+ if (MOZ_LIKELY(p != end)) {
+ unsigned char fourth = *p;
+ if (MOZ_LIKELY((fourth & 0xC0U) == 0x80U)) {
+ ++p;
+ *aBuffer = reinterpret_cast<const char*>(p);
+ return ((uint32_t(first) & 0x7U) << 18) |
+ ((uint32_t(second) & 0x3FU) << 12) |
+ ((uint32_t(third) & 0x3FU) << 6) |
+ (uint32_t(fourth) & 0x3FU);
+ }
+ }
+ }
+ }
+ }
+ *aBuffer = reinterpret_cast<const char*>(p);
+ if (aErr) {
+ *aErr = true;
+ }
+ return 0xFFFDU;
+ }
+};
+
+/**
+ * Extract the next Unicode scalar value from the buffer and return it. The
+ * pointer passed in is advanced to the start of the next character in the
+ * buffer. Upon error, the return value is 0xFFFD, *aBuffer is advanced over
+ * the unpaired surrogate and *aErr is set to true (if aErr is not null).
+ *
+ * Note: This method never sets *aErr to false to allow error accumulation
+ * across multiple calls.
+ *
+ * Precondition: *aBuffer < aEnd
+ */
+class UTF16CharEnumerator {
+ public:
+ static inline char32_t NextChar(const char16_t** aBuffer,
+ const char16_t* aEnd, bool* aErr = nullptr) {
+ MOZ_ASSERT(aBuffer, "null buffer pointer pointer");
+ MOZ_ASSERT(aEnd, "null end pointer");
+
+ const char16_t* p = *aBuffer;
+
+ MOZ_ASSERT(p, "null buffer");
+ MOZ_ASSERT(p < aEnd, "Bogus range");
+
+ char16_t c = *p++;
+
+ // Let's use encoding_rs-style code golf here.
+ // Unsigned underflow is defined behavior
+ char16_t cMinusSurrogateStart = c - 0xD800U;
+ if (MOZ_LIKELY(cMinusSurrogateStart > (0xDFFFU - 0xD800U))) {
+ *aBuffer = p;
+ return c;
+ }
+ if (MOZ_LIKELY(cMinusSurrogateStart <= (0xDBFFU - 0xD800U))) {
+ // High surrogate
+ if (MOZ_LIKELY(p != aEnd)) {
+ char16_t second = *p;
+ // Unsigned underflow is defined behavior
+ if (MOZ_LIKELY((second - 0xDC00U) <= (0xDFFFU - 0xDC00U))) {
+ *aBuffer = ++p;
+ return (uint32_t(c) << 10) + uint32_t(second) -
+ (((0xD800U << 10) - 0x10000U) + 0xDC00U);
+ }
+ }
+ }
+ // Unpaired surrogate
+ *aBuffer = p;
+ if (aErr) {
+ *aErr = true;
+ }
+ return 0xFFFDU;
+ }
+};
+
+template <typename Char, typename UnsignedT>
+inline UnsignedT RewindToPriorUTF8Codepoint(const Char* utf8Chars,
+ UnsignedT index) {
+ static_assert(std::is_same_v<Char, char> ||
+ std::is_same_v<Char, unsigned char> ||
+ std::is_same_v<Char, signed char>,
+ "UTF-8 data must be in 8-bit units");
+ static_assert(std::is_unsigned_v<UnsignedT>, "index type must be unsigned");
+ while (index > 0 && (utf8Chars[index] & 0xC0) == 0x80) --index;
+
+ return index;
+}
+
+#undef UTF8UTILS_WARNING
+
+#endif /* !defined(nsUTF8Utils_h_) */