/* -*- 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 * * 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 "nsIFrameManager.h" #include "nsIFrame.h" #include "nsIPresContext.h" #include "nsIPresShell.h" #include "nsIStyleSet.h" #include "nsIStyleContext.h" #include "nsStyleChangeList.h" #include "nsIEventQueueService.h" #include "nsIServiceManager.h" #include "nsCOMPtr.h" #include "prthread.h" #include "plhash.h" #include "pldhash.h" #include "nsPlaceholderFrame.h" #include "nsLayoutAtoms.h" #include "nsHTMLAtoms.h" #ifdef NS_DEBUG #include "nsISupportsArray.h" #include "nsIStyleRule.h" #endif #include "nsILayoutHistoryState.h" #include "nsIStatefulFrame.h" #include "nsIPresState.h" #include "nsIContent.h" #include "nsINameSpaceManager.h" #include "nsIXMLContent.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 "nsIContentList.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #ifdef DEBUG #undef NOISY_DEBUG #else #undef NOISY_DEBUG #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 { 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); nsCOMPtr content; entry->frame->GetContent(getter_AddRefs(content)); return content; // and then release it, but we know the frame still owns it :-) } PR_STATIC_CALLBACK(PRBool) PrimaryFrameMapMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, const void *key) { const PrimaryFrameMapEntry *entry = NS_STATIC_CAST(const PrimaryFrameMapEntry*, hdr); nsCOMPtr content; entry->frame->GetContent(getter_AddRefs(content)); return content == 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, nsIStyleContext* aStyle) { MOZ_COUNT_CTOR(UndisplayedNode); mContent = aContent; mStyle = aStyle; NS_ADDREF(mStyle); mNext = nsnull; } UndisplayedNode(nsIStyleContext* aPseudoStyle) { MOZ_COUNT_CTOR(UndisplayedNode); mContent = nsnull; mStyle = aPseudoStyle; NS_ADDREF(mStyle); mNext = nsnull; } ~UndisplayedNode(void) { MOZ_COUNT_DTOR(UndisplayedNode); NS_RELEASE(mStyle); if (mNext) { delete mNext; } } nsIContent* mContent; nsIStyleContext* mStyle; UndisplayedNode* mNext; }; class UndisplayedMap { public: UndisplayedMap(PRUint32 aNumBuckets = 16); ~UndisplayedMap(void); UndisplayedNode* GetFirstNode(nsIContent* aParentContent); nsresult AddNodeFor(nsIContent* aParentContent, nsIContent* aChild, nsIStyleContext* aStyle); nsresult AddNodeFor(nsIContent* aParentContent, nsIStyleContext* aPseudoStyle); nsresult RemoveNodeFor(nsIContent* aParentContent, UndisplayedNode* aNode); nsresult RemoveNodesFor(nsIContent* aParentContent); nsresult GetNodeFor(nsIContent* aContent, nsIStyleContext** aResult); // Removes all entries from the hash table void Clear(void); protected: PLHashEntry** GetEntryFor(nsIContent* aParentContent); nsresult AppendNodeFor(UndisplayedNode* aNode, nsIContent* aParentContent); PLHashTable* mTable; PLHashEntry** mLastLookup; }; //---------------------------------------------------------------------- class FrameManager; // 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(FrameManager* aFrameManager, nsIFrame* aFrame); nsIFrame* mFrame; // the frame that can't be rendered CantRenderReplacedElementEvent* mNext; // next event in the list }; class FrameManager : public nsIFrameManager { public: FrameManager(); virtual ~FrameManager(); NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW // nsISupports NS_DECL_ISUPPORTS // nsIFrameManager NS_IMETHOD Init(nsIPresShell* aPresShell, nsIStyleSet* aStyleSet); NS_IMETHOD Destroy(); // Gets and sets the root frame NS_IMETHOD GetRootFrame(nsIFrame** aRootFrame) const; NS_IMETHOD SetRootFrame(nsIFrame* aRootFrame); // Get the canvas frame: searches from the Root frame down, may be null NS_IMETHOD GetCanvasFrame(nsIPresContext* aPresContext, nsIFrame** aCanvasFrame) const; // Primary frame functions NS_IMETHOD GetPrimaryFrameFor(nsIContent* aContent, nsIFrame** aPrimaryFrame); NS_IMETHOD SetPrimaryFrameFor(nsIContent* aContent, nsIFrame* aPrimaryFrame); NS_IMETHOD ClearPrimaryFrameMap(); // Placeholder frame functions NS_IMETHOD GetPlaceholderFrameFor(nsIFrame* aFrame, nsIFrame** aPlaceholderFrame) const; NS_IMETHOD RegisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame); NS_IMETHOD UnregisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame); NS_IMETHOD ClearPlaceholderFrameMap(); // Undisplayed content functions NS_IMETHOD GetUndisplayedContent(nsIContent* aContent, nsIStyleContext** aStyleContext); NS_IMETHOD SetUndisplayedContent(nsIContent* aContent, nsIStyleContext* aStyleContext); NS_IMETHOD SetUndisplayedPseudoIn(nsIStyleContext* aPseudoContext, nsIContent* aParentContent); NS_IMETHOD ClearUndisplayedContentIn(nsIContent* aContent, nsIContent* aParentContent); NS_IMETHOD ClearAllUndisplayedContentIn(nsIContent* aParentContent); NS_IMETHOD ClearUndisplayedContentMap(); // Functions for manipulating the frame model NS_IMETHOD AppendFrames(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIFrame* aParentFrame, nsIAtom* aListName, nsIFrame* aFrameList); NS_IMETHOD InsertFrames(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIFrame* aParentFrame, nsIAtom* aListName, nsIFrame* aPrevFrame, nsIFrame* aFrameList); NS_IMETHOD RemoveFrame(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIFrame* aParentFrame, nsIAtom* aListName, nsIFrame* aOldFrame); NS_IMETHOD ReplaceFrame(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIFrame* aParentFrame, nsIAtom* aListName, nsIFrame* aOldFrame, nsIFrame* aNewFrame); NS_IMETHOD CantRenderReplacedElement(nsIPresContext* aPresContext, nsIFrame* aFrame); NS_IMETHOD NotifyDestroyingFrame(nsIFrame* aFrame); NS_IMETHOD ReParentStyleContext(nsIPresContext* aPresContext, nsIFrame* aFrame, nsIStyleContext* aNewParentContext); NS_IMETHOD ComputeStyleChangeFor(nsIPresContext* aPresContext, nsIFrame* aFrame, PRInt32 aAttrNameSpaceID, nsIAtom* aAttribute, nsStyleChangeList& aChangeList, PRInt32 aMinChange, PRInt32& aTopLevelChange); NS_IMETHOD AttributeAffectsStyle(nsIAtom *aAttribute, nsIContent *aContent, PRBool &aAffects); // Capture state from the entire frame heirarchy and store in aState NS_IMETHOD CaptureFrameState(nsIPresContext* aPresContext, nsIFrame* aFrame, nsILayoutHistoryState* aState); NS_IMETHOD RestoreFrameState(nsIPresContext* aPresContext, nsIFrame* aFrame, nsILayoutHistoryState* aState); // Add/restore state for one frame (special, global type, like scroll position) NS_IMETHOD CaptureFrameStateFor(nsIPresContext* aPresContext, nsIFrame* aFrame, nsILayoutHistoryState* aState, nsIStatefulFrame::SpecialStateID aID = nsIStatefulFrame::eNoID); NS_IMETHOD RestoreFrameStateFor(nsIPresContext* aPresContext, nsIFrame* aFrame, nsILayoutHistoryState* aState, nsIStatefulFrame::SpecialStateID aID = nsIStatefulFrame::eNoID); NS_IMETHOD GenerateStateKey(nsIContent* aContent, nsIStatefulFrame::SpecialStateID aID, nsCString& aString); // Gets and sets properties on a given frame NS_IMETHOD GetFrameProperty(nsIFrame* aFrame, nsIAtom* aPropertyName, PRUint32 aOptions, void** aPropertyValue); NS_IMETHOD SetFrameProperty(nsIFrame* aFrame, nsIAtom* aPropertyName, void* aPropertyValue, NSFMPropertyDtorFunc aPropDtorFunc); NS_IMETHOD RemoveFrameProperty(nsIFrame* aFrame, nsIAtom* aPropertyName); #ifdef NS_DEBUG NS_IMETHOD DebugVerifyStyleTree(nsIPresContext* aPresContext, nsIFrame* aFrame); #endif struct PropertyList { nsCOMPtr mName; // property name PLDHashTable mFrameValueMap; // map of frame/value pairs NSFMPropertyDtorFunc mDtorFunc; // property specific value dtor function PropertyList* mNext; PropertyList(nsIAtom* aName, NSFMPropertyDtorFunc aDtorFunc); ~PropertyList(); // Removes the property associated with the given frame, and destroys // the property value PRBool RemovePropertyForFrame(nsIPresContext* aPresContext, nsIFrame* aFrame); // Destroy all remaining properties (without removing them) void Destroy(nsIPresContext* aPresContext); }; private: nsIPresShell* mPresShell; // weak link, because the pres shell owns us nsIStyleSet* mStyleSet; // weak link. pres shell holds a reference nsIFrame* mRootFrame; PLDHashTable mPrimaryFrameMap; PLDHashTable mPlaceholderMap; UndisplayedMap* mUndisplayedMap; CantRenderReplacedElementEvent* mPostedEvents; PropertyList* mPropertyList; nsCOMPtr mHTMLForms; nsCOMPtr mHTMLFormControls; PRBool mIsDestroyingFrames; void ReResolveStyleContext(nsIPresContext* aPresContext, nsIFrame* aFrame, nsIContent* aParentContent, PRInt32 aAttrNameSpaceID, nsIAtom* aAttribute, nsStyleChangeList& aChangeList, PRInt32 aMinChange, PRInt32& aResultChange); nsresult RevokePostedEvents(); CantRenderReplacedElementEvent** FindPostedEventFor(nsIFrame* aFrame); void DequeuePostedEventFor(nsIFrame* aFrame); void DestroyPropertyList(nsIPresContext* aPresContext); PropertyList* GetPropertyListFor(nsIAtom* aPropertyName) const; void RemoveAllPropertiesFor(nsIPresContext* aPresContext, nsIFrame* aFrame); friend struct CantRenderReplacedElementEvent; static void HandlePLEvent(CantRenderReplacedElementEvent* aEvent); static void DestroyPLEvent(CantRenderReplacedElementEvent* aEvent); }; //---------------------------------------------------------------------- NS_EXPORT nsresult NS_NewFrameManager(nsIFrameManager** aInstancePtrResult) { NS_ENSURE_ARG_POINTER(aInstancePtrResult); if (!aInstancePtrResult) { return NS_ERROR_NULL_POINTER; } FrameManager* it = new FrameManager; if (!it) { return NS_ERROR_OUT_OF_MEMORY; } return it->QueryInterface(NS_GET_IID(nsIFrameManager), (void **)aInstancePtrResult); } FrameManager::FrameManager() { NS_INIT_REFCNT(); } FrameManager::~FrameManager() { NS_ASSERTION(!mPresShell, "FrameManager::Destroy never called"); } NS_IMPL_ISUPPORTS1(FrameManager, nsIFrameManager) NS_IMETHODIMP FrameManager::Init(nsIPresShell* aPresShell, nsIStyleSet* aStyleSet) { NS_ASSERTION(aPresShell, "null aPresShell"); NS_ASSERTION(aStyleSet, "null aStyleSet"); mPresShell = aPresShell; mStyleSet = aStyleSet; // Force the forms and form control content lists to be added as // document observers *before* us (pres shell) so they will be // up to date when we try to use them. nsCOMPtr document; mPresShell->GetDocument(getter_AddRefs(document)); nsCOMPtr htmlDocument(do_QueryInterface(document)); nsCOMPtr domHtmlDocument(do_QueryInterface(htmlDocument)); if (domHtmlDocument) { nsCOMPtr forms; domHtmlDocument->GetForms(getter_AddRefs(forms)); mHTMLForms = do_QueryInterface(forms); nsCOMPtr formControls; htmlDocument->GetFormControlElements(getter_AddRefs(formControls)); mHTMLFormControls = do_QueryInterface(formControls); } return NS_OK; } NS_IMETHODIMP FrameManager::Destroy() { NS_ASSERTION(mPresShell, "Frame manager already shut down."); nsCOMPtr 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. nsresult rv = RevokePostedEvents(); NS_ASSERTION(NS_SUCCEEDED(rv), "RevokePostedEvents failed: might crash"); mPresShell = nsnull; // mPresShell isn't valid anymore. We // won't use it, either, but we check it // at the start of every function so that we'll // be OK when nsIPresShell is converted to IDL. return rv; } NS_IMETHODIMP FrameManager::GetRootFrame(nsIFrame** aRootFrame) const { NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE); NS_ENSURE_ARG_POINTER(aRootFrame); *aRootFrame = mRootFrame; return NS_OK; } NS_IMETHODIMP FrameManager::SetRootFrame(nsIFrame* aRootFrame) { NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE); NS_PRECONDITION(!mRootFrame, "already have a root frame"); if (mRootFrame) { return NS_ERROR_UNEXPECTED; } mRootFrame = aRootFrame; return NS_OK; } NS_IMETHODIMP FrameManager::GetCanvasFrame(nsIPresContext* aPresContext, nsIFrame** aCanvasFrame) const { NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE); NS_PRECONDITION(aCanvasFrame, "aCanvasFrame argument cannot be null"); NS_PRECONDITION(aPresContext, "aPresContext argument cannot be null"); *aCanvasFrame = nsnull; 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) { nsCOMPtr frameType; siblingFrame->GetFrameType(getter_AddRefs(frameType)); if (frameType.get() == nsLayoutAtoms::canvasFrame) { // this is it: set the out-arg and stop looking *aCanvasFrame = siblingFrame; break; } else { siblingFrame->GetNextSibling(&siblingFrame); } } // move on to the child's child childFrame->FirstChild(aPresContext, nsnull, &childFrame); } } return NS_OK; } //---------------------------------------------------------------------- // Primary frame functions NS_IMETHODIMP FrameManager::GetPrimaryFrameFor(nsIContent* aContent, nsIFrame** aResult) { NS_ASSERTION(aResult, "null out-param not supported"); *aResult = nsnull; // initialize out param (before possibly returning due to null args/members) NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE); NS_ENSURE_ARG_POINTER(aContent); if (mIsDestroyingFrames) { #ifdef DEBUG printf("GetPrimaryFrameFor() called while FrameManager is being destroyed!\n"); #endif return NS_ERROR_FAILURE; } nsresult rv; if (mPrimaryFrameMap.ops) { PrimaryFrameMapEntry *entry = NS_STATIC_CAST(PrimaryFrameMapEntry*, PL_DHashTableOperate(&mPrimaryFrameMap, aContent, PL_DHASH_LOOKUP)); if (PL_DHASH_ENTRY_IS_BUSY(entry)) { *aResult = entry->frame; } else { // 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