summaryrefslogtreecommitdiffstats
path: root/dom/bindings/DOMString.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/bindings/DOMString.h')
-rw-r--r--dom/bindings/DOMString.h332
1 files changed, 332 insertions, 0 deletions
diff --git a/dom/bindings/DOMString.h b/dom/bindings/DOMString.h
new file mode 100644
index 0000000000..9507f27c9d
--- /dev/null
+++ b/dom/bindings/DOMString.h
@@ -0,0 +1,332 @@
+/* -*- 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 "nsString.h"
+#include "nsStringBuffer.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "nsDOMString.h"
+#include "nsAtom.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 nsStringBuffer, 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 nsStringBuffer with length given by
+ // StringBufferLength() might give you something that is not null-terminated.
+ nsStringBuffer* 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;
+ }
+
+ // Tell the DOMString to relinquish ownership of its nsStringBuffer to the
+ // caller. Can only be called if HasStringBuffer().
+ void RelinquishBufferOwnership() {
+ MOZ_ASSERT(HasStringBuffer(),
+ "Don't call this if there is no stringbuffer");
+ if (mState == State::OwnedStringBuffer) {
+ // Just hand that ref over.
+ mState = State::UnownedStringBuffer;
+ } else {
+ // Caller should end up holding a ref.
+ mStringBuffer->AddRef();
+ }
+ }
+
+ 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;
+ }
+
+ bool HasAtom() 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::UnownedAtom;
+ }
+
+ // Get the atom. This can only be called if HasAtom() returned true. If
+ // that's true, it will never return null.
+ nsDynamicAtom* Atom() const {
+ MOZ_ASSERT(HasAtom(), "Don't ask for the atom if we don't have it");
+ MOZ_ASSERT(mAtom, "We better have an atom if we claim to");
+ return mAtom;
+ }
+
+ // Initialize the DOMString to a (nsStringBuffer, length) pair. The length
+ // does NOT have to be the full length of the (null-terminated) string in the
+ // nsStringBuffer.
+ void SetKnownLiveStringBuffer(nsStringBuffer* 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 nsStringBuffer.
+ void SetStringBuffer(nsStringBuffer* 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()) {
+ nsStringBuffer* buf = nsStringBuffer::FromString(aString);
+ if (buf) {
+ 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(!mAtom, "Setting atom twice?");
+ 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 {
+ mAtom = aAtom->AsDynamic();
+ mState = State::UnownedAtom;
+ }
+ } 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 nsStringBuffer with aString if the result would not
+ // be null-terminated.
+ nsStringBuffer* buf = StringBuffer();
+ uint32_t len = StringBufferLength();
+ auto chars = static_cast<char16_t*>(buf->Data());
+ if (chars[len] == '\0') {
+ // Safe to share the buffer.
+ buf->ToString(len, aString);
+ } else {
+ // We need to copy, unfortunately.
+ aString.Assign(chars, len);
+ }
+ } else if (HasLiteral()) {
+ aString.AssignLiteral(Literal(), LiteralLength());
+ } else if (HasAtom()) {
+ mAtom->ToString(aString);
+ } else {
+ aString = AsAString();
+ }
+ }
+
+ private:
+ void SetStringBufferInternal(nsStringBuffer* 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).
+ UnownedAtom, // mAtom is valid and we are not holding a ref.
+ // If we ever add an OwnedAtom state, XPCStringConvert::DynamicAtomToJSVal
+ // will need to grow an out param for whether the atom was shared.
+ 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 nsStringBuffer in the OwnedStringBuffer/UnownedStringBuffer cases.
+ nsStringBuffer* 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;
+ // The atom in the UnownedAtom case.
+ nsDynamicAtom* MOZ_UNSAFE_REF(
+ "The ways in which this can be safe are "
+ "documented above and enforced through "
+ "assertions") mAtom;
+ };
+
+ // Length in the stringbuffer and literal cases.
+ uint32_t mLength;
+
+ State mState;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_DOMString_h