1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_dom_DOMString_h
8 #define mozilla_dom_DOMString_h
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/StringBuffer.h"
15 #include "nsDOMString.h"
18 namespace mozilla::dom
{
21 * A class for representing string return values. This can be either passed to
22 * callees that have an nsString or nsAString out param or passed to a callee
23 * that actually knows about this class and can work with it. Such a callee may
26 * SetKnownLiveStringBuffer
32 * to assign a value to the DOMString without instantiating an actual nsString
33 * in the process, or use AsAString() to instantiate an nsString and work with
34 * it. These options are mutually exclusive! Don't do more than one of them.
36 * It's only OK to call
37 * SetKnownLiveStringBuffer/SetKnownLiveString/SetKnownLiveAtom if the caller of
38 * the method in question plans to keep holding a strong ref to the stringbuffer
39 * involved, whether it's a raw mozilla::StringBuffer, or stored inside the
40 * string or atom being passed. In the string/atom cases that means the caller
41 * must own the string or atom, and not mutate it (in the string case) for the
42 * lifetime of the DOMString.
44 * The proper way to extract a value is to check IsNull(). If not null, then
45 * check IsEmpty(). If neither of those is true, check HasStringBuffer(). If
46 * that's true, call StringBuffer()/StringBufferLength(). If HasStringBuffer()
47 * returns false, check HasLiteral, and if that returns true call
48 * Literal()/LiteralLength(). If HasLiteral() is false, call AsAString() and
49 * get the value from that.
51 class MOZ_STACK_CLASS DOMString
{
53 DOMString() : mStringBuffer(nullptr), mLength(0), mState(State::Empty
) {}
55 MOZ_ASSERT(!mString
|| !mStringBuffer
, "Shouldn't have both present!");
56 if (mState
== State::OwnedStringBuffer
) {
57 MOZ_ASSERT(mStringBuffer
);
58 mStringBuffer
->Release();
62 operator nsString
&() { return AsAString(); }
64 // It doesn't make any sense to convert a DOMString to a const nsString or
65 // nsAString reference; this class is meant for outparams only.
66 operator const nsString
&() = delete;
67 operator const nsAString
&() = delete;
69 nsString
& AsAString() {
70 MOZ_ASSERT(mState
== State::Empty
|| mState
== State::String
,
71 "Moving from nonempty state to another nonempty state?");
72 MOZ_ASSERT(!mStringBuffer
, "We already have a stringbuffer?");
75 mState
= State::String
;
80 bool HasStringBuffer() const {
81 MOZ_ASSERT(!mString
|| !mStringBuffer
, "Shouldn't have both present!");
82 MOZ_ASSERT(mState
> State::Null
,
83 "Caller should have checked IsNull() and IsEmpty() first");
84 return mState
>= State::OwnedStringBuffer
;
87 // Get the stringbuffer. This can only be called if HasStringBuffer()
88 // returned true. If that's true, it will never return null. Note that
89 // constructing a string from this mozilla::StringBuffer with length given by
90 // StringBufferLength() might give you something that is not null-terminated.
91 mozilla::StringBuffer
* StringBuffer() const {
92 MOZ_ASSERT(HasStringBuffer(),
93 "Don't ask for the stringbuffer if we don't have it");
94 MOZ_ASSERT(mStringBuffer
, "We better have a stringbuffer if we claim to");
98 // Get the length of the stringbuffer. Can only be called if
100 uint32_t StringBufferLength() const {
101 MOZ_ASSERT(HasStringBuffer(),
102 "Don't call this if there is no stringbuffer");
106 bool HasLiteral() const {
107 MOZ_ASSERT(!mString
|| !mStringBuffer
, "Shouldn't have both present!");
108 MOZ_ASSERT(mState
> State::Null
,
109 "Caller should have checked IsNull() and IsEmpty() first");
110 return mState
== State::Literal
;
113 // Get the literal string. This can only be called if HasLiteral()
114 // returned true. If that's true, it will never return null.
115 const char16_t
* Literal() const {
116 MOZ_ASSERT(HasLiteral(), "Don't ask for the literal if we don't have it");
117 MOZ_ASSERT(mLiteral
, "We better have a literal if we claim to");
121 // Get the length of the literal. Can only be called if HasLiteral().
122 uint32_t LiteralLength() const {
123 MOZ_ASSERT(HasLiteral(), "Don't call this if there is no literal");
127 // Initialize the DOMString to a (mozilla::StringBuffer, length) pair. The
128 // length does NOT have to be the full length of the (null-terminated) string
129 // in the mozilla::StringBuffer.
130 void SetKnownLiveStringBuffer(mozilla::StringBuffer
* aStringBuffer
,
132 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
134 SetStringBufferInternal(aStringBuffer
, aLength
);
135 mState
= State::UnownedStringBuffer
;
137 // else nothing to do
140 // Like SetKnownLiveStringBuffer, but holds a reference to the
141 // mozilla::StringBuffer.
142 void SetStringBuffer(mozilla::StringBuffer
* aStringBuffer
, uint32_t aLength
) {
143 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
145 SetStringBufferInternal(aStringBuffer
, aLength
);
146 aStringBuffer
->AddRef();
147 mState
= State::OwnedStringBuffer
;
149 // else nothing to do
152 void SetKnownLiveString(const nsAString
& aString
) {
153 MOZ_ASSERT(mString
.isNothing(), "We already have a string?");
154 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
155 MOZ_ASSERT(!mStringBuffer
, "Setting stringbuffer twice?");
156 if (MOZ_UNLIKELY(aString
.IsVoid())) {
158 } else if (!aString
.IsEmpty()) {
159 if (mozilla::StringBuffer
* buf
= aString
.GetStringBuffer()) {
160 SetKnownLiveStringBuffer(buf
, aString
.Length());
161 } else if (aString
.IsLiteral()) {
162 SetLiteralInternal(aString
.BeginReading(), aString
.Length());
164 AsAString() = aString
;
169 enum NullHandling
{ eTreatNullAsNull
, eTreatNullAsEmpty
, eNullNotExpected
};
171 void SetKnownLiveAtom(nsAtom
* aAtom
, NullHandling aNullHandling
) {
172 MOZ_ASSERT(mString
.isNothing(), "We already have a string?");
173 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
174 MOZ_ASSERT(aAtom
|| aNullHandling
!= eNullNotExpected
);
175 if (aNullHandling
== eNullNotExpected
|| aAtom
) {
176 if (aAtom
->IsStatic()) {
177 // Static atoms are backed by literals. Explicitly call AsStatic() here
178 // to avoid the extra IsStatic() checks in nsAtom::GetUTF16String().
179 SetLiteralInternal(aAtom
->AsStatic()->GetUTF16String(),
182 SetKnownLiveStringBuffer(aAtom
->AsDynamic()->StringBuffer(),
185 } else if (aNullHandling
== eTreatNullAsNull
) {
191 MOZ_ASSERT(!mStringBuffer
, "Should have no stringbuffer if null");
192 MOZ_ASSERT(mString
.isNothing(), "Should have no string if null");
193 MOZ_ASSERT(mState
== State::Empty
, "Already set to a value?");
194 mState
= State::Null
;
197 bool IsNull() const {
198 MOZ_ASSERT(!mStringBuffer
|| mString
.isNothing(),
199 "How could we have a stringbuffer and a nonempty string?");
200 return mState
== State::Null
|| (mString
&& mString
->IsVoid());
203 bool IsEmpty() const {
204 MOZ_ASSERT(!mStringBuffer
|| mString
.isNothing(),
205 "How could we have a stringbuffer and a nonempty string?");
206 // This is not exact, because we might still have an empty XPCOM string.
207 // But that's OK; in that case the callers will try the XPCOM string
209 return mState
== State::Empty
;
212 void ToString(nsAString
& aString
) {
214 SetDOMStringToNull(aString
);
215 } else if (IsEmpty()) {
217 } else if (HasStringBuffer()) {
218 // Don't share the mozilla::StringBuffer with aString if the result would
219 // not be null-terminated.
220 mozilla::StringBuffer
* buf
= StringBuffer();
221 uint32_t len
= StringBufferLength();
222 auto chars
= static_cast<char16_t
*>(buf
->Data());
223 if (chars
[len
] == '\0') {
224 // Safe to share the buffer.
225 aString
.Assign(buf
, len
);
227 // We need to copy, unfortunately.
228 aString
.Assign(chars
, len
);
230 } else if (HasLiteral()) {
231 aString
.AssignLiteral(Literal(), LiteralLength());
233 aString
= AsAString();
238 void SetStringBufferInternal(mozilla::StringBuffer
* aStringBuffer
,
240 MOZ_ASSERT(mString
.isNothing(), "We already have a string?");
241 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
242 MOZ_ASSERT(!mStringBuffer
, "Setting stringbuffer twice?");
243 MOZ_ASSERT(aStringBuffer
, "Why are we getting null?");
244 MOZ_ASSERT(aLength
!= 0, "Should not have empty string here");
245 mStringBuffer
= aStringBuffer
;
249 void SetLiteralInternal(const char16_t
* aLiteral
, uint32_t aLength
) {
250 MOZ_ASSERT(!mLiteral
, "What's going on here?");
253 mState
= State::Literal
;
256 enum class State
: uint8_t {
257 Empty
, // An empty string. Default state.
258 Null
, // Null (not a string at all)
260 // All states that involve actual string data should come after
263 String
, // An XPCOM string stored in mString.
264 Literal
, // A string literal (static lifetime).
265 OwnedStringBuffer
, // mStringBuffer is valid and we have a ref to it.
266 UnownedStringBuffer
, // mStringBuffer is valid; we are not holding a ref.
267 // The two string buffer values must come last. This lets us avoid doing
268 // two tests to figure out whether we have a stringbuffer.
271 // We need to be able to act like a string as needed
272 Maybe
<nsAutoString
> mString
;
275 // The mozilla::StringBuffer in the OwnedStringBuffer/UnownedStringBuffer
277 mozilla::StringBuffer
* MOZ_UNSAFE_REF(
278 "The ways in which this can be safe are "
279 "documented above and enforced through "
280 "assertions") mStringBuffer
;
281 // The literal in the Literal case.
282 const char16_t
* mLiteral
;
285 // Length in the stringbuffer and literal cases.
291 } // namespace mozilla::dom
293 #endif // mozilla_dom_DOMString_h