/* -*- 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.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "nsContainerFrame.h" #include "nsIContent.h" #include "nsIPresContext.h" #include "nsIRenderingContext.h" #include "nsIRunaround.h" #include "nsISpaceManager.h" #include "nsIStyleContext.h" #include "nsIContentDelegate.h" #include "nsRect.h" #include "nsPoint.h" #include "nsGUIEvent.h" #include "nsStyleConsts.h" #include "nsIView.h" #include "nsVoidArray.h" #include "nsISizeOfHandler.h" #ifdef NS_DEBUG #undef NOISY #else #undef NOISY #endif nsContainerFrame::nsContainerFrame(nsIContent* aContent, nsIFrame* aParent) : nsSplittableFrame(aContent, aParent), mLastContentIsComplete(PR_TRUE) { } nsContainerFrame::~nsContainerFrame() { // Delete our child frames before doing anything else. In particular // we do all of this before our base class releases it's hold on the // view. for (nsIFrame* child = mFirstChild; child; ) { nsIFrame* nextChild; child->GetNextSibling(nextChild); child->DeleteFrame(); child = nextChild; } } NS_IMETHODIMP nsContainerFrame::SizeOf(nsISizeOfHandler* aHandler) const { aHandler->Add(sizeof(*this)); nsContainerFrame::SizeOfWithoutThis(aHandler); return NS_OK; } void nsContainerFrame::SizeOfWithoutThis(nsISizeOfHandler* aHandler) const { nsSplittableFrame::SizeOfWithoutThis(aHandler); for (nsIFrame* child = mFirstChild; child; ) { child->SizeOf(aHandler); child->GetNextSibling(child); } } void nsContainerFrame::PrepareContinuingFrame(nsIPresContext& aPresContext, nsIFrame* aParent, nsIStyleContext* aStyleContext, nsContainerFrame* aContFrame) { // Append the continuing frame to the flow aContFrame->AppendToFlow(this); // Initialize it's content offsets. Note that we assume for now that // the continuingFrame will map the remainder of the content and // that therefore mLastContentIsComplete will be true. PRInt32 nextOffset = NextChildOffset(); aContFrame->mFirstContentOffset = nextOffset; aContFrame->mLastContentOffset = nextOffset; aContFrame->mLastContentIsComplete = PR_TRUE; aContFrame->SetStyleContext(&aPresContext, aStyleContext); } NS_METHOD nsContainerFrame::CreateContinuingFrame(nsIPresContext& aPresContext, nsIFrame* aParent, nsIStyleContext* aStyleContext, nsIFrame*& aContinuingFrame) { nsIContentDelegate* contentDelegate = mContent->GetDelegate(&aPresContext); nsresult rv = contentDelegate->CreateFrame(&aPresContext, mContent, aParent, aStyleContext, aContinuingFrame); NS_RELEASE(contentDelegate); if (NS_OK == rv) { PrepareContinuingFrame(aPresContext, aParent, aStyleContext, (nsContainerFrame*)aContinuingFrame); } return rv; } NS_METHOD nsContainerFrame::DidReflow(nsIPresContext& aPresContext, nsDidReflowStatus aStatus) { NS_FRAME_TRACE_MSG(NS_FRAME_TRACE_CALLS, ("enter nsContainerFrame::DidReflow: status=%d", aStatus)); if (NS_FRAME_REFLOW_FINISHED == aStatus) { nsIFrame* kid; FirstChild(kid); while (nsnull != kid) { kid->DidReflow(aPresContext, aStatus); kid->GetNextSibling(kid); } } NS_FRAME_TRACE_OUT("nsContainerFrame::DidReflow"); // Let nsFrame position and size our view (if we have one), and clear // the NS_FRAME_IN_REFLOW bit return nsFrame::DidReflow(aPresContext, aStatus); } ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration NS_METHOD nsContainerFrame::ChildCount(PRInt32& aChildCount) const { aChildCount = mChildCount; return NS_OK; } NS_METHOD nsContainerFrame::ChildAt(PRInt32 aIndex, nsIFrame*& aFrame) const { // Check that the index is in range if ((aIndex < 0) || (aIndex >= mChildCount)) { aFrame = nsnull; return NS_OK; } aFrame = mFirstChild; while ((aIndex-- > 0) && (aFrame != nsnull)) { aFrame->GetNextSibling(aFrame); } return NS_OK; } NS_METHOD nsContainerFrame::IndexOf(const nsIFrame* aChild, PRInt32& aIndex) const { aIndex = -1; // initialize out parameter for (nsIFrame* f = mFirstChild; f != nsnull; f->GetNextSibling(f)) { aIndex++; if (f == aChild) break; } return NS_OK; } NS_METHOD nsContainerFrame::FirstChild(nsIFrame*& aFirstChild) const { aFirstChild = mFirstChild; return NS_OK; } NS_METHOD nsContainerFrame::NextChild(const nsIFrame* aChild, nsIFrame*& aNextChild) const { NS_PRECONDITION(aChild != nsnull, "null pointer"); aChild->GetNextSibling(aNextChild); return NS_OK; } NS_METHOD nsContainerFrame::PrevChild(const nsIFrame* aChild, nsIFrame*& aPrevChild) const { NS_PRECONDITION(aChild != nsnull, "null pointer"); if (mFirstChild == aChild) { aPrevChild = nsnull; } else { aPrevChild = mFirstChild; while ((aPrevChild != nsnull)) { nsIFrame* nextChild; aPrevChild->GetNextSibling(nextChild); if (nextChild == aChild) { break; } aPrevChild = nextChild; } } return NS_OK; } NS_METHOD nsContainerFrame::LastChild(nsIFrame*& aLastChild) const { aLastChild = mFirstChild; if (nsnull != aLastChild) { nsIFrame* nextChild; aLastChild->GetNextSibling(nextChild); while (nextChild != nsnull) { aLastChild = nextChild; aLastChild->GetNextSibling(nextChild); } } return NS_OK; } ///////////////////////////////////////////////////////////////////////////// // Painting NS_METHOD nsContainerFrame::Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { PaintChildren(aPresContext, aRenderingContext, aDirtyRect); return NS_OK; } // aDirtyRect is in our coordinate system // child rect's are also in our coordinate system void nsContainerFrame::PaintChildren(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { // Set clip rect so that children don't leak out of us const nsStyleDisplay* disp = (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); PRBool hidden = PR_FALSE; if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) { aRenderingContext.PushState(); aRenderingContext.SetClipRect(nsRect(0, 0, mRect.width, mRect.height), nsClipCombine_kIntersect); hidden = PR_TRUE; } // XXX reminder: use the coordinates in the dirty rect to figure out // which set of children are impacted and only do the intersection // work for them. In addition, stop when we no longer overlap. nsIFrame* kid = mFirstChild; while (nsnull != kid) { nsIView *pView; kid->GetView(pView); if (nsnull == pView) { nsRect kidRect; kid->GetRect(kidRect); nsRect damageArea; #ifdef NS_DEBUG PRBool overlap = PR_FALSE; if (nsIFrame::GetShowFrameBorders() && ((kidRect.width == 0) || (kidRect.height == 0))) { nscoord xmost = aDirtyRect.XMost(); nscoord ymost = aDirtyRect.YMost(); if ((aDirtyRect.x <= kidRect.x) && (kidRect.x < xmost) && (aDirtyRect.y <= kidRect.y) && (kidRect.y < ymost)) { overlap = PR_TRUE; } } else { overlap = damageArea.IntersectRect(aDirtyRect, kidRect); } #else PRBool overlap = damageArea.IntersectRect(aDirtyRect, kidRect); #endif if (overlap) { // Translate damage area into kid's coordinate system nsRect kidDamageArea(damageArea.x - kidRect.x, damageArea.y - kidRect.y, damageArea.width, damageArea.height); aRenderingContext.PushState(); aRenderingContext.Translate(kidRect.x, kidRect.y); kid->Paint(aPresContext, aRenderingContext, kidDamageArea); #ifdef NS_DEBUG if (nsIFrame::GetShowFrameBorders() && (0 != kidRect.width) && (0 != kidRect.height)) { nsIView* view; GetView(view); if (nsnull != view) { aRenderingContext.SetColor(NS_RGB(0,0,255)); } else { aRenderingContext.SetColor(NS_RGB(255,0,0)); } aRenderingContext.DrawRect(0, 0, kidRect.width, kidRect.height); } #endif aRenderingContext.PopState(); } } kid->GetNextSibling(kid); } if (hidden) { aRenderingContext.PopState(); } } ///////////////////////////////////////////////////////////////////////////// // Events NS_METHOD nsContainerFrame::HandleEvent(nsIPresContext& aPresContext, nsGUIEvent* aEvent, nsEventStatus& aEventStatus) { aEventStatus = nsEventStatus_eIgnore; nsIFrame* kid; FirstChild(kid); while (nsnull != kid) { nsRect kidRect; kid->GetRect(kidRect); if (kidRect.Contains(aEvent->point)) { aEvent->point.MoveBy(-kidRect.x, -kidRect.y); kid->HandleEvent(aPresContext, aEvent, aEventStatus); aEvent->point.MoveBy(kidRect.x, kidRect.y); break; } kid->GetNextSibling(kid); } return NS_OK; } NS_METHOD nsContainerFrame::GetCursorAndContentAt(nsIPresContext& aPresContext, const nsPoint& aPoint, nsIFrame** aFrame, nsIContent** aContent, PRInt32& aCursor) { aCursor = NS_STYLE_CURSOR_INHERIT; *aContent = mContent; nsIFrame* kid; FirstChild(kid); nsPoint tmp; while (nsnull != kid) { nsRect kidRect; kid->GetRect(kidRect); if (kidRect.Contains(aPoint)) { tmp.MoveTo(aPoint.x - kidRect.x, aPoint.y - kidRect.y); kid->GetCursorAndContentAt(aPresContext, tmp, aFrame, aContent, aCursor); break; } kid->GetNextSibling(kid); } return NS_OK; } ///////////////////////////////////////////////////////////////////////////// // Managing content mapping // Get the offset for the next child content, i.e. the child after the // last child that fit in us PRInt32 nsContainerFrame::NextChildOffset() const { PRInt32 result; if (mChildCount > 0) { result = mLastContentOffset; if (mLastContentIsComplete) { result++; } } else { result = mFirstContentOffset; } return result; } /** * Sets the first content offset based on the first child frame. */ void nsContainerFrame::SetFirstContentOffset(const nsIFrame* aFirstChild) { NS_PRECONDITION(nsnull != aFirstChild, "bad argument"); NS_PRECONDITION(IsChild(aFirstChild), "bad geometric parent"); if (ChildIsPseudoFrame(aFirstChild)) { nsContainerFrame* pseudoFrame = (nsContainerFrame*)aFirstChild; mFirstContentOffset = pseudoFrame->mFirstContentOffset; } else { // XXX TROY Change API to pass in content index if possible... aFirstChild->GetContentIndex(mFirstContentOffset); } } /** * Sets the last content offset based on the last child frame. If the last * child is a pseudo frame then it sets mLastContentIsComplete to be the same * as the last child's mLastContentIsComplete */ void nsContainerFrame::SetLastContentOffset(const nsIFrame* aLastChild) { NS_PRECONDITION(nsnull != aLastChild, "bad argument"); NS_PRECONDITION(IsChild(aLastChild), "bad geometric parent"); if (ChildIsPseudoFrame(aLastChild)) { nsContainerFrame* pseudoFrame = (nsContainerFrame*)aLastChild; mLastContentOffset = pseudoFrame->mLastContentOffset; #ifdef NS_DEBUG pseudoFrame->VerifyLastIsComplete(); #endif mLastContentIsComplete = pseudoFrame->mLastContentIsComplete; } else { // XXX TROY Change API to pass in content index if possible... aLastChild->GetContentIndex(mLastContentOffset); } #ifdef NS_DEBUG if (mLastContentOffset < mFirstContentOffset) { DumpTree(); } #endif NS_ASSERTION(mLastContentOffset >= mFirstContentOffset, "unexpected content mapping"); } ///////////////////////////////////////////////////////////////////////////// // Pseudo frame related // Returns true if this frame is being used a pseudo frame PRBool nsContainerFrame::IsPseudoFrame() const { PRBool result = PR_FALSE; if (nsnull != mGeometricParent) { nsIContent* parentContent; mGeometricParent->GetContent(parentContent); if (parentContent == mContent) { result = PR_TRUE; } NS_RELEASE(parentContent); } return result; } // Returns true if aChild is being used as a pseudo frame PRBool nsContainerFrame::ChildIsPseudoFrame(const nsIFrame* aChild) const { nsIContent* childContent; PRBool result; aChild->GetContent(childContent); result = childContent == mContent; NS_RELEASE(childContent); return result; } ///////////////////////////////////////////////////////////////////////////// // Helper member functions /** * Reflow a child frame and return the status of the reflow. If the * child is complete and it has next-in-flows (it was a splittable child) * then delete the next-in-flows. */ nsReflowStatus nsContainerFrame::ReflowChild(nsIFrame* aKidFrame, nsIPresContext* aPresContext, nsReflowMetrics& aDesiredSize, const nsReflowState& aReflowState) { nsReflowStatus status; NS_PRECONDITION(aReflowState.frame == aKidFrame, "bad reflow state"); #ifdef NS_DEBUG nsFrameState kidFrameState; aKidFrame->GetFrameState(kidFrameState); NS_ASSERTION(kidFrameState & NS_FRAME_IN_REFLOW, "kid frame is not in reflow"); #endif aKidFrame->Reflow(*aPresContext, aDesiredSize, aReflowState, status); if (NS_FRAME_IS_COMPLETE(status)) { nsIFrame* kidNextInFlow; aKidFrame->GetNextInFlow(kidNextInFlow); if (nsnull != kidNextInFlow) { // Remove all of the childs next-in-flows. Make sure that we ask // the right parent to do the removal (it's possible that the // parent is not this because we are executing pullup code) nsIFrame* parent; aKidFrame->GetGeometricParent(parent); ((nsContainerFrame*)parent)->DeleteChildsNextInFlow(aKidFrame); } } return status; } /** * Remove and delete aChild's next-in-flow(s). Updates the sibling and flow * pointers * * Updates the child count and content offsets of all containers that are * affected * * @param aChild child this child's next-in-flow * @return PR_TRUE if successful and PR_FALSE otherwise */ PRBool nsContainerFrame::DeleteChildsNextInFlow(nsIFrame* aChild) { NS_PRECONDITION(IsChild(aChild), "bad geometric parent"); nsIFrame* nextInFlow; nsContainerFrame* parent; aChild->GetNextInFlow(nextInFlow); NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow"); nextInFlow->GetGeometricParent((nsIFrame*&)parent); // If the next-in-flow has a next-in-flow then delete it, too (and // delete it first). nsIFrame* nextNextInFlow; nextInFlow->GetNextInFlow(nextNextInFlow); if (nsnull != nextNextInFlow) { parent->DeleteChildsNextInFlow(nextInFlow); } #ifdef NS_DEBUG PRInt32 childCount; nsIFrame* firstChild; nextInFlow->ChildCount(childCount); nextInFlow->FirstChild(firstChild); if ((0 != childCount) || (nsnull != firstChild)) { nsIFrame* top = nextInFlow; for (;;) { nsIFrame* parent; top->GetGeometricParent(parent); if (nsnull == parent) { break; } top = parent; } top->List(); } NS_ASSERTION((0 == childCount) && (nsnull == firstChild), "deleting !empty next-in-flow"); #endif // Disconnect the next-in-flow from the flow list nextInFlow->BreakFromPrevFlow(); // Take the next-in-flow out of the parent's child list if (parent->mFirstChild == nextInFlow) { nextInFlow->GetNextSibling(parent->mFirstChild); if (nsnull != parent->mFirstChild) { parent->SetFirstContentOffset(parent->mFirstChild); if (parent->IsPseudoFrame()) { // Tell the parent's parent to update its content offsets nsContainerFrame* pp = (nsContainerFrame*) parent->mGeometricParent; pp->PropagateContentOffsets(parent, parent->mFirstContentOffset, parent->mLastContentOffset, parent->mLastContentIsComplete); } } // When a parent loses it's last child and that last child is a // pseudo-frame then the parent's content offsets are now wrong. // However, we know that the parent will eventually be reflowed // in one of two ways: it will either get a chance to pullup // children or it will be deleted because it's prev-in-flow // (e.g. this) is complete. In either case, the content offsets // will be repaired. } else { nsIFrame* nextSibling; // Because the next-in-flow is not the first child of the parent // we know that it shares a parent with aChild. Therefore, we need // to capture the next-in-flow's next sibling (in case the // next-in-flow is the last next-in-flow for aChild AND the // next-in-flow is not the last child in parent) NS_ASSERTION(((nsContainerFrame*)parent)->IsChild(aChild), "screwy flow"); aChild->GetNextSibling(nextSibling); NS_ASSERTION(nextSibling == nextInFlow, "unexpected sibling"); nextInFlow->GetNextSibling(nextSibling); aChild->SetNextSibling(nextSibling); } // Delete the next-in-flow frame and adjust its parents child count WillDeleteNextInFlowFrame(nextInFlow); nextInFlow->DeleteFrame(); parent->mChildCount--; #ifdef NS_DEBUG aChild->GetNextInFlow(nextInFlow); NS_POSTCONDITION(nsnull == nextInFlow, "non null next-in-flow"); #endif return PR_TRUE; } void nsContainerFrame::WillDeleteNextInFlowFrame(nsIFrame* aNextInFlow) { } void nsContainerFrame::PropagateContentOffsets(nsIFrame* aChild, PRInt32 aFirstContentOffset, PRInt32 aLastContentOffset, PRBool aLastContentIsComplete) { NS_PRECONDITION(ChildIsPseudoFrame(aChild), "not a pseudo frame"); // First update our offsets if (mFirstChild == aChild) { mFirstContentOffset = aFirstContentOffset; } nsIFrame* lastChild; LastChild(lastChild); if (lastChild == aChild) { mLastContentOffset = aLastContentOffset; mLastContentIsComplete = aLastContentIsComplete; } // If we are a pseudo-frame then we need to update our parent if (IsPseudoFrame()) { nsContainerFrame* parent = (nsContainerFrame*) mGeometricParent; parent->PropagateContentOffsets(this, mFirstContentOffset, mLastContentOffset, mLastContentIsComplete); } } /** * Push aFromChild and its next siblings to the next-in-flow. Change the * geometric parent of each frame that's pushed. If there is no next-in-flow * the frames are placed on the overflow list (and the geometric parent is * left unchanged). * * Updates the next-in-flow's child count and content offsets. Does * not update the pusher's child count or last content offset. * * @param aFromChild the first child frame to push. It is disconnected from * aPrevSibling * @param aPrevSibling aFromChild's previous sibling. Must not be null. It's * an error to push a parent's first child frame * @param aNextInFlowsLastChildIsComplete the next-in-flow's * mLastContentIsComplete flag. This is used when refilling an * empty next-in-flow that was drained by the caller. * */ // Note: we cannot VerifyLastIsComplete here because the caller is // responsible for setting it. void nsContainerFrame::PushChildren(nsIFrame* aFromChild, nsIFrame* aPrevSibling, PRBool aNextInFlowsLastContentIsComplete) { NS_PRECONDITION(nsnull != aFromChild, "null pointer"); NS_PRECONDITION(nsnull != aPrevSibling, "pushing first child"); #ifdef NS_DEBUG nsIFrame* prevNextSibling; aPrevSibling->GetNextSibling(prevNextSibling); NS_PRECONDITION(prevNextSibling == aFromChild, "bad prev sibling"); #endif // Disconnect aFromChild from its previous sibling aPrevSibling->SetNextSibling(nsnull); // Do we have a next-in-flow? nsContainerFrame* nextInFlow = (nsContainerFrame*)mNextInFlow; if (nsnull != nextInFlow) { PRInt32 numChildren = 0; nsIFrame* lastChild = nsnull; #ifdef NOISY ListTag(stdout); printf(": pushing kids (childCount=%d)\n", mChildCount); { nsContainerFrame* flow = (nsContainerFrame*) mNextInFlow; while (flow != 0) { printf(" %p: [%d,%d,%c]\n", flow, flow->mFirstContentOffset, flow->mLastContentOffset, (flow->mLastContentIsComplete ? 'T' : 'F')); flow = (nsContainerFrame*) flow->mNextInFlow; } } #endif // Compute the number of children being pushed, and for each child change // its geometric parent. Remember the last child for (nsIFrame* f = aFromChild; nsnull != f; f->GetNextSibling(f)) { numChildren++; #ifdef NOISY printf(" "); ((nsFrame*)f)->ListTag(stdout); printf("\n"); #endif lastChild = f; f->SetGeometricParent(nextInFlow); nsIFrame* contentParent; f->GetContentParent(contentParent); if (this == contentParent) { f->SetContentParent(nextInFlow); } } NS_ASSERTION(numChildren > 0, "no children to push"); // Prepend the frames to our next-in-flow's child list lastChild->SetNextSibling(nextInFlow->mFirstChild); nextInFlow->mFirstChild = aFromChild; // Update our next-in-flow's first content offset and child count nextInFlow->SetFirstContentOffset(aFromChild); if (0 == nextInFlow->mChildCount) { nextInFlow->SetLastContentOffset(lastChild); // If the child is pseudo-frame then SetLastContentOffset will // have updated the next-in-flow's mLastContentIsComplete flag, // otherwise we have to do it. if (!nextInFlow->ChildIsPseudoFrame(lastChild)) { nsIFrame* lastChildNextInFlow; lastChild->GetNextInFlow(lastChildNextInFlow); nextInFlow->mLastContentIsComplete = (nsnull == lastChildNextInFlow); } } nextInFlow->mChildCount += numChildren; // If the next-in-flow is being used as a pseudo frame then we need // to propagate the content offsets upwards to its parent frame if (nextInFlow->IsPseudoFrame()) { nsContainerFrame* parent = (nsContainerFrame*) nextInFlow->mGeometricParent; parent->PropagateContentOffsets(nextInFlow, nextInFlow->mFirstContentOffset, nextInFlow->mLastContentOffset, nextInFlow->mLastContentIsComplete); } #ifdef NOISY ListTag(stdout); printf(": push kids done (childCount=%d)\n", mChildCount); { nsContainerFrame* flow = (nsContainerFrame*) mNextInFlow; while (flow != 0) { printf(" %p: [%d,%d,%c]\n", flow, flow->mFirstContentOffset, flow->mLastContentOffset, (flow->mLastContentIsComplete ? 'T' : 'F')); flow = (nsContainerFrame*) flow->mNextInFlow; } } #endif } else { // Add the frames to our overflow list NS_ASSERTION(nsnull == mOverflowList, "bad overflow list"); #ifdef NOISY ListTag(stdout); printf(": pushing kids to my overflow list\n"); #endif mOverflowList = aFromChild; } } /** * Moves any frames on the overflwo lists (the prev-in-flow's overflow list and * the receiver's overflow list) to the child list. * * Updates this frame's child count and content mapping. * * @return PR_TRUE if any frames were moved and PR_FALSE otherwise */ PRBool nsContainerFrame::MoveOverflowToChildList() { PRBool result = PR_FALSE; // Check for an overflow list with our prev-in-flow nsContainerFrame* prevInFlow = (nsContainerFrame*)mPrevInFlow; if (nsnull != prevInFlow) { if (nsnull != prevInFlow->mOverflowList) { NS_ASSERTION(nsnull == mFirstChild, "bad overflow list"); AppendChildren(prevInFlow->mOverflowList); prevInFlow->mOverflowList = nsnull; result = PR_TRUE; } } // It's also possible that we have an overflow list for ourselves if (nsnull != mOverflowList) { NS_ASSERTION(nsnull != mFirstChild, "overflow list but no mapped children"); AppendChildren(mOverflowList, PR_FALSE); mOverflowList = nsnull; result = PR_TRUE; } return result; } /** * Append child list starting at aChild to this frame's child list. Used for * processing of the overflow list. * * Updates this frame's child count and content mapping. * * @param aChild the beginning of the child list * @param aSetParent if true each child's geometric (and content parent if * they're the same) parent is set to this frame. */ void nsContainerFrame::AppendChildren(nsIFrame* aChild, PRBool aSetParent) { // Link the frames into our child frame list if (nsnull == mFirstChild) { // We have no children so aChild becomes the first child mFirstChild = aChild; } else { nsIFrame* lastChild; LastChild(lastChild); lastChild->SetNextSibling(aChild); } // Update our child count and last content offset nsIFrame* lastChild; for (nsIFrame* f = aChild; nsnull != f; f->GetNextSibling(f)) { lastChild = f; mChildCount++; // Reset the geometric parent if requested if (aSetParent) { nsIFrame* geometricParent; nsIFrame* contentParent; f->GetGeometricParent(geometricParent); f->GetContentParent(contentParent); if (contentParent == geometricParent) { f->SetContentParent(this); } f->SetGeometricParent(this); } } // Update our content mapping if (mFirstChild == aChild) { SetFirstContentOffset(mFirstChild); } SetLastContentOffset(lastChild); if (!ChildIsPseudoFrame(lastChild)) { nsIFrame* nextInFlow; lastChild->GetNextInFlow(nextInFlow); mLastContentIsComplete = (nsnull == nextInFlow); } #ifdef NS_DEBUG VerifyLastIsComplete(); #endif } /** * Called after pulling-up children from the next-in-flow. Adjusts the first * content offset of all the empty next-in-flows * * It's an error to call this function if all of the next-in-flow frames * are empty. * * @return PR_TRUE if successful and PR_FALSE if all the next-in-flows are * empty */ void nsContainerFrame::AdjustOffsetOfEmptyNextInFlows() { // If the first next-in-flow is not empty then the caller drained // more than one next-in-flow and then pushed back into it's // immediate next-in-flow. nsContainerFrame* nextInFlow = (nsContainerFrame*)mNextInFlow; PRInt32 nextOffset; if ((nsnull != nextInFlow) && (nsnull != nextInFlow->mFirstChild)) { // Use the offset from this frame's first next-in-flow (because // it's not empty) to propagate into the next next-in-flow (and // onward if necessary) nextOffset = nextInFlow->NextChildOffset(); // Use next next-in-flow nextInFlow = (nsContainerFrame*) nextInFlow->mNextInFlow; // When this happens, there *must* be a next next-in-flow and the // next next-in-flow must be empty. NS_ASSERTION(nsnull != nextInFlow, "bad adjust offsets"); NS_ASSERTION(nsnull == nextInFlow->mFirstChild, "bad adjust offsets"); } else { // Use our offset (since our next-in-flow is empty) to propagate // into our empty next-in-flows nextOffset = NextChildOffset(); } while (nsnull != nextInFlow) { if (nsnull == nextInFlow->mFirstChild) { NS_ASSERTION(nextInFlow->IsEmpty(), "bad state"); nextInFlow->mFirstContentOffset = nextOffset; // If the next-in-flow is a pseudo-frame then we need to have it // update it's parents offsets too. if (nextInFlow->IsPseudoFrame()) { nsContainerFrame* parent = (nsContainerFrame*) nextInFlow->mGeometricParent; parent->PropagateContentOffsets(nextInFlow, nextInFlow->mFirstContentOffset, nextInFlow->mLastContentOffset, nextInFlow->mLastContentIsComplete); } } else { // We found a non-empty frame. Verify that its first content offset // is correct NS_ASSERTION(nextInFlow->mFirstContentOffset == nextOffset, "bad first content offset"); return; } nextInFlow = (nsContainerFrame*)nextInFlow->mNextInFlow; } // Make sure that all the next-in-flows weren't empty NS_ASSERTION(nsnull != ((nsContainerFrame*)mNextInFlow)->mFirstChild, "Every next-in-flow is empty!"); } ///////////////////////////////////////////////////////////////////////////// // Debugging NS_METHOD nsContainerFrame::List(FILE* out, PRInt32 aIndent) const { // Indent for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); // Output the tag ListTag(out); nsIView* view; GetView(view); if (nsnull != view) { fprintf(out, " [view=%p]", view); } // Output the first/last content offset fprintf(out, "[%d,%d,%c] ", mFirstContentOffset, mLastContentOffset, (mLastContentIsComplete ? 'T' : 'F')); if (nsnull != mPrevInFlow) { fprintf(out, "prev-in-flow=%p ", mPrevInFlow); } if (nsnull != mNextInFlow) { fprintf(out, "next-in-flow=%p ", mNextInFlow); } // Output the rect out << mRect; // Output the children if (mChildCount > 0) { if (0 != mState) { fprintf(out, " [state=%08x]", mState); } fputs("<\n", out); for (nsIFrame* child = mFirstChild; child; NextChild(child, child)) { child->List(out, aIndent + 1); } for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); fputs(">\n", out); } else { if (0 != mState) { fprintf(out, " [state=%08x]", mState); } fputs("<>\n", out); } return NS_OK; } NS_METHOD nsContainerFrame::ListTag(FILE* out) const { if ((nsnull != mGeometricParent) && IsPseudoFrame()) { fputs("*", out); } nsFrame::ListTag(out); return NS_OK; } #define VERIFY_ASSERT(_expr, _msg) \ if (!(_expr)) { \ DumpTree(); \ } \ NS_ASSERTION(_expr, _msg) NS_METHOD nsContainerFrame::VerifyTree() const { #ifdef NS_DEBUG NS_ASSERTION(0 == (mState & NS_FRAME_IN_REFLOW), "frame is in reflow"); // Check our child count PRInt32 len = LengthOf(mFirstChild); VERIFY_ASSERT(len == mChildCount, "bad child count"); nsIFrame* lastChild; LastChild(lastChild); if (len != 0) { VERIFY_ASSERT(nsnull != lastChild, "bad last child"); } VERIFY_ASSERT(nsnull == mOverflowList, "bad overflow list"); // Make sure our content offsets are sane VERIFY_ASSERT(mFirstContentOffset <= mLastContentOffset, "bad offsets"); // Verify child content offsets and index-in-parents PRInt32 offset = mFirstContentOffset; nsIFrame* child = mFirstChild; while (nsnull != child) { // Make sure that the child's tree is valid child->VerifyTree(); // Make sure child's index-in-parent is correct if (ChildIsPseudoFrame(child)) { nsContainerFrame* pseudo = (nsContainerFrame*) child; if (pseudo == mFirstChild) { VERIFY_ASSERT(pseudo->mFirstContentOffset == offset, "bad pseudo first content offset"); } if (pseudo == lastChild) { VERIFY_ASSERT(pseudo->mFirstContentOffset == offset, "bad pseudo first content offset"); VERIFY_ASSERT(pseudo->mLastContentOffset == mLastContentOffset, "bad pseudo last content offset"); VERIFY_ASSERT(pseudo->mLastContentIsComplete == mLastContentIsComplete, "bad pseudo last content is complete"); } offset = pseudo->mLastContentOffset; if (pseudo->mLastContentIsComplete) { offset++; } } else { PRInt32 indexInParent; child->GetContentIndex(indexInParent); VERIFY_ASSERT(offset == indexInParent, "bad child offset"); nsIFrame* nextInFlow; child->GetNextInFlow(nextInFlow); if (nsnull == nextInFlow) { offset++; } } child->GetNextSibling(child); } // Verify that our last content offset is correct if (0 != mChildCount) { if (mLastContentIsComplete) { VERIFY_ASSERT(offset == mLastContentOffset + 1, "bad last offset"); } else { VERIFY_ASSERT(offset == mLastContentOffset, "bad last offset"); } } // Make sure that our flow blocks offsets are all correct if (nsnull == mPrevInFlow) { PRInt32 nextOffset = NextChildOffset(); nsContainerFrame* nextInFlow = (nsContainerFrame*) mNextInFlow; while (nsnull != nextInFlow) { VERIFY_ASSERT(0 != nextInFlow->mChildCount, "empty next-in-flow"); VERIFY_ASSERT(nextInFlow->GetFirstContentOffset() == nextOffset, "bad next-in-flow first offset"); nextOffset = nextInFlow->NextChildOffset(); nextInFlow = (nsContainerFrame*) nextInFlow->mNextInFlow; } } #endif return NS_OK; } PRInt32 nsContainerFrame::LengthOf(nsIFrame* aFrame) { PRInt32 result = 0; while (nsnull != aFrame) { result++; aFrame->GetNextSibling(aFrame); } return result; } ///////////////////////////////////////////////////////////////////////////// #ifdef NS_DEBUG PRBool nsContainerFrame::IsChild(const nsIFrame* aChild) const { // Check the geometric parent nsIFrame* parent; aChild->GetGeometricParent(parent); if (parent != (nsIFrame*)this) { return PR_FALSE; } // XXX Turn back on once all frame code has been changed to always add // a child frame to the sibling list before reflowing the child. Right now // that's not true for inline and column when reflowing unmapped children... #if 0 // Check that aChild is in our sibling list PRInt32 index; IndexOf(aChild, index); if (-1 == index) { return PR_FALSE; } #endif return PR_TRUE; } PRBool nsContainerFrame::IsLastChild(const nsIFrame* aChild) const { // Check the geometric parent nsIFrame* parent; aChild->GetGeometricParent(parent); if (parent != (nsIFrame*)this) { return PR_FALSE; } // Check that aChild is in our sibling list nsIFrame* lastChild; LastChild(lastChild); if (lastChild != aChild) { return PR_FALSE; } return PR_TRUE; } void nsContainerFrame::DumpTree() const { nsIFrame* root = (nsIFrame*)this; nsIFrame* parent = mGeometricParent; while (nsnull != parent) { root = parent; parent->GetGeometricParent(parent); } root->List(); } void nsContainerFrame::CheckContentOffsets() { NS_PRECONDITION(nsnull != mFirstChild, "null first child"); // Verify that our first content offset is correct if (ChildIsPseudoFrame(mFirstChild)) { nsContainerFrame* pseudoFrame = (nsContainerFrame*)mFirstChild; if (pseudoFrame->GetFirstContentOffset() != mFirstContentOffset) { DumpTree(); } NS_ASSERTION(pseudoFrame->GetFirstContentOffset() == mFirstContentOffset, "bad first content offset"); } else { PRInt32 indexInParent; mFirstChild->GetContentIndex(indexInParent); if (indexInParent != mFirstContentOffset) { DumpTree(); } NS_ASSERTION(indexInParent == mFirstContentOffset, "bad first content offset"); } // Verify that our last content offset is correct nsIFrame* lastChild; LastChild(lastChild); if (ChildIsPseudoFrame(lastChild)) { nsContainerFrame* pseudoFrame = (nsContainerFrame*)lastChild; NS_ASSERTION(pseudoFrame->GetLastContentOffset() == mLastContentOffset, "bad last content offset"); } else { PRInt32 indexInParent; lastChild->GetContentIndex(indexInParent); NS_ASSERTION(indexInParent == mLastContentOffset, "bad last content offset"); } } void nsContainerFrame::PreReflowCheck() { PRInt32 len = LengthOf(mFirstChild); NS_ASSERTION(len == mChildCount, "bad child count"); if (0 == mChildCount) { NS_ASSERTION(nsnull == mFirstChild, "bad child-count/first-child"); } else { NS_ASSERTION(nsnull != mFirstChild, "bad child-count/first-child"); CheckContentOffsets(); } VerifyLastIsComplete(); } void nsContainerFrame::PostReflowCheck(nsReflowStatus aStatus) { PRInt32 len = LengthOf(mFirstChild) ; NS_ASSERTION(len == mChildCount, "bad child count"); if (0 == mChildCount) { NS_ASSERTION(nsnull == mFirstChild, "bad child-count/first-child"); } else { NS_ASSERTION(nsnull != mFirstChild, "bad child-count/first-child"); CheckContentOffsets(); } VerifyLastIsComplete(); } /** * A container is empty if it has no children, or it has exactly one * child and that child is a pseudo-frame and it's empty (recursively * applied if that child contains one child which is a * pseudo-frame...) */ PRBool nsContainerFrame::IsEmpty() { if (0 == mChildCount) { return PR_TRUE; } if (mChildCount > 1) { return PR_FALSE; } if (ChildIsPseudoFrame(mFirstChild)) { return ((nsContainerFrame*)mFirstChild)->IsEmpty(); } return PR_FALSE; } void nsContainerFrame::CheckNextInFlowOffsets() { // We have to be in a safe state before we can even consider // checking our next-in-flows state. if (!SafeToCheckLastContentOffset(this)) { return; } PRInt32 nextOffset = NextChildOffset(); nsContainerFrame* nextInFlow = (nsContainerFrame*)mNextInFlow; while (nsnull != nextInFlow) { if (!SafeToCheckLastContentOffset(nextInFlow)) { return; } #ifdef NS_DEBUG if (nextInFlow->GetFirstContentOffset() != nextOffset) { DumpTree(); } #endif NS_ASSERTION(nextInFlow->GetFirstContentOffset() == nextOffset, "bad next-in-flow first offset"); // Verify the content offsets are in sync with the first/last child nextInFlow->CheckContentOffsets(); nextOffset = nextInFlow->NextChildOffset(); // Move to the next-in-flow nextInFlow = (nsContainerFrame*)nextInFlow->mNextInFlow; } } PRBool nsContainerFrame::SafeToCheckLastContentOffset(nsContainerFrame* aContainer) { if (0 == aContainer->mChildCount) { // We can't use the last content offset on an empty frame return PR_FALSE; } if (nsnull != aContainer->mOverflowList) { // If a frame has an overflow list then it's last content offset // cannot be used for assertion checks (because it's not done // being reflowed). return PR_FALSE; } nsIFrame* lastChild; aContainer->LastChild(lastChild); if (aContainer->ChildIsPseudoFrame(lastChild)) { // If the containers last child is a pseudo-frame then the // containers last content offset is determined by the child. Ask // the child if it's safe to use the last content offset. return SafeToCheckLastContentOffset((nsContainerFrame*)lastChild); } return PR_TRUE; } void nsContainerFrame::VerifyLastIsComplete() const { if (nsnull == mFirstChild) { // If we have no children, our mLastContentIsComplete doesn't mean // anything return; } if (nsnull != mOverflowList) { // If we can an overflow list then our mLastContentIsComplete and // mLastContentOffset are un-verifyable because a reflow is in // process. return; } nsIFrame* lastKid; LastChild(lastKid); if (ChildIsPseudoFrame(lastKid)) { // When my last child is a pseudo-frame it means that our // mLastContentIsComplete is a copy of it's. nsContainerFrame* pseudoFrame = (nsContainerFrame*) lastKid; NS_ASSERTION(mLastContentIsComplete == pseudoFrame->mLastContentIsComplete, "bad mLastContentIsComplete"); } else { // If my last child has a next-in-flow then our // mLastContentIsComplete must be false (because our last child is // obviously not complete) nsIFrame* lastKidNextInFlow; lastKid->GetNextInFlow(lastKidNextInFlow); if (nsnull != lastKidNextInFlow) { if (mLastContentIsComplete) { DumpTree(); } NS_ASSERTION(mLastContentIsComplete == PR_FALSE, "bad mLastContentIsComplete"); } else { // We don't know what state our mLastContentIsComplete should be in. } } } #endif