Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / bindings / DOMString.h
blobc5404f53511ad071ec860fce0c2b57f29a32c5bd
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
10 #include "nsString.h"
11 #include "nsStringBuffer.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/Maybe.h"
15 #include "nsDOMString.h"
16 #include "nsAtom.h"
18 namespace mozilla::dom {
20 /**
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
24 * call these setters:
26 * SetKnownLiveStringBuffer
27 * SetStringBuffer
28 * SetKnownLiveString
29 * SetKnownLiveAtom
30 * SetNull
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
42 * 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 {
52 public:
53 DOMString() : mStringBuffer(nullptr), mLength(0), mState(State::Empty) {}
54 ~DOMString() {
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?");
73 if (!mString) {
74 mString.emplace();
75 mState = State::String;
77 return *mString;
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");
95 return mStringBuffer;
98 // Get the length of the stringbuffer. Can only be called if
99 // HasStringBuffer().
100 uint32_t StringBufferLength() const {
101 MOZ_ASSERT(HasStringBuffer(),
102 "Don't call this if there is no stringbuffer");
103 return mLength;
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;
114 } else {
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");
132 return mLiteral;
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");
138 return mLength;
141 // Initialize the DOMString to a (nsStringBuffer, length) pair. The length
142 // does NOT have to be the full length of the (null-terminated) string in the
143 // nsStringBuffer.
144 void SetKnownLiveStringBuffer(nsStringBuffer* aStringBuffer,
145 uint32_t aLength) {
146 MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
147 if (aLength != 0) {
148 SetStringBufferInternal(aStringBuffer, aLength);
149 mState = State::UnownedStringBuffer;
151 // else nothing to do
154 // Like SetKnownLiveStringBuffer, but holds a reference to the nsStringBuffer.
155 void SetStringBuffer(nsStringBuffer* aStringBuffer, uint32_t aLength) {
156 MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
157 if (aLength != 0) {
158 SetStringBufferInternal(aStringBuffer, aLength);
159 aStringBuffer->AddRef();
160 mState = State::OwnedStringBuffer;
162 // else nothing to do
165 void SetKnownLiveString(const nsAString& aString) {
166 MOZ_ASSERT(mString.isNothing(), "We already have a string?");
167 MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
168 MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?");
169 if (MOZ_UNLIKELY(aString.IsVoid())) {
170 SetNull();
171 } else if (!aString.IsEmpty()) {
172 nsStringBuffer* buf = nsStringBuffer::FromString(aString);
173 if (buf) {
174 SetKnownLiveStringBuffer(buf, aString.Length());
175 } else if (aString.IsLiteral()) {
176 SetLiteralInternal(aString.BeginReading(), aString.Length());
177 } else {
178 AsAString() = aString;
183 enum NullHandling { eTreatNullAsNull, eTreatNullAsEmpty, eNullNotExpected };
185 void SetKnownLiveAtom(nsAtom* aAtom, NullHandling aNullHandling) {
186 MOZ_ASSERT(mString.isNothing(), "We already have a string?");
187 MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
188 MOZ_ASSERT(aAtom || aNullHandling != eNullNotExpected);
189 if (aNullHandling == eNullNotExpected || aAtom) {
190 if (aAtom->IsStatic()) {
191 // Static atoms are backed by literals. Explicitly call AsStatic() here
192 // to avoid the extra IsStatic() checks in nsAtom::GetUTF16String().
193 SetLiteralInternal(aAtom->AsStatic()->GetUTF16String(),
194 aAtom->GetLength());
195 } else {
196 SetKnownLiveStringBuffer(aAtom->AsDynamic()->StringBuffer(),
197 aAtom->GetLength());
199 } else if (aNullHandling == eTreatNullAsNull) {
200 SetNull();
204 void SetNull() {
205 MOZ_ASSERT(!mStringBuffer, "Should have no stringbuffer if null");
206 MOZ_ASSERT(mString.isNothing(), "Should have no string if null");
207 MOZ_ASSERT(mState == State::Empty, "Already set to a value?");
208 mState = State::Null;
211 bool IsNull() const {
212 MOZ_ASSERT(!mStringBuffer || mString.isNothing(),
213 "How could we have a stringbuffer and a nonempty string?");
214 return mState == State::Null || (mString && mString->IsVoid());
217 bool IsEmpty() const {
218 MOZ_ASSERT(!mStringBuffer || mString.isNothing(),
219 "How could we have a stringbuffer and a nonempty string?");
220 // This is not exact, because we might still have an empty XPCOM string.
221 // But that's OK; in that case the callers will try the XPCOM string
222 // themselves.
223 return mState == State::Empty;
226 void ToString(nsAString& aString) {
227 if (IsNull()) {
228 SetDOMStringToNull(aString);
229 } else if (IsEmpty()) {
230 aString.Truncate();
231 } else if (HasStringBuffer()) {
232 // Don't share the nsStringBuffer with aString if the result would not
233 // be null-terminated.
234 nsStringBuffer* buf = StringBuffer();
235 uint32_t len = StringBufferLength();
236 auto chars = static_cast<char16_t*>(buf->Data());
237 if (chars[len] == '\0') {
238 // Safe to share the buffer.
239 buf->ToString(len, aString);
240 } else {
241 // We need to copy, unfortunately.
242 aString.Assign(chars, len);
244 } else if (HasLiteral()) {
245 aString.AssignLiteral(Literal(), LiteralLength());
246 } else {
247 aString = AsAString();
251 private:
252 void SetStringBufferInternal(nsStringBuffer* aStringBuffer,
253 uint32_t aLength) {
254 MOZ_ASSERT(mString.isNothing(), "We already have a string?");
255 MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
256 MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?");
257 MOZ_ASSERT(aStringBuffer, "Why are we getting null?");
258 MOZ_ASSERT(aLength != 0, "Should not have empty string here");
259 mStringBuffer = aStringBuffer;
260 mLength = aLength;
263 void SetLiteralInternal(const char16_t* aLiteral, uint32_t aLength) {
264 MOZ_ASSERT(!mLiteral, "What's going on here?");
265 mLiteral = aLiteral;
266 mLength = aLength;
267 mState = State::Literal;
270 enum class State : uint8_t {
271 Empty, // An empty string. Default state.
272 Null, // Null (not a string at all)
274 // All states that involve actual string data should come after
275 // Empty and Null.
277 String, // An XPCOM string stored in mString.
278 Literal, // A string literal (static lifetime).
279 OwnedStringBuffer, // mStringBuffer is valid and we have a ref to it.
280 UnownedStringBuffer, // mStringBuffer is valid; we are not holding a ref.
281 // The two string buffer values must come last. This lets us avoid doing
282 // two tests to figure out whether we have a stringbuffer.
285 // We need to be able to act like a string as needed
286 Maybe<nsAutoString> mString;
288 union {
289 // The nsStringBuffer in the OwnedStringBuffer/UnownedStringBuffer cases.
290 nsStringBuffer* MOZ_UNSAFE_REF(
291 "The ways in which this can be safe are "
292 "documented above and enforced through "
293 "assertions") mStringBuffer;
294 // The literal in the Literal case.
295 const char16_t* mLiteral;
298 // Length in the stringbuffer and literal cases.
299 uint32_t mLength;
301 State mState;
304 } // namespace mozilla::dom
306 #endif // mozilla_dom_DOMString_h