DOMString.h (11115B)
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/. */ 6 7 #ifndef mozilla_dom_DOMString_h 8 #define mozilla_dom_DOMString_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Attributes.h" 12 #include "mozilla/Maybe.h" 13 #include "mozilla/StringBuffer.h" 14 #include "nsAtom.h" 15 #include "nsDOMString.h" 16 #include "nsString.h" 17 18 namespace mozilla::dom { 19 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: 25 * 26 * SetKnownLiveStringBuffer 27 * SetStringBuffer 28 * SetKnownLiveString 29 * SetKnownLiveAtom 30 * SetNull 31 * 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. 35 * 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. 43 * 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. 50 */ 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(); 59 } 60 } 61 62 operator nsString&() { return AsAString(); } 63 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; 68 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; 76 } 77 return *mString; 78 } 79 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; 85 } 86 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"); 95 return mStringBuffer; 96 } 97 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; 104 } 105 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; 111 } 112 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"); 118 return mLiteral; 119 } 120 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"); 124 return mLength; 125 } 126 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, 131 uint32_t aLength) { 132 MOZ_ASSERT(mState == State::Empty, "We're already set to a value"); 133 if (aLength != 0) { 134 SetStringBufferInternal(aStringBuffer, aLength); 135 mState = State::UnownedStringBuffer; 136 } 137 // else nothing to do 138 } 139 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"); 144 if (aLength != 0) { 145 SetStringBufferInternal(aStringBuffer, aLength); 146 aStringBuffer->AddRef(); 147 mState = State::OwnedStringBuffer; 148 } 149 // else nothing to do 150 } 151 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())) { 157 SetNull(); 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()); 163 } else { 164 AsAString() = aString; 165 } 166 } 167 } 168 169 enum NullHandling { eTreatNullAsNull, eTreatNullAsEmpty, eNullNotExpected }; 170 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(), 180 aAtom->GetLength()); 181 } else { 182 SetKnownLiveStringBuffer(aAtom->AsDynamic()->StringBuffer(), 183 aAtom->GetLength()); 184 } 185 } else if (aNullHandling == eTreatNullAsNull) { 186 SetNull(); 187 } 188 } 189 190 void SetNull() { 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; 195 } 196 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()); 201 } 202 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 208 // themselves. 209 return mState == State::Empty; 210 } 211 212 void ToString(nsAString& aString) { 213 if (IsNull()) { 214 SetDOMStringToNull(aString); 215 } else if (IsEmpty()) { 216 aString.Truncate(); 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); 226 } else { 227 // We need to copy, unfortunately. 228 aString.Assign(chars, len); 229 } 230 } else if (HasLiteral()) { 231 aString.AssignLiteral(Literal(), LiteralLength()); 232 } else { 233 aString = AsAString(); 234 } 235 } 236 237 private: 238 void SetStringBufferInternal(mozilla::StringBuffer* aStringBuffer, 239 uint32_t aLength) { 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; 246 mLength = aLength; 247 } 248 249 void SetLiteralInternal(const char16_t* aLiteral, uint32_t aLength) { 250 MOZ_ASSERT(!mLiteral, "What's going on here?"); 251 mLiteral = aLiteral; 252 mLength = aLength; 253 mState = State::Literal; 254 } 255 256 enum class State : uint8_t { 257 Empty, // An empty string. Default state. 258 Null, // Null (not a string at all) 259 260 // All states that involve actual string data should come after 261 // Empty and Null. 262 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. 269 }; 270 271 // We need to be able to act like a string as needed 272 Maybe<nsAutoString> mString; 273 274 union { 275 // The mozilla::StringBuffer in the OwnedStringBuffer/UnownedStringBuffer 276 // cases. 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; 283 }; 284 285 // Length in the stringbuffer and literal cases. 286 uint32_t mLength; 287 288 State mState; 289 }; 290 291 } // namespace mozilla::dom 292 293 #endif // mozilla_dom_DOMString_h