1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
|
/* -*- 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_dom_DOMString_h
#define mozilla_dom_DOMString_h
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/StringBuffer.h"
#include "nsAtom.h"
#include "nsDOMString.h"
#include "nsString.h"
namespace mozilla::dom {
/**
* A class for representing string return values. This can be either passed to
* callees that have an nsString or nsAString out param or passed to a callee
* that actually knows about this class and can work with it. Such a callee may
* call these setters:
*
* SetKnownLiveStringBuffer
* SetStringBuffer
* SetKnownLiveString
* SetKnownLiveAtom
* SetNull
*
* to assign a value to the DOMString without instantiating an actual nsString
* in the process, or use AsAString() to instantiate an nsString and work with
* it. These options are mutually exclusive! Don't do more than one of them.
*
* It's only OK to call
* SetKnownLiveStringBuffer/SetKnownLiveString/SetKnownLiveAtom if the caller of
* the method in question plans to keep holding a strong ref to the stringbuffer
* involved, whether it's a raw mozilla::StringBuffer, or stored inside the
* string or atom being passed. In the string/atom cases that means the caller
* must own the string or atom, and not mutate it (in the string case) for the
* lifetime of the DOMString.
*
* The proper way to extract a value is to check IsNull(). If not null, then
* check IsEmpty(). If neither of those is true, check HasStringBuffer(). If
* that's true, call StringBuffer()/StringBufferLength(). If HasStringBuffer()
* returns false, check HasLiteral, and if that returns true call
* Literal()/LiteralLength(). If HasLiteral() is false, call AsAString() and
* get the value from that.
*/
class MOZ_STACK_CLASS DOMString {
public:
DOMString() : mStringBuffer(nullptr), mLength(0), mState(State::Empty) {}
~DOMString() {
MOZ_ASSERT(!mString || !mStringBuffer, "Shouldn't have both present!");
if (mState == State::OwnedStringBuffer) {
MOZ_ASSERT(mStringBuffer);
mStringBuffer->Release();
}
}
operator nsString&() { return AsAString(); }
// It doesn't make any sense to convert a DOMString to a const nsString or
// nsAString reference; this class is meant for outparams only.
operator const nsString&() = delete;
operator const nsAString&() = delete;
nsString& AsAString() {
MOZ_ASSERT(mState == State::Empty || mState == State::String,
"Moving from nonempty state to another nonempty state?");
MOZ_ASSERT(!mStringBuffer, "We already have a stringbuffer?");
if (!mString) {
mString.emplace();
mState = State::String;
}
return *mString;
}
bool HasStringBuffer() const {
MOZ_ASSERT(!mString || !mStringBuffer, "Shouldn't have both present!");
MOZ_ASSERT(mState > State::Null,
"Caller should have checked IsNull() and IsEmpty() first");
return mState >= State::OwnedStringBuffer;
}
// Get the stringbuffer. This can only be called if HasStringBuffer()
// returned true. If that's true, it will never return null. Note that
// constructing a string from this mozilla::StringBuffer with length given by
// StringBufferLength() might give you something that is not null-terminated.
mozilla::StringBuffer* StringBuffer() const {
MOZ_ASSERT(HasStringBuffer(),
"Don't ask for the stringbuffer if we don't have it");
MOZ_ASSERT(mStringBuffer, "We better have a stringbuffer if we claim to");
return mStringBuffer;
}
// Get the length of the stringbuffer. Can only be called if
// HasStringBuffer().
uint32_t StringBufferLength() const {
MOZ_ASSERT(HasStringBuffer(),
"Don't call this if there is no stringbuffer");
return mLength;
}
bool HasLiteral() const {
MOZ_ASSERT(!mString || !mStringBuffer, "Shouldn't have both present!");
MOZ_ASSERT(mState > State::Null,
"Caller should have checked IsNull() and IsEmpty() first");
return mState == State::Literal;
}
// Get the literal string. This can only be called if HasLiteral()
// returned true. If that's true, it will never return null.
const char16_t* Literal() const {
MOZ_ASSERT(HasLiteral(), "Don't ask for the literal if we don't have it");
MOZ_ASSERT(mLiteral, "We better have a literal if we claim to");
return mLiteral;
}
// Get the length of the literal. Can only be called if HasLiteral().
uint32_t LiteralLength() const {
MOZ_ASSERT(HasLiteral(), "Don't call this if there is no literal");
return mLength;
}
// Initialize the DOMString to a (mozilla::StringBuffer, length) pair. The
// length does NOT have to be the full length of the (null-terminated) string
// in the mozilla::StringBuffer.
void SetKnownLiveStringBuffer(mozilla::StringBuffer* aStringBuffer,
uint32_t aLength) {
MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
if (aLength != 0) {
SetStringBufferInternal(aStringBuffer, aLength);
mState = State::UnownedStringBuffer;
}
// else nothing to do
}
// Like SetKnownLiveStringBuffer, but holds a reference to the
// mozilla::StringBuffer.
void SetStringBuffer(mozilla::StringBuffer* aStringBuffer, uint32_t aLength) {
MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
if (aLength != 0) {
SetStringBufferInternal(aStringBuffer, aLength);
aStringBuffer->AddRef();
mState = State::OwnedStringBuffer;
}
// else nothing to do
}
void SetKnownLiveString(const nsAString& aString) {
MOZ_ASSERT(mString.isNothing(), "We already have a string?");
MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?");
if (MOZ_UNLIKELY(aString.IsVoid())) {
SetNull();
} else if (!aString.IsEmpty()) {
if (mozilla::StringBuffer* buf = aString.GetStringBuffer()) {
SetKnownLiveStringBuffer(buf, aString.Length());
} else if (aString.IsLiteral()) {
SetLiteralInternal(aString.BeginReading(), aString.Length());
} else {
AsAString() = aString;
}
}
}
enum NullHandling { eTreatNullAsNull, eTreatNullAsEmpty, eNullNotExpected };
void SetKnownLiveAtom(nsAtom* aAtom, NullHandling aNullHandling) {
MOZ_ASSERT(mString.isNothing(), "We already have a string?");
MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
MOZ_ASSERT(aAtom || aNullHandling != eNullNotExpected);
if (aNullHandling == eNullNotExpected || aAtom) {
if (aAtom->IsStatic()) {
// Static atoms are backed by literals. Explicitly call AsStatic() here
// to avoid the extra IsStatic() checks in nsAtom::GetUTF16String().
SetLiteralInternal(aAtom->AsStatic()->GetUTF16String(),
aAtom->GetLength());
} else {
SetKnownLiveStringBuffer(aAtom->AsDynamic()->StringBuffer(),
aAtom->GetLength());
}
} else if (aNullHandling == eTreatNullAsNull) {
SetNull();
}
}
void SetNull() {
MOZ_ASSERT(!mStringBuffer, "Should have no stringbuffer if null");
MOZ_ASSERT(mString.isNothing(), "Should have no string if null");
MOZ_ASSERT(mState == State::Empty, "Already set to a value?");
mState = State::Null;
}
bool IsNull() const {
MOZ_ASSERT(!mStringBuffer || mString.isNothing(),
"How could we have a stringbuffer and a nonempty string?");
return mState == State::Null || (mString && mString->IsVoid());
}
bool IsEmpty() const {
MOZ_ASSERT(!mStringBuffer || mString.isNothing(),
"How could we have a stringbuffer and a nonempty string?");
// This is not exact, because we might still have an empty XPCOM string.
// But that's OK; in that case the callers will try the XPCOM string
// themselves.
return mState == State::Empty;
}
void ToString(nsAString& aString) {
if (IsNull()) {
SetDOMStringToNull(aString);
} else if (IsEmpty()) {
aString.Truncate();
} else if (HasStringBuffer()) {
// Don't share the mozilla::StringBuffer with aString if the result would
// not be null-terminated.
mozilla::StringBuffer* buf = StringBuffer();
uint32_t len = StringBufferLength();
auto chars = static_cast<char16_t*>(buf->Data());
if (chars[len] == '\0') {
// Safe to share the buffer.
aString.Assign(buf, len);
} else {
// We need to copy, unfortunately.
aString.Assign(chars, len);
}
} else if (HasLiteral()) {
aString.AssignLiteral(Literal(), LiteralLength());
} else {
aString = AsAString();
}
}
private:
void SetStringBufferInternal(mozilla::StringBuffer* aStringBuffer,
uint32_t aLength) {
MOZ_ASSERT(mString.isNothing(), "We already have a string?");
MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?");
MOZ_ASSERT(aStringBuffer, "Why are we getting null?");
MOZ_ASSERT(aLength != 0, "Should not have empty string here");
mStringBuffer = aStringBuffer;
mLength = aLength;
}
void SetLiteralInternal(const char16_t* aLiteral, uint32_t aLength) {
MOZ_ASSERT(!mLiteral, "What's going on here?");
mLiteral = aLiteral;
mLength = aLength;
mState = State::Literal;
}
enum class State : uint8_t {
Empty, // An empty string. Default state.
Null, // Null (not a string at all)
// All states that involve actual string data should come after
// Empty and Null.
String, // An XPCOM string stored in mString.
Literal, // A string literal (static lifetime).
OwnedStringBuffer, // mStringBuffer is valid and we have a ref to it.
UnownedStringBuffer, // mStringBuffer is valid; we are not holding a ref.
// The two string buffer values must come last. This lets us avoid doing
// two tests to figure out whether we have a stringbuffer.
};
// We need to be able to act like a string as needed
Maybe<nsAutoString> mString;
union {
// The mozilla::StringBuffer in the OwnedStringBuffer/UnownedStringBuffer
// cases.
mozilla::StringBuffer* MOZ_UNSAFE_REF(
"The ways in which this can be safe are "
"documented above and enforced through "
"assertions") mStringBuffer;
// The literal in the Literal case.
const char16_t* mLiteral;
};
// Length in the stringbuffer and literal cases.
uint32_t mLength;
State mState;
};
} // namespace mozilla::dom
#endif // mozilla_dom_DOMString_h
|