michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * A struct that represents the value (type and actual data) of an michael@0: * attribute. michael@0: */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/HashFunctions.h" michael@0: michael@0: #include "nsAttrValue.h" michael@0: #include "nsAttrValueInlines.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/css/StyleRule.h" michael@0: #include "mozilla/css/Declaration.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "prprf.h" michael@0: #include "nsHTMLCSSStyleSheet.h" michael@0: #include "nsCSSParser.h" michael@0: #include "nsStyledElement.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIDocument.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define MISC_STR_PTR(_cont) \ michael@0: reinterpret_cast((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK) michael@0: michael@0: bool michael@0: MiscContainer::GetString(nsAString& aString) const michael@0: { michael@0: void* ptr = MISC_STR_PTR(this); michael@0: michael@0: if (!ptr) { michael@0: return false; michael@0: } michael@0: michael@0: if (static_cast(mStringBits & michael@0: NS_ATTRVALUE_BASETYPE_MASK) == michael@0: nsAttrValue::eStringBase) { michael@0: nsStringBuffer* buffer = static_cast(ptr); michael@0: if (!buffer) { michael@0: return false; michael@0: } michael@0: michael@0: buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString); michael@0: return true; michael@0: } michael@0: michael@0: nsIAtom* atom = static_cast(ptr); michael@0: if (!atom) { michael@0: return false; michael@0: } michael@0: michael@0: atom->ToString(aString); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: MiscContainer::Cache() michael@0: { michael@0: // Not implemented for anything else yet. michael@0: MOZ_ASSERT(mType == nsAttrValue::eCSSStyleRule); michael@0: MOZ_ASSERT(IsRefCounted()); michael@0: MOZ_ASSERT(mValue.mRefCount > 0); michael@0: MOZ_ASSERT(!mValue.mCached); michael@0: michael@0: css::StyleRule* rule = mValue.mCSSStyleRule; michael@0: nsHTMLCSSStyleSheet* sheet = rule->GetHTMLCSSStyleSheet(); michael@0: if (!sheet) { michael@0: return; michael@0: } michael@0: michael@0: nsString str; michael@0: bool gotString = GetString(str); michael@0: if (!gotString) { michael@0: return; michael@0: } michael@0: michael@0: sheet->CacheStyleAttr(str, this); michael@0: mValue.mCached = 1; michael@0: michael@0: // This has to be immutable once it goes into the cache. michael@0: css::Declaration* decl = rule->GetDeclaration(); michael@0: if (decl) { michael@0: decl->SetImmutable(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MiscContainer::Evict() michael@0: { michael@0: // Not implemented for anything else yet. michael@0: MOZ_ASSERT(mType == nsAttrValue::eCSSStyleRule); michael@0: MOZ_ASSERT(IsRefCounted()); michael@0: MOZ_ASSERT(mValue.mRefCount == 0); michael@0: michael@0: if (!mValue.mCached) { michael@0: return; michael@0: } michael@0: michael@0: css::StyleRule* rule = mValue.mCSSStyleRule; michael@0: nsHTMLCSSStyleSheet* sheet = rule->GetHTMLCSSStyleSheet(); michael@0: MOZ_ASSERT(sheet); michael@0: michael@0: nsString str; michael@0: DebugOnly gotString = GetString(str); michael@0: MOZ_ASSERT(gotString); michael@0: michael@0: sheet->EvictStyleAttr(str, this); michael@0: mValue.mCached = 0; michael@0: } michael@0: michael@0: nsTArray* nsAttrValue::sEnumTableArray = nullptr; michael@0: michael@0: nsAttrValue::nsAttrValue() michael@0: : mBits(0) michael@0: { michael@0: } michael@0: michael@0: nsAttrValue::nsAttrValue(const nsAttrValue& aOther) michael@0: : mBits(0) michael@0: { michael@0: SetTo(aOther); michael@0: } michael@0: michael@0: nsAttrValue::nsAttrValue(const nsAString& aValue) michael@0: : mBits(0) michael@0: { michael@0: SetTo(aValue); michael@0: } michael@0: michael@0: nsAttrValue::nsAttrValue(nsIAtom* aValue) michael@0: : mBits(0) michael@0: { michael@0: SetTo(aValue); michael@0: } michael@0: michael@0: nsAttrValue::nsAttrValue(css::StyleRule* aValue, const nsAString* aSerialized) michael@0: : mBits(0) michael@0: { michael@0: SetTo(aValue, aSerialized); michael@0: } michael@0: michael@0: nsAttrValue::nsAttrValue(const nsIntMargin& aValue) michael@0: : mBits(0) michael@0: { michael@0: SetTo(aValue); michael@0: } michael@0: michael@0: nsAttrValue::~nsAttrValue() michael@0: { michael@0: ResetIfSet(); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsAttrValue::Init() michael@0: { michael@0: NS_ASSERTION(!sEnumTableArray, "nsAttrValue already initialized"); michael@0: michael@0: sEnumTableArray = new nsTArray; michael@0: NS_ENSURE_TRUE(sEnumTableArray, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsAttrValue::Shutdown() michael@0: { michael@0: delete sEnumTableArray; michael@0: sEnumTableArray = nullptr; michael@0: } michael@0: michael@0: nsAttrValue::ValueType michael@0: nsAttrValue::Type() const michael@0: { michael@0: switch (BaseType()) { michael@0: case eIntegerBase: michael@0: { michael@0: return static_cast(mBits & NS_ATTRVALUE_INTEGERTYPE_MASK); michael@0: } michael@0: case eOtherBase: michael@0: { michael@0: return GetMiscContainer()->mType; michael@0: } michael@0: default: michael@0: { michael@0: return static_cast(static_cast(BaseType())); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::Reset() michael@0: { michael@0: switch(BaseType()) { michael@0: case eStringBase: michael@0: { michael@0: nsStringBuffer* str = static_cast(GetPtr()); michael@0: if (str) { michael@0: str->Release(); michael@0: } michael@0: michael@0: break; michael@0: } michael@0: case eOtherBase: michael@0: { michael@0: MiscContainer* cont = GetMiscContainer(); michael@0: if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) { michael@0: NS_RELEASE(cont); michael@0: break; michael@0: } michael@0: michael@0: delete ClearMiscContainer(); michael@0: michael@0: break; michael@0: } michael@0: case eAtomBase: michael@0: { michael@0: nsIAtom* atom = GetAtomValue(); michael@0: NS_RELEASE(atom); michael@0: michael@0: break; michael@0: } michael@0: case eIntegerBase: michael@0: { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: mBits = 0; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsAttrValue& aOther) michael@0: { michael@0: if (this == &aOther) { michael@0: return; michael@0: } michael@0: michael@0: switch (aOther.BaseType()) { michael@0: case eStringBase: michael@0: { michael@0: ResetIfSet(); michael@0: nsStringBuffer* str = static_cast(aOther.GetPtr()); michael@0: if (str) { michael@0: str->AddRef(); michael@0: SetPtrValueAndType(str, eStringBase); michael@0: } michael@0: return; michael@0: } michael@0: case eOtherBase: michael@0: { michael@0: break; michael@0: } michael@0: case eAtomBase: michael@0: { michael@0: ResetIfSet(); michael@0: nsIAtom* atom = aOther.GetAtomValue(); michael@0: NS_ADDREF(atom); michael@0: SetPtrValueAndType(atom, eAtomBase); michael@0: return; michael@0: } michael@0: case eIntegerBase: michael@0: { michael@0: ResetIfSet(); michael@0: mBits = aOther.mBits; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: MiscContainer* otherCont = aOther.GetMiscContainer(); michael@0: if (otherCont->IsRefCounted()) { michael@0: delete ClearMiscContainer(); michael@0: NS_ADDREF(otherCont); michael@0: SetPtrValueAndType(otherCont, eOtherBase); michael@0: return; michael@0: } michael@0: michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: switch (otherCont->mType) { michael@0: case eInteger: michael@0: { michael@0: cont->mValue.mInteger = otherCont->mValue.mInteger; michael@0: break; michael@0: } michael@0: case eEnum: michael@0: { michael@0: cont->mValue.mEnumValue = otherCont->mValue.mEnumValue; michael@0: break; michael@0: } michael@0: case ePercent: michael@0: { michael@0: cont->mValue.mPercent = otherCont->mValue.mPercent; michael@0: break; michael@0: } michael@0: case eColor: michael@0: { michael@0: cont->mValue.mColor = otherCont->mValue.mColor; michael@0: break; michael@0: } michael@0: case eCSSStyleRule: michael@0: { michael@0: MOZ_CRASH("These should be refcounted!"); michael@0: } michael@0: case eURL: michael@0: { michael@0: NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL); michael@0: break; michael@0: } michael@0: case eImage: michael@0: { michael@0: NS_ADDREF(cont->mValue.mImage = otherCont->mValue.mImage); michael@0: break; michael@0: } michael@0: case eAtomArray: michael@0: { michael@0: if (!EnsureEmptyAtomArray() || michael@0: !GetAtomArrayValue()->AppendElements(*otherCont->mValue.mAtomArray)) { michael@0: Reset(); michael@0: return; michael@0: } michael@0: break; michael@0: } michael@0: case eDoubleValue: michael@0: { michael@0: cont->mDoubleValue = otherCont->mDoubleValue; michael@0: break; michael@0: } michael@0: case eIntMarginValue: michael@0: { michael@0: if (otherCont->mValue.mIntMargin) michael@0: cont->mValue.mIntMargin = michael@0: new nsIntMargin(*otherCont->mValue.mIntMargin); michael@0: break; michael@0: } michael@0: default: michael@0: { michael@0: if (IsSVGType(otherCont->mType)) { michael@0: // All SVG types are just pointers to classes and will therefore have michael@0: // the same size so it doesn't really matter which one we assign michael@0: cont->mValue.mSVGAngle = otherCont->mValue.mSVGAngle; michael@0: } else { michael@0: NS_NOTREACHED("unknown type stored in MiscContainer"); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void* otherPtr = MISC_STR_PTR(otherCont); michael@0: if (otherPtr) { michael@0: if (static_cast(otherCont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) == michael@0: eStringBase) { michael@0: static_cast(otherPtr)->AddRef(); michael@0: } else { michael@0: static_cast(otherPtr)->AddRef(); michael@0: } michael@0: cont->mStringBits = otherCont->mStringBits; michael@0: } michael@0: // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't michael@0: // work correctly. michael@0: cont->mType = otherCont->mType; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsAString& aValue) michael@0: { michael@0: ResetIfSet(); michael@0: nsStringBuffer* buf = GetStringBuffer(aValue).take(); michael@0: if (buf) { michael@0: SetPtrValueAndType(buf, eStringBase); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(nsIAtom* aValue) michael@0: { michael@0: ResetIfSet(); michael@0: if (aValue) { michael@0: NS_ADDREF(aValue); michael@0: SetPtrValueAndType(aValue, eAtomBase); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(int16_t aInt) michael@0: { michael@0: ResetIfSet(); michael@0: SetIntValueAndType(aInt, eInteger, nullptr); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) michael@0: { michael@0: ResetIfSet(); michael@0: SetIntValueAndType(aInt, eInteger, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) michael@0: { michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: cont->mDoubleValue = aValue; michael@0: cont->mType = eDoubleValue; michael@0: SetMiscAtomOrString(aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(css::StyleRule* aValue, const nsAString* aSerialized) michael@0: { michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: MOZ_ASSERT(cont->mValue.mRefCount == 0); michael@0: NS_ADDREF(cont->mValue.mCSSStyleRule = aValue); michael@0: cont->mType = eCSSStyleRule; michael@0: NS_ADDREF(cont); michael@0: SetMiscAtomOrString(aSerialized); michael@0: MOZ_ASSERT(cont->mValue.mRefCount == 1); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(css::URLValue* aValue, const nsAString* aSerialized) michael@0: { michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: NS_ADDREF(cont->mValue.mURL = aValue); michael@0: cont->mType = eURL; michael@0: SetMiscAtomOrString(aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsIntMargin& aValue) michael@0: { michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: cont->mValue.mIntMargin = new nsIntMargin(aValue); michael@0: cont->mType = eIntMarginValue; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetToSerialized(const nsAttrValue& aOther) michael@0: { michael@0: if (aOther.Type() != nsAttrValue::eString && michael@0: aOther.Type() != nsAttrValue::eAtom) { michael@0: nsAutoString val; michael@0: aOther.ToString(val); michael@0: SetTo(val); michael@0: } else { michael@0: SetTo(aOther); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsSVGAngle& aValue, const nsAString* aSerialized) michael@0: { michael@0: SetSVGType(eSVGAngle, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsSVGIntegerPair& aValue, const nsAString* aSerialized) michael@0: { michael@0: SetSVGType(eSVGIntegerPair, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsSVGLength2& aValue, const nsAString* aSerialized) michael@0: { michael@0: SetSVGType(eSVGLength, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const SVGLengthList& aValue, michael@0: const nsAString* aSerialized) michael@0: { michael@0: // While an empty string will parse as a length list, there's no need to store michael@0: // it (and SetMiscAtomOrString will assert if we try) michael@0: if (aSerialized && aSerialized->IsEmpty()) { michael@0: aSerialized = nullptr; michael@0: } michael@0: SetSVGType(eSVGLengthList, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const SVGNumberList& aValue, michael@0: const nsAString* aSerialized) michael@0: { michael@0: // While an empty string will parse as a number list, there's no need to store michael@0: // it (and SetMiscAtomOrString will assert if we try) michael@0: if (aSerialized && aSerialized->IsEmpty()) { michael@0: aSerialized = nullptr; michael@0: } michael@0: SetSVGType(eSVGNumberList, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsSVGNumberPair& aValue, const nsAString* aSerialized) michael@0: { michael@0: SetSVGType(eSVGNumberPair, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const SVGPathData& aValue, michael@0: const nsAString* aSerialized) michael@0: { michael@0: // While an empty string will parse as path data, there's no need to store it michael@0: // (and SetMiscAtomOrString will assert if we try) michael@0: if (aSerialized && aSerialized->IsEmpty()) { michael@0: aSerialized = nullptr; michael@0: } michael@0: SetSVGType(eSVGPathData, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const SVGPointList& aValue, michael@0: const nsAString* aSerialized) michael@0: { michael@0: // While an empty string will parse as a point list, there's no need to store michael@0: // it (and SetMiscAtomOrString will assert if we try) michael@0: if (aSerialized && aSerialized->IsEmpty()) { michael@0: aSerialized = nullptr; michael@0: } michael@0: SetSVGType(eSVGPointList, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue, michael@0: const nsAString* aSerialized) michael@0: { michael@0: SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const SVGStringList& aValue, michael@0: const nsAString* aSerialized) michael@0: { michael@0: // While an empty string will parse as a string list, there's no need to store michael@0: // it (and SetMiscAtomOrString will assert if we try) michael@0: if (aSerialized && aSerialized->IsEmpty()) { michael@0: aSerialized = nullptr; michael@0: } michael@0: SetSVGType(eSVGStringList, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const SVGTransformList& aValue, michael@0: const nsAString* aSerialized) michael@0: { michael@0: // While an empty string will parse as a transform list, there's no need to michael@0: // store it (and SetMiscAtomOrString will assert if we try) michael@0: if (aSerialized && aSerialized->IsEmpty()) { michael@0: aSerialized = nullptr; michael@0: } michael@0: SetSVGType(eSVGTransformList, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetTo(const nsSVGViewBox& aValue, const nsAString* aSerialized) michael@0: { michael@0: SetSVGType(eSVGViewBox, &aValue, aSerialized); michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SwapValueWith(nsAttrValue& aOther) michael@0: { michael@0: uintptr_t tmp = aOther.mBits; michael@0: aOther.mBits = mBits; michael@0: mBits = tmp; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::ToString(nsAString& aResult) const michael@0: { michael@0: MiscContainer* cont = nullptr; michael@0: if (BaseType() == eOtherBase) { michael@0: cont = GetMiscContainer(); michael@0: michael@0: if (cont->GetString(aResult)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: switch(Type()) { michael@0: case eString: michael@0: { michael@0: nsStringBuffer* str = static_cast(GetPtr()); michael@0: if (str) { michael@0: str->ToString(str->StorageSize()/sizeof(char16_t) - 1, aResult); michael@0: } michael@0: else { michael@0: aResult.Truncate(); michael@0: } michael@0: break; michael@0: } michael@0: case eAtom: michael@0: { michael@0: nsIAtom *atom = static_cast(GetPtr()); michael@0: atom->ToString(aResult); michael@0: michael@0: break; michael@0: } michael@0: case eInteger: michael@0: { michael@0: nsAutoString intStr; michael@0: intStr.AppendInt(GetIntegerValue()); michael@0: aResult = intStr; michael@0: michael@0: break; michael@0: } michael@0: #ifdef DEBUG michael@0: case eColor: michael@0: { michael@0: NS_NOTREACHED("color attribute without string data"); michael@0: aResult.Truncate(); michael@0: break; michael@0: } michael@0: #endif michael@0: case eEnum: michael@0: { michael@0: GetEnumString(aResult, false); michael@0: break; michael@0: } michael@0: case ePercent: michael@0: { michael@0: nsAutoString intStr; michael@0: intStr.AppendInt(cont ? cont->mValue.mPercent : GetIntInternal()); michael@0: aResult = intStr + NS_LITERAL_STRING("%"); michael@0: michael@0: break; michael@0: } michael@0: case eCSSStyleRule: michael@0: { michael@0: aResult.Truncate(); michael@0: MiscContainer *container = GetMiscContainer(); michael@0: css::Declaration *decl = michael@0: container->mValue.mCSSStyleRule->GetDeclaration(); michael@0: if (decl) { michael@0: decl->ToString(aResult); michael@0: } michael@0: const_cast(this)->SetMiscAtomOrString(&aResult); michael@0: michael@0: break; michael@0: } michael@0: case eDoubleValue: michael@0: { michael@0: aResult.Truncate(); michael@0: aResult.AppendFloat(GetDoubleValue()); michael@0: break; michael@0: } michael@0: case eSVGAngle: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGAngle, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGIntegerPair: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGIntegerPair, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGLength: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGLengthList: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGNumberList: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGNumberPair: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberPair, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGPathData: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGPointList: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGPreserveAspectRatio: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPreserveAspectRatio, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGStringList: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGTransformList: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGTransformList, michael@0: aResult); michael@0: break; michael@0: } michael@0: case eSVGViewBox: michael@0: { michael@0: SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGViewBox, michael@0: aResult); michael@0: break; michael@0: } michael@0: default: michael@0: { michael@0: aResult.Truncate(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsAttrValue::GetAsAtom() const michael@0: { michael@0: switch (Type()) { michael@0: case eString: michael@0: return do_GetAtom(GetStringValue()); michael@0: michael@0: case eAtom: michael@0: { michael@0: nsCOMPtr atom = GetAtomValue(); michael@0: return atom.forget(); michael@0: } michael@0: michael@0: default: michael@0: { michael@0: nsAutoString val; michael@0: ToString(val); michael@0: return do_GetAtom(val); michael@0: } michael@0: } michael@0: } michael@0: michael@0: const nsCheapString michael@0: nsAttrValue::GetStringValue() const michael@0: { michael@0: NS_PRECONDITION(Type() == eString, "wrong type"); michael@0: michael@0: return nsCheapString(static_cast(GetPtr())); michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::GetColorValue(nscolor& aColor) const michael@0: { michael@0: if (Type() != eColor) { michael@0: // Unparseable value, treat as unset. michael@0: NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr"); michael@0: return false; michael@0: } michael@0: michael@0: aColor = GetMiscContainer()->mValue.mColor; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const michael@0: { michael@0: NS_PRECONDITION(Type() == eEnum, "wrong type"); michael@0: michael@0: uint32_t allEnumBits = michael@0: (BaseType() == eIntegerBase) ? static_cast(GetIntInternal()) michael@0: : GetMiscContainer()->mValue.mEnumValue; michael@0: int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS; michael@0: const EnumTable* table = sEnumTableArray-> michael@0: ElementAt(allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK); michael@0: michael@0: while (table->tag) { michael@0: if (table->value == val) { michael@0: aResult.AssignASCII(table->tag); michael@0: if (!aRealTag && allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) { michael@0: nsContentUtils::ASCIIToUpper(aResult); michael@0: } michael@0: return; michael@0: } michael@0: table++; michael@0: } michael@0: michael@0: NS_NOTREACHED("couldn't find value in EnumTable"); michael@0: } michael@0: michael@0: uint32_t michael@0: nsAttrValue::GetAtomCount() const michael@0: { michael@0: ValueType type = Type(); michael@0: michael@0: if (type == eAtom) { michael@0: return 1; michael@0: } michael@0: michael@0: if (type == eAtomArray) { michael@0: return GetAtomArrayValue()->Length(); michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsAttrValue::AtomAt(int32_t aIndex) const michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0, "Index must not be negative"); michael@0: NS_PRECONDITION(GetAtomCount() > uint32_t(aIndex), "aIndex out of range"); michael@0: michael@0: if (BaseType() == eAtomBase) { michael@0: return GetAtomValue(); michael@0: } michael@0: michael@0: NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused"); michael@0: michael@0: return GetAtomArrayValue()->ElementAt(aIndex); michael@0: } michael@0: michael@0: uint32_t michael@0: nsAttrValue::HashValue() const michael@0: { michael@0: switch(BaseType()) { michael@0: case eStringBase: michael@0: { michael@0: nsStringBuffer* str = static_cast(GetPtr()); michael@0: if (str) { michael@0: uint32_t len = str->StorageSize()/sizeof(char16_t) - 1; michael@0: return HashString(static_cast(str->Data()), len); michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: case eOtherBase: michael@0: { michael@0: break; michael@0: } michael@0: case eAtomBase: michael@0: case eIntegerBase: michael@0: { michael@0: // mBits and uint32_t might have different size. This should silence michael@0: // any warnings or compile-errors. This is what the implementation of michael@0: // NS_PTR_TO_INT32 does to take care of the same problem. michael@0: return mBits - 0; michael@0: } michael@0: } michael@0: michael@0: MiscContainer* cont = GetMiscContainer(); michael@0: if (static_cast(cont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) michael@0: == eAtomBase) { michael@0: return cont->mStringBits - 0; michael@0: } michael@0: michael@0: switch (cont->mType) { michael@0: case eInteger: michael@0: { michael@0: return cont->mValue.mInteger; michael@0: } michael@0: case eEnum: michael@0: { michael@0: return cont->mValue.mEnumValue; michael@0: } michael@0: case ePercent: michael@0: { michael@0: return cont->mValue.mPercent; michael@0: } michael@0: case eColor: michael@0: { michael@0: return cont->mValue.mColor; michael@0: } michael@0: case eCSSStyleRule: michael@0: { michael@0: return NS_PTR_TO_INT32(cont->mValue.mCSSStyleRule); michael@0: } michael@0: // Intentionally identical, so that loading the image does not change the michael@0: // hash code. michael@0: case eURL: michael@0: case eImage: michael@0: { michael@0: nsString str; michael@0: ToString(str); michael@0: return HashString(str); michael@0: } michael@0: case eAtomArray: michael@0: { michael@0: uint32_t hash = 0; michael@0: uint32_t count = cont->mValue.mAtomArray->Length(); michael@0: for (nsCOMPtr *cur = cont->mValue.mAtomArray->Elements(), michael@0: *end = cur + count; michael@0: cur != end; ++cur) { michael@0: hash = AddToHash(hash, cur->get()); michael@0: } michael@0: return hash; michael@0: } michael@0: case eDoubleValue: michael@0: { michael@0: // XXX this is crappy, but oh well michael@0: return cont->mDoubleValue; michael@0: } michael@0: case eIntMarginValue: michael@0: { michael@0: return NS_PTR_TO_INT32(cont->mValue.mIntMargin); michael@0: } michael@0: default: michael@0: { michael@0: if (IsSVGType(cont->mType)) { michael@0: // All SVG types are just pointers to classes so we can treat them alike michael@0: return NS_PTR_TO_INT32(cont->mValue.mSVGAngle); michael@0: } michael@0: NS_NOTREACHED("unknown type stored in MiscContainer"); michael@0: return 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::Equals(const nsAttrValue& aOther) const michael@0: { michael@0: if (BaseType() != aOther.BaseType()) { michael@0: return false; michael@0: } michael@0: michael@0: switch(BaseType()) { michael@0: case eStringBase: michael@0: { michael@0: return GetStringValue().Equals(aOther.GetStringValue()); michael@0: } michael@0: case eOtherBase: michael@0: { michael@0: break; michael@0: } michael@0: case eAtomBase: michael@0: case eIntegerBase: michael@0: { michael@0: return mBits == aOther.mBits; michael@0: } michael@0: } michael@0: michael@0: MiscContainer* thisCont = GetMiscContainer(); michael@0: MiscContainer* otherCont = aOther.GetMiscContainer(); michael@0: if (thisCont == otherCont) { michael@0: return true; michael@0: } michael@0: michael@0: if (thisCont->mType != otherCont->mType) { michael@0: return false; michael@0: } michael@0: michael@0: bool needsStringComparison = false; michael@0: michael@0: switch (thisCont->mType) { michael@0: case eInteger: michael@0: { michael@0: if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) { michael@0: needsStringComparison = true; michael@0: } michael@0: break; michael@0: } michael@0: case eEnum: michael@0: { michael@0: if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) { michael@0: needsStringComparison = true; michael@0: } michael@0: break; michael@0: } michael@0: case ePercent: michael@0: { michael@0: if (thisCont->mValue.mPercent == otherCont->mValue.mPercent) { michael@0: needsStringComparison = true; michael@0: } michael@0: break; michael@0: } michael@0: case eColor: michael@0: { michael@0: if (thisCont->mValue.mColor == otherCont->mValue.mColor) { michael@0: needsStringComparison = true; michael@0: } michael@0: break; michael@0: } michael@0: case eCSSStyleRule: michael@0: { michael@0: return thisCont->mValue.mCSSStyleRule == otherCont->mValue.mCSSStyleRule; michael@0: } michael@0: case eURL: michael@0: { michael@0: return thisCont->mValue.mURL == otherCont->mValue.mURL; michael@0: } michael@0: case eImage: michael@0: { michael@0: return thisCont->mValue.mImage == otherCont->mValue.mImage; michael@0: } michael@0: case eAtomArray: michael@0: { michael@0: // For classlists we could be insensitive to order, however michael@0: // classlists are never mapped attributes so they are never compared. michael@0: michael@0: if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) { michael@0: return false; michael@0: } michael@0: michael@0: needsStringComparison = true; michael@0: break; michael@0: } michael@0: case eDoubleValue: michael@0: { michael@0: return thisCont->mDoubleValue == otherCont->mDoubleValue; michael@0: } michael@0: case eIntMarginValue: michael@0: { michael@0: return thisCont->mValue.mIntMargin == otherCont->mValue.mIntMargin; michael@0: } michael@0: default: michael@0: { michael@0: if (IsSVGType(thisCont->mType)) { michael@0: // Currently this method is never called for nsAttrValue objects that michael@0: // point to SVG data types. michael@0: // If that changes then we probably want to add methods to the michael@0: // corresponding SVG types to compare their base values. michael@0: // As a shortcut, however, we can begin by comparing the pointers. michael@0: NS_ABORT_IF_FALSE(false, "Comparing nsAttrValues that point to SVG " michael@0: "data"); michael@0: return false; michael@0: } michael@0: NS_NOTREACHED("unknown type stored in MiscContainer"); michael@0: return false; michael@0: } michael@0: } michael@0: if (needsStringComparison) { michael@0: if (thisCont->mStringBits == otherCont->mStringBits) { michael@0: return true; michael@0: } michael@0: if ((static_cast(thisCont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) == michael@0: eStringBase) && michael@0: (static_cast(otherCont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) == michael@0: eStringBase)) { michael@0: return nsCheapString(reinterpret_cast(thisCont->mStringBits)).Equals( michael@0: nsCheapString(reinterpret_cast(otherCont->mStringBits))); michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::Equals(const nsAString& aValue, michael@0: nsCaseTreatment aCaseSensitive) const michael@0: { michael@0: switch (BaseType()) { michael@0: case eStringBase: michael@0: { michael@0: nsStringBuffer* str = static_cast(GetPtr()); michael@0: if (str) { michael@0: nsDependentString dep(static_cast(str->Data()), michael@0: str->StorageSize()/sizeof(char16_t) - 1); michael@0: return aCaseSensitive == eCaseMatters ? aValue.Equals(dep) : michael@0: nsContentUtils::EqualsIgnoreASCIICase(aValue, dep); michael@0: } michael@0: return aValue.IsEmpty(); michael@0: } michael@0: case eAtomBase: michael@0: if (aCaseSensitive == eCaseMatters) { michael@0: return static_cast(GetPtr())->Equals(aValue); michael@0: } michael@0: return nsContentUtils::EqualsIgnoreASCIICase( michael@0: nsDependentAtomString(static_cast(GetPtr())), michael@0: aValue); michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: nsAutoString val; michael@0: ToString(val); michael@0: return aCaseSensitive == eCaseMatters ? val.Equals(aValue) : michael@0: nsContentUtils::EqualsIgnoreASCIICase(val, aValue); michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::Equals(nsIAtom* aValue, nsCaseTreatment aCaseSensitive) const michael@0: { michael@0: if (aCaseSensitive != eCaseMatters) { michael@0: // Need a better way to handle this! michael@0: nsAutoString value; michael@0: aValue->ToString(value); michael@0: return Equals(value, aCaseSensitive); michael@0: } michael@0: michael@0: switch (BaseType()) { michael@0: case eStringBase: michael@0: { michael@0: nsStringBuffer* str = static_cast(GetPtr()); michael@0: if (str) { michael@0: nsDependentString dep(static_cast(str->Data()), michael@0: str->StorageSize()/sizeof(char16_t) - 1); michael@0: return aValue->Equals(dep); michael@0: } michael@0: return aValue == nsGkAtoms::_empty; michael@0: } michael@0: case eAtomBase: michael@0: { michael@0: return static_cast(GetPtr()) == aValue; michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: nsAutoString val; michael@0: ToString(val); michael@0: return aValue->Equals(val); michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const michael@0: { michael@0: if (Type() == aOther.Type()) { michael@0: return Equals(aOther); michael@0: } michael@0: michael@0: // We need to serialize at least one nsAttrValue before passing to michael@0: // Equals(const nsAString&), but we can avoid unnecessarily serializing both michael@0: // by checking if one is already of a string type. michael@0: bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase); michael@0: const nsAttrValue& lhs = thisIsString ? *this : aOther; michael@0: const nsAttrValue& rhs = thisIsString ? aOther : *this; michael@0: michael@0: switch (rhs.BaseType()) { michael@0: case eAtomBase: michael@0: return lhs.Equals(rhs.GetAtomValue(), eCaseMatters); michael@0: michael@0: case eStringBase: michael@0: return lhs.Equals(rhs.GetStringValue(), eCaseMatters); michael@0: michael@0: default: michael@0: { michael@0: nsAutoString val; michael@0: rhs.ToString(val); michael@0: return lhs.Equals(val, eCaseMatters); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::Contains(nsIAtom* aValue, nsCaseTreatment aCaseSensitive) const michael@0: { michael@0: switch (BaseType()) { michael@0: case eAtomBase: michael@0: { michael@0: nsIAtom* atom = GetAtomValue(); michael@0: michael@0: if (aCaseSensitive == eCaseMatters) { michael@0: return aValue == atom; michael@0: } michael@0: michael@0: // For performance reasons, don't do a full on unicode case insensitive michael@0: // string comparison. This is only used for quirks mode anyway. michael@0: return michael@0: nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(aValue), michael@0: nsDependentAtomString(atom)); michael@0: } michael@0: default: michael@0: { michael@0: if (Type() == eAtomArray) { michael@0: AtomArray* array = GetAtomArrayValue(); michael@0: if (aCaseSensitive == eCaseMatters) { michael@0: return array->Contains(aValue); michael@0: } michael@0: michael@0: nsDependentAtomString val1(aValue); michael@0: michael@0: for (nsCOMPtr *cur = array->Elements(), michael@0: *end = cur + array->Length(); michael@0: cur != end; ++cur) { michael@0: // For performance reasons, don't do a full on unicode case michael@0: // insensitive string comparison. This is only used for quirks mode michael@0: // anyway. michael@0: if (nsContentUtils::EqualsIgnoreASCIICase(val1, michael@0: nsDependentAtomString(*cur))) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: struct AtomArrayStringComparator { michael@0: bool Equals(nsIAtom* atom, const nsAString& string) const { michael@0: return atom->Equals(string); michael@0: } michael@0: }; michael@0: michael@0: bool michael@0: nsAttrValue::Contains(const nsAString& aValue) const michael@0: { michael@0: switch (BaseType()) { michael@0: case eAtomBase: michael@0: { michael@0: nsIAtom* atom = GetAtomValue(); michael@0: return atom->Equals(aValue); michael@0: } michael@0: default: michael@0: { michael@0: if (Type() == eAtomArray) { michael@0: AtomArray* array = GetAtomArrayValue(); michael@0: return array->Contains(aValue, AtomArrayStringComparator()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::ParseAtom(const nsAString& aValue) michael@0: { michael@0: ResetIfSet(); michael@0: michael@0: nsCOMPtr atom = NS_NewAtom(aValue); michael@0: if (atom) { michael@0: SetPtrValueAndType(atom.forget().take(), eAtomBase); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::ParseAtomArray(const nsAString& aValue) michael@0: { michael@0: nsAString::const_iterator iter, end; michael@0: aValue.BeginReading(iter); michael@0: aValue.EndReading(end); michael@0: bool hasSpace = false; michael@0: michael@0: // skip initial whitespace michael@0: while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { michael@0: hasSpace = true; michael@0: ++iter; michael@0: } michael@0: michael@0: if (iter == end) { michael@0: SetTo(aValue); michael@0: return; michael@0: } michael@0: michael@0: nsAString::const_iterator start(iter); michael@0: michael@0: // get first - and often only - atom michael@0: do { michael@0: ++iter; michael@0: } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter)); michael@0: michael@0: nsCOMPtr classAtom = do_GetAtom(Substring(start, iter)); michael@0: if (!classAtom) { michael@0: Reset(); michael@0: return; michael@0: } michael@0: michael@0: // skip whitespace michael@0: while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { michael@0: hasSpace = true; michael@0: ++iter; michael@0: } michael@0: michael@0: if (iter == end && !hasSpace) { michael@0: // we only found one classname and there was no whitespace so michael@0: // don't bother storing a list michael@0: ResetIfSet(); michael@0: nsIAtom* atom = nullptr; michael@0: classAtom.swap(atom); michael@0: SetPtrValueAndType(atom, eAtomBase); michael@0: return; michael@0: } michael@0: michael@0: if (!EnsureEmptyAtomArray()) { michael@0: return; michael@0: } michael@0: michael@0: AtomArray* array = GetAtomArrayValue(); michael@0: michael@0: if (!array->AppendElement(classAtom)) { michael@0: Reset(); michael@0: return; michael@0: } michael@0: michael@0: // parse the rest of the classnames michael@0: while (iter != end) { michael@0: start = iter; michael@0: michael@0: do { michael@0: ++iter; michael@0: } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter)); michael@0: michael@0: classAtom = do_GetAtom(Substring(start, iter)); michael@0: michael@0: if (!array->AppendElement(classAtom)) { michael@0: Reset(); michael@0: return; michael@0: } michael@0: michael@0: // skip whitespace michael@0: while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { michael@0: ++iter; michael@0: } michael@0: } michael@0: michael@0: SetMiscAtomOrString(&aValue); michael@0: return; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::ParseStringOrAtom(const nsAString& aValue) michael@0: { michael@0: uint32_t len = aValue.Length(); michael@0: // Don't bother with atoms if it's an empty string since michael@0: // we can store those efficently anyway. michael@0: if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) { michael@0: ParseAtom(aValue); michael@0: } michael@0: else { michael@0: SetTo(aValue); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType, michael@0: const nsAString* aStringValue) michael@0: { michael@0: if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE || michael@0: aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) { michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: switch (aType) { michael@0: case eInteger: michael@0: { michael@0: cont->mValue.mInteger = aValue; michael@0: break; michael@0: } michael@0: case ePercent: michael@0: { michael@0: cont->mValue.mPercent = aValue; michael@0: break; michael@0: } michael@0: case eEnum: michael@0: { michael@0: cont->mValue.mEnumValue = aValue; michael@0: break; michael@0: } michael@0: default: michael@0: { michael@0: NS_NOTREACHED("unknown integer type"); michael@0: break; michael@0: } michael@0: } michael@0: cont->mType = aType; michael@0: SetMiscAtomOrString(aStringValue); michael@0: } else { michael@0: NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!"); michael@0: mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType; michael@0: } michael@0: } michael@0: michael@0: int16_t michael@0: nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) michael@0: { michael@0: int16_t index = sEnumTableArray->IndexOf(aTable); michael@0: if (index < 0) { michael@0: index = sEnumTableArray->Length(); michael@0: NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE, michael@0: "too many enum tables"); michael@0: sEnumTableArray->AppendElement(aTable); michael@0: } michael@0: michael@0: return index; michael@0: } michael@0: michael@0: int32_t michael@0: nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable, michael@0: const EnumTable* aTableEntry) michael@0: { michael@0: int16_t index = GetEnumTableIndex(aEnumTable); michael@0: int32_t value = (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + michael@0: index; michael@0: return value; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParseEnumValue(const nsAString& aValue, michael@0: const EnumTable* aTable, michael@0: bool aCaseSensitive, michael@0: const EnumTable* aDefaultValue) michael@0: { michael@0: ResetIfSet(); michael@0: const EnumTable* tableEntry = aTable; michael@0: michael@0: while (tableEntry->tag) { michael@0: if (aCaseSensitive ? aValue.EqualsASCII(tableEntry->tag) : michael@0: aValue.LowerCaseEqualsASCII(tableEntry->tag)) { michael@0: int32_t value = EnumTableEntryToValue(aTable, tableEntry); michael@0: michael@0: bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry->tag); michael@0: if (!equals) { michael@0: nsAutoString tag; michael@0: tag.AssignASCII(tableEntry->tag); michael@0: nsContentUtils::ASCIIToUpper(tag); michael@0: if ((equals = tag.Equals(aValue))) { michael@0: value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER; michael@0: } michael@0: } michael@0: SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue); michael@0: NS_ASSERTION(GetEnumValue() == tableEntry->value, michael@0: "failed to store enum properly"); michael@0: michael@0: return true; michael@0: } michael@0: tableEntry++; michael@0: } michael@0: michael@0: if (aDefaultValue) { michael@0: NS_PRECONDITION(aTable <= aDefaultValue && aDefaultValue < tableEntry, michael@0: "aDefaultValue not inside aTable?"); michael@0: SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), michael@0: eEnum, &aValue); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParseSpecialIntValue(const nsAString& aString) michael@0: { michael@0: ResetIfSet(); michael@0: michael@0: nsresult ec; michael@0: bool strict; michael@0: bool isPercent = false; michael@0: nsAutoString tmp(aString); michael@0: int32_t originalVal = StringToInteger(aString, &strict, &ec, true, &isPercent); michael@0: michael@0: if (NS_FAILED(ec)) { michael@0: return false; michael@0: } michael@0: michael@0: int32_t val = std::max(originalVal, 0); michael@0: michael@0: // % (percent) michael@0: if (isPercent || tmp.RFindChar('%') >= 0) { michael@0: isPercent = true; michael@0: } michael@0: michael@0: strict = strict && (originalVal == val); michael@0: michael@0: SetIntValueAndType(val, michael@0: isPercent ? ePercent : eInteger, michael@0: strict ? nullptr : &aString); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParseIntWithBounds(const nsAString& aString, michael@0: int32_t aMin, int32_t aMax) michael@0: { michael@0: NS_PRECONDITION(aMin < aMax, "bad boundaries"); michael@0: michael@0: ResetIfSet(); michael@0: michael@0: nsresult ec; michael@0: bool strict; michael@0: int32_t originalVal = StringToInteger(aString, &strict, &ec); michael@0: if (NS_FAILED(ec)) { michael@0: return false; michael@0: } michael@0: michael@0: int32_t val = std::max(originalVal, aMin); michael@0: val = std::min(val, aMax); michael@0: strict = strict && (originalVal == val); michael@0: SetIntValueAndType(val, eInteger, strict ? nullptr : &aString); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) michael@0: { michael@0: ResetIfSet(); michael@0: michael@0: nsresult ec; michael@0: bool strict; michael@0: int32_t originalVal = StringToInteger(aString, &strict, &ec); michael@0: if (NS_FAILED(ec) || originalVal < 0) { michael@0: return false; michael@0: } michael@0: michael@0: SetIntValueAndType(originalVal, eInteger, strict ? nullptr : &aString); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParsePositiveIntValue(const nsAString& aString) michael@0: { michael@0: ResetIfSet(); michael@0: michael@0: nsresult ec; michael@0: bool strict; michael@0: int32_t originalVal = StringToInteger(aString, &strict, &ec); michael@0: if (NS_FAILED(ec) || originalVal <= 0) { michael@0: return false; michael@0: } michael@0: michael@0: SetIntValueAndType(originalVal, eInteger, strict ? nullptr : &aString); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) michael@0: { michael@0: nsStringBuffer* buf = GetStringBuffer(aString).take(); michael@0: if (!buf) { michael@0: return; michael@0: } michael@0: michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: cont->mValue.mColor = aColor; michael@0: cont->mType = eColor; michael@0: michael@0: // Save the literal string we were passed for round-tripping. michael@0: cont->mStringBits = reinterpret_cast(buf) | eStringBase; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParseColor(const nsAString& aString) michael@0: { michael@0: ResetIfSet(); michael@0: michael@0: // FIXME (partially, at least): HTML5's algorithm says we shouldn't do michael@0: // the whitespace compression, trimming, or the test for emptiness. michael@0: // (I'm a little skeptical that we shouldn't do the whitespace michael@0: // trimming; WebKit also does it.) michael@0: nsAutoString colorStr(aString); michael@0: colorStr.CompressWhitespace(true, true); michael@0: if (colorStr.IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: nscolor color; michael@0: // No color names begin with a '#'; in standards mode, all acceptable michael@0: // numeric colors do. michael@0: if (colorStr.First() == '#') { michael@0: nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1); michael@0: if (NS_HexToRGB(withoutHash, &color)) { michael@0: SetColorValue(color, aString); michael@0: return true; michael@0: } michael@0: } else { michael@0: if (NS_ColorNameToRGB(colorStr, &color)) { michael@0: SetColorValue(color, aString); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // FIXME (maybe): HTML5 says we should handle system colors. This michael@0: // means we probably need another storage type, since we'd need to michael@0: // handle dynamic changes. However, I think this is a bad idea: michael@0: // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html michael@0: michael@0: // Use NS_LooseHexToRGB as a fallback if nothing above worked. michael@0: if (NS_LooseHexToRGB(colorStr, &color)) { michael@0: SetColorValue(color, aString); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool nsAttrValue::ParseDoubleValue(const nsAString& aString) michael@0: { michael@0: ResetIfSet(); michael@0: michael@0: nsresult ec; michael@0: double val = PromiseFlatString(aString).ToDouble(&ec); michael@0: if (NS_FAILED(ec)) { michael@0: return false; michael@0: } michael@0: michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: cont->mDoubleValue = val; michael@0: cont->mType = eDoubleValue; michael@0: nsAutoString serializedFloat; michael@0: serializedFloat.AppendFloat(val); michael@0: SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParseIntMarginValue(const nsAString& aString) michael@0: { michael@0: ResetIfSet(); michael@0: michael@0: nsIntMargin margins; michael@0: if (!nsContentUtils::ParseIntMarginValue(aString, margins)) michael@0: return false; michael@0: michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: cont->mValue.mIntMargin = new nsIntMargin(margins); michael@0: cont->mType = eIntMarginValue; michael@0: SetMiscAtomOrString(&aString); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::LoadImage(nsIDocument* aDocument) michael@0: { michael@0: NS_ASSERTION(Type() == eURL, "wrong type"); michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: nsString val; michael@0: ToString(val); michael@0: NS_ASSERTION(!val.IsEmpty(), michael@0: "How did we end up with an empty string for eURL"); michael@0: } michael@0: #endif michael@0: michael@0: MiscContainer* cont = GetMiscContainer(); michael@0: mozilla::css::URLValue* url = cont->mValue.mURL; michael@0: mozilla::css::ImageValue* image = michael@0: new css::ImageValue(url->GetURI(), url->mString, url->mReferrer, michael@0: url->mOriginPrincipal, aDocument); michael@0: michael@0: NS_ADDREF(image); michael@0: cont->mValue.mImage = image; michael@0: NS_RELEASE(url); michael@0: cont->mType = eImage; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::ParseStyleAttribute(const nsAString& aString, michael@0: nsStyledElementNotElementCSSInlineStyle* aElement) michael@0: { michael@0: nsIDocument* ownerDoc = aElement->OwnerDoc(); michael@0: nsHTMLCSSStyleSheet* sheet = ownerDoc->GetInlineStyleSheet(); michael@0: nsCOMPtr baseURI = aElement->GetBaseURI(); michael@0: nsIURI* docURI = ownerDoc->GetDocumentURI(); michael@0: michael@0: NS_ASSERTION(aElement->NodePrincipal() == ownerDoc->NodePrincipal(), michael@0: "This is unexpected"); michael@0: michael@0: // If the (immutable) document URI does not match the element's base URI michael@0: // (the common case is that they do match) do not cache the rule. This is michael@0: // because the results of the CSS parser are dependent on these URIs, and we michael@0: // do not want to have to account for the URIs in the hash lookup. michael@0: bool cachingAllowed = sheet && baseURI == docURI; michael@0: if (cachingAllowed) { michael@0: MiscContainer* cont = sheet->LookupStyleAttr(aString); michael@0: if (cont) { michael@0: // Set our MiscContainer to the cached one. michael@0: NS_ADDREF(cont); michael@0: SetPtrValueAndType(cont, eOtherBase); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: css::Loader* cssLoader = ownerDoc->CSSLoader(); michael@0: nsCSSParser cssParser(cssLoader); michael@0: michael@0: nsRefPtr rule; michael@0: cssParser.ParseStyleAttribute(aString, docURI, baseURI, michael@0: aElement->NodePrincipal(), michael@0: getter_AddRefs(rule)); michael@0: if (rule) { michael@0: rule->SetHTMLCSSStyleSheet(sheet); michael@0: SetTo(rule, &aString); michael@0: if (cachingAllowed) { michael@0: MiscContainer* cont = GetMiscContainer(); michael@0: cont->Cache(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) michael@0: { michael@0: NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!"); michael@0: NS_ASSERTION(!GetMiscContainer()->mStringBits, michael@0: "Trying to re-set atom or string!"); michael@0: if (aValue) { michael@0: uint32_t len = aValue->Length(); michael@0: // * We're allowing eCSSStyleRule attributes to store empty strings as it michael@0: // can be beneficial to store an empty style attribute as a parsed rule. michael@0: // * We're allowing enumerated values because sometimes the empty michael@0: // string corresponds to a particular enumerated value, especially michael@0: // for enumerated values that are not limited enumerated. michael@0: // Add other types as needed. michael@0: NS_ASSERTION(len || Type() == eCSSStyleRule || Type() == eEnum, michael@0: "Empty string?"); michael@0: MiscContainer* cont = GetMiscContainer(); michael@0: if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) { michael@0: nsCOMPtr atom = NS_NewAtom(*aValue); michael@0: if (atom) { michael@0: cont->mStringBits = michael@0: reinterpret_cast(atom.forget().take()) | eAtomBase; michael@0: } michael@0: } else { michael@0: nsStringBuffer* buf = GetStringBuffer(*aValue).take(); michael@0: if (buf) { michael@0: cont->mStringBits = reinterpret_cast(buf) | eStringBase; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::ResetMiscAtomOrString() michael@0: { michael@0: MiscContainer* cont = GetMiscContainer(); michael@0: void* ptr = MISC_STR_PTR(cont); michael@0: if (ptr) { michael@0: if (static_cast(cont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) == michael@0: eStringBase) { michael@0: static_cast(ptr)->Release(); michael@0: } else { michael@0: static_cast(ptr)->Release(); michael@0: } michael@0: cont->mStringBits = 0; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAttrValue::SetSVGType(ValueType aType, const void* aValue, michael@0: const nsAString* aSerialized) { michael@0: NS_ABORT_IF_FALSE(IsSVGType(aType), "Not an SVG type"); michael@0: michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: // All SVG types are just pointers to classes so just setting any of them michael@0: // will do. We'll lose type-safety but the signature of the calling michael@0: // function should ensure we don't get anything unexpected, and once we michael@0: // stick aValue in a union we lose type information anyway. michael@0: cont->mValue.mSVGAngle = static_cast(aValue); michael@0: cont->mType = aType; michael@0: SetMiscAtomOrString(aSerialized); michael@0: } michael@0: michael@0: MiscContainer* michael@0: nsAttrValue::ClearMiscContainer() michael@0: { michael@0: MiscContainer* cont = nullptr; michael@0: if (BaseType() == eOtherBase) { michael@0: cont = GetMiscContainer(); michael@0: if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) { michael@0: // This MiscContainer is shared, we need a new one. michael@0: NS_RELEASE(cont); michael@0: michael@0: cont = new MiscContainer; michael@0: SetPtrValueAndType(cont, eOtherBase); michael@0: } michael@0: else { michael@0: switch (cont->mType) { michael@0: case eCSSStyleRule: michael@0: { michael@0: MOZ_ASSERT(cont->mValue.mRefCount == 1); michael@0: cont->Release(); michael@0: cont->Evict(); michael@0: NS_RELEASE(cont->mValue.mCSSStyleRule); michael@0: break; michael@0: } michael@0: case eURL: michael@0: { michael@0: NS_RELEASE(cont->mValue.mURL); michael@0: break; michael@0: } michael@0: case eImage: michael@0: { michael@0: NS_RELEASE(cont->mValue.mImage); michael@0: break; michael@0: } michael@0: case eAtomArray: michael@0: { michael@0: delete cont->mValue.mAtomArray; michael@0: break; michael@0: } michael@0: case eIntMarginValue: michael@0: { michael@0: delete cont->mValue.mIntMargin; michael@0: break; michael@0: } michael@0: default: michael@0: { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: ResetMiscAtomOrString(); michael@0: } michael@0: else { michael@0: ResetIfSet(); michael@0: } michael@0: michael@0: return cont; michael@0: } michael@0: michael@0: MiscContainer* michael@0: nsAttrValue::EnsureEmptyMiscContainer() michael@0: { michael@0: MiscContainer* cont = ClearMiscContainer(); michael@0: if (cont) { michael@0: MOZ_ASSERT(BaseType() == eOtherBase); michael@0: ResetMiscAtomOrString(); michael@0: cont = GetMiscContainer(); michael@0: } michael@0: else { michael@0: cont = new MiscContainer; michael@0: SetPtrValueAndType(cont, eOtherBase); michael@0: } michael@0: michael@0: return cont; michael@0: } michael@0: michael@0: bool michael@0: nsAttrValue::EnsureEmptyAtomArray() michael@0: { michael@0: if (Type() == eAtomArray) { michael@0: ResetMiscAtomOrString(); michael@0: GetAtomArrayValue()->Clear(); michael@0: return true; michael@0: } michael@0: michael@0: AtomArray* array = new AtomArray; michael@0: if (!array) { michael@0: Reset(); michael@0: return false; michael@0: } michael@0: michael@0: MiscContainer* cont = EnsureEmptyMiscContainer(); michael@0: cont->mValue.mAtomArray = array; michael@0: cont->mType = eAtomArray; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsAttrValue::GetStringBuffer(const nsAString& aValue) const michael@0: { michael@0: uint32_t len = aValue.Length(); michael@0: if (!len) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr buf = nsStringBuffer::FromString(aValue); michael@0: if (buf && (buf->StorageSize()/sizeof(char16_t) - 1) == len) { michael@0: return buf.forget(); michael@0: } michael@0: michael@0: buf = nsStringBuffer::Alloc((len + 1) * sizeof(char16_t)); michael@0: if (!buf) { michael@0: return nullptr; michael@0: } michael@0: char16_t *data = static_cast(buf->Data()); michael@0: CopyUnicodeTo(aValue, 0, data, len); michael@0: data[len] = char16_t(0); michael@0: return buf.forget(); michael@0: } michael@0: michael@0: int32_t michael@0: nsAttrValue::StringToInteger(const nsAString& aValue, bool* aStrict, michael@0: nsresult* aErrorCode, michael@0: bool aCanBePercent, michael@0: bool* aIsPercent) const michael@0: { michael@0: *aStrict = true; michael@0: *aErrorCode = NS_ERROR_ILLEGAL_VALUE; michael@0: if (aCanBePercent) { michael@0: *aIsPercent = false; michael@0: } michael@0: michael@0: nsAString::const_iterator iter, end; michael@0: aValue.BeginReading(iter); michael@0: aValue.EndReading(end); michael@0: michael@0: while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { michael@0: *aStrict = false; michael@0: ++iter; michael@0: } michael@0: michael@0: if (iter == end) { michael@0: return 0; michael@0: } michael@0: michael@0: bool negate = false; michael@0: if (*iter == char16_t('-')) { michael@0: negate = true; michael@0: ++iter; michael@0: } else if (*iter == char16_t('+')) { michael@0: *aStrict = false; michael@0: ++iter; michael@0: } michael@0: michael@0: int32_t value = 0; michael@0: int32_t pValue = 0; // Previous value, used to check integer overflow michael@0: while (iter != end) { michael@0: if (*iter >= char16_t('0') && *iter <= char16_t('9')) { michael@0: value = (value * 10) + (*iter - char16_t('0')); michael@0: ++iter; michael@0: // Checking for integer overflow. michael@0: if (pValue > value) { michael@0: *aStrict = false; michael@0: *aErrorCode = NS_ERROR_ILLEGAL_VALUE; michael@0: break; michael@0: } else { michael@0: pValue = value; michael@0: *aErrorCode = NS_OK; michael@0: } michael@0: } else if (aCanBePercent && *iter == char16_t('%')) { michael@0: ++iter; michael@0: *aIsPercent = true; michael@0: if (iter != end) { michael@0: *aStrict = false; michael@0: break; michael@0: } michael@0: } else { michael@0: *aStrict = false; michael@0: break; michael@0: } michael@0: } michael@0: if (negate) { michael@0: value = -value; michael@0: // Checking the special case of -0. michael@0: if (!value) { michael@0: *aStrict = false; michael@0: } michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: size_t michael@0: nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = 0; michael@0: michael@0: switch (BaseType()) { michael@0: case eStringBase: michael@0: { michael@0: nsStringBuffer* str = static_cast(GetPtr()); michael@0: n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0; michael@0: break; michael@0: } michael@0: case eOtherBase: michael@0: { michael@0: MiscContainer* container = GetMiscContainer(); michael@0: if (!container) { michael@0: break; michael@0: } michael@0: n += aMallocSizeOf(container); michael@0: michael@0: void* otherPtr = MISC_STR_PTR(container); michael@0: // We only count the size of the object pointed by otherPtr if it's a michael@0: // string. When it's an atom, it's counted separatly. michael@0: if (otherPtr && michael@0: static_cast(container->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) { michael@0: nsStringBuffer* str = static_cast(otherPtr); michael@0: n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0; michael@0: } michael@0: michael@0: if (Type() == eCSSStyleRule && container->mValue.mCSSStyleRule) { michael@0: // TODO: mCSSStyleRule might be owned by another object which would michael@0: // make us count them twice, bug 677493. michael@0: //n += container->mCSSStyleRule->SizeOfIncludingThis(aMallocSizeOf); michael@0: } else if (Type() == eAtomArray && container->mValue.mAtomArray) { michael@0: // Don't measure each nsIAtom, they are measured separatly. michael@0: n += container->mValue.mAtomArray->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: break; michael@0: } michael@0: case eAtomBase: // Atoms are counted separately. michael@0: case eIntegerBase: // The value is in mBits, nothing to do. michael@0: break; michael@0: } michael@0: michael@0: return n; michael@0: } michael@0: