/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim:cindent:ts=2:et:sw=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>
 *
 * This Original Code has been modified by IBM Corporation. Modifications made by IBM 
 * described herein are Copyright (c) International Business Machines Corporation, 2000.
 * Modifications to Mozilla code or documentation identified per MPL Section 3.3
 *
 * Date             Modified by     Description of modification
 * 04/20/2000       IBM Corp.      OS/2 VisualAge build.
 */
#include "nscore.h"
#include "nsIPresContext.h"
#include "nsIPresShell.h"
#include "nsStyleSet.h"
#include "nsCSSFrameConstructor.h"
#include "nsStyleContext.h"
#include "nsStyleChangeList.h"
#include "nsIEventQueueService.h"
#include "nsIServiceManager.h"
#include "nsCOMPtr.h"
#include "prthread.h"
#include "plhash.h"
#include "nsPlaceholderFrame.h"
#include "nsLayoutAtoms.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSPseudoElements.h"
#include "nsHTMLAtoms.h"
#ifdef NS_DEBUG
#include "nsISupportsArray.h"
#include "nsIStyleRule.h"
#endif
#include "nsILayoutHistoryState.h"
#include "nsIPresState.h"
#include "nsIContent.h"
#include "nsINameSpaceManager.h"
#include "nsIXBLBinding.h"
#include "nsIDocument.h"
#include "nsIBindingManager.h"
#include "nsIScrollableFrame.h"

#include "nsIHTMLDocument.h"
#include "nsIDOMHTMLDocument.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMHTMLCollection.h"
#include "nsIFormControl.h"
#include "nsIDOMElement.h"
#include "nsIDOMHTMLFormElement.h"
#include "nsIForm.h"
#include "nsContentUtils.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsPrintfCString.h"
#include "nsDummyLayoutRequest.h"
#include "nsLayoutErrors.h"
#include "nsLayoutUtils.h"
#include "nsAutoPtr.h"
#include "imgIRequest.h"

#include "nsFrameManager.h"

  #ifdef DEBUG
    //#define NOISY_DEBUG
    //#define DEBUG_UNDISPLAYED_MAP
  #else
    #undef NOISY_DEBUG
    #undef DEBUG_UNDISPLAYED_MAP
  #endif

  #ifdef NOISY_DEBUG
    #define NOISY_TRACE(_msg) \
      printf("%s",_msg);
    #define NOISY_TRACE_FRAME(_msg,_frame) \
      printf("%s ",_msg); nsFrame::ListTag(stdout,_frame); printf("\n");
  #else
    #define NOISY_TRACE(_msg);
    #define NOISY_TRACE_FRAME(_msg,_frame);
  #endif

// Class IID's
static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);

// IID's

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

struct PlaceholderMapEntry : public PLDHashEntryHdr {
  // key (the out of flow frame) can be obtained through placeholder frame
  nsPlaceholderFrame *placeholderFrame;
};

PR_STATIC_CALLBACK(const void *)
PlaceholderMapGetKey(PLDHashTable *table, PLDHashEntryHdr *hdr)
{
  PlaceholderMapEntry *entry = NS_STATIC_CAST(PlaceholderMapEntry*, hdr);
  return entry->placeholderFrame->GetOutOfFlowFrame();
}

PR_STATIC_CALLBACK(PRBool)
PlaceholderMapMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr,
                         const void *key)
{
  const PlaceholderMapEntry *entry =
    NS_STATIC_CAST(const PlaceholderMapEntry*, hdr);
  return entry->placeholderFrame->GetOutOfFlowFrame() == key;
}

static PLDHashTableOps PlaceholderMapOps = {
  PL_DHashAllocTable,
  PL_DHashFreeTable,
  PlaceholderMapGetKey,
  PL_DHashVoidPtrKeyStub,
  PlaceholderMapMatchEntry,
  PL_DHashMoveEntryStub,
  PL_DHashClearEntryStub,
  PL_DHashFinalizeStub,
  NULL
};

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

struct PropertyListMapEntry : public PLDHashEntryHdr {
  const nsIFrame *key;
  void           *value;
};

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

struct PrimaryFrameMapEntry : public PLDHashEntryHdr {
  // key (the content node) can almost always be obtained through the
  // frame.  If it weren't for the way image maps (mis)used the primary
  // frame map, we'd be able to have a 2 word entry instead of a 3 word
  // entry.
  nsIContent *content;
  nsIFrame *frame;
};

  // These ops should be used if/when we switch back to a 2-word entry.
  // See comment in |PrimaryFrameMapEntry| above.
#if 0
PR_STATIC_CALLBACK(const void *)
PrimaryFrameMapGetKey(PLDHashTable *table, PLDHashEntryHdr *hdr)
{
  PrimaryFrameMapEntry *entry = NS_STATIC_CAST(PrimaryFrameMapEntry*, hdr);
  return entry->frame->GetContent();
}

PR_STATIC_CALLBACK(PRBool)
PrimaryFrameMapMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr,
                         const void *key)
{
  const PrimaryFrameMapEntry *entry =
    NS_STATIC_CAST(const PrimaryFrameMapEntry*, hdr);
  return entry->frame->GetContent() == key;
}

static PLDHashTableOps PrimaryFrameMapOps = {
  PL_DHashAllocTable,
  PL_DHashFreeTable,
  PrimaryFrameMapGetKey,
  PL_DHashVoidPtrKeyStub,
  PrimaryFrameMapMatchEntry,
  PL_DHashMoveEntryStub,
  PL_DHashClearEntryStub,
  PL_DHashFinalizeStub,
  NULL
};
#endif /* 0 */

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

// XXXldb This seems too complicated for what I think it's doing, and it
// should also be using pldhash rather than plhash to use less memory.
MOZ_DECL_CTOR_COUNTER(UndisplayedNode)

class UndisplayedNode {
public:
  UndisplayedNode(nsIContent* aContent, nsStyleContext* aStyle)
    : mContent(aContent),
      mStyle(aStyle),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(UndisplayedNode);
  }

  NS_HIDDEN ~UndisplayedNode()
  {
    MOZ_COUNT_DTOR(UndisplayedNode);
    delete mNext;
  }

  nsCOMPtr<nsIContent>      mContent;
  nsRefPtr<nsStyleContext>  mStyle;
  UndisplayedNode*          mNext;
};

class nsFrameManagerBase::UndisplayedMap {
public:
  UndisplayedMap(PRUint32 aNumBuckets = 16) NS_HIDDEN;
  ~UndisplayedMap(void) NS_HIDDEN;

  NS_HIDDEN_(UndisplayedNode*) GetFirstNode(nsIContent* aParentContent);

  NS_HIDDEN_(nsresult) AddNodeFor(nsIContent* aParentContent,
                                  nsIContent* aChild, nsStyleContext* aStyle);

  NS_HIDDEN_(void) RemoveNodeFor(nsIContent* aParentContent,
                                 UndisplayedNode* aNode);

  NS_HIDDEN_(void) RemoveNodesFor(nsIContent* aParentContent);

  // Removes all entries from the hash table
  NS_HIDDEN_(void)  Clear(void);

protected:
  NS_HIDDEN_(PLHashEntry**) GetEntryFor(nsIContent* aParentContent);
  NS_HIDDEN_(void)          AppendNodeFor(UndisplayedNode* aNode,
                                          nsIContent* aParentContent);

  PLHashTable*  mTable;
  PLHashEntry** mLastLookup;
};

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

  // A CantRenderReplacedElementEvent has a weak pointer to the frame
  // manager, and the frame manager has a weak pointer to the event.
  // The event queue owns the event and the FrameManager will delete
  // the event if it's going to go away.
struct CantRenderReplacedElementEvent : public PLEvent {
  CantRenderReplacedElementEvent(nsFrameManager* aFrameManager, nsIFrame* aFrame, nsIPresShell* aPresShell) NS_HIDDEN;
  ~CantRenderReplacedElementEvent() NS_HIDDEN;
  // XXXldb Should the pres shell maintain a reference count on a single
  // dummy layout request instead of doing creation of a separate one
  // here (and per-event!)?
  NS_HIDDEN_(nsresult) AddLoadGroupRequest(nsIPresShell* aPresShell);
  NS_HIDDEN_(nsresult) RemoveLoadGroupRequest();

  nsIFrame*  mFrame;                     // the frame that can't be rendered
  CantRenderReplacedElementEvent* mNext; // next event in the list
  nsCOMPtr<nsIRequest> mDummyLayoutRequest; // load group request
  nsWeakPtr mPresShell;                     // for removing load group request later
};

struct nsFrameManagerBase::PropertyList {
  nsCOMPtr<nsIAtom>       mName;          // property name
  PLDHashTable            mFrameValueMap; // map of frame/value pairs
  NSFramePropertyDtorFunc mDtorFunc;      // property specific value dtor function
  PropertyList*           mNext;

  PropertyList(nsIAtom*                aName,
               NSFramePropertyDtorFunc aDtorFunc) NS_HIDDEN;
  ~PropertyList() NS_HIDDEN;

  // Removes the property associated with the given frame, and destroys
  // the property value
  NS_HIDDEN_(PRBool) RemovePropertyForFrame(nsIPresContext* aPresContext,
                                            const nsIFrame* aFrame);

  // Destroy all remaining properties (without removing them)
  NS_HIDDEN_(void) Destroy(nsIPresContext* aPresContext);
};


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

nsFrameManager::nsFrameManager()
{
}

nsFrameManager::~nsFrameManager()
{
  NS_ASSERTION(!mPresShell, "nsFrameManager::Destroy never called");
}

nsresult
nsFrameManager::Init(nsIPresShell* aPresShell,
                     nsStyleSet*  aStyleSet)
{
  if (!aPresShell) {
    NS_ERROR("null pres shell");
    return NS_ERROR_FAILURE;
  }

  if (!aStyleSet) {
    NS_ERROR("null style set");
    return NS_ERROR_FAILURE;
  }

  mPresShell = aPresShell;
  mStyleSet = aStyleSet;
  return NS_OK;
}

void
nsFrameManager::Destroy()
{
  NS_ASSERTION(mPresShell, "Frame manager already shut down.");

  nsCOMPtr<nsIPresContext> presContext;
  mPresShell->GetPresContext(getter_AddRefs(presContext));
  
  // Destroy the frame hierarchy. Don't destroy the property lists until after
  // we've destroyed the frame hierarchy because some frames may expect to be
  // able to retrieve their properties during destruction
  mPresShell->SetIgnoreFrameDestruction(PR_TRUE);

  mIsDestroyingFrames = PR_TRUE;  // This flag prevents GetPrimaryFrameFor from returning pointers to destroyed frames

  if (mRootFrame) {
    mRootFrame->Destroy(presContext);
    mRootFrame = nsnull;
  }
  
  if (mPrimaryFrameMap.ops) {
    PL_DHashTableFinish(&mPrimaryFrameMap);
    mPrimaryFrameMap.ops = nsnull;
  }
  if (mPlaceholderMap.ops) {
    PL_DHashTableFinish(&mPlaceholderMap);
    mPlaceholderMap.ops = nsnull;
  }
  delete mUndisplayedMap;
  DestroyPropertyList(presContext);

  // If we're not going to be used anymore, we should revoke the
  // pending |CantRenderReplacedElementEvent|s being sent to us.
#ifdef DEBUG
  nsresult rv =
#endif
    RevokePostedEvents();
  NS_ASSERTION(NS_SUCCEEDED(rv), "RevokePostedEvents failed:  might crash");

  mPresShell = nsnull;
}

nsIFrame*
nsFrameManager::GetCanvasFrame()
{
  if (mRootFrame) {
    // walk the children of the root frame looking for a frame with type==canvas
    // start at the root
    nsIFrame* childFrame = mRootFrame;
    while (childFrame) {
      // get each sibling of the child and check them, startig at the child
      nsIFrame *siblingFrame = childFrame;
      while (siblingFrame) {
        if (siblingFrame->GetType() == nsLayoutAtoms::canvasFrame) {
          // this is it
          return siblingFrame;
        } else {
          siblingFrame = siblingFrame->GetNextSibling();
        }
      }
      // move on to the child's child
      childFrame = childFrame->GetFirstChild(nsnull);
    }
  }
  return nsnull;
}

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

// Primary frame functions
nsIFrame*
nsFrameManager::GetPrimaryFrameFor(nsIContent* aContent)
{
  NS_ENSURE_TRUE(aContent, nsnull);

  if (mIsDestroyingFrames) {
#ifdef DEBUG
    printf("GetPrimaryFrameFor() called while nsFrameManager is being destroyed!\n");
#endif
    return nsnull;
  }

  if (mPrimaryFrameMap.ops) {
    PrimaryFrameMapEntry *entry = NS_STATIC_CAST(PrimaryFrameMapEntry*,
        PL_DHashTableOperate(&mPrimaryFrameMap, aContent, PL_DHASH_LOOKUP));
    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
      return entry->frame;
    }

    // XXX: todo:  Add a lookup into the undisplay map to skip searches 
    //             if we already know the content has no frame.
    //             nsCSSFrameConstructor calls SetUndisplayedContent() for every
    //             content node that has display: none.
    //             Today, the undisplay map doesn't quite support what we need.
    //             We need to see if we can add a method to make a search for aContent 
    //             very fast in the embedded hash table.
    //             This would almost completely remove the lookup penalty for things
    //             like <SCRIPT> and comments in very large documents.
    nsCOMPtr<nsIPresContext> presContext;

    // Give the frame construction code the opportunity to return the
    // frame that maps the content object
    mPresShell->GetPresContext(getter_AddRefs(presContext));
    NS_ASSERTION(presContext, "bad presContext");
    if (!presContext) {
      return nsnull;
    }

    // if the prev sibling of aContent has a cached primary frame,
    // pass that data in to the style set to speed things up
    // if any methods in here fail, don't report that failure
    // we're just trying to enhance performance here, not test for correctness
    nsFindFrameHint hint;
    nsIContent* parent = aContent->GetParent();
    if (parent)
    {
      PRInt32 index = parent->IndexOf(aContent);
      if (index > 0)  // no use looking if it's the first child
      {
        nsIContent *prevSibling;
        nsIAtom *tag;
        do {
          prevSibling = parent->GetChildAt(--index);
          tag = prevSibling->Tag();
        } while (index &&
                 (tag == nsLayoutAtoms::textTagName ||
                  tag == nsLayoutAtoms::commentTagName ||
                  tag == nsLayoutAtoms::processingInstructionTagName));
        if (prevSibling) {
          entry = NS_STATIC_CAST(PrimaryFrameMapEntry*,
                          PL_DHashTableOperate(&mPrimaryFrameMap, prevSibling,
                                               PL_DHASH_LOOKUP));
          if (PL_DHASH_ENTRY_IS_BUSY(entry))
            hint.mPrimaryFrameForPrevSibling = entry->frame;
        }
      }
    }

    // walk the frame tree to find the frame that maps aContent.  
    // Use the hint if we have it.
    nsIFrame *result;

    mPresShell->FrameConstructor()->
      FindPrimaryFrameFor(presContext, this, aContent, &result, 
                          hint.mPrimaryFrameForPrevSibling ? &hint : nsnull);

    return result;
  }

  return nsnull;
}

nsresult
nsFrameManager::SetPrimaryFrameFor(nsIContent* aContent,
                                   nsIFrame*   aPrimaryFrame)
{
  NS_ENSURE_ARG_POINTER(aContent);
  // it's ok if aPrimaryFrame is null

  // If aPrimaryFrame is NULL, then remove the mapping
  if (!aPrimaryFrame) {
    if (mPrimaryFrameMap.ops) {
      PL_DHashTableOperate(&mPrimaryFrameMap, aContent, PL_DHASH_REMOVE);
    }
  } else {
  // This code should be used if/when we switch back to a 2-word entry
  // in the primary frame map.
#if 0
    NS_PRECONDITION(aPrimaryFrame->GetContent() == aContent, "wrong content");
#endif

    // Create a new hashtable if necessary
    if (!mPrimaryFrameMap.ops) {
      if (!PL_DHashTableInit(&mPrimaryFrameMap, PL_DHashGetStubOps(), nsnull,
                             sizeof(PrimaryFrameMapEntry), 16)) {
        mPrimaryFrameMap.ops = nsnull;
        return NS_ERROR_OUT_OF_MEMORY;
      }
    }

    // Add a mapping to the hash table
    PrimaryFrameMapEntry *entry = NS_STATIC_CAST(PrimaryFrameMapEntry*,
        PL_DHashTableOperate(&mPrimaryFrameMap, aContent, PL_DHASH_ADD));
#ifdef DEBUG_dbaron
    if (entry->frame) {
      NS_WARNING("already have primary frame for content");
    }
#endif
    entry->frame = aPrimaryFrame;
    entry->content = aContent;
  }
    
  return NS_OK;
}

void
nsFrameManager::ClearPrimaryFrameMap()
{
  if (mPrimaryFrameMap.ops) {
    PL_DHashTableFinish(&mPrimaryFrameMap);
    mPrimaryFrameMap.ops = nsnull;
  }
}

// Placeholder frame functions
nsPlaceholderFrame*
nsFrameManager::GetPlaceholderFrameFor(nsIFrame*  aFrame)
{
  NS_PRECONDITION(aFrame, "null param unexpected");

  if (mPlaceholderMap.ops) {
    PlaceholderMapEntry *entry = NS_STATIC_CAST(PlaceholderMapEntry*,
           PL_DHashTableOperate(NS_CONST_CAST(PLDHashTable*, &mPlaceholderMap),
                                aFrame, PL_DHASH_LOOKUP));
    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
      return entry->placeholderFrame;
    }
  }

  return nsnull;
}

nsresult
nsFrameManager::RegisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame)
{
  NS_PRECONDITION(aPlaceholderFrame, "null param unexpected");
  NS_PRECONDITION(nsLayoutAtoms::placeholderFrame == aPlaceholderFrame->GetType(),
                  "unexpected frame type");
  if (!mPlaceholderMap.ops) {
    if (!PL_DHashTableInit(&mPlaceholderMap, &PlaceholderMapOps, nsnull,
                           sizeof(PlaceholderMapEntry), 16)) {
      mPlaceholderMap.ops = nsnull;
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }
  PlaceholderMapEntry *entry = NS_STATIC_CAST(PlaceholderMapEntry*, 
         PL_DHashTableOperate(&mPlaceholderMap,
                              aPlaceholderFrame->GetOutOfFlowFrame(),
                              PL_DHASH_ADD));
  if (!entry)
    return NS_ERROR_OUT_OF_MEMORY;

  NS_ASSERTION(!entry->placeholderFrame, "Registering a placeholder for a frame that already has a placeholder!");
  entry->placeholderFrame = aPlaceholderFrame;
  return NS_OK;
}

void
nsFrameManager::UnregisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame)
{
  NS_PRECONDITION(aPlaceholderFrame, "null param unexpected");
  NS_PRECONDITION(nsLayoutAtoms::placeholderFrame == aPlaceholderFrame->GetType(),
                  "unexpected frame type");

  /*
   * nsCSSFrameConstructor::ReconstructDocElementHierarchy calls
   * ClearPlaceholderFrameMap and _then_ removes the fixed-positioned
   * frames one by one.  As these are removed they call
   * UnregisterPlaceholderFrame on their placeholders, but this is all
   * happening when mPlaceholderMap is already finished, so there is
   * nothing to do here.  See bug 144479.
   */
  if (mPlaceholderMap.ops) {
    PL_DHashTableOperate(&mPlaceholderMap,
                         aPlaceholderFrame->GetOutOfFlowFrame(),
                         PL_DHASH_REMOVE);
  }
}

void
nsFrameManager::ClearPlaceholderFrameMap()
{
  if (mPlaceholderMap.ops) {
    PL_DHashTableFinish(&mPlaceholderMap);
    mPlaceholderMap.ops = nsnull;
  }
}

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

nsStyleContext*
nsFrameManager::GetUndisplayedContent(nsIContent* aContent)
{
  if (!aContent || !mUndisplayedMap)
    return nsnull;

  nsIContent* parent = aContent->GetParent();
  if (!parent)
    return nsnull;

  for (UndisplayedNode* node = mUndisplayedMap->GetFirstNode(parent);
         node; node = node->mNext) {
    if (node->mContent == aContent)
      return node->mStyle;
  }

  return nsnull;
}
  
void
nsFrameManager::SetUndisplayedContent(nsIContent* aContent, 
                                      nsStyleContext* aStyleContext)
{
#ifdef DEBUG_UNDISPLAYED_MAP
   static int i = 0;
   printf("SetUndisplayedContent(%d): p=%p \n", i++, (void *)aContent);
#endif

  if (! mUndisplayedMap) {
    mUndisplayedMap = new UndisplayedMap;
  }
  if (mUndisplayedMap) {
    nsIContent* parent = aContent->GetParent();
    NS_ASSERTION(parent, "undisplayed content must have a parent");
    if (parent) {
      mUndisplayedMap->AddNodeFor(parent, aContent, aStyleContext);
    }
  }
}

void
nsFrameManager::ChangeUndisplayedContent(nsIContent* aContent, 
                                         nsStyleContext* aStyleContext)
{
  NS_ASSERTION(mUndisplayedMap, "no existing undisplayed content");
  
#ifdef DEBUG_UNDISPLAYED_MAP
   static int i = 0;
   printf("ChangeUndisplayedContent(%d): p=%p \n", i++, (void *)aContent);
#endif

  for (UndisplayedNode* node = mUndisplayedMap->GetFirstNode(aContent->GetParent());
         node; node = node->mNext) {
    if (node->mContent == aContent) {
      node->mStyle = aStyleContext;
      return;
    }
  }

  NS_NOTREACHED("no existing undisplayed content");
}

void
nsFrameManager::ClearUndisplayedContentIn(nsIContent* aContent,
                                          nsIContent* aParentContent)
{
#ifdef DEBUG_UNDISPLAYED_MAP
  static int i = 0;
  printf("ClearUndisplayedContent(%d): content=%p parent=%p --> ", i++, (void *)aContent, (void*)aParentContent);
#endif
  
  if (mUndisplayedMap) {
    UndisplayedNode* node = mUndisplayedMap->GetFirstNode(aParentContent);
    while (node) {
      if (node->mContent == aContent) {
        mUndisplayedMap->RemoveNodeFor(aParentContent, node);

#ifdef DEBUG_UNDISPLAYED_MAP
        printf( "REMOVED!\n");
#endif
#ifdef DEBUG
        // make sure that there are no more entries for the same content
        nsStyleContext *context = GetUndisplayedContent(aContent);
        NS_ASSERTION(context == nsnull, "Found more undisplayed content data after removal");
#endif
        return;
      }
      node = node->mNext;
    }
  }
}

void
nsFrameManager::ClearAllUndisplayedContentIn(nsIContent* aParentContent)
{
#ifdef DEBUG_UNDISPLAYED_MAP
  static int i = 0;
  printf("ClearAllUndisplayedContentIn(%d): parent=%p \n", i++, (void*)aParentContent);
#endif

  if (mUndisplayedMap) {
    mUndisplayedMap->RemoveNodesFor(aParentContent);
  }
}

void
nsFrameManager::ClearUndisplayedContentMap()
{
#ifdef DEBUG_UNDISPLAYED_MAP
  static int i = 0;
  printf("ClearUndisplayedContentMap(%d)\n", i++);
#endif

  if (mUndisplayedMap) {
    mUndisplayedMap->Clear();
  }
}

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

nsresult
nsFrameManager::InsertFrames(nsIFrame*       aParentFrame,
                             nsIAtom*        aListName,
                             nsIFrame*       aPrevFrame,
                             nsIFrame*       aFrameList)
{
#ifdef IBMBIDI
  if (aPrevFrame) {
    // Insert aFrameList after the last bidi continuation of aPrevFrame.
    nsIFrame* nextBidi;
    for (; ;) {
      nextBidi =
        NS_STATIC_CAST(nsIFrame*, GetFrameProperty(aPrevFrame,
                                                   nsLayoutAtoms::nextBidi,
                                                   0));
      if (!nextBidi) {
        break;
      }
      aPrevFrame = nextBidi;
    }
  }
#endif // IBMBIDI

  return aParentFrame->InsertFrames(GetPresContext(), *GetPresShell(),
                                    aListName, aPrevFrame, aFrameList);
}

nsresult
nsFrameManager::RemoveFrame(nsIFrame*       aParentFrame,
                            nsIAtom*        aListName,
                            nsIFrame*       aOldFrame)
{
#ifdef IBMBIDI
  // Don't let the parent remove next bidi. In the other cases the it should NOT be removed.
  nsIFrame* nextBidi =
    NS_STATIC_CAST(nsIFrame*, GetFrameProperty(aOldFrame,
                                               nsLayoutAtoms::nextBidi, 0));
  if (nextBidi) {
    RemoveFrame(aParentFrame, aListName, nextBidi);
  }
#endif // IBMBIDI

  return aParentFrame->RemoveFrame(GetPresContext(), *GetPresShell(),
                                   aListName, aOldFrame);
}

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

void
nsFrameManager::NotifyDestroyingFrame(nsIFrame* aFrame)
{
  // Dequeue and destroy and posted events for this frame
  DequeuePostedEventFor(aFrame);

  // Remove all properties associated with the frame
  nsCOMPtr<nsIPresContext> presContext;
  mPresShell->GetPresContext(getter_AddRefs(presContext));

  RemoveAllPropertiesFor(presContext, aFrame);

#ifdef DEBUG
  if (mPrimaryFrameMap.ops) {
    PrimaryFrameMapEntry *entry = NS_STATIC_CAST(PrimaryFrameMapEntry*,
        PL_DHashTableOperate(&mPrimaryFrameMap, aFrame->GetContent(), PL_DHASH_LOOKUP));
    NS_ASSERTION(!PL_DHASH_ENTRY_IS_BUSY(entry) || entry->frame != aFrame,
                 "frame was not removed from primary frame map before "
                 "destruction or was readded to map after being removed");
  }
#endif
}

nsresult
nsFrameManager::RevokePostedEvents()
{
  nsresult rv = NS_OK;
#ifdef NOISY_EVENTS
  printf("%p ~RevokePostedEvents() start\n", this);
#endif
  if (mPostedEvents) {
    mPostedEvents = nsnull;

    // Revoke any events in the event queue that are owned by us
    nsCOMPtr<nsIEventQueueService> eventService =
      do_GetService(kEventQueueServiceCID, &rv);

    if (NS_SUCCEEDED(rv)) {
      nsCOMPtr<nsIEventQueue> eventQueue;
      rv = eventService->GetThreadEventQueue(NS_CURRENT_THREAD, 
                                             getter_AddRefs(eventQueue));

      if (NS_SUCCEEDED(rv) && eventQueue) {
        rv = eventQueue->RevokeEvents(this);
      }
    }
  }
#ifdef NOISY_EVENTS
  printf("%p ~RevokePostedEvents() end\n", this);
#endif
  return rv;
}

CantRenderReplacedElementEvent**
nsFrameManager::FindPostedEventFor(nsIFrame* aFrame)
{
  CantRenderReplacedElementEvent** event = &mPostedEvents;

  while (*event) {
    if ((*event)->mFrame == aFrame) {
      return event;
    }
    event = &(*event)->mNext;
  }

  return event;
}

void
nsFrameManager::DequeuePostedEventFor(nsIFrame* aFrame)
{
  // If there's a posted event for this frame, then remove it
  CantRenderReplacedElementEvent** event = FindPostedEventFor(aFrame);
  if (*event) {
    CantRenderReplacedElementEvent* tmp = *event;

    // Remove it from our linked list of posted events
    *event = (*event)->mNext;
    
    // Dequeue it from the event queue
    nsresult              rv;

    nsCOMPtr<nsIEventQueueService> eventService =
      do_GetService(kEventQueueServiceCID, &rv);

    NS_ASSERTION(NS_SUCCEEDED(rv),
            "will crash soon due to event holding dangling pointer to frame");
    if (NS_SUCCEEDED(rv)) {
      nsCOMPtr<nsIEventQueue> eventQueue;
      rv = eventService->GetThreadEventQueue(NS_CURRENT_THREAD, 
                                             getter_AddRefs(eventQueue));

      NS_ASSERTION(NS_SUCCEEDED(rv) && eventQueue,
            "will crash soon due to event holding dangling pointer to frame");
      if (NS_SUCCEEDED(rv) && eventQueue) {
        PLEventQueue* plqueue;

        eventQueue->GetPLEventQueue(&plqueue);
        NS_ASSERTION(plqueue,
            "will crash soon due to event holding dangling pointer to frame");
        if (plqueue) {
          // Remove the event and then destroy it
          PL_DequeueEvent(tmp, plqueue);
          PL_DestroyEvent(tmp);
        }
      }
    }
  }
}

void
nsFrameManager::HandlePLEvent(CantRenderReplacedElementEvent* aEvent)
{
#ifdef NOISY_EVENTS
  printf("nsFrameManager::HandlePLEvent() start for FM %p\n", aEvent->owner);
#endif
  nsFrameManager* frameManager = (nsFrameManager*)aEvent->owner;
  NS_ASSERTION(frameManager, "null frame manager");

  if (!frameManager->mPresShell) {
    NS_ASSERTION(frameManager->mPresShell,
                 "event not removed from queue on shutdown");
    return;
  }

  // Remove the posted event from the linked list
  CantRenderReplacedElementEvent** events = &frameManager->mPostedEvents;
  while (*events) {
    if (*events == aEvent) {
      *events = (*events)->mNext;
      break;
    }
    events = &(*events)->mNext;
    NS_ASSERTION(*events, "event not in queue");
  }

  // Notify the style system and then process any reflow commands that
  // are generated
  nsCOMPtr<nsIPresContext> presContext;    
  frameManager->mPresShell->GetPresContext(getter_AddRefs(presContext));
  frameManager->mPresShell->FrameConstructor()->
    CantRenderReplacedElement(frameManager->mPresShell, presContext,
                              aEvent->mFrame);

#ifdef NOISY_EVENTS
  printf("nsFrameManager::HandlePLEvent() end for FM %p\n", aEvent->owner);
#endif
}

void
nsFrameManager::DestroyPLEvent(CantRenderReplacedElementEvent* aEvent)
{
  delete aEvent;
}

CantRenderReplacedElementEvent::CantRenderReplacedElementEvent(nsFrameManager* aFrameManager,
                                                               nsIFrame*     aFrame,
                                                               nsIPresShell* aPresShell)
{
  PL_InitEvent(this, aFrameManager,
               (PLHandleEventProc)&nsFrameManager::HandlePLEvent,
               (PLDestroyEventProc)&nsFrameManager::DestroyPLEvent);
  mFrame = aFrame;
  
  if (nsLayoutAtoms::objectFrame == aFrame->GetType()) {
    AddLoadGroupRequest(aPresShell);
  }
}

CantRenderReplacedElementEvent::~CantRenderReplacedElementEvent()
{
  RemoveLoadGroupRequest();
}

// Add a load group request in order to delay the onLoad handler when we have
// pending replacements
nsresult
CantRenderReplacedElementEvent::AddLoadGroupRequest(nsIPresShell* aPresShell)
{
  nsCOMPtr<nsIDocument> doc;
  aPresShell->GetDocument(getter_AddRefs(doc));
  if (!doc) return NS_ERROR_FAILURE;

  nsresult rv = nsDummyLayoutRequest::Create(getter_AddRefs(mDummyLayoutRequest), aPresShell);
  if (NS_FAILED(rv)) return rv;
  if (!mDummyLayoutRequest) return NS_ERROR_FAILURE;

  nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
  if (!loadGroup) return NS_ERROR_FAILURE;
  
  rv = mDummyLayoutRequest->SetLoadGroup(loadGroup);
  if (NS_FAILED(rv)) return rv;
  
  mPresShell = do_GetWeakReference(aPresShell);

  return loadGroup->AddRequest(mDummyLayoutRequest, nsnull);
}

// Remove the load group request added above
nsresult
CantRenderReplacedElementEvent::RemoveLoadGroupRequest()
{
  nsresult rv = NS_OK;

  if (mDummyLayoutRequest) {
    nsCOMPtr<nsIRequest> request = mDummyLayoutRequest;
    mDummyLayoutRequest = nsnull;

    nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
    if (!presShell) return NS_ERROR_FAILURE;
    
    nsCOMPtr<nsIDocument> doc;
    presShell->GetDocument(getter_AddRefs(doc));
    if (!doc) return NS_ERROR_FAILURE;;

    nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
    if (!loadGroup) return NS_ERROR_FAILURE;

    rv = loadGroup->RemoveRequest(request, nsnull, NS_OK);
  }
  return rv;
}

nsresult
nsFrameManager::CantRenderReplacedElement(nsIFrame* aFrame)
{
#ifdef NOISY_EVENTS
  printf("%p nsFrameManager::CantRenderReplacedElement called\n", this);
#endif

  // We need to notify the style stystem, but post the notification so it
  // doesn't happen now
  nsresult rv;
  nsCOMPtr<nsIEventQueueService> eventService =
    do_GetService(kEventQueueServiceCID, &rv);

  if (eventService) {
    nsCOMPtr<nsIEventQueue> eventQueue;
    rv = eventService->GetThreadEventQueue(NS_CURRENT_THREAD, 
                                           getter_AddRefs(eventQueue));

    if (NS_SUCCEEDED(rv) && eventQueue) {
      // Verify that there isn't already a posted event associated with
      // this frame.
      if (*FindPostedEventFor(aFrame))
        return NS_OK;

      CantRenderReplacedElementEvent* ev;

      // Create a new event
      ev = new CantRenderReplacedElementEvent(this, aFrame, mPresShell);

      // Post the event
      rv = eventQueue->PostEvent(ev);
      if (NS_FAILED(rv)) {
        NS_ERROR("failed to post event");
        PL_DestroyEvent(ev);
      }
      else {
        // Add the event to our linked list of posted events
        ev->mNext = mPostedEvents;
        mPostedEvents = ev;
      }
    }
  }

  return rv;
}

#ifdef NS_DEBUG
static void
DumpContext(nsIFrame* aFrame, nsStyleContext* aContext)
{
  if (aFrame) {
    fputs("frame: ", stdout);
    nsAutoString  name;
    nsIFrameDebug*  frameDebug;

    if (NS_SUCCEEDED(aFrame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
      frameDebug->GetFrameName(name);
      fputs(NS_LossyConvertUCS2toASCII(name).get(), stdout);
    }
    fprintf(stdout, " (%p)", NS_STATIC_CAST(void*, aFrame));
  }
  if (aContext) {
    fprintf(stdout, " style: %p ", NS_STATIC_CAST(void*, aContext));

    nsIAtom* pseudoTag = aContext->GetPseudoType();
    if (pseudoTag) {
      nsAutoString  buffer;
      pseudoTag->ToString(buffer);
      fputs(NS_LossyConvertUCS2toASCII(buffer).get(), stdout);
      fputs(" ", stdout);
    }

/* XXXdwh fix debugging here.  Need to add a List method to nsRuleNode
   and have the context call list on its rule node.
    PRInt32 count = aContext->GetStyleRuleCount();
    if (0 < count) {
      fputs("{\n", stdout);
      nsISupportsArray* rules = aContext->GetStyleRules();
      PRInt32 ix;
      for (ix = 0; ix < count; ix++) {
        nsIStyleRule* rule = (nsIStyleRule*)rules->ElementAt(ix);
        rule->List(stdout, 1);
        NS_RELEASE(rule);
      }
      NS_RELEASE(rules);
      fputs("}\n", stdout);
    }
    else 
    */
    {
      fputs("{}\n", stdout);
    }
  }
}

static void
VerifySameTree(nsStyleContext* aContext1, nsStyleContext* aContext2)
{
  nsStyleContext* top1 = aContext1;
  nsStyleContext* top2 = aContext2;
  nsStyleContext* parent;
  for (;;) {
    parent = top1->GetParent();
    if (!parent)
      break;
    top1 = parent;
  }
  for (;;) {
    parent = top2->GetParent();
    if (!parent)
      break;
    top2 = parent;
  }
  if (top1 != top2)
    printf("Style contexts are not in the same style context tree.\n");
}

static void
VerifyContextParent(nsIPresContext* aPresContext, nsIFrame* aFrame, 
                    nsStyleContext* aContext, nsStyleContext* aParentContext)
{
  // get the contexts not provided
  if (!aContext) {
    aContext = aFrame->GetStyleContext();
  }

  if (!aParentContext) {
    // Get the correct parent context from the frame
    //  - if the frame is a placeholder, we get the out of flow frame's context 
    //    as the parent context instead of asking the frame

    // get the parent context from the frame (indirectly)
    nsIFrame* providerFrame = nsnull;
    PRBool providerIsChild;
    aFrame->GetParentStyleContextFrame(aPresContext,
                                       &providerFrame, &providerIsChild);
    if (providerFrame)
      aParentContext = providerFrame->GetStyleContext();
    // aParentContext could still be null
  }

  NS_ASSERTION(aContext, "Failure to get required contexts");
  nsStyleContext* actualParentContext = aContext->GetParent();

  if (aParentContext) {
    if (aParentContext != actualParentContext) {
      DumpContext(aFrame, aContext);
      if (aContext == aParentContext) {
        fputs("Using parent's style context\n\n", stdout);
      }
      else {
        fputs("Wrong parent style context: ", stdout);
        DumpContext(nsnull, actualParentContext);
        fputs("should be using: ", stdout);
        DumpContext(nsnull, aParentContext);
        VerifySameTree(actualParentContext, aParentContext);
        fputs("\n", stdout);
      }
    }
  }
  else {
    if (actualParentContext) {
      DumpContext(aFrame, aContext);
      fputs("Has parent context: ", stdout);
      DumpContext(nsnull, actualParentContext);
      fputs("Should be null\n\n", stdout);
    }
  }
}

static void
VerifyStyleTree(nsIPresContext* aPresContext, nsIFrame* aFrame,
                nsStyleContext* aParentContext)
{
  nsStyleContext*  context = aFrame->GetStyleContext();
  VerifyContextParent(aPresContext, aFrame, context, nsnull);

  PRInt32 listIndex = 0;
  nsIAtom* childList = nsnull;
  nsIFrame* child;

  do {
    child = aFrame->GetFirstChild(childList);
    while (child) {
      if (NS_FRAME_OUT_OF_FLOW != (child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
        // only do frames that are in flow
        if (nsLayoutAtoms::placeholderFrame == child->GetType()) { 
          // placeholder: first recirse and verify the out of flow frame,
          // then verify the placeholder's context
          nsIFrame* outOfFlowFrame = ((nsPlaceholderFrame*)child)->GetOutOfFlowFrame();
          NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");

          // recurse to out of flow frame, letting the parent context get resolved
          VerifyStyleTree(aPresContext, outOfFlowFrame, nsnull);

          // verify placeholder using the parent frame's context as
          // parent context
          VerifyContextParent(aPresContext, child, nsnull, nsnull);
        }
        else { // regular frame
          VerifyStyleTree(aPresContext, child, nsnull);
        }
      }
      child = child->GetNextSibling();
    }

    childList = aFrame->GetAdditionalChildListName(listIndex++);
  } while (childList);
  
  // do additional contexts 
  PRInt32 contextIndex = -1;
  while (1) {
    nsStyleContext* extraContext = aFrame->GetAdditionalStyleContext(++contextIndex);
    if (extraContext) {
      VerifyContextParent(aPresContext, aFrame, extraContext, context);
    }
    else {
      break;
    }
  }
}

void
nsFrameManager::DebugVerifyStyleTree(nsIFrame* aFrame)
{
  if (aFrame) {
    nsStyleContext* context = aFrame->GetStyleContext();
    nsStyleContext* parentContext = context->GetParent();
    VerifyStyleTree(GetPresContext(), aFrame, parentContext);
  }
}

#endif // DEBUG

nsresult
nsFrameManager::ReParentStyleContext(nsIFrame* aFrame, 
                                     nsStyleContext* aNewParentContext)
{
  nsresult result = NS_ERROR_NULL_POINTER;
  if (aFrame) {
    // DO NOT verify the style tree before reparenting.  The frame
    // tree has already been changed, so this check would just fail.
    nsStyleContext* oldContext = aFrame->GetStyleContext();
    if (oldContext) {
      nsIPresContext *presContext = GetPresContext();
      nsRefPtr<nsStyleContext> newContext;
      result = NS_OK;
      newContext = mStyleSet->ReParentStyleContext(presContext, oldContext,
                                                   aNewParentContext);
      if (newContext) {
        if (newContext != oldContext) {
          PRInt32 listIndex = 0;
          nsIAtom* childList = nsnull;
          nsIFrame* child;
          
          aFrame->SetStyleContext(presContext, newContext);

          do {
            child = aFrame->GetFirstChild(childList);
            while (child) {
              if (NS_FRAME_OUT_OF_FLOW != (child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
                // only do frames that are in flow
                if (nsLayoutAtoms::placeholderFrame == child->GetType()) {
                  // get out of flow frame and recurse there
                  nsIFrame* outOfFlowFrame = ((nsPlaceholderFrame*)child)->GetOutOfFlowFrame();
                  NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");

                  result = ReParentStyleContext(outOfFlowFrame, newContext);

                  // reparent placeholder's context under out of flow frame
                  nsStyleContext* outOfFlowContext = outOfFlowFrame->GetStyleContext();
                  ReParentStyleContext(child, outOfFlowContext);
                }
                else { // regular frame
                  result = ReParentStyleContext(child, newContext);
                }
              }

              child = child->GetNextSibling();
            }

            childList = aFrame->GetAdditionalChildListName(listIndex++);
          } while (childList);

          // do additional contexts 
          PRInt32 contextIndex = -1;
          while (1) {
            nsStyleContext* oldExtraContext = aFrame->GetAdditionalStyleContext(++contextIndex);
            if (oldExtraContext) {
              nsRefPtr<nsStyleContext> newExtraContext;
              newExtraContext = mStyleSet->ReParentStyleContext(presContext,
                                                                oldExtraContext,
                                                                newContext);
              if (newExtraContext) {
                aFrame->SetAdditionalStyleContext(contextIndex, newExtraContext);
              }
            }
            else {
              result = NS_OK; // ok not to have extras (or run out)
              break;
            }
          }
#ifdef DEBUG
          VerifyStyleTree(GetPresContext(), aFrame, aNewParentContext);
#endif
        }
      }
    }
  }
  return result;
}

static nsChangeHint
CaptureChange(nsStyleContext* aOldContext, nsStyleContext* aNewContext,
              nsIFrame* aFrame, nsIContent* aContent,
              nsStyleChangeList* aChangeList, nsChangeHint aMinChange)
{
  nsChangeHint ourChange = NS_STYLE_HINT_NONE;
  ourChange = aOldContext->CalcStyleDifference(aNewContext);
  if (NS_UpdateHint(aMinChange, ourChange)) {
    aChangeList->AppendChange(aFrame, aContent, ourChange);
  }
  return aMinChange;
}

nsChangeHint
nsFrameManager::ReResolveStyleContext(nsIPresContext    *aPresContext,
                                      nsIFrame          *aFrame,
                                      nsIContent        *aParentContent,
                                      nsStyleChangeList *aChangeList, 
                                      nsChangeHint       aMinChange)
{
  // XXXldb get new context from prev-in-flow if possible, to avoid
  // duplication.  (Or should we just let |GetContext| handle that?)
  // Getting the hint would be nice too, but that's harder.

  // XXXbryner we may be able to avoid some of the refcounting goop here.
  // We do need a reference to oldContext for the lifetime of this function, and it's possible
  // that the frame has the last reference to it, so AddRef it here.

  nsChangeHint resultChange = NS_STYLE_HINT_NONE;
  nsStyleContext* oldContext = aFrame->GetStyleContext();
  nsStyleSet* styleSet = aPresContext->StyleSet();

  if (oldContext) {
    oldContext->AddRef();
    nsIAtom* const pseudoTag = oldContext->GetPseudoType();
    nsIContent* localContent = aFrame->GetContent();
    nsIContent* content = localContent ? localContent : aParentContent;

    nsStyleContext* parentContext;
    nsIFrame* resolvedChild = nsnull;
    // Get the frame providing the parent style context.  If it is a
    // child, then resolve the provider first.
    nsIFrame* providerFrame = nsnull;
    PRBool providerIsChild = PR_FALSE;
    aFrame->GetParentStyleContextFrame(aPresContext,
                                       &providerFrame, &providerIsChild); 
    if (!providerIsChild) {
      if (providerFrame)
        parentContext = providerFrame->GetStyleContext();
      else
        parentContext = nsnull;
    }
    else {
      // resolve the provider here (before aFrame below)
      resultChange = ReResolveStyleContext(aPresContext, providerFrame,
                                           content, aChangeList, aMinChange);
      // The provider's new context becomes the parent context of
      // aFrame's context.
      parentContext = providerFrame->GetStyleContext();
      // Set |resolvedChild| so we don't bother resolving the
      // provider again.
      resolvedChild = providerFrame;
    }
    
    // do primary context
    nsStyleContext* newContext = nsnull;
    if (pseudoTag == nsCSSAnonBoxes::mozNonElement) {
      NS_ASSERTION(localContent,
                   "non pseudo-element frame without content node");
      newContext = styleSet->ResolveStyleForNonElement(parentContext).get();
    }
    else if (pseudoTag) {
      nsIContent* pseudoContent =
          aParentContent ? aParentContent : localContent;
      if (pseudoTag == nsCSSPseudoElements::before ||
          pseudoTag == nsCSSPseudoElements::after) {
        // XXX what other pseudos do we need to treat like this?
        newContext = styleSet->ProbePseudoStyleFor(pseudoContent,
                                                   pseudoTag,
                                                   parentContext).get();
        if (!newContext) {
          // This pseudo should no longer exist; gotta reframe
          NS_UpdateHint(aMinChange, nsChangeHint_ReconstructFrame);
          aChangeList->AppendChange(aFrame, pseudoContent,
                                    nsChangeHint_ReconstructFrame);
          // We're reframing anyway; just keep the same context
          newContext = oldContext;
          newContext->AddRef();
        }
      } else {
        newContext = styleSet->ResolvePseudoStyleFor(pseudoContent,
                                                     pseudoTag,
                                                     parentContext).get();
      }
    }
    else {
      NS_ASSERTION(localContent,
                   "non pseudo-element frame without content node");
      newContext = styleSet->ResolveStyleFor(content, parentContext).get();
    }
    NS_ASSERTION(newContext, "failed to get new style context");
    if (newContext) {
      if (!parentContext) {
        if (oldContext->GetRuleNode() == newContext->GetRuleNode()) {
          // We're the root of the style context tree and the new style
          // context returned has the same rule node.  This means that
          // we can use FindChildWithRules to keep a lot of the old
          // style contexts around.  However, we need to start from the
          // same root.
          newContext->Release();
          newContext = oldContext;
          newContext->AddRef();
        }
      }

      if (newContext != oldContext) {
        aMinChange = CaptureChange(oldContext, newContext, aFrame,
                                   content, aChangeList, aMinChange);
        if (!(aMinChange & nsChangeHint_ReconstructFrame)) {
          // if frame gets regenerated, let it keep old context
          aFrame->SetStyleContext(aPresContext, newContext);
        }
        // if old context had image and new context does not have the same image, 
        // stop the image load for the frame
        const nsStyleBackground* oldColor = oldContext->GetStyleBackground();
        const nsStyleBackground* newColor = newContext->GetStyleBackground();

        if (oldColor->mBackgroundImage) {
          PRBool stopImages = !newColor->mBackgroundImage;
          if (!stopImages) {
            nsCOMPtr<nsIURI> oldURI, newURI;
            oldColor->mBackgroundImage->GetURI(getter_AddRefs(oldURI));
            newColor->mBackgroundImage->GetURI(getter_AddRefs(newURI));
            PRBool equal;
            stopImages =
              NS_FAILED(oldURI->Equals(newURI, &equal)) || !equal;
          }
          if (stopImages) {
            // stop the image loading for the frame, the image has changed
            aPresContext->StopImagesFor(aFrame);
          }
        }
      }
      oldContext->Release();
    }
    else {
      NS_ERROR("resolve style context failed");
      newContext = oldContext;  // new context failed, recover... (take ref)
      oldContext = nsnull;
    }

    // do additional contexts 
    PRInt32 contextIndex = -1;
    while (1 == 1) {
      nsStyleContext* oldExtraContext = nsnull;
      oldExtraContext = aFrame->GetAdditionalStyleContext(++contextIndex);
      if (oldExtraContext) {
        nsStyleContext* newExtraContext = nsnull;
        nsIAtom* const extraPseudoTag = oldExtraContext->GetPseudoType();
        NS_ASSERTION(extraPseudoTag &&
                     extraPseudoTag != nsCSSAnonBoxes::mozNonElement,
                     "extra style context is not pseudo element");
        newExtraContext = styleSet->ResolvePseudoStyleFor(content,
                                                          extraPseudoTag,
                                                          newContext).get();
        if (newExtraContext) {
          if (oldExtraContext != newExtraContext) {
            aMinChange = CaptureChange(oldExtraContext, newExtraContext,
                                       aFrame, content, aChangeList,
                                       aMinChange);
            if (!(aMinChange & nsChangeHint_ReconstructFrame)) {
              aFrame->SetAdditionalStyleContext(contextIndex, newExtraContext);
            }
          }
          newExtraContext->Release();
        }
      }
      else {
        break;
      }
    }

    // now look for undisplayed child content and pseudos
    if (localContent && mUndisplayedMap) {
      for (UndisplayedNode* undisplayed =
                                   mUndisplayedMap->GetFirstNode(localContent);
           undisplayed; undisplayed = undisplayed->mNext) {
        nsRefPtr<nsStyleContext> undisplayedContext;
        nsIAtom* const undisplayedPseudoTag = undisplayed->mStyle->GetPseudoType();
        if (!undisplayedPseudoTag) {  // child content
          undisplayedContext = styleSet->ResolveStyleFor(undisplayed->mContent,
                                                         newContext);
        }
        else if (undisplayedPseudoTag == nsCSSAnonBoxes::mozNonElement) {
          undisplayedContext = styleSet->ResolveStyleForNonElement(newContext);
        }
        else {  // pseudo element
          NS_NOTREACHED("no pseudo elements in undisplayed map");
          NS_ASSERTION(undisplayedPseudoTag, "pseudo element without tag");
          undisplayedContext = styleSet->ResolvePseudoStyleFor(localContent,
                                                               undisplayedPseudoTag,
                                                               newContext);
        }
        if (undisplayedContext) {
          const nsStyleDisplay* display = undisplayedContext->GetStyleDisplay();
          if (display->mDisplay != NS_STYLE_DISPLAY_NONE) {
            aChangeList->AppendChange(nsnull,
                                      undisplayed->mContent
                                      ? NS_STATIC_CAST(nsIContent*,
                                                       undisplayed->mContent)
                                      : localContent, 
                                      NS_STYLE_HINT_FRAMECHANGE);
            // The node should be removed from the undisplayed map when
            // we reframe it.
          } else {
            // update the undisplayed node with the new context
            undisplayed->mStyle = undisplayedContext;
          }
        }
      }
    }

    resultChange = aMinChange;

    if (!(aMinChange & nsChangeHint_ReconstructFrame)) {
      // Make sure not to do this for pseudo-frames -- those can't have :before
      // or :after content.
      if (!pseudoTag && localContent &&
          localContent->IsContentOfType(nsIContent::eELEMENT)) {
        // Check for a new :before pseudo and an existing :before
        // frame, but only if the frame is the first-in-flow.
        nsIFrame* prevInFlow = nsnull;
        aFrame->GetPrevInFlow(&prevInFlow);
        if (!prevInFlow) {
          // Checking for a :before frame is cheaper than getting the
          // :before style context.
          if (!nsLayoutUtils::GetBeforeFrame(aFrame, aPresContext) &&
              nsLayoutUtils::HasPseudoStyle(localContent, newContext,
                                            nsCSSPseudoElements::before,
                                            aPresContext)) {
            // Have to create the new :before frame
            NS_UpdateHint(aMinChange, nsChangeHint_ReconstructFrame);
            aChangeList->AppendChange(aFrame, content,
                                      nsChangeHint_ReconstructFrame);
          }
        }
      }
    }

    
    if (!(aMinChange & nsChangeHint_ReconstructFrame)) {
      // Make sure not to do this for pseudo-frames -- those can't have :before
      // or :after content.
      if (!pseudoTag && localContent &&
          localContent->IsContentOfType(nsIContent::eELEMENT)) {
        // Check for new :after content, but only if the frame is the
        // first-in-flow.
        nsIFrame* nextInFlow = nsnull;
        aFrame->GetNextInFlow(&nextInFlow);

        if (!nextInFlow) {
          // Getting the :after frame is more expensive than getting the pseudo
          // context, so get the pseudo context first.
          if (nsLayoutUtils::HasPseudoStyle(localContent, newContext,
                                            nsCSSPseudoElements::after,
                                            aPresContext) &&
              !nsLayoutUtils::GetAfterFrame(aFrame, aPresContext)) {
            // have to create the new :after frame
            NS_UpdateHint(aMinChange, nsChangeHint_ReconstructFrame);
            aChangeList->AppendChange(aFrame, content,
                                      nsChangeHint_ReconstructFrame);
          }
        }      
      }
    }
    
    if (!(aMinChange & nsChangeHint_ReconstructFrame)) {
      
      // There is no need to waste time crawling into a frame's children on a frame change.
      // The act of reconstructing frames will force new style contexts to be resolved on all
      // of this frame's descendants anyway, so we want to avoid wasting time processing
      // style contexts that we're just going to throw away anyway. - dwh

      // now do children
      PRInt32 listIndex = 0;
      nsIAtom* childList = nsnull;

      do {
        nsIFrame* child = aFrame->GetFirstChild(childList);
        while (child) {
          if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
            // only do frames that are in flow
            if (nsLayoutAtoms::placeholderFrame == child->GetType()) { // placeholder
              // get out of flow frame and recur there
              nsIFrame* outOfFlowFrame =
                NS_STATIC_CAST(nsPlaceholderFrame*,child)->GetOutOfFlowFrame();
              NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");
              NS_ASSERTION(outOfFlowFrame != resolvedChild,
                           "out-of-flow frame not a true descendant");

              // |nsFrame::GetParentStyleContextFrame| checks being out
              // of flow so that this works correctly.
              ReResolveStyleContext(aPresContext, outOfFlowFrame,
                                    content, aChangeList, aMinChange);

              // reresolve placeholder's context under the same parent
              // as the out-of-flow frame
              ReResolveStyleContext(aPresContext, child, content,
                                    aChangeList, aMinChange);
            }
            else {  // regular child frame
              if (child != resolvedChild) {
                ReResolveStyleContext(aPresContext, child, content,
                                      aChangeList, aMinChange);
              } else {
                NOISY_TRACE_FRAME("child frame already resolved as descendent, skipping",aFrame);
              }
            }
          }
          child = child->GetNextSibling();
        }

        childList = aFrame->GetAdditionalChildListName(listIndex++);
      } while (childList);
      // XXX need to do overflow frames???
    }

    newContext->Release();
  }

  return resultChange;
}

nsChangeHint
nsFrameManager::ComputeStyleChangeFor(nsIFrame          *aFrame, 
                                      nsStyleChangeList *aChangeList,
                                      nsChangeHint       aMinChange)
{
  nsChangeHint topLevelChange = aMinChange;

  nsIFrame* frame = aFrame;
  nsIFrame* frame2 = aFrame;

#ifdef DEBUG
  {
    nsIFrame* prevInFlow;
    frame->GetPrevInFlow(&prevInFlow);
    NS_ASSERTION(!prevInFlow, "must start with the first in flow");
  }
#endif

  // We want to start with this frame and walk all its next-in-flows,
  // as well as all its special siblings and their next-in-flows,
  // reresolving style on all the frames we encounter in this walk.
  
  do {
    // Outer loop over special siblings
    do {
      // Inner loop over next-in-flows of the current frame
      nsChangeHint frameChange =
        ReResolveStyleContext(GetPresContext(), frame, nsnull,
                              aChangeList, topLevelChange);
      NS_UpdateHint(topLevelChange, frameChange);

      if (topLevelChange & nsChangeHint_ReconstructFrame) {
        // If it's going to cause a framechange, then don't bother
        // with the continuations or special siblings since they'll be
        // clobbered by the frame reconstruct anyway.
#ifdef NS_DEBUG
        nsIFrame* prevInFlow;
        frame->GetPrevInFlow(&prevInFlow);
        NS_ASSERTION(!prevInFlow, "continuing frame had more severe impact than first-in-flow");
#endif
        return topLevelChange;
      }

      frame->GetNextInFlow(&frame);
    } while (frame);

    // Might we have special siblings?
    if (!(frame2->GetStateBits() & NS_FRAME_IS_SPECIAL)) {
      // nothing more to do here
      return topLevelChange;
    }
    
    frame2 = NS_STATIC_CAST(nsIFrame*, GetFrameProperty(frame2,
                                     nsLayoutAtoms::IBSplitSpecialSibling, 0));
    frame = frame2;
  } while (frame2);
  return topLevelChange;
}


nsReStyleHint
nsFrameManager::HasAttributeDependentStyle(nsIContent *aContent,
                                           nsIAtom *aAttribute,
                                           PRInt32 aModType)
{
  nsReStyleHint hint = mStyleSet->HasAttributeDependentStyle(GetPresContext(),
                                                             aContent,
                                                             aAttribute,
                                                             aModType);

  if (aAttribute == nsHTMLAtoms::style) {
    // Perhaps should check that it's XUL, SVG, (or HTML) namespace, but
    // it doesn't really matter.  Or we could even let
    // HTMLCSSStyleSheetImpl::HasAttributeDependentStyle handle it.
    hint = nsReStyleHint(hint | eReStyle_Self);
  }

  return hint;
}

// Capture state for a given frame.
// Accept a content id here, in some cases we may not have content (scroll position)
void
nsFrameManager::CaptureFrameStateFor(nsIFrame* aFrame,
                                     nsILayoutHistoryState* aState,
                                     nsIStatefulFrame::SpecialStateID aID)
{
  if (!aFrame || !aState) {
    NS_WARNING("null frame, or state");
    return;
  }

  // Only capture state for stateful frames
  nsIStatefulFrame* statefulFrame;
  CallQueryInterface(aFrame, &statefulFrame);

  if (!statefulFrame) {
    return;
  }

  // Capture the state, exit early if we get null (nothing to save)
  nsCOMPtr<nsIPresState> frameState;
  nsresult rv = NS_OK;
  rv = statefulFrame->SaveState(GetPresContext(), getter_AddRefs(frameState));
  if (!frameState) {
    return;
  }

  // Generate the hash key to store the state under
  // Exit early if we get empty key
  nsCAutoString stateKey;
  rv = nsContentUtils::GenerateStateKey(aFrame->GetContent(), aID, stateKey);
  if(NS_FAILED(rv) || stateKey.IsEmpty()) {
    return;
  }

  // Store the state
  aState->AddState(stateKey, frameState);
}

void
nsFrameManager::CaptureFrameState(nsIFrame* aFrame,
                                  nsILayoutHistoryState* aState)
{
  NS_PRECONDITION(nsnull != aFrame && nsnull != aState, "null parameters passed in");

  CaptureFrameStateFor(aFrame, aState);

  // Now capture state recursively for the frame hierarchy rooted at aFrame
  nsIAtom*  childListName = nsnull;
  PRInt32   childListIndex = 0;
  do {    
    nsIFrame* childFrame = aFrame->GetFirstChild(childListName);
    while (childFrame) {             
      CaptureFrameState(childFrame, aState);
      // Get the next sibling child frame
      childFrame = childFrame->GetNextSibling();
    }
    childListName = aFrame->GetAdditionalChildListName(childListIndex++);
  } while (childListName);
}

// Restore state for a given frame.
// Accept a content id here, in some cases we may not have content (scroll position)
void
nsFrameManager::RestoreFrameStateFor(nsIFrame* aFrame,
                                     nsILayoutHistoryState* aState,
                                     nsIStatefulFrame::SpecialStateID aID)
{
  if (!aFrame || !aState) {
    NS_WARNING("null frame or state");
    return;
  }

  // Only capture state for stateful frames
  nsIStatefulFrame* statefulFrame;
  CallQueryInterface(aFrame, &statefulFrame);
  if (!statefulFrame) {
    return;
  }

  // Generate the hash key the state was stored under
  // Exit early if we get empty key
  nsIContent* content = aFrame->GetContent();
  // If we don't have content, we can't generate a hash
  // key and there's probably no state information for us.
  if (!content) {
    return;
  }

  nsCAutoString stateKey;
  nsresult rv = nsContentUtils::GenerateStateKey(content, aID, stateKey);
  if (NS_FAILED(rv) || stateKey.IsEmpty()) {
    return;
  }

  // Get the state from the hash
  nsCOMPtr<nsIPresState> frameState;
  rv = aState->GetState(stateKey, getter_AddRefs(frameState));
  if (!frameState) {
    return;
  }

  // Restore it
  rv = statefulFrame->RestoreState(GetPresContext(), frameState);
  if (NS_FAILED(rv)) {
    return;
  }

  // If we restore ok, remove the state from the state table
  aState->RemoveState(stateKey);
}

void
nsFrameManager::RestoreFrameState(nsIFrame* aFrame,
                                  nsILayoutHistoryState* aState)
{
  NS_PRECONDITION(nsnull != aFrame && nsnull != aState, "null parameters passed in");
  
  RestoreFrameStateFor(aFrame, aState);

  // Now restore state recursively for the frame hierarchy rooted at aFrame
  nsIAtom*  childListName = nsnull;
  PRInt32   childListIndex = 0;
  do {    
    nsIFrame* childFrame = aFrame->GetFirstChild(childListName);
    while (childFrame) {
      RestoreFrameState(childFrame, aState);
      // Get the next sibling child frame
      childFrame = childFrame->GetNextSibling();
    }
    childListName = aFrame->GetAdditionalChildListName(childListIndex++);
  } while (childListName);
}

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

static PLHashNumber
HashKey(void* key)
{
  return NS_PTR_TO_INT32(key);
}

static PRIntn
CompareKeys(void* key1, void* key2)
{
  return key1 == key2;
}

void
nsFrameManager::DestroyPropertyList(nsIPresContext* aPresContext)
{
  if (mPropertyList) {
    while (mPropertyList) {
      PropertyList* tmp = mPropertyList;

      mPropertyList = mPropertyList->mNext;
      tmp->Destroy(aPresContext);
      delete tmp;
    }
  }
}

nsFrameManagerBase::PropertyList*
nsFrameManager::GetPropertyListFor(nsIAtom* aPropertyName) const
{
  PropertyList* result;

  for (result = mPropertyList; result; result = result->mNext) {
    if (result->mName.get() == aPropertyName) {
      break;
    }
  }

  return result;
}

void
nsFrameManager::RemoveAllPropertiesFor(nsIPresContext* aPresContext,
                                       nsIFrame*       aFrame)
{
  for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
    prop->RemovePropertyForFrame(aPresContext, aFrame);
  }
}

void*
nsFrameManager::GetFrameProperty(const nsIFrame* aFrame,
                                 nsIAtom*        aPropertyName,
                                 PRUint32        aOptions,
                                 nsresult*       aResult)
{
  NS_PRECONDITION(aPropertyName && aFrame, "unexpected null param");
  nsresult rv = NS_IFRAME_MGR_PROP_NOT_THERE;
  void *propValue = nsnull;

  PropertyList* propertyList = GetPropertyListFor(aPropertyName);
  if (propertyList) {
    PropertyListMapEntry *entry = NS_STATIC_CAST(PropertyListMapEntry*,
        PL_DHashTableOperate(&propertyList->mFrameValueMap, aFrame,
                             PL_DHASH_LOOKUP));
    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
      propValue = entry->value;
      if (aOptions & NS_IFRAME_MGR_REMOVE_PROP) {
        // don't call propertyList->mDtorFunc.  That's the caller's job now.
        PL_DHashTableRawRemove(&propertyList->mFrameValueMap, entry);
      }
      rv = NS_OK;
    }
  }

  if (aResult)
    *aResult = rv;

  return propValue;
}

nsresult
nsFrameManager::SetFrameProperty(const nsIFrame*         aFrame,
                                 nsIAtom*                aPropertyName,
                                 void*                   aPropertyValue,
                                 NSFramePropertyDtorFunc aPropDtorFunc)
{
  NS_PRECONDITION(aPropertyName && aFrame, "unexpected null param");

  PropertyList* propertyList = GetPropertyListFor(aPropertyName);

  if (propertyList) {
    // Make sure the dtor function matches
    if (aPropDtorFunc != propertyList->mDtorFunc) {
      return NS_ERROR_INVALID_ARG;
    }

  } else {
    propertyList = new PropertyList(aPropertyName, aPropDtorFunc);
    if (!propertyList)
      return NS_ERROR_OUT_OF_MEMORY;
    if (!propertyList->mFrameValueMap.ops) {
      delete propertyList;
      return NS_ERROR_OUT_OF_MEMORY;
    }

    propertyList->mNext = mPropertyList;
    mPropertyList = propertyList;
  }

  // The current property value (if there is one) is replaced and the current
  // value is destroyed
  nsresult result = NS_OK;
  PropertyListMapEntry *entry = NS_STATIC_CAST(PropertyListMapEntry*,
    PL_DHashTableOperate(&propertyList->mFrameValueMap, aFrame, PL_DHASH_ADD));
  if (!entry)
    return NS_ERROR_OUT_OF_MEMORY;
  // A NULL entry->key is the sign that the entry has just been allocated
  // for us.  If it's non-NULL then we have an existing entry.
  if (entry->key && propertyList->mDtorFunc) {
    nsCOMPtr<nsIPresContext> presContext;
    mPresShell->GetPresContext(getter_AddRefs(presContext));
    propertyList->mDtorFunc(presContext, NS_CONST_CAST(nsIFrame*, entry->key),
                            aPropertyName, entry->value);
    result = NS_IFRAME_MGR_PROP_OVERWRITTEN;
  }
  entry->key = aFrame;
  entry->value = aPropertyValue;

  return result;
}

nsresult
nsFrameManager::RemoveFrameProperty(const nsIFrame* aFrame,
                                    nsIAtom*        aPropertyName)
{
  NS_PRECONDITION(aPropertyName && aFrame, "unexpected null param");

  PropertyList* propertyList = GetPropertyListFor(aPropertyName);
  if (propertyList) {
    nsCOMPtr<nsIPresContext> presContext;
    mPresShell->GetPresContext(getter_AddRefs(presContext));
    
    if (propertyList->RemovePropertyForFrame(presContext, aFrame))
      return NS_OK;
  }

  return NS_IFRAME_MGR_PROP_NOT_THERE;
}

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

MOZ_DECL_CTOR_COUNTER(UndisplayedMap)

nsFrameManagerBase::UndisplayedMap::UndisplayedMap(PRUint32 aNumBuckets)
{
  MOZ_COUNT_CTOR(nsFrameManagerBase::UndisplayedMap);
  mTable = PL_NewHashTable(aNumBuckets, (PLHashFunction)HashKey,
                           (PLHashComparator)CompareKeys,
                           (PLHashComparator)nsnull,
                           nsnull, nsnull);
  mLastLookup = nsnull;
}

nsFrameManagerBase::UndisplayedMap::~UndisplayedMap(void)
{
  MOZ_COUNT_DTOR(nsFrameManagerBase::UndisplayedMap);
  Clear();
  PL_HashTableDestroy(mTable);
}

PLHashEntry**  
nsFrameManagerBase::UndisplayedMap::GetEntryFor(nsIContent* aParentContent)
{
  if (mLastLookup && (aParentContent == (*mLastLookup)->key)) {
    return mLastLookup;
  }
  PLHashNumber hashCode = NS_PTR_TO_INT32(aParentContent);
  PLHashEntry** entry = PL_HashTableRawLookup(mTable, hashCode, aParentContent);
  if (*entry) {
    mLastLookup = entry;
  }
  return entry;
}

UndisplayedNode* 
nsFrameManagerBase::UndisplayedMap::GetFirstNode(nsIContent* aParentContent)
{
  PLHashEntry** entry = GetEntryFor(aParentContent);
  if (*entry) {
    return (UndisplayedNode*)((*entry)->value);
  }
  return nsnull;
}

void
nsFrameManagerBase::UndisplayedMap::AppendNodeFor(UndisplayedNode* aNode,
                                                  nsIContent* aParentContent)
{
  PLHashEntry** entry = GetEntryFor(aParentContent);
  if (*entry) {
    UndisplayedNode*  node = (UndisplayedNode*)((*entry)->value);
    while (node->mNext) {
      if (node->mContent == aNode->mContent) {
        // We actually need to check this in optimized builds because
        // there are some callers that do this.  See bug 118014, bug
        // 136704, etc.
        NS_NOTREACHED("node in map twice");
        delete aNode;
        return;
      }
      node = node->mNext;
    }
    node->mNext = aNode;
  }
  else {
    PLHashNumber hashCode = NS_PTR_TO_INT32(aParentContent);
    PL_HashTableRawAdd(mTable, entry, hashCode, aParentContent, aNode);
    mLastLookup = nsnull; // hashtable may have shifted bucket out from under us
  }
}

nsresult 
nsFrameManagerBase::UndisplayedMap::AddNodeFor(nsIContent* aParentContent,
                                               nsIContent* aChild, 
                                               nsStyleContext* aStyle)
{
  UndisplayedNode*  node = new UndisplayedNode(aChild, aStyle);
  if (! node) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  AppendNodeFor(node, aParentContent);
  return NS_OK;
}

void
nsFrameManagerBase::UndisplayedMap::RemoveNodeFor(nsIContent* aParentContent,
                                                  UndisplayedNode* aNode)
{
  PLHashEntry** entry = GetEntryFor(aParentContent);
  NS_ASSERTION(*entry, "content not in map");
  if (*entry) {
    if ((UndisplayedNode*)((*entry)->value) == aNode) {  // first node
      if (aNode->mNext) {
        (*entry)->value = aNode->mNext;
        aNode->mNext = nsnull;
      }
      else {
        PL_HashTableRawRemove(mTable, entry, *entry);
        mLastLookup = nsnull; // hashtable may have shifted bucket out from under us
      }
    }
    else {
      UndisplayedNode*  node = (UndisplayedNode*)((*entry)->value);
      while (node->mNext) {
        if (node->mNext == aNode) {
          node->mNext = aNode->mNext;
          aNode->mNext = nsnull;
          break;
        }
        node = node->mNext;
      }
    }
  }
  delete aNode;
}

void
nsFrameManagerBase::UndisplayedMap::RemoveNodesFor(nsIContent* aParentContent)
{
  PLHashEntry** entry = GetEntryFor(aParentContent);
  NS_ASSERTION(entry, "content not in map");
  if (*entry) {
    UndisplayedNode*  node = (UndisplayedNode*)((*entry)->value);
    NS_ASSERTION(node, "null node for non-null entry in UndisplayedMap");
    delete node;
    PL_HashTableRawRemove(mTable, entry, *entry);
    mLastLookup = nsnull; // hashtable may have shifted bucket out from under us
  }
}

PR_STATIC_CALLBACK(PRIntn)
RemoveUndisplayedEntry(PLHashEntry* he, PRIntn i, void* arg)
{
  UndisplayedNode*  node = (UndisplayedNode*)(he->value);
  delete node;
  // Remove and free this entry and continue enumerating
  return HT_ENUMERATE_REMOVE | HT_ENUMERATE_NEXT;
}

void
nsFrameManagerBase::UndisplayedMap::Clear(void)
{
  mLastLookup = nsnull;
  PL_HashTableEnumerateEntries(mTable, RemoveUndisplayedEntry, 0);
}

//----------------------------------------------------------------------
    
nsFrameManagerBase::PropertyList::PropertyList(nsIAtom*                aName,
                                               NSFramePropertyDtorFunc aDtorFunc)
  : mName(aName), mDtorFunc(aDtorFunc), mNext(nsnull)
{
  PL_DHashTableInit(&mFrameValueMap, PL_DHashGetStubOps(), this,
                    sizeof(PropertyListMapEntry), 16);
}

nsFrameManagerBase::PropertyList::~PropertyList()
{
  PL_DHashTableFinish(&mFrameValueMap);
}

PR_STATIC_CALLBACK(PLDHashOperator)
DestroyPropertyEnumerator(PLDHashTable *table, PLDHashEntryHdr *hdr,
                          PRUint32 number, void *arg)
{
  nsFrameManagerBase::PropertyList *propList =
      NS_STATIC_CAST(nsFrameManagerBase::PropertyList*, table->data);
  nsIPresContext *presContext = NS_STATIC_CAST(nsIPresContext*, arg);
  PropertyListMapEntry* entry = NS_STATIC_CAST(PropertyListMapEntry*, hdr);

  propList->mDtorFunc(presContext, NS_CONST_CAST(nsIFrame*, entry->key),
                      propList->mName, entry->value);
  return PL_DHASH_NEXT;
}

void
nsFrameManagerBase::PropertyList::Destroy(nsIPresContext* aPresContext)
{
  // Enumerate any remaining frame/value pairs and destroy the value object
  if (mDtorFunc)
    PL_DHashTableEnumerate(&mFrameValueMap, DestroyPropertyEnumerator,
                           aPresContext);
}

PRBool
nsFrameManagerBase::PropertyList::RemovePropertyForFrame(nsIPresContext* aPresContext,
                                                         const nsIFrame* aFrame)
{
  PropertyListMapEntry *entry = NS_STATIC_CAST(PropertyListMapEntry*,
      PL_DHashTableOperate(&mFrameValueMap, aFrame, PL_DHASH_LOOKUP));
  if (!PL_DHASH_ENTRY_IS_BUSY(entry))
    return PR_FALSE;

  if (mDtorFunc)
    mDtorFunc(aPresContext, NS_CONST_CAST(nsIFrame*, aFrame),
              mName, entry->value);

  PL_DHashTableRawRemove(&mFrameValueMap, entry);

  return PR_TRUE;
}
