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 "nsStringBuffer.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/Maybe.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 nsStringBuffer, or stored inside the string or
40 * atom being passed. In the string/atom cases that means the caller must own
41 * the string or atom, and not mutate it (in the string case) for the lifetime
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 nsStringBuffer with length given by
90 // StringBufferLength() might give you something that is not null-terminated.
91 nsStringBuffer
* 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 // Tell the DOMString to relinquish ownership of its nsStringBuffer to the
107 // caller. Can only be called if HasStringBuffer().
108 void RelinquishBufferOwnership() {
109 MOZ_ASSERT(HasStringBuffer(),
110 "Don't call this if there is no stringbuffer");
111 if (mState
== State::OwnedStringBuffer
) {
112 // Just hand that ref over.
113 mState
= State::UnownedStringBuffer
;
115 // Caller should end up holding a ref.
116 mStringBuffer
->AddRef();
120 bool HasLiteral() const {
121 MOZ_ASSERT(!mString
|| !mStringBuffer
, "Shouldn't have both present!");
122 MOZ_ASSERT(mState
> State::Null
,
123 "Caller should have checked IsNull() and IsEmpty() first");
124 return mState
== State::Literal
;
127 // Get the literal string. This can only be called if HasLiteral()
128 // returned true. If that's true, it will never return null.
129 const char16_t
* Literal() const {
130 MOZ_ASSERT(HasLiteral(), "Don't ask for the literal if we don't have it");
131 MOZ_ASSERT(mLiteral
, "We better have a literal if we claim to");
135 // Get the length of the literal. Can only be called if HasLiteral().
136 uint32_t LiteralLength() const {
137 MOZ_ASSERT(HasLiteral(), "Don't call this if there is no literal");
141 bool HasAtom() const {
142 MOZ_ASSERT(!mString
|| !mStringBuffer
, "Shouldn't have both present!");
143 MOZ_ASSERT(mState
> State::Null
,
144 "Caller should have checked IsNull() and IsEmpty() first");
145 return mState
== State::UnownedAtom
;
148 // Get the atom. This can only be called if HasAtom() returned true. If
149 // that's true, it will never return null.
150 nsDynamicAtom
* Atom() const {
151 MOZ_ASSERT(HasAtom(), "Don't ask for the atom if we don't have it");
152 MOZ_ASSERT(mAtom
, "We better have an atom if we claim to");
156 // Initialize the DOMString to a (nsStringBuffer, length) pair. The length
157 // does NOT have to be the full length of the (null-terminated) string in the
159 void SetKnownLiveStringBuffer(nsStringBuffer
* aStringBuffer
,
161 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
163 SetStringBufferInternal(aStringBuffer
, aLength
);
164 mState
= State::UnownedStringBuffer
;
166 // else nothing to do
169 // Like SetKnownLiveStringBuffer, but holds a reference to the nsStringBuffer.
170 void SetStringBuffer(nsStringBuffer
* aStringBuffer
, uint32_t aLength
) {
171 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
173 SetStringBufferInternal(aStringBuffer
, aLength
);
174 aStringBuffer
->AddRef();
175 mState
= State::OwnedStringBuffer
;
177 // else nothing to do
180 void SetKnownLiveString(const nsAString
& aString
) {
181 MOZ_ASSERT(mString
.isNothing(), "We already have a string?");
182 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
183 MOZ_ASSERT(!mStringBuffer
, "Setting stringbuffer twice?");
184 if (MOZ_UNLIKELY(aString
.IsVoid())) {
186 } else if (!aString
.IsEmpty()) {
187 nsStringBuffer
* buf
= nsStringBuffer::FromString(aString
);
189 SetKnownLiveStringBuffer(buf
, aString
.Length());
190 } else if (aString
.IsLiteral()) {
191 SetLiteralInternal(aString
.BeginReading(), aString
.Length());
193 AsAString() = aString
;
198 enum NullHandling
{ eTreatNullAsNull
, eTreatNullAsEmpty
, eNullNotExpected
};
200 void SetKnownLiveAtom(nsAtom
* aAtom
, NullHandling aNullHandling
) {
201 MOZ_ASSERT(mString
.isNothing(), "We already have a string?");
202 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
203 MOZ_ASSERT(!mAtom
, "Setting atom twice?");
204 MOZ_ASSERT(aAtom
|| aNullHandling
!= eNullNotExpected
);
205 if (aNullHandling
== eNullNotExpected
|| aAtom
) {
206 if (aAtom
->IsStatic()) {
207 // Static atoms are backed by literals. Explicitly call AsStatic() here
208 // to avoid the extra IsStatic() checks in nsAtom::GetUTF16String().
209 SetLiteralInternal(aAtom
->AsStatic()->GetUTF16String(),
212 mAtom
= aAtom
->AsDynamic();
213 mState
= State::UnownedAtom
;
215 } else if (aNullHandling
== eTreatNullAsNull
) {
221 MOZ_ASSERT(!mStringBuffer
, "Should have no stringbuffer if null");
222 MOZ_ASSERT(mString
.isNothing(), "Should have no string if null");
223 MOZ_ASSERT(mState
== State::Empty
, "Already set to a value?");
224 mState
= State::Null
;
227 bool IsNull() const {
228 MOZ_ASSERT(!mStringBuffer
|| mString
.isNothing(),
229 "How could we have a stringbuffer and a nonempty string?");
230 return mState
== State::Null
|| (mString
&& mString
->IsVoid());
233 bool IsEmpty() const {
234 MOZ_ASSERT(!mStringBuffer
|| mString
.isNothing(),
235 "How could we have a stringbuffer and a nonempty string?");
236 // This is not exact, because we might still have an empty XPCOM string.
237 // But that's OK; in that case the callers will try the XPCOM string
239 return mState
== State::Empty
;
242 void ToString(nsAString
& aString
) {
244 SetDOMStringToNull(aString
);
245 } else if (IsEmpty()) {
247 } else if (HasStringBuffer()) {
248 // Don't share the nsStringBuffer with aString if the result would not
249 // be null-terminated.
250 nsStringBuffer
* buf
= StringBuffer();
251 uint32_t len
= StringBufferLength();
252 auto chars
= static_cast<char16_t
*>(buf
->Data());
253 if (chars
[len
] == '\0') {
254 // Safe to share the buffer.
255 buf
->ToString(len
, aString
);
257 // We need to copy, unfortunately.
258 aString
.Assign(chars
, len
);
260 } else if (HasLiteral()) {
261 aString
.AssignLiteral(Literal(), LiteralLength());
262 } else if (HasAtom()) {
263 mAtom
->ToString(aString
);
265 aString
= AsAString();
270 void SetStringBufferInternal(nsStringBuffer
* aStringBuffer
,
272 MOZ_ASSERT(mString
.isNothing(), "We already have a string?");
273 MOZ_ASSERT(mState
== State::Empty
, "We're already set to a value");
274 MOZ_ASSERT(!mStringBuffer
, "Setting stringbuffer twice?");
275 MOZ_ASSERT(aStringBuffer
, "Why are we getting null?");
276 MOZ_ASSERT(aLength
!= 0, "Should not have empty string here");
277 mStringBuffer
= aStringBuffer
;
281 void SetLiteralInternal(const char16_t
* aLiteral
, uint32_t aLength
) {
282 MOZ_ASSERT(!mLiteral
, "What's going on here?");
285 mState
= State::Literal
;
288 enum class State
: uint8_t {
289 Empty
, // An empty string. Default state.
290 Null
, // Null (not a string at all)
292 // All states that involve actual string data should come after
295 String
, // An XPCOM string stored in mString.
296 Literal
, // A string literal (static lifetime).
297 UnownedAtom
, // mAtom is valid and we are not holding a ref.
298 // If we ever add an OwnedAtom state, XPCStringConvert::DynamicAtomToJSVal
299 // will need to grow an out param for whether the atom was shared.
300 OwnedStringBuffer
, // mStringBuffer is valid and we have a ref to it.
301 UnownedStringBuffer
, // mStringBuffer is valid; we are not holding a ref.
302 // The two string buffer values must come last. This lets us avoid doing
303 // two tests to figure out whether we have a stringbuffer.
306 // We need to be able to act like a string as needed
307 Maybe
<nsAutoString
> mString
;
310 // The nsStringBuffer in the OwnedStringBuffer/UnownedStringBuffer cases.
311 nsStringBuffer
* MOZ_UNSAFE_REF(
312 "The ways in which this can be safe are "
313 "documented above and enforced through "
314 "assertions") mStringBuffer
;
315 // The literal in the Literal case.
316 const char16_t
* mLiteral
;
317 // The atom in the UnownedAtom case.
318 nsDynamicAtom
* MOZ_UNSAFE_REF(
319 "The ways in which this can be safe are "
320 "documented above and enforced through "
324 // Length in the stringbuffer and literal cases.
330 } // namespace mozilla::dom
332 #endif // mozilla_dom_DOMString_h