diff options
Diffstat (limited to 'dom/bindings/DOMString.h')
-rw-r--r-- | dom/bindings/DOMString.h | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/dom/bindings/DOMString.h b/dom/bindings/DOMString.h new file mode 100644 index 000000000..72c8445ec --- /dev/null +++ b/dom/bindings/DOMString.h @@ -0,0 +1,245 @@ +/* -*- 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 "nsStringGlue.h" +#include "nsStringBuffer.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "nsDOMString.h" +#include "nsIAtom.h" + +namespace mozilla { +namespace 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 SetStringBuffer or SetEphemeralStringBuffer or SetOwnedString or + * SetOwnedAtom on this object. It's only OK to call + * SetStringBuffer/SetOwnedString/SetOwnedAtom 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 store a value in this class is to either to do nothing + * (which leaves this as an empty string), to call + * SetStringBuffer/SetEphemeralStringBuffer with a non-null stringbuffer, to + * call SetOwnedString, to call SetOwnedAtom, to call SetNull(), or to call + * AsAString() and set the value in the resulting nsString. These options are + * mutually exclusive! Don't do more than one of them. + * + * The proper way to extract a value is to check IsNull(). If not null, then + * check HasStringBuffer(). If that's true, check for a zero length, and if the + * length is nonzero call StringBuffer(). If the length is zero this is the + * empty string. If HasStringBuffer() returns false, call AsAString() and get + * the value from that. + */ +class MOZ_STACK_CLASS DOMString { +public: + DOMString() + : mStringBuffer(nullptr) + , mLength(0) + , mIsNull(false) + , mStringBufferOwned(false) + {} + ~DOMString() + { + MOZ_ASSERT(!mString || !mStringBuffer, + "Shouldn't have both present!"); + if (mStringBufferOwned) { + 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(!mStringBuffer, "We already have a stringbuffer?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + if (!mString) { + mString.emplace(); + } + return *mString; + } + + bool HasStringBuffer() const + { + MOZ_ASSERT(!mString || !mStringBuffer, + "Shouldn't have both present!"); + MOZ_ASSERT(!mIsNull, "Caller should have checked IsNull() first"); + return !mString; + } + + // Get the stringbuffer. This can only be called if HasStringBuffer() + // returned true and StringBufferLength() is nonzero. 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(!mIsNull, "Caller should have checked IsNull() first"); + MOZ_ASSERT(HasStringBuffer(), + "Don't ask for the stringbuffer if we don't have it"); + MOZ_ASSERT(StringBufferLength() != 0, "Why are you asking for this?"); + MOZ_ASSERT(mStringBuffer, + "If our length is nonzero, we better have a stringbuffer."); + 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 (mStringBufferOwned) { + // Just hand that ref over. + mStringBufferOwned = false; + } else { + // Caller should end up holding a ref. + mStringBuffer->AddRef(); + } + } + + // 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 SetStringBuffer(nsStringBuffer* aStringBuffer, uint32_t aLength) + { + MOZ_ASSERT(mString.isNothing(), "We already have a string?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?"); + MOZ_ASSERT(aStringBuffer, "Why are we getting null?"); + mStringBuffer = aStringBuffer; + mLength = aLength; + } + + // Like SetStringBuffer, but holds a reference to the nsStringBuffer. + void SetEphemeralStringBuffer(nsStringBuffer* aStringBuffer, uint32_t aLength) + { + // We rely on SetStringBuffer to ensure our state invariants. + SetStringBuffer(aStringBuffer, aLength); + aStringBuffer->AddRef(); + mStringBufferOwned = true; + } + + void SetOwnedString(const nsAString& aString) + { + MOZ_ASSERT(mString.isNothing(), "We already have a string?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?"); + nsStringBuffer* buf = nsStringBuffer::FromString(aString); + if (buf) { + SetStringBuffer(buf, aString.Length()); + } else if (aString.IsVoid()) { + SetNull(); + } else if (!aString.IsEmpty()) { + AsAString() = aString; + } + } + + enum NullHandling + { + eTreatNullAsNull, + eTreatNullAsEmpty, + eNullNotExpected + }; + + void SetOwnedAtom(nsIAtom* aAtom, NullHandling aNullHandling) + { + MOZ_ASSERT(mString.isNothing(), "We already have a string?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?"); + MOZ_ASSERT(aAtom || aNullHandling != eNullNotExpected); + if (aNullHandling == eNullNotExpected || aAtom) { + SetStringBuffer(aAtom->GetStringBuffer(), 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"); + mIsNull = true; + } + + bool IsNull() const + { + MOZ_ASSERT(!mStringBuffer || mString.isNothing(), + "How could we have a stringbuffer and a nonempty string?"); + return mIsNull || (mString && mString->IsVoid()); + } + + void ToString(nsAString& aString) + { + if (IsNull()) { + SetDOMStringToNull(aString); + } else if (HasStringBuffer()) { + if (StringBufferLength() == 0) { + aString.Truncate(); + } else { + // 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 { + aString = AsAString(); + } + } + +private: + // We need to be able to act like a string as needed + Maybe<nsAutoString> mString; + + // For callees that know we exist, we can be a stringbuffer/length/null-flag + // triple. + nsStringBuffer* MOZ_UNSAFE_REF("The ways in which this can be safe are " + "documented above and enforced through " + "assertions") mStringBuffer; + uint32_t mLength; + bool mIsNull; + bool mStringBufferOwned; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_DOMString_h |