Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/base/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 63 kB image not shown  

Quelle  nsAttrValue.cpp   Sprache: C

 
/* -*- 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/. */


/*
 * A struct that represents the value (type and actual data) of an
 * attribute.
 */


#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/HashFunctions.h"

#include "nsAttrValue.h"
#include "nsAttrValueInlines.h"
#include "nsUnicharUtils.h"
#include "mozilla/AttributeStyles.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/BloomFilter.h"
#include "mozilla/DeclarationBlock.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/ServoBindingTypes.h"
#include "mozilla/ServoUtils.h"
#include "mozilla/ShadowParts.h"
#include "mozilla/SVGAttrValueWrapper.h"
#include "mozilla/URLExtraData.h"
#include "mozilla/dom/Document.h"
#include "nsContentUtils.h"
#include "nsReadableUtils.h"
#include "nsStyledElement.h"
#include "nsIURI.h"
#include "ReferrerInfo.h"
#include <algorithm>

using namespace mozilla;

constexpr uint32_t kMiscContainerCacheSize = 128;
static void* gMiscContainerCache[kMiscContainerCacheSize];
static uint32_t gMiscContainerCount = 0;

/**
 * Global cache for eAtomArray MiscContainer objects, to speed up the parsing
 * of class attributes with multiple class names.
 * This cache doesn't keep anything alive - a MiscContainer removes itself from
 * the cache once its last reference is dropped.
 */

struct AtomArrayCache {
  // We don't keep any strong references, neither to the atom nor to the
  // MiscContainer. The MiscContainer removes itself from the cache when
  // the last reference to it is dropped, and the atom is kept alive by
  // the MiscContainer.
  using MapType = nsTHashMap<nsAtom*, MiscContainer*>;

  static MiscContainer* Lookup(nsAtom* aValue) {
    if (auto* instance = GetInstance()) {
      return instance->LookupImpl(aValue);
    }
    return nullptr;
  }

  static void Insert(nsAtom* aValue, MiscContainer* aCont) {
    if (auto* instance = GetInstance()) {
      instance->InsertImpl(aValue, aCont);
    }
  }

  static void Remove(nsAtom* aValue) {
    if (auto* instance = GetInstance()) {
      instance->RemoveImpl(aValue);
    }
  }

  static AtomArrayCache* GetInstance() {
    static StaticAutoPtr<AtomArrayCache> sInstance;
    if (!sInstance && !PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) {
      sInstance = new AtomArrayCache();
      ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownFinal);
    }
    return sInstance;
  }

 private:
  MiscContainer* LookupImpl(nsAtom* aValue) {
    auto lookupResult = mMap.Lookup(aValue);
    return lookupResult ? *lookupResult : nullptr;
  }

  void InsertImpl(nsAtom* aValue, MiscContainer* aCont) {
    MOZ_ASSERT(aCont);
    mMap.InsertOrUpdate(aValue, aCont);
  }

  void RemoveImpl(nsAtom* aValue) { mMap.Remove(aValue); }

  MapType mMap;
};

/* static */
MiscContainer* nsAttrValue::AllocMiscContainer() {
  MOZ_ASSERT(NS_IsMainThread());

  static_assert(sizeof(gMiscContainerCache) <= 1024);
  static_assert(sizeof(MiscContainer) <= 32);

  // Allocate MiscContainer objects in batches to improve performance.
  if (gMiscContainerCount == 0) {
    for (; gMiscContainerCount < kMiscContainerCacheSize;
         ++gMiscContainerCount) {
      gMiscContainerCache[gMiscContainerCount] =
          moz_xmalloc(sizeof(MiscContainer));
    }
  }

  return new (gMiscContainerCache[--gMiscContainerCount]) MiscContainer();
}

/* static */
void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) {
  MOZ_ASSERT(NS_IsMainThread());
  if (!aCont) {
    return;
  }

  aCont->~MiscContainer();

  if (gMiscContainerCount < kMiscContainerCacheSize) {
    gMiscContainerCache[gMiscContainerCount++] = aCont;
    return;
  }

  free(aCont);
}

bool MiscContainer::GetString(nsAString& aString) const {
  bool isString;
  void* ptr = GetStringOrAtomPtr(isString);
  if (!ptr) {
    return false;
  }
  if (isString) {
    auto* buffer = static_cast<mozilla::StringBuffer*>(ptr);
    aString.Assign(buffer, buffer->StorageSize() / sizeof(char16_t) - 1);
  } else {
    static_cast<nsAtom*>(ptr)->ToString(aString);
  }
  return true;
}

void MiscContainer::Cache() {
  switch (mType) {
    case nsAttrValue::eCSSDeclaration: {
      MOZ_ASSERT(IsRefCounted());
      MOZ_ASSERT(mValue.mRefCount > 0);
      MOZ_ASSERT(!mValue.mCached);

      AttributeStyles* attrStyles =
          mValue.mCSSDeclaration->GetAttributeStyles();
      if (!attrStyles) {
        return;
      }

      nsString str;
      bool gotString = GetString(str);
      if (!gotString) {
        return;
      }

      attrStyles->CacheStyleAttr(str, this);
      mValue.mCached = 1;

      // This has to be immutable once it goes into the cache.
      mValue.mCSSDeclaration->SetImmutable();
      break;
    }
    case nsAttrValue::eAtomArray: {
      MOZ_ASSERT(IsRefCounted());
      MOZ_ASSERT(mValue.mRefCount > 0);
      MOZ_ASSERT(!mValue.mCached);

      nsAtom* atom = GetStoredAtom();
      if (!atom) {
        return;
      }

      AtomArrayCache::Insert(atom, this);
      mValue.mCached = 1;
      break;
    }
    default:
      MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
      break;
  }
}

void MiscContainer::Evict() {
  switch (mType) {
    case nsAttrValue::eCSSDeclaration: {
      MOZ_ASSERT(IsRefCounted());
      MOZ_ASSERT(mValue.mRefCount == 0);

      if (!mValue.mCached) {
        return;
      }

      AttributeStyles* attrStyles =
          mValue.mCSSDeclaration->GetAttributeStyles();
      MOZ_ASSERT(attrStyles);

      nsString str;
      DebugOnly<bool> gotString = GetString(str);
      MOZ_ASSERT(gotString);

      attrStyles->EvictStyleAttr(str, this);
      mValue.mCached = 0;
      break;
    }
    case nsAttrValue::eAtomArray: {
      MOZ_ASSERT(IsRefCounted());
      MOZ_ASSERT(mValue.mRefCount == 0);

      if (!mValue.mCached) {
        return;
      }

      nsAtom* atom = GetStoredAtom();
      MOZ_ASSERT(atom);

      AtomArrayCache::Remove(atom);

      mValue.mCached = 0;
      break;
    }
    default:

      MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
      break;
  }
}

nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nullptr;

nsAttrValue::nsAttrValue() : mBits(0) {}

nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) {
  SetTo(aOther);
}

nsAttrValue::nsAttrValue(const nsAString& aValue) : mBits(0) { SetTo(aValue); }

nsAttrValue::nsAttrValue(nsAtom* aValue) : mBits(0) { SetTo(aValue); }

nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue,
                         const nsAString* aSerialized)
    : mBits(0) {
  SetTo(std::move(aValue), aSerialized);
}

nsAttrValue::~nsAttrValue() { ResetIfSet(); }

/* static */
void nsAttrValue::Init() {
  MOZ_ASSERT(!sEnumTableArray, "nsAttrValue already initialized");
  sEnumTableArray = new nsTArray<const EnumTable*>;
}

/* static */
void nsAttrValue::Shutdown() {
  MOZ_ASSERT(NS_IsMainThread());
  delete sEnumTableArray;
  sEnumTableArray = nullptr;

  for (uint32_t i = 0; i < gMiscContainerCount; ++i) {
    free(gMiscContainerCache[i]);
  }
  gMiscContainerCount = 0;
}

void nsAttrValue::Reset() {
  switch (BaseType()) {
    case eStringBase: {
      if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
        str->Release();
      }
      break;
    }
    case eOtherBase: {
      MiscContainer* cont = GetMiscContainer();
      if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
        NS_RELEASE(cont);
        break;
      }

      DeallocMiscContainer(ClearMiscContainer());

      break;
    }
    case eAtomBase: {
      nsAtom* atom = GetAtomValue();
      NS_RELEASE(atom);

      break;
    }
    case eIntegerBase: {
      break;
    }
  }

  mBits = 0;
}

void nsAttrValue::SetTo(const nsAttrValue& aOther) {
  if (this == &aOther) {
    return;
  }

  switch (aOther.BaseType()) {
    case eStringBase: {
      ResetIfSet();
      if (auto* str = static_cast<mozilla::StringBuffer*>(aOther.GetPtr())) {
        str->AddRef();
        SetPtrValueAndType(str, eStringBase);
      }
      return;
    }
    case eOtherBase: {
      break;
    }
    case eAtomBase: {
      ResetIfSet();
      nsAtom* atom = aOther.GetAtomValue();
      NS_ADDREF(atom);
      SetPtrValueAndType(atom, eAtomBase);
      return;
    }
    case eIntegerBase: {
      ResetIfSet();
      mBits = aOther.mBits;
      return;
    }
  }

  MiscContainer* otherCont = aOther.GetMiscContainer();
  if (otherCont->IsRefCounted()) {
    DeallocMiscContainer(ClearMiscContainer());
    NS_ADDREF(otherCont);
    SetPtrValueAndType(otherCont, eOtherBase);
    return;
  }

  MiscContainer* cont = EnsureEmptyMiscContainer();
  switch (otherCont->mType) {
    case eInteger: {
      cont->mValue.mInteger = otherCont->mValue.mInteger;
      break;
    }
    case eEnum: {
      cont->mValue.mEnumValue = otherCont->mValue.mEnumValue;
      break;
    }
    case ePercent: {
      cont->mDoubleValue = otherCont->mDoubleValue;
      break;
    }
    case eColor: {
      cont->mValue.mColor = otherCont->mValue.mColor;
      break;
    }
    case eAtomArray:
    case eShadowParts:
    case eCSSDeclaration: {
      MOZ_CRASH("These should be refcounted!");
    }
    case eURL: {
      NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL);
      break;
    }
    case eDoubleValue: {
      cont->mDoubleValue = otherCont->mDoubleValue;
      break;
    }
    default: {
      if (IsSVGType(otherCont->mType)) {
        // All SVG types are just pointers to classes and will therefore have
        // the same size so it doesn't really matter which one we assign
        cont->mValue.mSVGLength = otherCont->mValue.mSVGLength;
      } else {
        MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
      }
      break;
    }
  }

  bool isString;
  if (void* otherPtr = otherCont->GetStringOrAtomPtr(isString)) {
    if (isString) {
      static_cast<mozilla::StringBuffer*>(otherPtr)->AddRef();
    } else {
      static_cast<nsAtom*>(otherPtr)->AddRef();
    }
    cont->SetStringBitsMainThread(otherCont->mStringBits);
  }
  // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't
  // work correctly.
  cont->mType = otherCont->mType;
}

void nsAttrValue::SetTo(const nsAString& aValue) {
  ResetIfSet();
  mozilla::StringBuffer* buf = GetStringBuffer(aValue).take();
  if (buf) {
    SetPtrValueAndType(buf, eStringBase);
  }
}

void nsAttrValue::SetTo(nsAtom* aValue) {
  ResetIfSet();
  if (aValue) {
    NS_ADDREF(aValue);
    SetPtrValueAndType(aValue, eAtomBase);
  }
}

void nsAttrValue::SetTo(int16_t aInt) {
  ResetIfSet();
  SetIntValueAndType(aInt, eInteger, nullptr);
}

void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) {
  ResetIfSet();
  SetIntValueAndType(aInt, eInteger, aSerialized);
}

void nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) {
  MiscContainer* cont = EnsureEmptyMiscContainer();
  cont->mDoubleValue = aValue;
  cont->mType = eDoubleValue;
  SetMiscAtomOrString(aSerialized);
}

void nsAttrValue::SetTo(already_AddRefed<DeclarationBlock> aValue,
                        const nsAString* aSerialized) {
  MiscContainer* cont = EnsureEmptyMiscContainer();
  MOZ_ASSERT(cont->mValue.mRefCount == 0);
  cont->mValue.mCSSDeclaration = aValue.take();
  cont->mType = eCSSDeclaration;
  NS_ADDREF(cont);
  SetMiscAtomOrString(aSerialized);
  MOZ_ASSERT(cont->mValue.mRefCount == 1);
}

void nsAttrValue::SetTo(nsIURI* aValue, const nsAString* aSerialized) {
  MiscContainer* cont = EnsureEmptyMiscContainer();
  NS_ADDREF(cont->mValue.mURL = aValue);
  cont->mType = eURL;
  SetMiscAtomOrString(aSerialized);
}

void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) {
  if (aOther.Type() != nsAttrValue::eString &&
      aOther.Type() != nsAttrValue::eAtom) {
    nsAutoString val;
    aOther.ToString(val);
    SetTo(val);
  } else {
    SetTo(aOther);
  }
}

void nsAttrValue::SetTo(const SVGAnimatedOrient& aValue,
                        const nsAString* aSerialized) {
  SetSVGType(eSVGOrient, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGAnimatedIntegerPair& aValue,
                        const nsAString* aSerialized) {
  SetSVGType(eSVGIntegerPair, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGAnimatedLength& aValue,
                        const nsAString* aSerialized) {
  SetSVGType(eSVGLength, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGLengthList& aValue,
                        const nsAString* aSerialized) {
  // While an empty string will parse as a length list, there's no need to store
  // it (and SetMiscAtomOrString will assert if we try)
  if (aSerialized && aSerialized->IsEmpty()) {
    aSerialized = nullptr;
  }
  SetSVGType(eSVGLengthList, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGNumberList& aValue,
                        const nsAString* aSerialized) {
  // While an empty string will parse as a number list, there's no need to store
  // it (and SetMiscAtomOrString will assert if we try)
  if (aSerialized && aSerialized->IsEmpty()) {
    aSerialized = nullptr;
  }
  SetSVGType(eSVGNumberList, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGAnimatedNumberPair& aValue,
                        const nsAString* aSerialized) {
  SetSVGType(eSVGNumberPair, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGPathData& aValue,
                        const nsAString* aSerialized) {
  // While an empty string will parse as path data, there's no need to store it
  // (and SetMiscAtomOrString will assert if we try)
  if (aSerialized && aSerialized->IsEmpty()) {
    aSerialized = nullptr;
  }
  SetSVGType(eSVGPathData, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGPointList& aValue,
                        const nsAString* aSerialized) {
  // While an empty string will parse as a point list, there's no need to store
  // it (and SetMiscAtomOrString will assert if we try)
  if (aSerialized && aSerialized->IsEmpty()) {
    aSerialized = nullptr;
  }
  SetSVGType(eSVGPointList, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue,
                        const nsAString* aSerialized) {
  SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGStringList& aValue,
                        const nsAString* aSerialized) {
  // While an empty string will parse as a string list, there's no need to store
  // it (and SetMiscAtomOrString will assert if we try)
  if (aSerialized && aSerialized->IsEmpty()) {
    aSerialized = nullptr;
  }
  SetSVGType(eSVGStringList, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGTransformList& aValue,
                        const nsAString* aSerialized) {
  // While an empty string will parse as a transform list, there's no need to
  // store it (and SetMiscAtomOrString will assert if we try)
  if (aSerialized && aSerialized->IsEmpty()) {
    aSerialized = nullptr;
  }
  SetSVGType(eSVGTransformList, &aValue, aSerialized);
}

void nsAttrValue::SetTo(const SVGAnimatedViewBox& aValue,
                        const nsAString* aSerialized) {
  SetSVGType(eSVGViewBox, &aValue, aSerialized);
}

void nsAttrValue::SwapValueWith(nsAttrValue& aOther) {
  uintptr_t tmp = aOther.mBits;
  aOther.mBits = mBits;
  mBits = tmp;
}

void nsAttrValue::RemoveDuplicatesFromAtomArray() {
  if (Type() != eAtomArray) {
    return;
  }

  const AttrAtomArray* currentAtomArray = GetMiscContainer()->mValue.mAtomArray;
  UniquePtr<AttrAtomArray> deduplicatedAtomArray =
      currentAtomArray->CreateDeduplicatedCopyIfDifferent();

  if (!deduplicatedAtomArray) {
    // No duplicates found. Leave this value unchanged.
    return;
  }

  // We found duplicates. Wrap the new atom array into a fresh MiscContainer,
  // and copy over the existing container's string or atom.

  MiscContainer* oldCont = GetMiscContainer();
  MOZ_ASSERT(oldCont->IsRefCounted());

  uintptr_t stringBits = 0;
  bool isString = false;
  if (void* otherPtr = oldCont->GetStringOrAtomPtr(isString)) {
    stringBits = oldCont->mStringBits;
    if (isString) {
      static_cast<mozilla::StringBuffer*>(otherPtr)->AddRef();
    } else {
      static_cast<nsAtom*>(otherPtr)->AddRef();
    }
  }

  MiscContainer* cont = EnsureEmptyMiscContainer();
  MOZ_ASSERT(cont->mValue.mRefCount == 0);
  cont->mValue.mAtomArray = deduplicatedAtomArray.release();
  cont->mType = eAtomArray;
  NS_ADDREF(cont);
  MOZ_ASSERT(cont->mValue.mRefCount == 1);
  cont->SetStringBitsMainThread(stringBits);

  // Don't cache the new container. It would stomp over the undeduplicated
  // value in the cache. But we could have a separate cache for deduplicated
  // atom arrays, if repeated deduplication shows up in profiles.
}

void nsAttrValue::ToString(nsAString& aResult) const {
  MiscContainer* cont = nullptr;
  if (BaseType() == eOtherBase) {
    cont = GetMiscContainer();

    if (cont->GetString(aResult)) {
      return;
    }
  }

  switch (Type()) {
    case eString: {
      if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
        aResult.Assign(str, str->StorageSize() / sizeof(char16_t) - 1);
      } else {
        aResult.Truncate();
      }
      break;
    }
    case eAtom: {
      auto* atom = static_cast<nsAtom*>(GetPtr());
      atom->ToString(aResult);
      break;
    }
    case eInteger: {
      nsAutoString intStr;
      intStr.AppendInt(GetIntegerValue());
      aResult = intStr;

      break;
    }
#ifdef DEBUG
    case eColor: {
      MOZ_ASSERT_UNREACHABLE("color attribute without string data");
      aResult.Truncate();
      break;
    }
#endif
    case eEnum: {
      GetEnumString(aResult, false);
      break;
    }
    case ePercent: {
      nsAutoString str;
      if (cont) {
        str.AppendFloat(cont->mDoubleValue);
      } else {
        str.AppendInt(GetIntInternal());
      }
      aResult = str + u"%"_ns;

      break;
    }
    case eCSSDeclaration: {
      aResult.Truncate();
      MiscContainer* container = GetMiscContainer();
      if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) {
        nsAutoCString result;
        decl->ToString(result);
        CopyUTF8toUTF16(result, aResult);
      }

      // This can be reached during parallel selector matching with attribute
      // selectors on the style attribute. SetMiscAtomOrString handles this
      // case, and as of this writing this is the only consumer that needs it.
      const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult);

      break;
    }
    case eDoubleValue: {
      aResult.Truncate();
      aResult.AppendFloat(GetDoubleValue());
      break;
    }
    case eSVGIntegerPair: {
      SVGAttrValueWrapper::ToString(
          GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult);
      break;
    }
    case eSVGOrient: {
      SVGAttrValueWrapper::ToString(
          GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult);
      break;
    }
    case eSVGLength: {
      SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength,
                                    aResult);
      break;
    }
    case eSVGLengthList: {
      SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList,
                                    aResult);
      break;
    }
    case eSVGNumberList: {
      SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList,
                                    aResult);
      break;
    }
    case eSVGNumberPair: {
      SVGAttrValueWrapper::ToString(
          GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult);
      break;
    }
    case eSVGPathData: {
      SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData,
                                    aResult);
      break;
    }
    case eSVGPointList: {
      SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList,
                                    aResult);
      break;
    }
    case eSVGPreserveAspectRatio: {
      SVGAttrValueWrapper::ToString(
          GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult);
      break;
    }
    case eSVGStringList: {
      SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList,
                                    aResult);
      break;
    }
    case eSVGTransformList: {
      SVGAttrValueWrapper::ToString(
          GetMiscContainer()->mValue.mSVGTransformList, aResult);
      break;
    }
    case eSVGViewBox: {
      SVGAttrValueWrapper::ToString(
          GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult);
      break;
    }
    default: {
      aResult.Truncate();
      break;
    }
  }
}

already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const {
  switch (Type()) {
    case eString:
      return NS_AtomizeMainThread(GetStringValue());

    case eAtom: {
      RefPtr<nsAtom> atom = GetAtomValue();
      return atom.forget();
    }

    default: {
      nsAutoString val;
      ToString(val);
      return NS_AtomizeMainThread(val);
    }
  }
}

const nsCheapString nsAttrValue::GetStringValue() const {
  MOZ_ASSERT(Type() == eString, "wrong type");

  return nsCheapString(static_cast<mozilla::StringBuffer*>(GetPtr()));
}

bool nsAttrValue::GetColorValue(nscolor& aColor) const {
  if (Type() != eColor) {
    // Unparseable value, treat as unset.
    NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr");
    return false;
  }

  aColor = GetMiscContainer()->mValue.mColor;
  return true;
}

void nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const {
  MOZ_ASSERT(Type() == eEnum, "wrong type");

  uint32_t allEnumBits = (BaseType() == eIntegerBase)
                             ? static_cast<uint32_t>(GetIntInternal())
                             : GetMiscContainer()->mValue.mEnumValue;
  int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS;
  const EnumTable* table = sEnumTableArray->ElementAt(
      allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK);

  while (table->tag) {
    if (table->value == val) {
      aResult.AssignASCII(table->tag);
      if (!aRealTag &&
          allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) {
        nsContentUtils::ASCIIToUpper(aResult);
      }
      return;
    }
    table++;
  }

  MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable");
}

UniquePtr<AttrAtomArray> AttrAtomArray::CreateDeduplicatedCopyIfDifferentImpl()
    const {
  MOZ_ASSERT(mMayContainDuplicates);

  bool usingHashTable = false;
  BitBloomFilter<8, nsAtom> filter;
  nsTHashSet<nsAtom*> hash;

  auto CheckDuplicate = [&](size_t i) {
    nsAtom* atom = mArray[i];
    if (!usingHashTable) {
      if (!filter.mightContain(atom)) {
        filter.add(atom);
        return false;
      }
      for (size_t j = 0; j < i; ++j) {
        hash.Insert(mArray[j]);
      }
      usingHashTable = true;
    }
    return !hash.EnsureInserted(atom);
  };

  size_t len = mArray.Length();
  UniquePtr<AttrAtomArray> deduplicatedArray;
  for (size_t i = 0; i < len; ++i) {
    if (!CheckDuplicate(i)) {
      if (deduplicatedArray) {
        deduplicatedArray->mArray.AppendElement(mArray[i]);
      }
      continue;
    }
    // We've found a duplicate!
    if (!deduplicatedArray) {
      // Allocate the deduplicated copy and copy the preceding elements into it.
      deduplicatedArray = MakeUnique<AttrAtomArray>();
      deduplicatedArray->mMayContainDuplicates = false;
      deduplicatedArray->mArray.SetCapacity(len - 1);
      for (size_t indexToCopy = 0; indexToCopy < i; indexToCopy++) {
        deduplicatedArray->mArray.AppendElement(mArray[indexToCopy]);
      }
    }
  }

  if (!deduplicatedArray) {
    // This AttrAtomArray doesn't contain any duplicates, cache this information
    // for future invocations.
    mMayContainDuplicates = false;
  }
  return deduplicatedArray;
}

uint32_t nsAttrValue::GetAtomCount() const {
  ValueType type = Type();

  if (type == eAtom) {
    return 1;
  }

  if (type == eAtomArray) {
    return GetAtomArrayValue()->mArray.Length();
  }

  return 0;
}

nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const {
  MOZ_ASSERT(aIndex >= 0, "Index must not be negative");
  MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex), "aIndex out of range");

  if (BaseType() == eAtomBase) {
    return GetAtomValue();
  }

  NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused");
  return GetAtomArrayValue()->mArray.ElementAt(aIndex);
}

uint32_t nsAttrValue::HashValue() const {
  switch (BaseType()) {
    case eStringBase: {
      if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
        uint32_t len = str->StorageSize() / sizeof(char16_t) - 1;
        return HashString(static_cast<char16_t*>(str->Data()), len);
      }

      return 0;
    }
    case eOtherBase: {
      break;
    }
    case eAtomBase:
    case eIntegerBase: {
      // mBits and uint32_t might have different size. This should silence
      // any warnings or compile-errors. This is what the implementation of
      // NS_PTR_TO_INT32 does to take care of the same problem.
      return mBits - 0;
    }
  }

  MiscContainer* cont = GetMiscContainer();
  if (static_cast<ValueBaseType>(cont->mStringBits &
                                 NS_ATTRVALUE_BASETYPE_MASK) == eAtomBase) {
    return cont->mStringBits - 0;
  }

  switch (cont->mType) {
    case eInteger: {
      return cont->mValue.mInteger;
    }
    case eEnum: {
      return cont->mValue.mEnumValue;
    }
    case ePercent: {
      return cont->mDoubleValue;
    }
    case eColor: {
      return cont->mValue.mColor;
    }
    case eCSSDeclaration: {
      return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration);
    }
    case eURL: {
      nsString str;
      ToString(str);
      return HashString(str);
    }
    case eAtomArray: {
      uint32_t hash = 0;
      for (const auto& atom : cont->mValue.mAtomArray->mArray) {
        hash = AddToHash(hash, atom.get());
      }
      return hash;
    }
    case eDoubleValue: {
      // XXX this is crappy, but oh well
      return cont->mDoubleValue;
    }
    default: {
      if (IsSVGType(cont->mType)) {
        // All SVG types are just pointers to classes so we can treat them alike
        return NS_PTR_TO_INT32(cont->mValue.mSVGLength);
      }
      MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
      return 0;
    }
  }
}

bool nsAttrValue::Equals(const nsAttrValue& aOther) const {
  if (BaseType() != aOther.BaseType()) {
    return false;
  }

  switch (BaseType()) {
    case eStringBase: {
      return GetStringValue().Equals(aOther.GetStringValue());
    }
    case eOtherBase: {
      break;
    }
    case eAtomBase:
    case eIntegerBase: {
      return mBits == aOther.mBits;
    }
  }

  MiscContainer* thisCont = GetMiscContainer();
  MiscContainer* otherCont = aOther.GetMiscContainer();
  if (thisCont == otherCont) {
    return true;
  }

  if (thisCont->mType != otherCont->mType) {
    return false;
  }

  bool needsStringComparison = false;

  switch (thisCont->mType) {
    case eInteger: {
      if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) {
        needsStringComparison = true;
      }
      break;
    }
    case eEnum: {
      if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) {
        needsStringComparison = true;
      }
      break;
    }
    case ePercent: {
      if (thisCont->mDoubleValue == otherCont->mDoubleValue) {
        needsStringComparison = true;
      }
      break;
    }
    case eColor: {
      if (thisCont->mValue.mColor == otherCont->mValue.mColor) {
        needsStringComparison = true;
      }
      break;
    }
    case eCSSDeclaration: {
      return thisCont->mValue.mCSSDeclaration ==
             otherCont->mValue.mCSSDeclaration;
    }
    case eURL: {
      return thisCont->mValue.mURL == otherCont->mValue.mURL;
    }
    case eAtomArray: {
      // For classlists we could be insensitive to order, however
      // classlists are never mapped attributes so they are never compared.

      if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) {
        return false;
      }

      needsStringComparison = true;
      break;
    }
    case eDoubleValue: {
      return thisCont->mDoubleValue == otherCont->mDoubleValue;
    }
    default: {
      if (IsSVGType(thisCont->mType)) {
        // Currently this method is never called for nsAttrValue objects that
        // point to SVG data types.
        // If that changes then we probably want to add methods to the
        // corresponding SVG types to compare their base values.
        // As a shortcut, however, we can begin by comparing the pointers.
        MOZ_ASSERT(false"Comparing nsAttrValues that point to SVG data");
        return false;
      }
      MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
      return false;
    }
  }
  if (needsStringComparison) {
    if (thisCont->mStringBits == otherCont->mStringBits) {
      return true;
    }
    if ((static_cast<ValueBaseType>(thisCont->mStringBits &
                                    NS_ATTRVALUE_BASETYPE_MASK) ==
         eStringBase) &&
        (static_cast<ValueBaseType>(otherCont->mStringBits &
                                    NS_ATTRVALUE_BASETYPE_MASK) ==
         eStringBase)) {
      return nsCheapString(reinterpret_cast<mozilla::StringBuffer*>(
                               static_cast<uintptr_t>(thisCont->mStringBits)))
          .Equals(nsCheapString(reinterpret_cast<mozilla::StringBuffer*>(
              static_cast<uintptr_t>(otherCont->mStringBits))));
    }
  }
  return false;
}

bool nsAttrValue::Equals(const nsAString& aValue,
                         nsCaseTreatment aCaseSensitive) const {
  switch (BaseType()) {
    case eStringBase: {
      if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
        nsDependentString dep(static_cast<char16_t*>(str->Data()),
                              str->StorageSize() / sizeof(char16_t) - 1);
        return aCaseSensitive == eCaseMatters
                   ? aValue.Equals(dep)
                   : nsContentUtils::EqualsIgnoreASCIICase(aValue, dep);
      }
      return aValue.IsEmpty();
    }
    case eAtomBase: {
      auto* atom = static_cast<nsAtom*>(GetPtr());
      if (aCaseSensitive == eCaseMatters) {
        return atom->Equals(aValue);
      }
      return nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(atom),
                                                   aValue);
    }
    default:
      break;
  }

  nsAutoString val;
  ToString(val);
  return aCaseSensitive == eCaseMatters
             ? val.Equals(aValue)
             : nsContentUtils::EqualsIgnoreASCIICase(val, aValue);
}

bool nsAttrValue::Equals(const nsAtom* aValue,
                         nsCaseTreatment aCaseSensitive) const {
  switch (BaseType()) {
    case eAtomBase: {
      auto* atom = static_cast<nsAtom*>(GetPtr());
      if (atom == aValue) {
        return true;
      }
      if (aCaseSensitive == eCaseMatters) {
        return false;
      }
      if (atom->IsAsciiLowercase() && aValue->IsAsciiLowercase()) {
        return false;
      }
      return nsContentUtils::EqualsIgnoreASCIICase(
          nsDependentAtomString(atom), nsDependentAtomString(aValue));
    }
    case eStringBase: {
      if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
        size_t strLen = str->StorageSize() / sizeof(char16_t) - 1;
        if (aValue->GetLength() != strLen) {
          return false;
        }
        const char16_t* strData = static_cast<char16_t*>(str->Data());
        const char16_t* valData = aValue->GetUTF16String();
        if (aCaseSensitive == eCaseMatters) {
          // Avoid string construction / destruction for the easy case.
          return ArrayEqual(strData, valData, strLen);
        }
        nsDependentSubstring depStr(strData, strLen);
        nsDependentSubstring depVal(valData, strLen);
        return nsContentUtils::EqualsIgnoreASCIICase(depStr, depVal);
      }
      return aValue->IsEmpty();
    }
    default:
      break;
  }

  nsAutoString val;
  ToString(val);
  nsDependentAtomString dep(aValue);
  return aCaseSensitive == eCaseMatters
             ? val.Equals(dep)
             : nsContentUtils::EqualsIgnoreASCIICase(val, dep);
}

struct HasPrefixFn {
  static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
                    const nsAString& aSearchValue,
                    nsCaseTreatment aCaseSensitive) {
    if (aCaseSensitive == eCaseMatters) {
      if (aSearchValue.Length() > aAttrLen) {
        return false;
      }
      return !memcmp(aAttrValue, aSearchValue.BeginReading(),
                     aSearchValue.Length() * sizeof(char16_t));
    }
    return StringBeginsWith(nsDependentString(aAttrValue, aAttrLen),
                            aSearchValue,
                            nsASCIICaseInsensitiveStringComparator);
  }
};

struct HasSuffixFn {
  static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
                    const nsAString& aSearchValue,
                    nsCaseTreatment aCaseSensitive) {
    if (aCaseSensitive == eCaseMatters) {
      if (aSearchValue.Length() > aAttrLen) {
        return false;
      }
      return !memcmp(aAttrValue + aAttrLen - aSearchValue.Length(),
                     aSearchValue.BeginReading(),
                     aSearchValue.Length() * sizeof(char16_t));
    }
    return StringEndsWith(nsDependentString(aAttrValue, aAttrLen), aSearchValue,
                          nsASCIICaseInsensitiveStringComparator);
  }
};

struct HasSubstringFn {
  static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
                    const nsAString& aSearchValue,
                    nsCaseTreatment aCaseSensitive) {
    if (aCaseSensitive == eCaseMatters) {
      if (aSearchValue.IsEmpty()) {
        return true;
      }
      const char16_t* end = aAttrValue + aAttrLen;
      return std::search(aAttrValue, end, aSearchValue.BeginReading(),
                         aSearchValue.EndReading()) != end;
    }
    return FindInReadable(aSearchValue, nsDependentString(aAttrValue, aAttrLen),
                          nsASCIICaseInsensitiveStringComparator);
  }
};

template <typename F>
bool nsAttrValue::SubstringCheck(const nsAString& aValue,
                                 nsCaseTreatment aCaseSensitive) const {
  switch (BaseType()) {
    case eStringBase: {
      if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
        return F::Check(static_cast<char16_t*>(str->Data()),
                        str->StorageSize() / sizeof(char16_t) - 1, aValue,
                        aCaseSensitive);
      }
      return aValue.IsEmpty();
    }
    case eAtomBase: {
      auto* atom = static_cast<nsAtom*>(GetPtr());
      return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue,
                      aCaseSensitive);
    }
    default:
      break;
  }

  nsAutoString val;
  ToString(val);
  return F::Check(val.BeginReading(), val.Length(), aValue, aCaseSensitive);
}

bool nsAttrValue::HasPrefix(const nsAString& aValue,
                            nsCaseTreatment aCaseSensitive) const {
  return SubstringCheck<HasPrefixFn>(aValue, aCaseSensitive);
}

bool nsAttrValue::HasSuffix(const nsAString& aValue,
                            nsCaseTreatment aCaseSensitive) const {
  return SubstringCheck<HasSuffixFn>(aValue, aCaseSensitive);
}

bool nsAttrValue::HasSubstring(const nsAString& aValue,
                               nsCaseTreatment aCaseSensitive) const {
  return SubstringCheck<HasSubstringFn>(aValue, aCaseSensitive);
}

bool nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const {
  if (Type() == aOther.Type()) {
    return Equals(aOther);
  }

  // We need to serialize at least one nsAttrValue before passing to
  // Equals(const nsAString&), but we can avoid unnecessarily serializing both
  // by checking if one is already of a string type.
  bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase);
  const nsAttrValue& lhs = thisIsString ? *this : aOther;
  const nsAttrValue& rhs = thisIsString ? aOther : *this;

  switch (rhs.BaseType()) {
    case eAtomBase:
      return lhs.Equals(rhs.GetAtomValue(), eCaseMatters);

    case eStringBase:
      return lhs.Equals(rhs.GetStringValue(), eCaseMatters);

    default: {
      nsAutoString val;
      rhs.ToString(val);
      return lhs.Equals(val, eCaseMatters);
    }
  }
}

bool nsAttrValue::Contains(nsAtom* aValue,
                           nsCaseTreatment aCaseSensitive) const {
  switch (BaseType()) {
    case eAtomBase: {
      nsAtom* atom = GetAtomValue();
      if (aCaseSensitive == eCaseMatters) {
        return aValue == atom;
      }

      // For performance reasons, don't do a full on unicode case insensitive
      // string comparison. This is only used for quirks mode anyway.
      return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom);
    }
    default: {
      if (Type() == eAtomArray) {
        const AttrAtomArray* array = GetAtomArrayValue();
        if (aCaseSensitive == eCaseMatters) {
          return array->mArray.Contains(aValue);
        }

        for (const RefPtr<nsAtom>& cur : array->mArray) {
          // For performance reasons, don't do a full on unicode case
          // insensitive string comparison. This is only used for quirks mode
          // anyway.
          if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) {
            return true;
          }
        }
      }
    }
  }

  return false;
}

struct AtomArrayStringComparator {
  bool Equals(nsAtom* atom, const nsAString& string) const {
    return atom->Equals(string);
  }
};

bool nsAttrValue::Contains(const nsAString& aValue) const {
  switch (BaseType()) {
    case eAtomBase: {
      nsAtom* atom = GetAtomValue();
      return atom->Equals(aValue);
    }
    default: {
      if (Type() == eAtomArray) {
        const AttrAtomArray* array = GetAtomArrayValue();
        return array->mArray.Contains(aValue, AtomArrayStringComparator());
      }
    }
  }

  return false;
}

void nsAttrValue::ParseAtom(const nsAString& aValue) {
  ResetIfSet();

  RefPtr<nsAtom> atom = NS_Atomize(aValue);
  if (atom) {
    SetPtrValueAndType(atom.forget().take(), eAtomBase);
  }
}

void nsAttrValue::ParseAtomArray(nsAtom* aValue) {
  if (MiscContainer* cont = AtomArrayCache::Lookup(aValue)) {
    // Set our MiscContainer to the cached one.
    NS_ADDREF(cont);
    SetPtrValueAndType(cont, eOtherBase);
    return;
  }

  const char16_t* iter = aValue->GetUTF16String();
  const char16_t* end = iter + aValue->GetLength();
  bool hasSpace = false;

  // skip initial whitespace
  while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
    hasSpace = true;
    ++iter;
  }

  if (iter == end) {
    // The value is empty or only contains whitespace.
    // Set this attribute to the string value.
    // We don't call the SetTo(nsAtom*) overload because doing so would
    // leave us with a classList of length 1.
    SetTo(nsDependentAtomString(aValue));
    return;
  }

  const char16_t* start = iter;

  // get first - and often only - atom
  do {
    ++iter;
  } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));

  RefPtr<nsAtom> classAtom = iter == end && !hasSpace
                                 ? RefPtr<nsAtom>(aValue).forget()
                                 : NS_AtomizeMainThread(Substring(start, iter));
  if (!classAtom) {
    ResetIfSet();
    return;
  }

  // skip whitespace
  while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
    hasSpace = true;
    ++iter;
  }

  if (iter == end && !hasSpace) {
    // we only found one classname and there was no whitespace so
    // don't bother storing a list
    ResetIfSet();
    nsAtom* atom = nullptr;
    classAtom.swap(atom);
    SetPtrValueAndType(atom, eAtomBase);
    return;
  }

  // We have at least one class atom. Create a new AttrAtomArray.
  AttrAtomArray* array = new AttrAtomArray;

  // XXX(Bug 1631371) Check if this should use a fallible operation as it
  // pretended earlier.
  array->mArray.AppendElement(std::move(classAtom));

  // parse the rest of the classnames
  while (iter != end) {
    start = iter;

    do {
      ++iter;
    } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));

    classAtom = NS_AtomizeMainThread(Substring(start, iter));

    // XXX(Bug 1631371) Check if this should use a fallible operation as it
    // pretended earlier.
    array->mArray.AppendElement(std::move(classAtom));
    array->mMayContainDuplicates = true;

    // skip whitespace
    while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
      ++iter;
    }
  }

  // Wrap the AtomArray into a fresh MiscContainer.
  MiscContainer* cont = EnsureEmptyMiscContainer();
  MOZ_ASSERT(cont->mValue.mRefCount == 0);
  cont->mValue.mAtomArray = array;
  cont->mType = eAtomArray;
  NS_ADDREF(cont);
  MOZ_ASSERT(cont->mValue.mRefCount == 1);

  // Assign the atom to the container's string bits (like SetMiscAtomOrString
  // would do).
  MOZ_ASSERT(!IsInServoTraversal());
  aValue->AddRef();
  uintptr_t bits = reinterpret_cast<uintptr_t>(aValue) | eAtomBase;
  cont->SetStringBitsMainThread(bits);

  // Put the container in the cache.
  cont->Cache();
}

void nsAttrValue::ParseAtomArray(const nsAString& aValue) {
  if (aValue.IsVoid()) {
    ResetIfSet();
  } else {
    RefPtr<nsAtom> atom = NS_AtomizeMainThread(aValue);
    ParseAtomArray(atom);
  }
}

void nsAttrValue::ParseStringOrAtom(const nsAString& aValue) {
  uint32_t len = aValue.Length();
  // Don't bother with atoms if it's an empty string since
  // we can store those efficiently anyway.
  if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
    ParseAtom(aValue);
  } else {
    SetTo(aValue);
  }
}

void nsAttrValue::ParsePartMapping(const nsAString& aValue) {
  ResetIfSet();
  MiscContainer* cont = EnsureEmptyMiscContainer();

  cont->mType = eShadowParts;
  cont->mValue.mShadowParts = new ShadowParts(ShadowParts::Parse(aValue));
  NS_ADDREF(cont);
  SetMiscAtomOrString(&aValue);
  MOZ_ASSERT(cont->mValue.mRefCount == 1);
}

void nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType,
                                     const nsAString* aStringValue) {
  if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE ||
      aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) {
    MiscContainer* cont = EnsureEmptyMiscContainer();
    switch (aType) {
      case eInteger: {
        cont->mValue.mInteger = aValue;
        break;
      }
      case ePercent: {
        cont->mDoubleValue = aValue;
        break;
      }
      case eEnum: {
        cont->mValue.mEnumValue = aValue;
        break;
      }
      default: {
        MOZ_ASSERT_UNREACHABLE("unknown integer type");
        break;
      }
    }
    cont->mType = aType;
    SetMiscAtomOrString(aStringValue);
  } else {
    NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!");
    mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType;
  }
}

void nsAttrValue::SetDoubleValueAndType(double aValue, ValueType aType,
                                        const nsAString* aStringValue) {
  MOZ_ASSERT(aType == eDoubleValue || aType == ePercent, "Unexpected type");
  MiscContainer* cont = EnsureEmptyMiscContainer();
  cont->mDoubleValue = aValue;
  cont->mType = aType;
  SetMiscAtomOrString(aStringValue);
}

nsAtom* nsAttrValue::GetStoredAtom() const {
  if (BaseType() == eAtomBase) {
    return static_cast<nsAtom*>(GetPtr());
  }
  if (BaseType() == eOtherBase) {
    return GetMiscContainer()->GetStoredAtom();
  }
  return nullptr;
}

mozilla::StringBuffer* nsAttrValue::GetStoredStringBuffer() const {
  if (BaseType() == eStringBase) {
    return static_cast<mozilla::StringBuffer*>(GetPtr());
  }
  if (BaseType() == eOtherBase) {
    return GetMiscContainer()->GetStoredStringBuffer();
  }
  return nullptr;
}

int16_t nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) {
  int16_t index = sEnumTableArray->IndexOf(aTable);
  if (index < 0) {
    index = sEnumTableArray->Length();
    NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE,
                 "too many enum tables");
    sEnumTableArray->AppendElement(aTable);
  }

  return index;
}

int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable,
                                           const EnumTable* aTableEntry) {
  int16_t index = GetEnumTableIndex(aEnumTable);
  int32_t value =
      (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index;
  return value;
}

bool nsAttrValue::ParseEnumValue(const nsAString& aValue,
                                 const EnumTable* aTable, bool aCaseSensitive,
                                 const EnumTable* aDefaultValue) {
  ResetIfSet();
  const EnumTable* tableEntry = aTable;

  while (tableEntry->tag) {
    if (aCaseSensitive ? aValue.EqualsASCII(tableEntry->tag)
                       : aValue.LowerCaseEqualsASCII(tableEntry->tag)) {
      int32_t value = EnumTableEntryToValue(aTable, tableEntry);

      bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry->tag);
      if (!equals) {
        nsAutoString tag;
        tag.AssignASCII(tableEntry->tag);
        nsContentUtils::ASCIIToUpper(tag);
        if ((equals = tag.Equals(aValue))) {
          value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER;
        }
      }
      SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue);
      NS_ASSERTION(GetEnumValue() == tableEntry->value,
                   "failed to store enum properly");

      return true;
    }
    tableEntry++;
  }

  if (aDefaultValue) {
    MOZ_ASSERT(aTable <= aDefaultValue && aDefaultValue < tableEntry,
               "aDefaultValue not inside aTable?");
    SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), eEnum,
                       &aValue);
    return true;
  }

  return false;
}

bool nsAttrValue::DoParseHTMLDimension(const nsAString& aInput,
                                       bool aEnsureNonzero) {
  ResetIfSet();

  // We don't use nsContentUtils::ParseHTMLInteger here because we
  // need a bunch of behavioral differences from it.  We _could_ try to
  // use it, but it would not be a great fit.

  // https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values

  // Steps 1 and 2.
  const char16_t* position = aInput.BeginReading();
  const char16_t* end = aInput.EndReading();

  // We will need to keep track of whether this was a canonical representation
  // or not.  It's non-canonical if it has leading whitespace, leading '+',
  // leading '0' characters, or trailing garbage.
  bool canonical = true;

  // Step 3.
  while (position != end && nsContentUtils::IsHTMLWhitespace(*position)) {
    canonical = false;  // Leading whitespace
    ++position;
  }

  // Step 4.
  if (position == end || *position < char16_t('0') ||
      *position > char16_t('9')) {
    return false;
  }

  // Step 5.
  CheckedInt32 value = 0;

  // Collect up leading '0' first to avoid extra branching in the main
  // loop to set 'canonical' properly.
  while (position != end && *position == char16_t('0')) {
    canonical = false;  // Leading '0'
    ++position;
  }

  // Now collect up other digits.
  while (position != end && *position >= char16_t('0') &&
         *position <= char16_t('9')) {
    value = value * 10 + (*position - char16_t('0'));
    if (!value.isValid()) {
      // The spec assumes we can deal with arbitrary-size integers here, but we
      // really can't.  If someone sets something too big, just bail out and
      // ignore it.
      return false;
    }
    ++position;
  }

  // Step 6 is implemented implicitly via the various "position != end" guards
  // from this point on.

  Maybe<double> doubleValue;
  // Step 7.  The return in step 7.2 is handled by just falling through to the
  // code below this block when we reach end of input or a non-digit, because
  // the while loop will terminate at that point.
  if (position != end && *position == char16_t('.')) {
    canonical = false;  // Let's not rely on double serialization reproducing
                        // the string we started with.
    // Step 7.1.
    ++position;
    // If we have a '.' _not_ followed by digits, this is not as efficient as it
    // could be, because we will store as a double while we could have stored as
    // an int.  But that seems like a pretty rare case.
    doubleValue.emplace(value.value());
    // Step 7.3.
    double divisor = 1.0f;
    // Step 7.4.
    while (position != end && *position >= char16_t('0') &&
           *position <= char16_t('9')) {
      // Step 7.4.1.
      divisor = divisor * 10.0f;
      // Step 7.4.2.
      doubleValue.ref() += (*position - char16_t('0')) / divisor;
      // Step 7.4.3.
      ++position;
      // Step 7.4.4 and 7.4.5 are captured in the while loop condition and the
      // "position != end" checks below.
    }
  }

  if (aEnsureNonzero && value.value() == 0 &&
      (!doubleValue || *doubleValue == 0.0f)) {
    // Not valid.  Just drop it.
    return false;
  }

  // Step 8 and the spec's early return from step 7.2.
  ValueType type;
  if (position != end && *position == char16_t('%')) {
    type = ePercent;
    ++position;
  } else if (doubleValue) {
    type = eDoubleValue;
  } else {
    type = eInteger;
  }

  if (position != end) {
    canonical = false;
  }

  if (doubleValue) {
    MOZ_ASSERT(!canonical, "We set it false above!");
    SetDoubleValueAndType(*doubleValue, type, &aInput);
  } else {
    SetIntValueAndType(value.value(), type, canonical ? nullptr : &aInput);
  }

#ifdef DEBUG
  nsAutoString str;
  ToString(str);
  MOZ_ASSERT(str == aInput, "We messed up our 'canonical' boolean!");
#endif

  return true;
}

bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin,
                                     int32_t aMax) {
  MOZ_ASSERT(aMin < aMax, "bad boundaries");

  ResetIfSet();

  nsContentUtils::ParseHTMLIntegerResultFlags result;
  int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
  if (result & nsContentUtils::eParseHTMLInteger_Error) {
    return false;
  }

  int32_t val = std::max(originalVal, aMin);
  val = std::min(val, aMax);
  bool nonStrict =
      (val != originalVal) ||
      (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
      (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);

  SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);

  return true;
}

void nsAttrValue::ParseIntWithFallback(const nsAString& aString,
                                       int32_t aDefault, int32_t aMax) {
  ResetIfSet();

  nsContentUtils::ParseHTMLIntegerResultFlags result;
  int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
  bool nonStrict = false;
  if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) {
    val = aDefault;
    nonStrict = true;
  }

  if (val > aMax) {
    val = aMax;
    nonStrict = true;
  }

  if ((result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
      (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) {
    nonStrict = true;
  }

  SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
}

void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString,
                                             int32_t aDefault, int32_t aMin,
                                             int32_t aMax) {
  ResetIfSet();

  nsContentUtils::ParseHTMLIntegerResultFlags result;
  int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
  bool nonStrict =
      (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
      (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);

  if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) {
    if (result & nsContentUtils::eParseHTMLInteger_Negative) {
      val = aDefault;
    } else {
      val = aMax;
    }
    nonStrict = true;
  } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) {
    val = aDefault;
    nonStrict = true;
  } else if (val < aMin) {
    val = aMin;
    nonStrict = true;
  } else if (val > aMax) {
    val = aMax;
    nonStrict = true;
  }

  SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
}

bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) {
  ResetIfSet();

  nsContentUtils::ParseHTMLIntegerResultFlags result;
  int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
  if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) {
    return false;
  }

  bool nonStrict =
      (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
      (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);

  SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);

  return true;
}

bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) {
  ResetIfSet();

  nsContentUtils::ParseHTMLIntegerResultFlags result;
  int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
  if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) {
    return false;
  }

  bool nonStrict =
      (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
      (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);

  SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);

  return true;
}

bool nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) {
  mozilla::StringBuffer* buf = GetStringBuffer(aString).take();
  if (!buf) {
    return false;
  }

  MiscContainer* cont = EnsureEmptyMiscContainer();
  cont->mValue.mColor = aColor;
  cont->mType = eColor;

  // Save the literal string we were passed for round-tripping.
  cont->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf) | eStringBase);
  return true;
}

bool nsAttrValue::ParseColor(const nsAString& aString) {
  ResetIfSet();

  // FIXME (partially, at least): HTML5's algorithm says we shouldn't do
  // the whitespace compression, trimming, or the test for emptiness.
  // (I'm a little skeptical that we shouldn't do the whitespace
  // trimming; WebKit also does it.)
  nsAutoString colorStr(aString);
  colorStr.CompressWhitespace(truetrue);
  if (colorStr.IsEmpty()) {
    return false;
  }

  nscolor color;
  // No color names begin with a '#'; in standards mode, all acceptable
  // numeric colors do.
  if (colorStr.First() == '#') {
    nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1);
    if (NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
      return SetColorValue(color, aString);
    }
  } else if (colorStr.LowerCaseEqualsLiteral("transparent")) {
    return SetColorValue(NS_RGBA(0, 0, 0, 0), aString);
  } else {
    const NS_ConvertUTF16toUTF8 colorNameU8(colorStr);
    if (Servo_ColorNameToRgb(&colorNameU8, &color)) {
      return SetColorValue(color, aString);
    }
  }

  // FIXME (maybe): HTML5 says we should handle system colors.  This
  // means we probably need another storage type, since we'd need to
  // handle dynamic changes.  However, I think this is a bad idea:
  // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html

  // Use NS_LooseHexToRGB as a fallback if nothing above worked.
  if (NS_LooseHexToRGB(colorStr, &color)) {
    return SetColorValue(color, aString);
  }

  return false;
}

bool nsAttrValue::ParseDoubleValue(const nsAString& aString) {
  ResetIfSet();

  nsresult ec;
  double val = PromiseFlatString(aString).ToDouble(&ec);
  if (NS_FAILED(ec)) {
    return false;
  }

  MiscContainer* cont = EnsureEmptyMiscContainer();
  cont->mDoubleValue = val;
  cont->mType = eDoubleValue;
  nsAutoString serializedFloat;
  serializedFloat.AppendFloat(val);
  SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString);
  return true;
}

bool nsAttrValue::ParseStyleAttribute(const nsAString& aString,
                                      nsIPrincipal* aMaybeScriptedPrincipal,
                                      nsStyledElement* aElement) {
  dom::Document* doc = aElement->OwnerDoc();
  AttributeStyles* attrStyles = doc->GetAttributeStyles();
  NS_ASSERTION(aElement->NodePrincipal() == doc->NodePrincipal(),
               "This is unexpected");

  nsIPrincipal* principal = aMaybeScriptedPrincipal ? aMaybeScriptedPrincipal
                                                    : aElement->NodePrincipal();
  RefPtr<URLExtraData> data = aElement->GetURLDataForStyleAttr(principal);

  // If the (immutable) document URI does not match the element's base URI
  // (the common case is that they do match) do not cache the rule.  This is
  // because the results of the CSS parser are dependent on these URIs, and we
  // do not want to have to account for the URIs in the hash lookup.
  // Similarly, if the triggering principal does not match the node principal,
  // do not cache the rule, since the principal will be encoded in any parsed
  // URLs in the rule.
  const bool cachingAllowed = attrStyles &&
                              doc->GetDocumentURI() == data->BaseURI() &&
                              principal == aElement->NodePrincipal();
  if (cachingAllowed) {
    if (MiscContainer* cont = attrStyles->LookupStyleAttr(aString)) {
      // Set our MiscContainer to the cached one.
      NS_ADDREF(cont);
      SetPtrValueAndType(cont, eOtherBase);
      return true;
    }
  }

  RefPtr<DeclarationBlock> decl =
      DeclarationBlock::FromCssText(aString, data, doc->GetCompatibilityMode(),
                                    doc->CSSLoader(), StyleCssRuleType::Style);
  if (!decl) {
    return false;
  }
  decl->SetAttributeStyles(attrStyles);
  SetTo(decl.forget(), &aString);

  if (cachingAllowed) {
    MiscContainer* cont = GetMiscContainer();
    cont->Cache();
  }

  return true;
}

void nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) {
  NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!");
  NS_ASSERTION(!GetMiscContainer()->mStringBits || IsInServoTraversal(),
               "Trying to re-set atom or string!");
  if (aValue) {
    uint32_t len = aValue->Length();
    // * We're allowing eCSSDeclaration attributes to store empty
    //   strings as it can be beneficial to store an empty style
    //   attribute as a parsed rule.
    // * We're allowing enumerated values because sometimes the empty
    //   string corresponds to a particular enumerated value, especially
    //   for enumerated values that are not limited enumerated.
    // Add other types as needed.
    NS_ASSERTION(len || Type() == eCSSDeclaration || Type() == eEnum,
                 "Empty string?");
    MiscContainer* cont = GetMiscContainer();

    if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
      nsAtom* atom = MOZ_LIKELY(!IsInServoTraversal())
                         ? NS_AtomizeMainThread(*aValue).take()
                         : NS_Atomize(*aValue).take();
      NS_ENSURE_TRUE_VOID(atom);
      uintptr_t bits = reinterpret_cast<uintptr_t>(atom) | eAtomBase;

      // In the common case we're not in the servo traversal, and we can just
      // set the bits normally. The parallel case requires more care.
      if (MOZ_LIKELY(!IsInServoTraversal())) {
        cont->SetStringBitsMainThread(bits);
      } else if (!cont->mStringBits.compareExchange(0, bits)) {
        // We raced with somebody else setting the bits. Release our copy.
        atom->Release();
      }
    } else {
      mozilla::StringBuffer* buffer = GetStringBuffer(*aValue).take();
      NS_ENSURE_TRUE_VOID(buffer);
      uintptr_t bits = reinterpret_cast<uintptr_t>(buffer) | eStringBase;

      // In the common case we're not in the servo traversal, and we can just
      // set the bits normally. The parallel case requires more care.
      if (MOZ_LIKELY(!IsInServoTraversal())) {
        cont->SetStringBitsMainThread(bits);
      } else if (!cont->mStringBits.compareExchange(0, bits)) {
        // We raced with somebody else setting the bits. Release our copy.
        buffer->Release();
      }
    }
  }
}

void nsAttrValue::ResetMiscAtomOrString() {
  MiscContainer* cont = GetMiscContainer();
  bool isString;
  if (void* ptr = cont->GetStringOrAtomPtr(isString)) {
    if (isString) {
      static_cast<mozilla::StringBuffer*>(ptr)->Release();
    } else {
      static_cast<nsAtom*>(ptr)->Release();
    }
    cont->SetStringBitsMainThread(0);
  }
}

void nsAttrValue::SetSVGType(ValueType aType, const void* aValue,
                             const nsAString* aSerialized) {
  MOZ_ASSERT(IsSVGType(aType), "Not an SVG type");

  MiscContainer* cont = EnsureEmptyMiscContainer();
  // All SVG types are just pointers to classes so just setting any of them
  // will do. We'll lose type-safety but the signature of the calling
  // function should ensure we don't get anything unexpected, and once we
  // stick aValue in a union we lose type information anyway.
  cont->mValue.mSVGLength = static_cast<const SVGAnimatedLength*>(aValue);
  cont->mType = aType;
  SetMiscAtomOrString(aSerialized);
}

MiscContainer* nsAttrValue::ClearMiscContainer() {
  MiscContainer* cont = nullptr;
  if (BaseType() == eOtherBase) {
    cont = GetMiscContainer();
    if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
      // This MiscContainer is shared, we need a new one.
      NS_RELEASE(cont);

      cont = AllocMiscContainer();
      SetPtrValueAndType(cont, eOtherBase);
    } else {
      switch (cont->mType) {
        case eCSSDeclaration: {
          MOZ_ASSERT(cont->mValue.mRefCount == 1);
          cont->Release();
          cont->Evict();
          NS_RELEASE(cont->mValue.mCSSDeclaration);
          break;
        }
        case eShadowParts: {
          MOZ_ASSERT(cont->mValue.mRefCount == 1);
          cont->Release();
          delete cont->mValue.mShadowParts;
          break;
        }
        case eURL: {
          NS_RELEASE(cont->mValue.mURL);
          break;
        }
        case eAtomArray: {
          MOZ_ASSERT(cont->mValue.mRefCount == 1);
          cont->Release();
          cont->Evict();
          delete cont->mValue.mAtomArray;
          break;
        }
        default: {
          break;
        }
      }
    }
    ResetMiscAtomOrString();
  } else {
    ResetIfSet();
  }

  return cont;
}

MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() {
  MiscContainer* cont = ClearMiscContainer();
  if (cont) {
    MOZ_ASSERT(BaseType() == eOtherBase);
    ResetMiscAtomOrString();
    cont = GetMiscContainer();
  } else {
    cont = AllocMiscContainer();
    SetPtrValueAndType(cont, eOtherBase);
  }

  return cont;
}

already_AddRefed<mozilla::StringBuffer> nsAttrValue::GetStringBuffer(
    const nsAString& aValue) const {
  uint32_t len = aValue.Length();
  if (!len) {
    return nullptr;
  }
  if (mozilla::StringBuffer* buf = aValue.GetStringBuffer();
      buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) {
    // We can only reuse the buffer if it's exactly sized, since we rely on
    // StorageSize() to get the string length in ToString().
    return do_AddRef(buf);
  }
  return mozilla::StringBuffer::Create(aValue.Data(), aValue.Length());
}

size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
  size_t n = 0;

  switch (BaseType()) {
    case eStringBase: {
      mozilla::StringBuffer* str =
          static_cast<mozilla::StringBuffer*>(GetPtr());
      n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
      break;
    }
    case eOtherBase: {
      MiscContainer* container = GetMiscContainer();
      if (!container) {
        break;
      }
      if (container->IsRefCounted() && container->mValue.mRefCount > 1) {
        // We don't report this MiscContainer at all in order to avoid
        // twice-reporting it.
        // TODO DMD, bug 1027551 - figure out how to report this ref-counted
        // object just once.
        break;
      }
      n += aMallocSizeOf(container);

      // We only count the size of the object pointed by otherPtr if it's a
      // string. When it's an atom, it's counted separately.
      if (mozilla::StringBuffer* buf = container->GetStoredStringBuffer()) {
        n += buf->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
      }

      if (Type() == eCSSDeclaration && container->mValue.mCSSDeclaration) {
        // TODO: mCSSDeclaration might be owned by another object which
        //       would make us count them twice, bug 677493.
        // Bug 1281964: For DeclarationBlock if we do measure we'll
        // need a way to call the Servo heap_size_of function.
        // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf);
      } else if (Type() == eAtomArray && container->mValue.mAtomArray) {
        // Don't measure each nsAtom, because they are measured separately.
        n += container->mValue.mAtomArray->ShallowSizeOfIncludingThis(
            aMallocSizeOf);
      }
      break;
    }
    case eAtomBase:     // Atoms are counted separately.
    case eIntegerBase:  // The value is in mBits, nothing to do.
      break;
  }

  return n;
}

96%


¤ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.