content/base/src/nsAttrValueInlines.h
author Kyle Huey <khuey@kylehuey.com>
Sun, 30 Sep 2012 09:40:24 -0700
changeset 114994 72e482dbd38460db058c95827a3e4e6857986fa4
child 115126 c9a8f55d8541397f63894112088df7846c8078b8
permissions -rw-r--r--
Bug 760331: Coalesce data for inline style across nodes. r=bz This patch enables sharing of an nsAttrValue's MiscContainer between nodes for style rules. MiscContainers of type eCSSStyleRule are now refcounted (with some clever struct packing to ensure that the amount of memory allocated for MiscContainer remains unchanged on 32 and 64 bit). This infrastructure can be used to share most MiscContainer types in the future if we find advantages to sharing other types than just eCSSStyleRuley. A cache mapping strings to MiscContainers has been added to nsHTMLCSSStyleSheet. MiscContainers can be shared between nsAttrValues when one nsAttrValue is SetTo another nsAttrValue or when there is a cache hit in this cache. This patch also adds the ability to tell a style rule that it belongs to an nsHTMLCSSStyleSheet, with appropriate accessor functions to separate that from the existing case of belonging to an nsCSSStyleSheet. The primary use case is to reduce memory use for pages that have lots of inline style attributes with the same value. This can happen easily with large pages that are automatically generated. An (admittedly pathological) testcase in Bug 686975 sees over 250 MB of memory savings with this change. Reusing the same MiscContainer for multiple nodes saves the overhead of maintaining separate copies of the string containing the serialized value of the style attribute and of creating separate style rules for each node. Eliminating duplicate style rules enables further savings in layout through style context sharing. The testcase sees the amount of memory used by style contexts go from over 250 MB to 10 KB. Because the cache is based on the text value of the style attribute, it will not handle attributes that have different text values but are parsed into identical style rules. We also do not attempt to share MiscContainers when the node's base URI differs from the document URI. The effect of these limitations is expected to be low.

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsAttrValueInlines_h__
#define nsAttrValueInlines_h__

struct MiscContainer
{
  typedef nsAttrValue::ValueType ValueType;

  ValueType mType;
  // mStringBits points to either nsIAtom* or nsStringBuffer* and is used when
  // mType isn't mCSSStyleRule.
  // Note eStringBase and eAtomBase is used also to handle the type of
  // mStringBits.
  PtrBits mStringBits;
  union {
    struct {
      union {
        int32_t mInteger;
        nscolor mColor;
        uint32_t mEnumValue;
        int32_t mPercent;
        mozilla::css::StyleRule* mCSSStyleRule;
        mozilla::css::URLValue* mURL;
        mozilla::css::ImageValue* mImage;
        nsAttrValue::AtomArray* mAtomArray;
        nsIntMargin* mIntMargin;
        const nsSVGAngle* mSVGAngle;
        const nsSVGIntegerPair* mSVGIntegerPair;
        const nsSVGLength2* mSVGLength;
        const mozilla::SVGLengthList* mSVGLengthList;
        const mozilla::SVGNumberList* mSVGNumberList;
        const nsSVGNumberPair* mSVGNumberPair;
        const mozilla::SVGPathData* mSVGPathData;
        const mozilla::SVGPointList* mSVGPointList;
        const mozilla::SVGAnimatedPreserveAspectRatio* mSVGPreserveAspectRatio;
        const mozilla::SVGStringList* mSVGStringList;
        const mozilla::SVGTransformList* mSVGTransformList;
        const nsSVGViewBox* mSVGViewBox;
      };
      uint32_t mRefCount : 31;
      uint32_t mCached : 1;
    } mValue;
    double mDoubleValue;
  };

  MiscContainer()
    : mType(nsAttrValue::eColor),
      mStringBits(0)
  {
    MOZ_COUNT_CTOR(MiscContainer);
    mValue.mColor = 0;
    mValue.mRefCount = 0;
    mValue.mCached = 0;
  }

  ~MiscContainer()
  {
    if (IsRefCounted()) {
      MOZ_ASSERT(mValue.mRefCount == 0);
      MOZ_ASSERT(!mValue.mCached);
    }
    MOZ_COUNT_DTOR(MiscContainer);
  }

  bool GetString(nsDependentString& aString) const;

  inline bool IsRefCounted() const
  {
    // Nothing stops us from refcounting (and sharing) other types of
    // MiscContainer (except eDoubleValue types) but there's no compelling
    // reason to 
    return mType == nsAttrValue::eCSSStyleRule;
  }

  inline int32_t AddRef() {
    MOZ_ASSERT(IsRefCounted());
    return ++mValue.mRefCount;
  }

  inline int32_t Release() {
    MOZ_ASSERT(IsRefCounted());
    return --mValue.mRefCount;
  }

  void Cache();
  void Evict();
};


/**
 * Implementation of inline methods
 */

inline int32_t
nsAttrValue::GetIntegerValue() const
{
  NS_PRECONDITION(Type() == eInteger, "wrong type");
  return (BaseType() == eIntegerBase)
         ? GetIntInternal()
         : GetMiscContainer()->mValue.mInteger;
}

inline int16_t
nsAttrValue::GetEnumValue() const
{
  NS_PRECONDITION(Type() == eEnum, "wrong type");
  // We don't need to worry about sign extension here since we're
  // returning an int16_t which will cut away the top bits.
  return static_cast<int16_t>((
    (BaseType() == eIntegerBase)
    ? static_cast<uint32_t>(GetIntInternal())
    : GetMiscContainer()->mValue.mEnumValue)
      >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS);
}

inline float
nsAttrValue::GetPercentValue() const
{
  NS_PRECONDITION(Type() == ePercent, "wrong type");
  return ((BaseType() == eIntegerBase)
          ? GetIntInternal()
          : GetMiscContainer()->mValue.mPercent)
            / 100.0f;
}

inline nsAttrValue::AtomArray*
nsAttrValue::GetAtomArrayValue() const
{
  NS_PRECONDITION(Type() == eAtomArray, "wrong type");
  return GetMiscContainer()->mValue.mAtomArray;
}

inline mozilla::css::StyleRule*
nsAttrValue::GetCSSStyleRuleValue() const
{
  NS_PRECONDITION(Type() == eCSSStyleRule, "wrong type");
  return GetMiscContainer()->mValue.mCSSStyleRule;
}

inline mozilla::css::URLValue*
nsAttrValue::GetURLValue() const
{
  NS_PRECONDITION(Type() == eURL, "wrong type");
  return GetMiscContainer()->mValue.mURL;
}

inline mozilla::css::ImageValue*
nsAttrValue::GetImageValue() const
{
  NS_PRECONDITION(Type() == eImage, "wrong type");
  return GetMiscContainer()->mValue.mImage;
}

inline double
nsAttrValue::GetDoubleValue() const
{
  NS_PRECONDITION(Type() == eDoubleValue, "wrong type");
  return GetMiscContainer()->mDoubleValue;
}

inline bool
nsAttrValue::GetIntMarginValue(nsIntMargin& aMargin) const
{
  NS_PRECONDITION(Type() == eIntMarginValue, "wrong type");
  nsIntMargin* m = GetMiscContainer()->mValue.mIntMargin;
  if (!m)
    return false;
  aMargin = *m;
  return true;
}

inline bool
nsAttrValue::IsSVGType(ValueType aType) const
{
  return aType >= eSVGTypesBegin && aType <= eSVGTypesEnd;
}

inline void
nsAttrValue::SetPtrValueAndType(void* aValue, ValueBaseType aType)
{
  NS_ASSERTION(!(NS_PTR_TO_INT32(aValue) & ~NS_ATTRVALUE_POINTERVALUE_MASK),
               "pointer not properly aligned, this will crash");
  mBits = reinterpret_cast<PtrBits>(aValue) | aType;
}

inline void
nsAttrValue::ResetIfSet()
{
  if (mBits) {
    Reset();
  }
}

inline MiscContainer*
nsAttrValue::GetMiscContainer() const
{
  NS_ASSERTION(BaseType() == eOtherBase, "wrong type");
  return static_cast<MiscContainer*>(GetPtr());
}

inline int32_t
nsAttrValue::GetIntInternal() const
{
  NS_ASSERTION(BaseType() == eIntegerBase,
               "getting integer from non-integer");
  // Make sure we get a signed value.
  // Lets hope the optimizer optimizes this into a shift. Unfortunatly signed
  // bitshift right is implementaion dependant.
  return static_cast<int32_t>(mBits & ~NS_ATTRVALUE_INTEGERTYPE_MASK) /
         NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER;
}

#endif