--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2401,21 +2401,54 @@ Element::OnlyNotifySameValueSet(int32_t
}
nsAutoScriptBlocker scriptBlocker;
nsNodeUtils::AttributeSetToCurrentValue(this, aNamespaceID, aName);
return true;
}
nsresult
+Element::SetSingleClassFromParser(nsIAtom* aSingleClassName)
+{
+ // Keep this in sync with SetAttr and SetParsedAttr below.
+
+ if (!mAttrsAndChildren.CanFitMoreAttrs()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAttrValue value(aSingleClassName);
+
+ nsIDocument* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, false);
+
+ // In principle, BeforeSetAttr should be called here if a node type
+ // existed that wanted to do something special for class, but there
+ // is no such node type, so calling SetMayHaveClass() directly.
+ SetMayHaveClass();
+
+ return SetAttrAndNotify(kNameSpaceID_None,
+ nsGkAtoms::_class,
+ nullptr, // prefix
+ nullptr, // old value
+ value,
+ static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION),
+ false, // hasListeners
+ false, // notify
+ kCallAfterSetAttr,
+ document,
+ updateBatch);
+}
+
+nsresult
Element::SetAttr(int32_t aNamespaceID, nsIAtom* aName,
nsIAtom* aPrefix, const nsAString& aValue,
bool aNotify)
{
- // Keep this in sync with SetParsedAttr below
+ // Keep this in sync with SetParsedAttr below and SetSingleClassFromParser
+ // above.
NS_ENSURE_ARG_POINTER(aName);
NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
"Don't call SetAttr with unknown namespace");
if (!mAttrsAndChildren.CanFitMoreAttrs()) {
return NS_ERROR_FAILURE;
}
@@ -2470,17 +2503,17 @@ Element::SetAttr(int32_t aNamespaceID, n
kCallAfterSetAttr, document, updateBatch);
}
nsresult
Element::SetParsedAttr(int32_t aNamespaceID, nsIAtom* aName,
nsIAtom* aPrefix, nsAttrValue& aParsedValue,
bool aNotify)
{
- // Keep this in sync with SetAttr above
+ // Keep this in sync with SetAttr and SetSingleClassFromParser above
NS_ENSURE_ARG_POINTER(aName);
NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
"Don't call SetAttr with unknown namespace");
if (!mAttrsAndChildren.CanFitMoreAttrs()) {
return NS_ERROR_FAILURE;
}
@@ -2704,16 +2737,20 @@ Element::BeforeSetAttr(int32_t aNamespac
{
if (aNamespaceID == kNameSpaceID_None) {
if (aName == nsGkAtoms::_class) {
if (aValue) {
// Note: This flag is asymmetrical. It is never unset and isn't exact.
// If it is ever made to be exact, we probably need to handle this
// similarly to how ids are handled in PreIdMaybeChange and
// PostIdMaybeChange.
+ // Note that SetSingleClassFromParser inlines BeforeSetAttr and
+ // calls SetMayHaveClass directly. Making a subclass take action
+ // on the class attribute in a BeforeSetAttr override would
+ // require revising SetSingleClassFromParser.
SetMayHaveClass();
}
}
}
return NS_OK;
}
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -701,16 +701,23 @@ public:
*/
bool OnlyNotifySameValueSet(int32_t aNamespaceID, nsIAtom* aName,
nsIAtom* aPrefix,
const nsAttrValueOrString& aValue,
bool aNotify, nsAttrValue& aOldValue,
uint8_t* aModType, bool* aHasListeners,
bool* aOldValueSet);
+ /**
+ * Sets the class attribute to a value that contains no whitespace.
+ * Assumes that we are not notifying and that the attribute hasn't been
+ * set previously.
+ */
+ nsresult SetSingleClassFromParser(nsIAtom* aSingleClassName);
+
virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsIAtom* aPrefix,
const nsAString& aValue, bool aNotify) override;
// aParsedValue receives the old value of the attribute. That's useful if
// either the input or output value of aParsedValue is StoresOwnData.
nsresult SetParsedAttr(int32_t aNameSpaceID, nsIAtom* aName, nsIAtom* aPrefix,
nsAttrValue& aParsedValue, bool aNotify);
// GetAttr is not inlined on purpose, to keep down codesize from all
// the inlined nsAttrValue bits for C++ callers.
--- a/parser/html/javasrc/MetaScanner.java
+++ b/parser/html/javasrc/MetaScanner.java
@@ -792,23 +792,23 @@ public abstract class MetaScanner {
* @throws SAXException
*/
private void handleAttributeValue() throws SAXException {
if (metaState != A) {
return;
}
if (contentIndex == CONTENT.length && content == null) {
content = Portability.newStringFromBuffer(strBuf, 0, strBufLen
- // CPPONLY: , treeBuilder
+ // CPPONLY: , treeBuilder, false
);
return;
}
if (charsetIndex == CHARSET.length && charset == null) {
charset = Portability.newStringFromBuffer(strBuf, 0, strBufLen
- // CPPONLY: , treeBuilder
+ // CPPONLY: , treeBuilder, false
);
return;
}
if (httpEquivIndex == HTTP_EQUIV.length
&& httpEquivState == HTTP_EQUIV_NOT_SEEN) {
httpEquivState = (contentTypeIndex == CONTENT_TYPE.length) ? HTTP_EQUIV_CONTENT_TYPE
: HTTP_EQUIV_OTHER;
return;
--- a/parser/html/javasrc/Portability.java
+++ b/parser/html/javasrc/Portability.java
@@ -35,17 +35,17 @@ public final class Portability {
* Allocates a new local name object. In C++, the refcount must be set up in such a way that
* calling <code>releaseLocal</code> on the return value balances the refcount set by this method.
*/
public static @Local String newLocalNameFromBuffer(@NoLength char[] buf, int offset, int length, Interner interner) {
return new String(buf, offset, length).intern();
}
public static String newStringFromBuffer(@NoLength char[] buf, int offset, int length
- // CPPONLY: , TreeBuilder treeBuilder
+ // CPPONLY: , TreeBuilder treeBuilder, boolean maybeAtomize
) {
return new String(buf, offset, length);
}
public static String newEmptyString() {
return "";
}
--- a/parser/html/javasrc/Tokenizer.java
+++ b/parser/html/javasrc/Tokenizer.java
@@ -892,17 +892,17 @@ public class Tokenizer implements Locato
*
* <p>
* C++ memory note: The return value must be released.
*
* @return the buffer as a string
*/
protected String strBufToString() {
String str = Portability.newStringFromBuffer(strBuf, 0, strBufLen
- // CPPONLY: , tokenHandler
+ // CPPONLY: , tokenHandler, !newAttributesEachTime && attributeName == AttributeName.CLASS
);
clearStrBufAfterUse();
return str;
}
/**
* Returns the buffer as a local name. The return value is released in
* emitDoctypeToken().
--- a/parser/html/javasrc/TreeBuilder.java
+++ b/parser/html/javasrc/TreeBuilder.java
@@ -3288,17 +3288,17 @@ public abstract class TreeBuilder<T> imp
}
String charset = null;
if (start != -1) {
if (end == -1) {
end = buffer.length;
}
charset = Portability.newStringFromBuffer(buffer, start, end
- start
- // CPPONLY: , tb
+ // CPPONLY: , tb, false
);
}
return charset;
}
private void checkMetaCharset(HtmlAttributes attributes)
throws SAXException {
String charset = attributes.getValue(AttributeName.CHARSET);
--- a/parser/html/nsHtml5MetaScanner.cpp
+++ b/parser/html/nsHtml5MetaScanner.cpp
@@ -750,21 +750,23 @@ nsHtml5MetaScanner::addToBuffer(int32_t
void
nsHtml5MetaScanner::handleAttributeValue()
{
if (metaState != A) {
return;
}
if (contentIndex == CONTENT.length && !content) {
- content = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen, treeBuilder);
+ content = nsHtml5Portability::newStringFromBuffer(
+ strBuf, 0, strBufLen, treeBuilder, false);
return;
}
if (charsetIndex == CHARSET.length && !charset) {
- charset = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen, treeBuilder);
+ charset = nsHtml5Portability::newStringFromBuffer(
+ strBuf, 0, strBufLen, treeBuilder, false);
return;
}
if (httpEquivIndex == HTTP_EQUIV.length &&
httpEquivState == HTTP_EQUIV_NOT_SEEN) {
httpEquivState = (contentTypeIndex == CONTENT_TYPE.length)
? HTTP_EQUIV_CONTENT_TYPE
: HTTP_EQUIV_OTHER;
return;
--- a/parser/html/nsHtml5Portability.cpp
+++ b/parser/html/nsHtml5Portability.cpp
@@ -11,22 +11,40 @@
nsIAtom*
nsHtml5Portability::newLocalNameFromBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner)
{
NS_ASSERTION(!offset, "The offset should always be zero here.");
NS_ASSERTION(interner, "Didn't get an atom service.");
return interner->GetAtom(nsDependentSubstring(buf, buf + length));
}
+static bool
+ContainsWhiteSpace(mozilla::Span<char16_t> aSpan)
+{
+ for (char16_t c : aSpan) {
+ if (nsContentUtils::IsHTMLWhitespace(c)) {
+ return true;
+ }
+ }
+ return false;
+}
+
nsHtml5String
nsHtml5Portability::newStringFromBuffer(char16_t* buf,
int32_t offset,
int32_t length,
- nsHtml5TreeBuilder* treeBuilder)
+ nsHtml5TreeBuilder* treeBuilder,
+ bool maybeAtomize)
{
+ if (!length) {
+ return nsHtml5String::EmptyString();
+ }
+ if (maybeAtomize && !ContainsWhiteSpace(mozilla::MakeSpan(buf + offset, length))) {
+ return nsHtml5String::FromAtom(NS_AtomizeMainThread(nsDependentSubstring(buf + offset, length)));
+ }
return nsHtml5String::FromBuffer(buf + offset, length, treeBuilder);
}
nsHtml5String
nsHtml5Portability::newEmptyString()
{
return nsHtml5String::EmptyString();
}
--- a/parser/html/nsHtml5Portability.h
+++ b/parser/html/nsHtml5Portability.h
@@ -56,17 +56,18 @@ class nsHtml5StateSnapshot;
class nsHtml5Portability
{
public:
static nsIAtom* newLocalNameFromBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner);
static nsHtml5String newStringFromBuffer(char16_t* buf,
int32_t offset,
int32_t length,
- nsHtml5TreeBuilder* treeBuilder);
+ nsHtml5TreeBuilder* treeBuilder,
+ bool maybeAtomize);
static nsHtml5String newEmptyString();
static nsHtml5String newStringFromLiteral(const char* literal);
static nsHtml5String newStringFromString(nsHtml5String string);
static jArray<char16_t,int32_t> newCharArrayFromLocal(nsIAtom* local);
static jArray<char16_t, int32_t> newCharArrayFromString(
nsHtml5String string);
static nsIAtom* newLocalFromLocal(nsIAtom* local, nsHtml5AtomTable* interner);
static bool localEqualsBuffer(nsIAtom* local, char16_t* buf, int32_t offset, int32_t length);
--- a/parser/html/nsHtml5String.cpp
+++ b/parser/html/nsHtml5String.cpp
@@ -2,92 +2,59 @@
* 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/. */
#include "nsHtml5String.h"
#include "nsCharTraits.h"
#include "nsUTF8Utils.h"
#include "nsHtml5TreeBuilder.h"
-nsHtml5String::nsHtml5String(already_AddRefed<nsStringBuffer> aBuffer,
- uint32_t aLength)
- : mBuffer(aBuffer.take())
- , mLength(aLength)
-{
- if (mBuffer) {
- MOZ_ASSERT(aLength);
- } else {
- MOZ_ASSERT(!aLength || aLength == UINT32_MAX);
- }
-}
-
void
nsHtml5String::ToString(nsAString& aString)
{
- if (mBuffer) {
- mBuffer->ToString(mLength, aString);
- } else {
- aString.Truncate();
- if (mLength) {
+ switch (GetKind()) {
+ case eStringBuffer:
+ return AsStringBuffer()->ToString(Length(), aString);
+ case eAtom:
+ return AsAtom()->ToString(aString);
+ case eEmpty:
+ aString.Truncate();
+ return;
+ default:
+ aString.Truncate();
aString.SetIsVoid(true);
- }
+ return;
}
}
void
-nsHtml5String::CopyToBuffer(char16_t* aBuffer)
-{
- if (mBuffer) {
- memcpy(aBuffer, mBuffer->Data(), mLength * sizeof(char16_t));
- }
-}
-
-bool
-nsHtml5String::LowerCaseEqualsASCII(const char* aLowerCaseLiteral)
+nsHtml5String::CopyToBuffer(char16_t* aBuffer) const
{
- if (!mBuffer) {
- if (mLength) {
- // This string is null
- return false;
- }
- // this string is empty
- return !(*aLowerCaseLiteral);
- }
- return !nsCharTraits<char16_t>::compareLowerCaseToASCIINullTerminated(
- reinterpret_cast<char16_t*>(mBuffer->Data()), Length(), aLowerCaseLiteral);
+ memcpy(aBuffer, AsPtr(), Length() * sizeof(char16_t));
}
bool
-nsHtml5String::EqualsASCII(const char* aLiteral)
+nsHtml5String::LowerCaseEqualsASCII(const char* aLowerCaseLiteral) const
{
- if (!mBuffer) {
- if (mLength) {
- // This string is null
- return false;
- }
- // this string is empty
- return !(*aLiteral);
- }
- return !nsCharTraits<char16_t>::compareASCIINullTerminated(
- reinterpret_cast<char16_t*>(mBuffer->Data()), Length(), aLiteral);
+ return !nsCharTraits<char16_t>::compareLowerCaseToASCIINullTerminated(
+ AsPtr(), Length(), aLowerCaseLiteral);
}
bool
-nsHtml5String::LowerCaseStartsWithASCII(const char* aLowerCaseLiteral)
+nsHtml5String::EqualsASCII(const char* aLiteral) const
{
- if (!mBuffer) {
- if (mLength) {
- // This string is null
- return false;
- }
- // this string is empty
- return !(*aLowerCaseLiteral);
- }
+ return !nsCharTraits<char16_t>::compareASCIINullTerminated(
+ AsPtr(), Length(), aLiteral);
+}
+
+bool
+nsHtml5String::LowerCaseStartsWithASCII(const char* aLowerCaseLiteral) const
+{
const char* litPtr = aLowerCaseLiteral;
- const char16_t* strPtr = reinterpret_cast<char16_t*>(mBuffer->Data());
+ const char16_t* strPtr = AsPtr();
const char16_t* end = strPtr + Length();
char16_t litChar;
while ((litChar = *litPtr) && (strPtr != end)) {
MOZ_ASSERT(!(litChar >= 'A' && litChar <= 'Z'),
"Literal isn't in lower case.");
char16_t strChar = *strPtr;
if (strChar >= 'A' && strChar <= 'Z') {
strChar += 0x20;
@@ -97,57 +64,67 @@ nsHtml5String::LowerCaseStartsWithASCII(
}
++litPtr;
++strPtr;
}
return true;
}
bool
-nsHtml5String::Equals(nsHtml5String aOther)
+nsHtml5String::Equals(nsHtml5String aOther) const
{
MOZ_ASSERT(operator bool());
MOZ_ASSERT(aOther);
- if (mLength != aOther.mLength) {
+ if (Length() != aOther.Length()) {
return false;
}
- if (!mBuffer) {
- return true;
- }
- MOZ_ASSERT(aOther.mBuffer);
return !memcmp(
- mBuffer->Data(), aOther.mBuffer->Data(), Length() * sizeof(char16_t));
+ AsPtr(), aOther.AsPtr(), Length() * sizeof(char16_t));
}
nsHtml5String
nsHtml5String::Clone()
{
- MOZ_ASSERT(operator bool());
- RefPtr<nsStringBuffer> ref(mBuffer);
- return nsHtml5String(ref.forget(), mLength);
+ switch (GetKind()) {
+ case eStringBuffer:
+ AsStringBuffer()->AddRef();
+ break;
+ case eAtom:
+ AsAtom()->AddRef();
+ break;
+ default:
+ break;
+ }
+ return nsHtml5String(mBits);
}
void
nsHtml5String::Release()
{
- if (mBuffer) {
- mBuffer->Release();
- mBuffer = nullptr;
+ switch (GetKind()) {
+ case eStringBuffer:
+ AsStringBuffer()->Release();
+ break;
+ case eAtom:
+ AsAtom()->Release();
+ break;
+ default:
+ break;
}
- mLength = UINT32_MAX;
+ mBits = eNull;
}
// static
nsHtml5String
nsHtml5String::FromBuffer(char16_t* aBuffer,
int32_t aLength,
nsHtml5TreeBuilder* aTreeBuilder)
{
if (!aLength) {
- return nsHtml5String(nullptr, 0U);
+ return nsHtml5String(eEmpty);
}
// Work with nsStringBuffer directly to make sure that storage is actually
// nsStringBuffer and to make sure the allocation strategy matches
// nsAttrValue::GetStringBuffer, so that it doesn't need to reallocate and
// copy.
RefPtr<nsStringBuffer> buffer(
nsStringBuffer::Alloc((aLength + 1) * sizeof(char16_t)));
if (!buffer) {
@@ -158,68 +135,75 @@ nsHtml5String::FromBuffer(char16_t* aBuf
buffer = nsStringBuffer::Alloc(2 * sizeof(char16_t));
if (!buffer) {
MOZ_CRASH(
"Out of memory so badly that couldn't even allocate placeholder.");
}
char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
data[0] = 0xFFFD;
data[1] = 0;
- return nsHtml5String(buffer.forget(), 1);
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
}
char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
memcpy(data, aBuffer, aLength * sizeof(char16_t));
data[aLength] = 0;
- return nsHtml5String(buffer.forget(), aLength);
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
}
// static
nsHtml5String
nsHtml5String::FromLiteral(const char* aLiteral)
{
size_t length = std::strlen(aLiteral);
if (!length) {
- return nsHtml5String(nullptr, 0U);
+ return nsHtml5String(eEmpty);
}
// Work with nsStringBuffer directly to make sure that storage is actually
// nsStringBuffer and to make sure the allocation strategy matches
// nsAttrValue::GetStringBuffer, so that it doesn't need to reallocate and
// copy.
RefPtr<nsStringBuffer> buffer(
nsStringBuffer::Alloc((length + 1) * sizeof(char16_t)));
if (!buffer) {
MOZ_CRASH("Out of memory.");
}
char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
LossyConvertEncoding8to16 converter(data);
converter.write(aLiteral, length);
data[length] = 0;
- return nsHtml5String(buffer.forget(), length);
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
}
// static
nsHtml5String
nsHtml5String::FromString(const nsAString& aString)
{
auto length = aString.Length();
if (!length) {
- return nsHtml5String(nullptr, 0U);
+ return nsHtml5String(eEmpty);
}
RefPtr<nsStringBuffer> buffer = nsStringBuffer::FromString(aString);
- if (buffer) {
- return nsHtml5String(buffer.forget(), length);
+ if (buffer && (length == buffer->StorageSize()/sizeof(char16_t) - 1)) {
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
}
buffer = nsStringBuffer::Alloc((length + 1) * sizeof(char16_t));
if (!buffer) {
MOZ_CRASH("Out of memory.");
}
char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
memcpy(data, aString.BeginReading(), length * sizeof(char16_t));
data[length] = 0;
- return nsHtml5String(buffer.forget(), length);
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
+}
+
+// static
+nsHtml5String
+nsHtml5String::FromAtom(already_AddRefed<nsIAtom> aAtom)
+{
+ return nsHtml5String(reinterpret_cast<uintptr_t>(aAtom.take()) | eAtom);
}
// static
nsHtml5String
nsHtml5String::EmptyString()
{
- return nsHtml5String(nullptr, 0U);
+ return nsHtml5String(eEmpty);
}
--- a/parser/html/nsHtml5String.h
+++ b/parser/html/nsHtml5String.h
@@ -1,95 +1,153 @@
/* 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 nsHtml5String_h
#define nsHtml5String_h
#include "nsString.h"
+#include "nsIAtom.h"
class nsHtml5TreeBuilder;
/**
- * A pass-by-value type that combines an unsafe `nsStringBuffer*` with its
- * logical length (`uint32_t`). (`nsStringBuffer` knows its capacity but not
- * its logical length, i.e. how much of the capacity is in use.)
+ * A pass-by-value type that can represent
+ * * nullptr
+ * * empty string
+ * * Non-empty string as exactly-sized (capacity is length) `nsStringBuffer*`
+ * * Non-empty string as an nsIAtom*
*
* Holding or passing this type is as unsafe as holding or passing
- * `nsStringBuffer*`.
- *
- * Empty strings and null strings are distinct. Since an empty nsString does
- * not have a an `nsStringBuffer`, both empty and null `nsHtml5String` have
- * `nullptr` as `mBuffer`. If `mBuffer` is `nullptr`, the empty case is marked
- * with `mLength` being zero and the null case with `mLength` being non-zero.
+ * `nsStringBuffer*`/`nsIAtom*`.
*/
class nsHtml5String final
{
+private:
+
+ static const uintptr_t kKindMask = uintptr_t(3);
+
+ static const uintptr_t kPtrMask = ~kKindMask;
+
+ enum Kind : uintptr_t {
+ eNull = 0,
+ eEmpty = 1,
+ eStringBuffer = 2,
+ eAtom = 3,
+ };
+
+ inline Kind GetKind() const { return (Kind)(mBits & kKindMask); }
+
+ inline nsStringBuffer* AsStringBuffer() const
+ {
+ MOZ_ASSERT(GetKind() == eStringBuffer);
+ return reinterpret_cast<nsStringBuffer*>(mBits & kPtrMask);
+ }
+
+ inline nsIAtom* AsAtom() const
+ {
+ MOZ_ASSERT(GetKind() == eAtom);
+ return reinterpret_cast<nsIAtom*>(mBits & kPtrMask);
+ }
+
+ inline const char16_t* AsPtr() const
+ {
+ switch (GetKind()) {
+ case eStringBuffer:
+ return reinterpret_cast<char16_t*>(AsStringBuffer()->Data());
+ case eAtom:
+ return AsAtom()->GetUTF16String();
+ default:
+ return nullptr;
+ }
+ }
+
public:
/**
* Default constructor.
*/
inline nsHtml5String()
: nsHtml5String(nullptr)
{
}
/**
* Constructor from nullptr.
*/
inline MOZ_IMPLICIT nsHtml5String(decltype(nullptr))
- : mBuffer(nullptr)
- , mLength(UINT32_MAX)
+ : mBits(eNull)
{
}
- inline uint32_t Length() const { return mBuffer ? mLength : 0; }
+ inline uint32_t Length() const
+ {
+ switch (GetKind()) {
+ case eStringBuffer:
+ return (AsStringBuffer()->StorageSize()/sizeof(char16_t) - 1);
+ case eAtom:
+ return AsAtom()->GetLength();
+ default:
+ return 0;
+ }
+ }
/**
* False iff the string is logically null
*/
- inline MOZ_IMPLICIT operator bool() const { return !(!mBuffer && mLength); }
+ inline MOZ_IMPLICIT operator bool() const { return mBits; }
+
+ /**
+ * Get the underlying nsIAtom* or nullptr if this nsHtml5String
+ * does not hold an atom.
+ */
+ inline nsIAtom* MaybeAsAtom()
+ {
+ if (GetKind() == eAtom) {
+ return AsAtom();
+ }
+ return nullptr;
+ }
void ToString(nsAString& aString);
- void CopyToBuffer(char16_t* aBuffer);
+ void CopyToBuffer(char16_t* aBuffer) const;
- bool LowerCaseEqualsASCII(const char* aLowerCaseLiteral);
+ bool LowerCaseEqualsASCII(const char* aLowerCaseLiteral) const;
- bool EqualsASCII(const char* aLiteral);
+ bool EqualsASCII(const char* aLiteral) const;
- bool LowerCaseStartsWithASCII(const char* aLowerCaseLiteral);
+ bool LowerCaseStartsWithASCII(const char* aLowerCaseLiteral) const;
- bool Equals(nsHtml5String aOther);
+ bool Equals(nsHtml5String aOther) const;
nsHtml5String Clone();
void Release();
static nsHtml5String FromBuffer(char16_t* aBuffer,
int32_t aLength,
nsHtml5TreeBuilder* aTreeBuilder);
static nsHtml5String FromLiteral(const char* aLiteral);
static nsHtml5String FromString(const nsAString& aString);
+ static nsHtml5String FromAtom(already_AddRefed<nsIAtom> aAtom);
+
static nsHtml5String EmptyString();
private:
- /**
- * Constructor from raw parts.
- */
- nsHtml5String(already_AddRefed<nsStringBuffer> aBuffer, uint32_t aLength);
/**
- * nullptr if the string is logically null or logically empty
+ * Constructor from raw bits.
*/
- nsStringBuffer* mBuffer;
+ explicit nsHtml5String(uintptr_t aBits) : mBits(aBits) {};
/**
- * The length of the string. non-zero if the string is logically null.
+ * Zero if null, one if empty, otherwise tagged pointer
+ * to either nsIAtom or nsStringBuffer. The two least-significant
+ * bits are tag bits.
*/
- uint32_t mLength;
+ uintptr_t mBits;
};
#endif // nsHtml5String_h
--- a/parser/html/nsHtml5Tokenizer.cpp
+++ b/parser/html/nsHtml5Tokenizer.cpp
@@ -226,18 +226,23 @@ nsHtml5Tokenizer::emitOrAppendCharRefBuf
charRefBufLen = 0;
}
}
}
nsHtml5String
nsHtml5Tokenizer::strBufToString()
{
- nsHtml5String str =
- nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen, tokenHandler);
+ nsHtml5String str = nsHtml5Portability::newStringFromBuffer(
+ strBuf,
+ 0,
+ strBufLen,
+ tokenHandler,
+ !newAttributesEachTime &&
+ attributeName == nsHtml5AttributeName::ATTR_CLASS);
clearStrBufAfterUse();
return str;
}
void
nsHtml5Tokenizer::strBufToDoctypeName()
{
doctypeName = nsHtml5Portability::newLocalNameFromBuffer(strBuf, 0, strBufLen, interner);
--- a/parser/html/nsHtml5TreeBuilder.cpp
+++ b/parser/html/nsHtml5TreeBuilder.cpp
@@ -2215,18 +2215,18 @@ nsHtml5TreeBuilder::extractCharsetFromCo
}
}
charsetloop_end: ;
nsHtml5String charset = nullptr;
if (start != -1) {
if (end == -1) {
end = buffer.length;
}
- charset =
- nsHtml5Portability::newStringFromBuffer(buffer, start, end - start, tb);
+ charset = nsHtml5Portability::newStringFromBuffer(
+ buffer, start, end - start, tb, false);
}
return charset;
}
void
nsHtml5TreeBuilder::checkMetaCharset(nsHtml5HtmlAttributes* attributes)
{
nsHtml5String charset =
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -408,45 +408,51 @@ nsHtml5TreeOperation::CreateHTMLElement(
}
if (!aAttributes) {
return newContent;
}
int32_t len = aAttributes->getLength();
for (int32_t i = 0; i < len; i++) {
- // prefix doesn't need regetting. it is always null or a static atom
- // local name is never null
- nsCOMPtr<nsIAtom> localName =
- Reget(aAttributes->getLocalNameNoBoundsCheck(i));
- nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
- int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+ nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
+ nsIAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ newContent->SetSingleClassFromParser(klass);
+ } else {
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ nsCOMPtr<nsIAtom> localName =
+ Reget(aAttributes->getLocalNameNoBoundsCheck(i));
+ nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
- nsString value; // Not Auto, because using it to hold nsStringBuffer*
- aAttributes->getValueNoBoundsCheck(i).ToString(value);
- if (nsGkAtoms::a == aName && nsGkAtoms::name == localName) {
- // This is an HTML5-incompliant Geckoism.
- // Remove when fixing bug 582361
- NS_ConvertUTF16toUTF8 cname(value);
- NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting()));
- newContent->SetAttr(nsuri,
- localName,
- prefix,
- uv,
- false);
- } else {
- newContent->SetAttr(nsuri,
- localName,
- prefix,
- value,
- false);
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ val.ToString(value);
+ if (nsGkAtoms::a == aName && nsGkAtoms::name == localName) {
+ // This is an HTML5-incompliant Geckoism.
+ // Remove when fixing bug 582361
+ NS_ConvertUTF16toUTF8 cname(value);
+ NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting()));
+ newContent->SetAttr(nsuri,
+ localName,
+ prefix,
+ uv,
+ false);
+ } else {
+ newContent->SetAttr(nsuri,
+ localName,
+ prefix,
+ value,
+ false);
- // Custom element setup may be needed if there is an "is" attribute.
- if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) {
- nsContentUtils::SetupCustomElement(newContent, &value);
+ // Custom element setup may be needed if there is an "is" attribute.
+ if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) {
+ nsContentUtils::SetupCustomElement(newContent, &value);
+ }
}
}
}
return newContent;
}
nsIContent*
nsHtml5TreeOperation::CreateSVGElement(
@@ -492,26 +498,32 @@ nsHtml5TreeOperation::CreateSVGElement(
}
if (!aAttributes) {
return newContent;
}
int32_t len = aAttributes->getLength();
for (int32_t i = 0; i < len; i++) {
- // prefix doesn't need regetting. it is always null or a static atom
- // local name is never null
- nsCOMPtr<nsIAtom> localName =
- Reget(aAttributes->getLocalNameNoBoundsCheck(i));
- nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
- int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+ nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
+ nsIAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ newContent->SetSingleClassFromParser(klass);
+ } else {
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ nsCOMPtr<nsIAtom> localName =
+ Reget(aAttributes->getLocalNameNoBoundsCheck(i));
+ nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
- nsString value; // Not Auto, because using it to hold nsStringBuffer*
- aAttributes->getValueNoBoundsCheck(i).ToString(value);
- newContent->SetAttr(nsuri, localName, prefix, value, false);
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ val.ToString(value);
+ newContent->SetAttr(nsuri, localName, prefix, value, false);
+ }
}
return newContent;
}
nsIContent*
nsHtml5TreeOperation::CreateMathMLElement(nsIAtom* aName,
nsHtml5HtmlAttributes* aAttributes,
nsNodeInfoManager* aNodeInfoManager,
@@ -540,26 +552,32 @@ nsHtml5TreeOperation::CreateMathMLElemen
aBuilder->HoldElement(newElement.forget());
if (!aAttributes) {
return newContent;
}
int32_t len = aAttributes->getLength();
for (int32_t i = 0; i < len; i++) {
- // prefix doesn't need regetting. it is always null or a static atom
- // local name is never null
- nsCOMPtr<nsIAtom> localName =
- Reget(aAttributes->getLocalNameNoBoundsCheck(i));
- nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
- int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+ nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
+ nsIAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ newContent->SetSingleClassFromParser(klass);
+ } else {
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ nsCOMPtr<nsIAtom> localName =
+ Reget(aAttributes->getLocalNameNoBoundsCheck(i));
+ nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
- nsString value; // Not Auto, because using it to hold nsStringBuffer*
- aAttributes->getValueNoBoundsCheck(i).ToString(value);
- newContent->SetAttr(nsuri, localName, prefix, value, false);
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ val.ToString(value);
+ newContent->SetAttr(nsuri, localName, prefix, value, false);
+ }
}
return newContent;
}
void
nsHtml5TreeOperation::SetFormElement(nsIContent* aNode, nsIContent* aParent)
{
nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(aNode));