/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 *   Pierre Phaneuf <pp@ludusdesign.com>
 */

#include "nsIHTMLAttributes.h"
#include "nsIHTMLStyleSheet.h"
#include "nsIStyleRule.h"
#include "nsString.h"
#include "nsISupportsArray.h"
#include "nsCRT.h"
#include "nsIArena.h"
#include "nsIStyleContext.h"
#include "nsHTMLAtoms.h"
#include "nsIHTMLContent.h"
#include "nsVoidArray.h"
#include "nsISizeOfHandler.h"
#include "nsCOMPtr.h"

#include "nsIStyleSet.h"

static NS_DEFINE_IID(kIHTMLAttributesIID, NS_IHTML_ATTRIBUTES_IID);

MOZ_DECL_CTOR_COUNTER(HTMLAttribute);

struct HTMLAttribute {
  HTMLAttribute(void)
    : mAttribute(nsnull),
      mValue(),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
  }

  HTMLAttribute(nsIAtom* aAttribute, const nsString& aValue)
    : mAttribute(aAttribute),
      mValue(aValue),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
    NS_IF_ADDREF(mAttribute);
  }

  HTMLAttribute(nsIAtom* aAttribute, const nsHTMLValue& aValue)
    : mAttribute(aAttribute),
      mValue(aValue),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
    NS_IF_ADDREF(mAttribute);
  }

  HTMLAttribute(const HTMLAttribute& aCopy)
    : mAttribute(aCopy.mAttribute),
      mValue(aCopy.mValue),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
    NS_IF_ADDREF(mAttribute);
  }

  ~HTMLAttribute(void)
  {
    MOZ_COUNT_DTOR(HTMLAttribute);
    NS_IF_RELEASE(mAttribute);
  }

  HTMLAttribute& operator=(const HTMLAttribute& aCopy)
  {
    NS_IF_RELEASE(mAttribute);
    mAttribute = aCopy.mAttribute;
    NS_IF_ADDREF(mAttribute);
    mValue = aCopy.mValue;
    return *this;
  }

  PRBool operator==(const HTMLAttribute& aOther) const
  {
    return PRBool((mAttribute == aOther.mAttribute) &&
                  (mValue == aOther.mValue));
  }

  PRUint32 HashValue(void) const
  {
    return PRUint32(mAttribute) ^ mValue.HashValue();
  }

  void Reset(void)
  {
    NS_IF_RELEASE(mAttribute);
    mValue.Reset();
  }

  void Set(nsIAtom* aAttribute, const nsHTMLValue& aValue)
  {
    NS_IF_RELEASE(mAttribute);
    mAttribute = aAttribute;
    NS_IF_ADDREF(mAttribute);
    mValue = aValue;
  }

  void Set(nsIAtom* aAttribute, const nsString& aValue)
  {
    NS_IF_RELEASE(mAttribute);
    mAttribute = aAttribute;
    NS_IF_ADDREF(mAttribute);
    mValue.SetStringValue(aValue);
  }

  void AppendToString(nsString& aBuffer) const
  {
    if (nsnull != mAttribute) {
      nsAutoString temp;
      mAttribute->ToString(temp);
      aBuffer.Append(temp);
      if (eHTMLUnit_Null != mValue.GetUnit()) {
        aBuffer.AppendWithConversion(" = ");
        mValue.AppendToString(aBuffer);
      }
    }
    else {
      aBuffer.AppendWithConversion("null");
    }
  }

  void ToString(nsString& aBuffer) const
  {
    if (nsnull != mAttribute) {
      mAttribute->ToString(aBuffer);
      if (eHTMLUnit_Null != mValue.GetUnit()) {
        aBuffer.AppendWithConversion(" = ");
        mValue.AppendToString(aBuffer);
      }
    }
    else {
      aBuffer.AssignWithConversion("null");
    }
  }

  static void
  CopyHTMLAttributes(HTMLAttribute* aSource, HTMLAttribute** aDest)
  {
    while (aSource && aDest) {
      (*aDest) = new HTMLAttribute(*aSource);
      if (! *aDest) {
        break;
      }
      aDest = &((*aDest)->mNext);
      aSource = aSource->mNext;
    }
  }

  static void
  DeleteHTMLAttributes(HTMLAttribute* aAttr)
  {
    while (aAttr) {
      HTMLAttribute* deadBeef = aAttr;
      aAttr = aAttr->mNext;
      delete deadBeef;
    }
  }

  static HTMLAttribute*
  FindHTMLAttribute(nsIAtom* aAttrName, HTMLAttribute* aAttr)
  {
    while (aAttr) {
      if (aAttrName == aAttr->mAttribute) {
        return aAttr;
      }
      aAttr = aAttr->mNext;
    }
    return nsnull;
  }

  static const HTMLAttribute*
  FindHTMLAttribute(nsIAtom* aAttrName, const HTMLAttribute* aAttr)
  {
    while (aAttr) {
      if (aAttrName == aAttr->mAttribute) {
        return aAttr;
      }
      aAttr = aAttr->mNext;
    }
    return nsnull;
  }

  static PRBool
  RemoveHTMLAttribute(nsIAtom* aAttrName, HTMLAttribute** aAttr)
  {
    while (*aAttr) {
      if ((*aAttr)->mAttribute == aAttrName) {
        HTMLAttribute*  attr = *aAttr;
        *aAttr = (*aAttr)->mNext;
        delete attr;
        return PR_TRUE;
      }
      aAttr = &((*aAttr)->mNext);
    }
    return PR_FALSE;
  }

  nsresult SizeOf(nsISizeOfHandler* aSizer, PRUint32* aResult) const {
    if (!aResult) {
      return NS_ERROR_NULL_POINTER;
    }
    *aResult = sizeof(*this);
    return NS_OK;
  }

  nsIAtom*        mAttribute;
  nsHTMLValue     mValue;
  HTMLAttribute*  mNext;
};

// ----------------

MOZ_DECL_CTOR_COUNTER(nsClassList);

struct nsClassList {
  nsClassList(nsIAtom* aAtom)
    : mAtom(aAtom), // take ref
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(nsClassList);
  }

  nsClassList(const nsClassList& aCopy)
    : mAtom(aCopy.mAtom),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(nsClassList);
    NS_IF_ADDREF(mAtom);
    if (aCopy.mNext) {
      mNext = new nsClassList(*(aCopy.mNext));
    }
  }

  ~nsClassList(void)
  {
    MOZ_COUNT_DTOR(nsClassList);
    Reset();
  }

  void Reset(void)
  {
    NS_IF_RELEASE(mAtom);
    if (mNext) {
      delete mNext;
      mNext = nsnull;
    }
  }

  nsIAtom*      mAtom;
  nsClassList*  mNext;
};

// ----------------

class nsHTMLMappedAttributes : public nsIHTMLMappedAttributes, public nsIStyleRule {
public:
  nsHTMLMappedAttributes(void);
  nsHTMLMappedAttributes(const nsHTMLMappedAttributes& aCopy);
  virtual ~nsHTMLMappedAttributes(void);

  NS_DECL_ISUPPORTS

  NS_IMETHOD Init(nsIHTMLStyleSheet* aSheet,
                  nsMapAttributesFunc aFontMapFunc, nsMapAttributesFunc aMapFunc);
  NS_IMETHOD Clone(nsHTMLMappedAttributes** aInstancePtrResult) const;
  NS_IMETHOD Reset(void);
  NS_IMETHOD SetMappingFunctions(nsMapAttributesFunc aFontMapFunc, nsMapAttributesFunc aMapFunc);

  NS_IMETHOD SetAttribute(nsIAtom* aAttrName, const nsString& aValue);
  NS_IMETHOD SetAttribute(nsIAtom* aAttrName, const nsHTMLValue& aValue);
  NS_IMETHOD UnsetAttribute(nsIAtom* aAttrName, PRInt32& aAttrCount);

  NS_IMETHOD GetAttribute(nsIAtom* aAttrName,
                          nsHTMLValue& aValue) const;
  NS_IMETHOD GetAttribute(nsIAtom* aAttrName,
                          const nsHTMLValue** aValue) const;

  NS_IMETHOD GetAttributeCount(PRInt32& aCount) const;

  NS_IMETHOD Equals(const nsIHTMLMappedAttributes* aAttributes, PRBool& aResult) const;
  NS_IMETHOD HashValue(PRUint32& aValue) const;

  NS_IMETHOD AddUse(void);
  NS_IMETHOD ReleaseUse(void);
  NS_IMETHOD GetUseCount(PRInt32& aUseCount) const;

  NS_IMETHOD SetUniqued(PRBool aUniqued);
  NS_IMETHOD GetUniqued(PRBool& aUniqued);
  NS_IMETHOD DropStyleSheetReference(void);

  // nsIStyleRule 
  NS_IMETHOD GetStyleSheet(nsIStyleSheet*& aSheet) const;
  NS_IMETHOD SetStyleSheet(nsIHTMLStyleSheet* aSheet);
  // Strength is an out-of-band weighting, always 0 here
  NS_IMETHOD GetStrength(PRInt32& aStrength) const;
  NS_IMETHOD MapFontStyleInto(nsIMutableStyleContext* aContext, nsIPresContext* aPresContext);
  NS_IMETHOD MapStyleInto(nsIMutableStyleContext* aContext, nsIPresContext* aPresContext);

  NS_IMETHOD List(FILE* out, PRInt32 aIndent) const;

  void SizeOf(nsISizeOfHandler* aSizer, PRUint32 &aResult);

  nsIHTMLStyleSheet*  mSheet;
  PRInt32             mUseCount;
  PRInt32             mAttrCount;
  HTMLAttribute       mFirst;
  nsMapAttributesFunc mFontMapper;
  nsMapAttributesFunc mMapper;
  PRBool              mUniqued;
};

nsHTMLMappedAttributes::nsHTMLMappedAttributes(void)
  : mSheet(nsnull),
    mUseCount(0),
    mAttrCount(0),
    mFirst(),
    mFontMapper(nsnull),
    mMapper(nsnull),
    mUniqued(PR_FALSE)
{
  NS_INIT_ISUPPORTS();
}

nsHTMLMappedAttributes::nsHTMLMappedAttributes(const nsHTMLMappedAttributes& aCopy)
  : mSheet(aCopy.mSheet),
    mUseCount(0),
    mAttrCount(aCopy.mAttrCount),
    mFirst(aCopy.mFirst),
    mFontMapper(aCopy.mFontMapper),
    mMapper(aCopy.mMapper),
    mUniqued(PR_FALSE)
{
  NS_INIT_ISUPPORTS();

  HTMLAttribute::CopyHTMLAttributes(aCopy.mFirst.mNext, &(mFirst.mNext));
}

nsHTMLMappedAttributes::~nsHTMLMappedAttributes(void)
{
  Reset();
}

NS_IMPL_ADDREF(nsHTMLMappedAttributes);
NS_IMPL_RELEASE(nsHTMLMappedAttributes);

nsresult 
nsHTMLMappedAttributes::QueryInterface(const nsIID& aIID,
                                       void** aInstancePtrResult)
{
  NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer");
  if (nsnull == aInstancePtrResult) {
    return NS_ERROR_NULL_POINTER;
  }
  static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
  if (aIID.Equals(NS_GET_IID(nsIHTMLMappedAttributes))) {
    *aInstancePtrResult = (void*) ((nsIHTMLMappedAttributes*)this);
    NS_ADDREF_THIS();
    return NS_OK;
  }
  if (aIID.Equals(NS_GET_IID(nsIStyleRule))) {
    *aInstancePtrResult = (void*) ((nsIStyleRule*)this);
    NS_ADDREF_THIS();
    return NS_OK;
  }
  if (aIID.Equals(kISupportsIID)) {
    *aInstancePtrResult = (void*) ((nsIHTMLMappedAttributes*)this);
    NS_ADDREF_THIS();
    return NS_OK;
  }
  return NS_NOINTERFACE;
}


NS_IMETHODIMP
nsHTMLMappedAttributes::Init(nsIHTMLStyleSheet* aSheet,
                             nsMapAttributesFunc aFontMapFunc, 
                             nsMapAttributesFunc aMapFunc)
{
  mSheet = aSheet;
  mFontMapper = aFontMapFunc;
  mMapper = aMapFunc;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::Clone(nsHTMLMappedAttributes** aInstancePtrResult) const
{
  if (aInstancePtrResult == nsnull) {
    return NS_ERROR_NULL_POINTER;
  }

  nsHTMLMappedAttributes* clone = new nsHTMLMappedAttributes(*this);

  if (! clone) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  NS_ADDREF(clone);
  *aInstancePtrResult = clone;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::Reset(void)
{
  mAttrCount = 0;
  mFirst.Reset();
  HTMLAttribute::DeleteHTMLAttributes(mFirst.mNext);
  mUniqued = PR_FALSE;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetMappingFunctions(nsMapAttributesFunc aFontMapFunc, 
                                            nsMapAttributesFunc aMapFunc)
{
  mFontMapper = aFontMapFunc;
  mMapper = aMapFunc;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetAttribute(nsIAtom* aAttrName, const nsString& aValue)
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  if (mFirst.mAttribute) {  // do we already have any?
    HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
    if (attr) {
      attr->mValue.SetStringValue(aValue);
    }
    else {
      // add new attribute
      // keep these arbitrarily sorted so they'll equal regardless of set order
      if (aAttrName < mFirst.mAttribute) {  // before first, move first down one
        attr = new HTMLAttribute(mFirst);
        if (attr) {
          attr->mNext = mFirst.mNext;
          mFirst.mNext = attr;
          mFirst.Set(aAttrName, aValue);
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      else {
        attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          HTMLAttribute* prev = &mFirst;
          while (prev->mNext && (prev->mNext->mAttribute < aAttrName)) {
            prev = prev->mNext;
          }
          attr->mNext = prev->mNext;
          prev->mNext = attr;
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      mAttrCount++;
    }
  }
  else {
    mFirst.Set(aAttrName, aValue);
    mAttrCount++;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetAttribute(nsIAtom* aAttrName, const nsHTMLValue& aValue)
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  if (mFirst.mAttribute) {  // do we already have any?
    HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
    if (attr) {
      attr->mValue = aValue;
    }
    else {
      // add new attribute
      // keep these arbitrarily sorted so they'll hash regardless of set order
      if (aAttrName < mFirst.mAttribute) {  // before first, move first down one
        attr = new HTMLAttribute(mFirst);
        if (attr) {
          attr->mNext = mFirst.mNext;
          mFirst.mNext = attr;
          mFirst.Set(aAttrName, aValue);
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      else {
        attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          HTMLAttribute* prev = &mFirst;
          while (prev->mNext && (prev->mNext->mAttribute < aAttrName)) {
            prev = prev->mNext;
          }
          attr->mNext = prev->mNext;
          prev->mNext = attr;
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      mAttrCount++;
    }
  }
  else {
    mFirst.Set(aAttrName, aValue);
    mAttrCount++;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::UnsetAttribute(nsIAtom* aAttrName, PRInt32& aAttrCount)
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  if (aAttrName == mFirst.mAttribute) {
    if (mFirst.mNext) {
      HTMLAttribute*  attr = mFirst.mNext;
      mFirst = *attr;
      mFirst.mNext = attr->mNext;
      delete attr;
    }
    else {
      mFirst.Reset();
    }
    mAttrCount--;
  }
  else {
    if (HTMLAttribute::RemoveHTMLAttribute(aAttrName, &(mFirst.mNext))) {
      mAttrCount--;
    }
  }
  aAttrCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetAttribute(nsIAtom* aAttrName,
                                     nsHTMLValue& aValue) const
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  const HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
  if (attr) {
    aValue = attr->mValue;
    return ((attr->mValue.GetUnit() == eHTMLUnit_Null) ?
            NS_CONTENT_ATTR_NO_VALUE :
            NS_CONTENT_ATTR_HAS_VALUE);
  }
  aValue.Reset();
  return NS_CONTENT_ATTR_NOT_THERE;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetAttribute(nsIAtom* aAttrName,
                                     const nsHTMLValue** aValue) const
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  const HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
  if (attr) {
    *aValue = &attr->mValue;
    return ((attr->mValue.GetUnit() == eHTMLUnit_Null) ?
            NS_CONTENT_ATTR_NO_VALUE :
            NS_CONTENT_ATTR_HAS_VALUE);
  }
  *aValue = nsnull;
  return NS_CONTENT_ATTR_NOT_THERE;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetAttributeCount(PRInt32& aCount) const
{
  aCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::Equals(const nsIHTMLMappedAttributes* aOther, PRBool& aResult) const
{
  const nsHTMLMappedAttributes* other = (const nsHTMLMappedAttributes*)aOther;

  if (this == other) {
    aResult = PR_TRUE;
  }
  else {
    aResult = PR_FALSE;
    if ((mFontMapper == other->mFontMapper) && (mMapper == other->mMapper) && 
        (mAttrCount == other->mAttrCount)) {
      const HTMLAttribute* attr = &mFirst;
      const HTMLAttribute* otherAttr = &(other->mFirst);

      aResult = PR_TRUE;
      while (attr) {
        if (! ((*attr) == (*otherAttr))) {
          aResult = PR_FALSE;
          break;
        }
        attr = attr->mNext;
        otherAttr = otherAttr->mNext;
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::HashValue(PRUint32& aValue) const
{
  aValue = PRUint32(mFontMapper);
  aValue = aValue ^ PRUint32(mMapper);

  const HTMLAttribute* attr = &mFirst;
  while (nsnull != attr) {
    if (nsnull != attr->mAttribute) {
      aValue = aValue ^ attr->HashValue();
    }
    attr = attr->mNext;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::AddUse(void)
{
  mUseCount++;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::ReleaseUse(void)
{
  mUseCount--;

  if (mSheet && (0 == mUseCount) && mUniqued) {
    mSheet->DropMappedAttributes(this);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetUseCount(PRInt32& aUseCount) const
{
  aUseCount = mUseCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetUniqued(PRBool aUniqued)
{
  mUniqued = aUniqued;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetUniqued(PRBool& aUniqued)
{
  aUniqued = mUniqued;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::DropStyleSheetReference(void)
{
  mSheet = nsnull;
  mUniqued = PR_FALSE;
  return NS_OK;
}


NS_IMETHODIMP
nsHTMLMappedAttributes::GetStyleSheet(nsIStyleSheet*& aSheet) const
{
  aSheet = mSheet;
  NS_IF_ADDREF(aSheet);
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetStyleSheet(nsIHTMLStyleSheet* aSheet)
{
  if (mSheet && mUniqued) {
    mSheet->DropMappedAttributes(this);
  }
  mSheet = aSheet;  // not ref counted
  mUniqued = PR_FALSE;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetStrength(PRInt32& aStrength) const
{
  aStrength = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::MapFontStyleInto(nsIMutableStyleContext* aContext, nsIPresContext* aPresContext)
{
  if (0 < mAttrCount) {
    if (mFontMapper) {
      (*mFontMapper)(this, aContext, aPresContext);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::MapStyleInto(nsIMutableStyleContext* aContext, nsIPresContext* aPresContext)
{
  if (0 < mAttrCount) {
    NS_ASSERTION(mMapper || mFontMapper, "no mapping function");
    if (mMapper) {
      (*mMapper)(this, aContext, aPresContext);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::List(FILE* out, PRInt32 aIndent) const
{
  const HTMLAttribute*  attr = &mFirst;
  nsAutoString buffer;

  while (nsnull != attr) {
    PRInt32 index;
    for (index = aIndent; --index >= 0; ) fputs("  ", out);
    attr->ToString(buffer);
    fputs(buffer, out);
    fputs("\n", out);
    attr = attr->mNext;
  }
  return NS_OK;
}

/******************************************************************************
* SizeOf method:
*
*  Self (reported as nsHTMLMappedAttributes's size): 
*    1) sizeof(*this)
*
*  Contained / Aggregated data (not reported as nsHTMLMappedAttributes's size):
*    none
*
*  Children / siblings / parents:
*    none
*    
******************************************************************************/
void nsHTMLMappedAttributes::SizeOf(nsISizeOfHandler* aSizer, PRUint32 &aResult)
{
  // first get the unique items collection
  UNIQUE_STYLE_ITEMS(uniqueItems);
  if(! uniqueItems->AddItem((void*)this)){
    // this is already accounted for
    return;
  }
  nsCOMPtr<nsIAtom> tag;
  tag = getter_AddRefs(NS_NewAtom("HTMLMappedAttributes"));
  aResult = sizeof(*this);
  aSizer->AddSize(tag, aResult);
}

//--------------------

const PRInt32 kNameBufferSize = 4;

class HTMLAttributesImpl: public nsIHTMLAttributes {
public:
  void* operator new(size_t size);
  void* operator new(size_t size, nsIArena* aArena);
  void operator delete(void* ptr);

  HTMLAttributesImpl(void);
  HTMLAttributesImpl(const HTMLAttributesImpl& aCopy);
  virtual ~HTMLAttributesImpl(void);

  NS_IMETHOD QueryInterface(const nsIID& aIID, void** aInstancePtr);
  NS_IMETHOD_(nsrefcnt) AddRef();
  NS_IMETHOD_(nsrefcnt) Release();

  // nsIHTMLAttributes
  NS_IMETHOD SetAttributeFor(nsIAtom* aAttrName, const nsHTMLValue& aValue,
                             PRBool aMappedToStyle, 
                             nsIHTMLContent* aContent,
                             nsIHTMLStyleSheet* aSheet,
                             PRInt32& aAttrCount);
  NS_IMETHOD SetAttributeFor(nsIAtom* aAttrName, const nsString& aValue,
                             PRBool aMappedToStyle,
                             nsIHTMLContent* aContent,
                             nsIHTMLStyleSheet* aSheet);
  NS_IMETHOD UnsetAttributeFor(nsIAtom* aAttrName, 
                               nsIHTMLContent* aContent,
                               nsIHTMLStyleSheet* aSheet,
                               PRInt32& aAttrCount);

  NS_IMETHOD GetAttribute(nsIAtom* aAttrName,
                          nsHTMLValue& aValue) const;
  NS_IMETHOD GetAttribute(nsIAtom* aAttribute,
                          const nsHTMLValue** aValue) const;

  NS_IMETHOD GetAttributeNameAt(PRInt32 aIndex,
                                nsIAtom*& aName) const;

  NS_IMETHOD GetAttributeCount(PRInt32& aCount) const;

  NS_IMETHOD GetID(nsIAtom*& aResult) const;
  NS_IMETHOD GetClasses(nsVoidArray& aArray) const;
  NS_IMETHOD HasClass(nsIAtom* aClass) const;

  NS_IMETHOD Clone(nsIHTMLAttributes** aInstancePtrResult) const;

  NS_IMETHOD SetStyleSheet(nsIHTMLStyleSheet* aSheet);

  NS_IMETHOD GetMappedAttributeStyleRules(nsISupportsArray* aArray) const;

#ifdef UNIQUE_ATTR_SUPPORT
  NS_IMETHOD AddContentRef(void);
  NS_IMETHOD ReleaseContentRef(void);
  NS_IMETHOD GetContentRefCount(PRInt32& aCount) const;
#endif
  NS_IMETHOD Reset(void);

  NS_IMETHOD List(FILE* out = stdout, PRInt32 aIndent = 0) const;

  void SizeOf(nsISizeOfHandler* aSizer, PRUint32 &aResult);

protected:
  virtual nsresult SetAttributeName(nsIAtom* aAttrName, PRBool& aFound);
  virtual nsresult UnsetAttributeName(nsIAtom* aAttrName, PRBool& aFound);
  virtual nsresult EnsureSingleMappedFor(nsIHTMLContent* aContent, 
                                         nsIHTMLStyleSheet* aSheet,
                                         PRBool aCreate);
  virtual nsresult UniqueMapped(nsIHTMLStyleSheet* aSheet);

private:
  HTMLAttributesImpl& operator=(const HTMLAttributesImpl& aCopy);
  PRBool operator==(const HTMLAttributesImpl& aCopy) const;

protected:
  PRUint32 mInHeap : 1;
  PRUint32 mRefCnt : 31;
  NS_DECL_OWNINGTHREAD // for thread-safety checking

  nsIAtom**               mAttrNames;
  PRInt32                 mAttrCount;
  PRInt32                 mAttrSize;
  HTMLAttribute*          mFirstUnmapped;
  nsHTMLMappedAttributes* mMapped;
  nsIAtom*                mID;
  nsClassList             mFirstClass;

  nsIAtom*                mNameBuffer[kNameBufferSize];
};

void* HTMLAttributesImpl::operator new(size_t size)
{
  HTMLAttributesImpl* rv = (HTMLAttributesImpl*) ::operator new(size);
#ifdef NS_DEBUG
  if (nsnull != rv) {
    nsCRT::memset(rv, 0xEE, size);
  }
#endif
  rv->mInHeap = 1;
  return (void*) rv;
}

void* HTMLAttributesImpl::operator new(size_t size, nsIArena* aArena)
{
  HTMLAttributesImpl* rv = (HTMLAttributesImpl*) aArena->Alloc(PRInt32(size));
#ifdef NS_DEBUG
  if (nsnull != rv) {
    nsCRT::memset(rv, 0xEE, size);
  }
#endif
  rv->mInHeap = 0;
  return (void*) rv;
}

void HTMLAttributesImpl::operator delete(void* ptr)
{
  HTMLAttributesImpl* attr = (HTMLAttributesImpl*) ptr;
  if (nsnull != attr) {
    if (attr->mInHeap) {
      ::operator delete(ptr);
    }
  }
}

HTMLAttributesImpl::HTMLAttributesImpl(void)
  : mAttrNames(mNameBuffer),
    mAttrCount(0),
    mAttrSize(kNameBufferSize),
    mFirstUnmapped(nsnull),
    mMapped(nsnull),
    mID(nsnull),
    mFirstClass(nsnull)
{
  NS_INIT_REFCNT();
}

HTMLAttributesImpl::HTMLAttributesImpl(const HTMLAttributesImpl& aCopy)
  : mAttrNames(mNameBuffer),
    mAttrCount(aCopy.mAttrCount),
    mAttrSize(kNameBufferSize),
    mFirstUnmapped(nsnull),
    mMapped(aCopy.mMapped),
    mID(aCopy.mID),
    mFirstClass(aCopy.mFirstClass)
{
  NS_INIT_REFCNT();

  if (mAttrCount) {
    if (mAttrSize < mAttrCount) {
      mAttrNames = new nsIAtom*[mAttrCount];
      if (mAttrNames) {
        mAttrSize = mAttrCount;
      }
      else {  // new buffer failed, deal with it
        mAttrNames = mNameBuffer;
        mAttrCount = 0;
      }
    }
    PRInt32 index = mAttrCount;
    while (0 < index--) {
      mAttrNames[index] = aCopy.mAttrNames[index];
      NS_ADDREF(mAttrNames[index]);
    }
  }

  HTMLAttribute::CopyHTMLAttributes(aCopy.mFirstUnmapped, &mFirstUnmapped);
  if (mMapped) {
    mMapped->AddUse();
    NS_ADDREF(mMapped);
  }

  NS_IF_ADDREF(mID);
}

HTMLAttributesImpl::~HTMLAttributesImpl(void)
{
  Reset();
}

NS_IMPL_ADDREF(HTMLAttributesImpl)
NS_IMPL_RELEASE(HTMLAttributesImpl)

NS_IMPL_QUERY_INTERFACE(HTMLAttributesImpl, kIHTMLAttributesIID);

const PRUnichar kNullCh = PRUnichar('\0');

static void ParseClasses(const nsString& aClassString, nsClassList& aClassList)
{
  nsAutoString  classStr(aClassString);  // copy to work buffer
  classStr.Append(kNullCh);  // put an extra null at the end

  PRUnichar* start = (PRUnichar*)(const PRUnichar*)classStr.GetUnicode();
  PRUnichar* end   = start;

  nsClassList*  list = &aClassList;
  while (list && (kNullCh != *start)) {
    while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) {  // skip leading space
      start++;
    }
    end = start;

    while ((kNullCh != *end) && (PR_FALSE == nsCRT::IsAsciiSpace(*end))) { // look for space or end
      end++;
    }
    *end = kNullCh; // end string here

    if (start < end) {
      if (! list->mAtom) {
        list->mAtom = NS_NewAtom(start);
      }
      else {
        list->mNext = new nsClassList(NS_NewAtom(start));
        list = list->mNext;
      }
    }

    start = ++end;
  }
}

nsresult
HTMLAttributesImpl::SetAttributeName(nsIAtom* aAttrName, PRBool& aFound)
{
  PRInt32 index = mAttrCount;
  while (0 < index--) {
    if (mAttrNames[index] == aAttrName) {
      aFound = PR_TRUE;
      return NS_OK;
    }
  }
  aFound = PR_FALSE;
  if (mAttrCount == mAttrSize) {  // no more room, grow buffer
    nsIAtom** buffer = new nsIAtom*[mAttrSize + 4]; 
    if (buffer) {
      nsCRT::memcpy(buffer, mAttrNames, sizeof(nsIAtom*) * mAttrCount);
      mAttrSize += 4;
      if (mAttrNames != mNameBuffer) {
        delete [] mAttrNames;
      }
      mAttrNames = buffer;
    }
    else {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }
  mAttrNames[mAttrCount++] = aAttrName;
  NS_ADDREF(aAttrName);
  return NS_OK;
}

nsresult
HTMLAttributesImpl::UnsetAttributeName(nsIAtom* aAttrName, PRBool& aFound)
{
  PRInt32 index = mAttrCount;
  while (0 < index--) {
    if (mAttrNames[index] == aAttrName) {
      mAttrCount--;
      if ((mAttrNames != mNameBuffer) && (mAttrCount <= (kNameBufferSize / 2))) {
        // go back to using internal buffer
        if (0 < index) {
          nsCRT::memcpy(mNameBuffer, mAttrNames, index * sizeof(nsIAtom*));
        }
        if (index < mAttrCount) {
          nsCRT::memcpy(&(mNameBuffer[index]), &(mAttrNames[index + 1]), 
                        (mAttrCount - index) * sizeof(nsIAtom*));
        }
        delete [] mAttrNames;
        mAttrNames = mNameBuffer;
        mAttrSize = kNameBufferSize;
      }
      else {
        if (index < mAttrCount) {
          nsCRT::memmove(&(mAttrNames[index]), &(mAttrNames[index + 1]), 
                         (mAttrCount - index) * sizeof(nsIAtom*));
        }
      }
      NS_RELEASE(aAttrName);
      aFound = PR_TRUE;
      return NS_OK;
    }
  }
  aFound = PR_FALSE;
  return NS_OK;
}

nsresult
HTMLAttributesImpl::EnsureSingleMappedFor(nsIHTMLContent* aContent,
                                          nsIHTMLStyleSheet* aSheet,
                                          PRBool aCreate)
{
  nsresult result = NS_OK;
  if (mMapped) {
    PRInt32 useCount;
    mMapped->GetUseCount(useCount);
    if (1 < useCount) { // shared, clone it
      nsHTMLMappedAttributes* single;
      result = mMapped->Clone(&single);
      if (NS_SUCCEEDED(result)) {
        mMapped->ReleaseUse();
        NS_RELEASE(mMapped);
        mMapped = single;
        mMapped->AddUse();
      }
    }
    else {  // single use, remove it from unique table before modifying
      if (aSheet) {
        aSheet->DropMappedAttributes(mMapped);
      }
    }
  }
  else if (aCreate) {  // create one
    mMapped = new nsHTMLMappedAttributes();
    if (mMapped) {
      NS_ADDREF(mMapped);
      mMapped->AddUse();
      if (aContent) {
        nsMapAttributesFunc fontMapFunc;
        nsMapAttributesFunc mapFunc;
        aContent->GetAttributeMappingFunctions(fontMapFunc, mapFunc);
        result = mMapped->Init(aSheet, fontMapFunc, mapFunc);
      }
    }
    else {
      result = NS_ERROR_OUT_OF_MEMORY;
    }
  }
  return result;
}

nsresult
HTMLAttributesImpl::UniqueMapped(nsIHTMLStyleSheet* aSheet)
{
  nsresult result = NS_OK;

  if (aSheet) {
    nsIHTMLMappedAttributes*  mapped;
    result = aSheet->UniqueMappedAttributes(mMapped, mapped);
    if (NS_SUCCEEDED(result)) {
      if (mapped != mMapped) {
        mMapped->ReleaseUse();
        NS_RELEASE(mMapped);
        mMapped = (nsHTMLMappedAttributes*)mapped; // take ref
        mMapped->AddUse();
      }
      else {
        NS_RELEASE(mapped);
      }
    }
  }
  return result;
}

NS_IMETHODIMP
HTMLAttributesImpl::SetAttributeFor(nsIAtom* aAttrName, const nsString& aValue,
                                    PRBool aMappedToStyle, nsIHTMLContent* aContent,
                                    nsIHTMLStyleSheet* aSheet)
{
  nsresult result = NS_OK;

  if (nsHTMLAtoms::id == aAttrName) {
    NS_IF_RELEASE(mID);
    mID = NS_NewAtom(aValue);
  }
  else if (nsHTMLAtoms::kClass == aAttrName) {
    mFirstClass.Reset();
    ParseClasses(aValue, mFirstClass);
  }

  PRBool  haveAttr;
  result = SetAttributeName(aAttrName, haveAttr);
  if (NS_SUCCEEDED(result)) {
    if (aMappedToStyle) {
      result = EnsureSingleMappedFor(aContent, aSheet, PR_TRUE);
      if (mMapped) {
        result = mMapped->SetAttribute(aAttrName, aValue);
        UniqueMapped(aSheet);
      }
    }
    else {
      if (haveAttr) {
        HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);
        NS_ASSERTION(attr, "failed to find attribute");
        if (attr) {
          attr->mValue.SetStringValue(aValue);
        }
      }
      else {
        HTMLAttribute*  attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          attr->mNext = mFirstUnmapped;
          mFirstUnmapped = attr;
        }
        else {
          result = NS_ERROR_OUT_OF_MEMORY;
        }
      }
    }
  }

  return result;
}

NS_IMETHODIMP
HTMLAttributesImpl::SetAttributeFor(nsIAtom* aAttrName,
                                    const nsHTMLValue& aValue,
                                    PRBool aMappedToStyle,
                                    nsIHTMLContent* aContent,
                                    nsIHTMLStyleSheet* aSheet,
                                    PRInt32& aCount)
{
  nsresult result = NS_OK;

  if (eHTMLUnit_Null == aValue.GetUnit()) {
    return UnsetAttributeFor(aAttrName, aContent, aSheet, aCount);
  }
  
  if (nsHTMLAtoms::id == aAttrName) {
    NS_IF_RELEASE(mID);
    if (eHTMLUnit_String == aValue.GetUnit()) {
      nsAutoString  buffer;
      mID = NS_NewAtom(aValue.GetStringValue(buffer));
    }
  }
  else if (nsHTMLAtoms::kClass == aAttrName) {
    mFirstClass.Reset();
    if (eHTMLUnit_String == aValue.GetUnit()) {
      nsAutoString  buffer;
      aValue.GetStringValue(buffer);
      ParseClasses(buffer, mFirstClass);
    }
  }

  PRBool  haveAttr;
  result = SetAttributeName(aAttrName, haveAttr);
  if (NS_SUCCEEDED(result)) {
    if (aMappedToStyle) {
      result = EnsureSingleMappedFor(aContent, aSheet, PR_TRUE);
      if (mMapped) {
        result = mMapped->SetAttribute(aAttrName, aValue);
        UniqueMapped(aSheet);
      }
    }
    else {
      if (haveAttr) {
        HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);
        NS_ASSERTION(attr, "failed to find attribute");
        if (attr) {
          attr->mValue = aValue;
        }
      }
      else {
        HTMLAttribute*  attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          attr->mNext = mFirstUnmapped;
          mFirstUnmapped = attr;
        }
        else {
          result = NS_ERROR_OUT_OF_MEMORY;
        }
      }
    }
  }
  aCount = mAttrCount;

  return result;
}

NS_IMETHODIMP
HTMLAttributesImpl::UnsetAttributeFor(nsIAtom* aAttrName,
                                      nsIHTMLContent* aContent,
                                      nsIHTMLStyleSheet* aSheet,
                                      PRInt32& aCount)
{
  nsresult result = NS_OK;
  if (nsHTMLAtoms::id == aAttrName) {
    NS_IF_RELEASE(mID);
  }
  else if (nsHTMLAtoms::kClass == aAttrName) {
    mFirstClass.Reset();
  }

  PRBool haveAttr;
  result = UnsetAttributeName(aAttrName, haveAttr);
  if (NS_SUCCEEDED(result) && haveAttr) {
    if (! HTMLAttribute::RemoveHTMLAttribute(aAttrName, &mFirstUnmapped)) {
      // must be mapped
      if (mMapped) {
        EnsureSingleMappedFor(aContent, aSheet, PR_FALSE);
        PRInt32 mappedCount = 0;
        mMapped->UnsetAttribute(aAttrName, mappedCount);
        if (! mappedCount) {  // toss it when empty
          mMapped->ReleaseUse();
          NS_RELEASE(mMapped);
        }
        else {
          UniqueMapped(aSheet);
        }
      }
    }
  }

  aCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetAttribute(nsIAtom* aAttrName,
                                 nsHTMLValue& aValue) const
{
  nsresult result = NS_CONTENT_ATTR_NOT_THERE;

  if (mMapped) {
    result = mMapped->GetAttribute(aAttrName, aValue);
  }

  if (NS_CONTENT_ATTR_NOT_THERE == result) {
    const HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);

    if (attr) {
      aValue = attr->mValue;
      result = ((attr->mValue.GetUnit() == eHTMLUnit_Null) ? 
                NS_CONTENT_ATTR_NO_VALUE : 
                NS_CONTENT_ATTR_HAS_VALUE);
    }
    else {
      aValue.Reset();
    }
  }

  return result;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetAttribute(nsIAtom* aAttrName,
                                 const nsHTMLValue** aValue) const
{
  nsresult result = NS_CONTENT_ATTR_NOT_THERE;

  if (mMapped) {
    result = mMapped->GetAttribute(aAttrName, aValue);
  }

  if (NS_CONTENT_ATTR_NOT_THERE == result) {
    const HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);

    if (attr) {
      *aValue = &attr->mValue;
      result = ((attr->mValue.GetUnit() == eHTMLUnit_Null) ? 
                NS_CONTENT_ATTR_NO_VALUE : 
                NS_CONTENT_ATTR_HAS_VALUE);
    }
    else {
      *aValue = nsnull;
    }
  }

  return result;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetAttributeNameAt(PRInt32 aIndex,
                                       nsIAtom*& aName) const
{
  nsresult result = NS_ERROR_ILLEGAL_VALUE;

  if ((0 <= aIndex) && (aIndex < mAttrCount)) {
    aName = mAttrNames[aIndex];
    NS_ADDREF(aName);
    result = NS_OK;
  }
  return result;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetAttributeCount(PRInt32& aCount) const
{
  aCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetID(nsIAtom*& aResult) const
{
  aResult = mID;
  NS_IF_ADDREF(aResult);
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetClasses(nsVoidArray& aArray) const
{
  aArray.Clear();
  const nsClassList* classList = &mFirstClass;
  while (classList && classList->mAtom) {
    aArray.AppendElement(classList->mAtom); // NOTE atom is not addrefed
    classList = classList->mNext;
  }
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::HasClass(nsIAtom* aClass) const
{
  const nsClassList* classList = &mFirstClass;
  while (classList) {
    if (classList->mAtom == aClass) {
      return NS_OK;
    }
    classList = classList->mNext;
  }
  return NS_COMFALSE;
}

#ifdef UNIQUE_ATTR_SUPPORT
NS_IMETHODIMP
HTMLAttributesImpl::AddContentRef(void)
{
  ++mContentRefCount;
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::ReleaseContentRef(void)
{
  --mContentRefCount;
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetContentRefCount(PRInt32& aCount) const
{
  aCount = mContentRefCount;
  return NS_OK;
}
#endif

NS_IMETHODIMP
HTMLAttributesImpl::Clone(nsIHTMLAttributes** aInstancePtrResult) const
{
  if (aInstancePtrResult == nsnull) {
    return NS_ERROR_NULL_POINTER;
  }

  HTMLAttributesImpl* clone = new HTMLAttributesImpl(*this);

  if (nsnull == clone) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return clone->QueryInterface(NS_GET_IID(nsIHTMLAttributes), (void **) aInstancePtrResult);
}

NS_IMETHODIMP
HTMLAttributesImpl::SetStyleSheet(nsIHTMLStyleSheet* aSheet)
{
  if (mMapped && (aSheet != mMapped->mSheet)) {
    mMapped->SetStyleSheet(aSheet);
    return UniqueMapped(aSheet);
  }
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::GetMappedAttributeStyleRules(nsISupportsArray* aArray) const
{
  if (aArray && mMapped) {
    aArray->AppendElement((nsIStyleRule*)mMapped);
  }
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::Reset(void)
{
  // Release atoms first, then the table if it was malloc'd
  PRInt32 i, n = mAttrCount;
  for (i = 0; i < n; i++) {
    NS_IF_RELEASE(mAttrNames[i]);
  }
  if (mAttrNames != mNameBuffer) {
    delete [] mAttrNames;
    mAttrNames = mNameBuffer;
    mAttrSize = kNameBufferSize;
  }
  mAttrCount = 0;

  if (mFirstUnmapped) {
    HTMLAttribute::DeleteHTMLAttributes(mFirstUnmapped);
  }

  if (mMapped) {
    mMapped->ReleaseUse();
    NS_RELEASE(mMapped);
  }

  NS_IF_RELEASE(mID);
  mFirstClass.Reset();
  return NS_OK;
}

NS_IMETHODIMP
HTMLAttributesImpl::List(FILE* out, PRInt32 aIndent) const
{
  PRInt32 index = 0;
  while (index < mAttrCount) {
    PRInt32 indent;
    for (indent = aIndent; --indent >= 0; ) fputs("  ", out);

    nsHTMLValue value;
    GetAttribute(mAttrNames[index], value);
    nsAutoString  buffer;
    mAttrNames[index]->ToString(buffer);
    if (eHTMLUnit_Null != value.GetUnit()) {
      buffer.AppendWithConversion(" = ");
      value.AppendToString(buffer);
    }
    fputs(buffer, out);
  }
  return NS_OK;
}

void HTMLAttributesImpl::SizeOf(nsISizeOfHandler* aSizer, PRUint32 &aResult)
{
  PRUint32 sum = 0;

  // first get the unique items collection
  UNIQUE_STYLE_ITEMS(uniqueItems);
  if(! uniqueItems->AddItem((void*)this)){
    // this is already accounted for
    return;
  }

  // XXX step through this again
  sum = sizeof(*this);
  if (mAttrNames != mNameBuffer) {
    sum += sizeof(*mAttrNames) * mAttrSize;
  }
  if (mFirstUnmapped) {
    HTMLAttribute* ha = mFirstUnmapped;
    while (ha) {
      PRUint32 asum = 0;
      ha->SizeOf(aSizer, &asum);  // XXX Unique???
      sum += asum;
      ha = ha->mNext;
    }
  }
  if (mMapped) {
    PRBool recorded;
    aSizer->RecordObject((void*)mMapped, &recorded);
    if (!recorded) {
      PRUint32 asum = 0;
      mMapped->SizeOf(aSizer, asum);  // XXX Unique???
      sum += asum;
    }
  }

  aResult = sum;

  nsCOMPtr<nsIAtom> tag;
  tag = getter_AddRefs(NS_NewAtom("HTMLAttributesImpl"));
  aSizer->AddSize(tag, aResult);
}

extern NS_HTML nsresult
  NS_NewHTMLAttributes(nsIHTMLAttributes** aInstancePtrResult)
{
  if (aInstancePtrResult == nsnull) {
    return NS_ERROR_NULL_POINTER;
  }

  HTMLAttributesImpl  *it = new HTMLAttributesImpl();

  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return it->QueryInterface(NS_GET_IID(nsIHTMLAttributes), (void **) aInstancePtrResult);
}


