From 633e4df4a014316d9d9c170bd35a47d2347c8a7e Mon Sep 17 00:00:00 2001 From: "roc+%cs.cmu.edu" Date: Wed, 17 Jan 2007 22:48:24 +0000 Subject: [PATCH] Bug 363813. When inserting frames into a list of out-of-flow frames, finding the correct position by comparing positions in the content tree doesn't always work, in particular when the frames are for anonymous content. So compare the positions of placeholders in the frame tree, which should always work (if we're careful). r+sr=dbaron git-svn-id: svn://10.0.0.236/trunk@218557 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/layout/base/nsCSSFrameConstructor.cpp | 23 +++-- mozilla/layout/base/nsLayoutUtils.cpp | 90 +++++++++++++++++++ mozilla/layout/base/nsLayoutUtils.h | 40 ++++++++- 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/mozilla/layout/base/nsCSSFrameConstructor.cpp b/mozilla/layout/base/nsCSSFrameConstructor.cpp index 879b1c4979b..68617e143a5 100644 --- a/mozilla/layout/base/nsCSSFrameConstructor.cpp +++ b/mozilla/layout/base/nsCSSFrameConstructor.cpp @@ -1232,9 +1232,17 @@ nsFrameConstructorState::nsFrameConstructorState(nsIPresShell* aPresShell, nsFrameConstructorState::~nsFrameConstructorState() { + // Frame order comparison functions only work properly when the placeholders + // have been inserted into the frame tree. So for example if we have a new float + // containing the placeholder for a new abs-pos frame, and we process the abs-pos + // insertion first, then we won't be able to find the right place to insert in + // in the abs-pos list. So put floats in first, because they can contain placeholders + // for abs-pos and fixed-pos items whose containing blocks are outside the floats. + // Then put abs-pos frames in, because they can contain placeholders for fixed-pos + // items whose containing block is outside the abs-pos frames. + ProcessFrameInsertions(mFloatedItems, nsGkAtoms::floatList); ProcessFrameInsertions(mAbsoluteItems, nsGkAtoms::absoluteList); ProcessFrameInsertions(mFixedItems, nsGkAtoms::fixedList); - ProcessFrameInsertions(mFloatedItems, nsGkAtoms::floatList); } static nsIFrame* @@ -1473,19 +1481,20 @@ nsFrameConstructorState::ProcessFrameInsertions(nsAbsoluteItems& aFrameItems, // So first test the last child of the containing block nsIFrame* lastChild = nsLayoutUtils::GetLastSibling(firstChild); + // CompareTreePosition uses placeholder hierarchy for out of flow frames, + // so this will make out-of-flows respect the ordering of placeholders, + // which is great because it takes care of anonymous content. if (!lastChild || - nsLayoutUtils::CompareTreePosition(lastChild->GetContent(), - firstNewFrame->GetContent(), - containingBlock->GetContent()) < 0) { + nsLayoutUtils::CompareTreePosition(lastChild, firstNewFrame, containingBlock) < 0) { // no lastChild, or lastChild comes before the new children, so just append rv = containingBlock->AppendFrames(aChildListName, firstNewFrame); } else { nsIFrame* insertionPoint = nsnull; // try the other children for (nsIFrame* f = firstChild; f != lastChild; f = f->GetNextSibling()) { - if (nsLayoutUtils::CompareTreePosition(f->GetContent(), - firstNewFrame->GetContent(), - containingBlock->GetContent()) > 0) { + PRInt32 compare = + nsLayoutUtils::CompareTreePosition(f, firstNewFrame, containingBlock); + if (compare > 0) { // f comes after the new children, so stop here and insert after // the previous frame break; diff --git a/mozilla/layout/base/nsLayoutUtils.cpp b/mozilla/layout/base/nsLayoutUtils.cpp index e4a1c33da0e..f9d86682987 100644 --- a/mozilla/layout/base/nsLayoutUtils.cpp +++ b/mozilla/layout/base/nsLayoutUtils.cpp @@ -360,6 +360,96 @@ nsLayoutUtils::DoCompareTreePosition(nsIContent* aContent1, return index1 - index2; } +static nsIFrame* FillAncestors(nsIFrame* aFrame, + nsIFrame* aStopAtAncestor, nsFrameManager* aFrameManager, + nsTArray* aAncestors) +{ + while (aFrame && aFrame != aStopAtAncestor) { + aAncestors->AppendElement(aFrame); + aFrame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrameManager, aFrame); + } + return aFrame; +} + +// Return true if aFrame1 is after aFrame2 +static PRBool IsFrameAfter(nsIFrame* aFrame1, nsIFrame* aFrame2) +{ + nsIFrame* f = aFrame2; + do { + f = f->GetNextSibling(); + if (f == aFrame1) + return PR_TRUE; + } while (f); + return PR_FALSE; +} + +// static +PRInt32 +nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1, + nsIFrame* aFrame2, + PRInt32 aIf1Ancestor, + PRInt32 aIf2Ancestor, + nsIFrame* aCommonAncestor) +{ + NS_PRECONDITION(aFrame1, "aFrame1 must not be null"); + NS_PRECONDITION(aFrame2, "aFrame2 must not be null"); + + nsPresContext* presContext = aFrame1->GetPresContext(); + if (presContext != aFrame2->GetPresContext()) { + NS_ERROR("no common ancestor at all, different documents"); + return 0; + } + nsFrameManager* frameManager = presContext->PresShell()->FrameManager(); + + nsAutoTArray frame1Ancestors; + if (!FillAncestors(aFrame1, aCommonAncestor, frameManager, &frame1Ancestors)) { + // We reached the root of the frame tree ... if aCommonAncestor was set, + // it is wrong + aCommonAncestor = nsnull; + } + + nsAutoTArray frame2Ancestors; + if (!FillAncestors(aFrame2, aCommonAncestor, frameManager, &frame2Ancestors) && + aCommonAncestor) { + // We reached the root of the frame tree ... aCommonAncestor was wrong. + // Try again with no hint. + return DoCompareTreePosition(aFrame1, aFrame2, + aIf1Ancestor, aIf2Ancestor, nsnull); + } + + PRInt32 last1 = PRInt32(frame1Ancestors.Length()) - 1; + PRInt32 last2 = PRInt32(frame2Ancestors.Length()) - 1; + while (last1 >= 0 && last2 >= 0 && + frame1Ancestors[last1] == frame2Ancestors[last2]) { + last1--; + last2--; + } + + if (last1 < 0) { + if (last2 < 0) { + NS_ASSERTION(aFrame1 == aFrame2, "internal error?"); + return 0; + } + // aFrame1 is an ancestor of aFrame2 + return aIf1Ancestor; + } + + if (last2 < 0) { + // aFrame2 is an ancestor of aFrame1 + return aIf2Ancestor; + } + + nsIFrame* ancestor1 = frame1Ancestors[last1]; + nsIFrame* ancestor2 = frame2Ancestors[last2]; + // Now we should be able to walk sibling chains to find which one is first + if (IsFrameAfter(ancestor2, ancestor1)) + return -1; + if (IsFrameAfter(ancestor1, ancestor2)) + return 1; + NS_WARNING("Frames were in different child lists???"); + return 0; +} + // static nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) { if (!aFrame) { diff --git a/mozilla/layout/base/nsLayoutUtils.h b/mozilla/layout/base/nsLayoutUtils.h index 58311336f5b..0f1a77612ae 100644 --- a/mozilla/layout/base/nsLayoutUtils.h +++ b/mozilla/layout/base/nsLayoutUtils.h @@ -144,7 +144,45 @@ public: PRInt32 aIf1Ancestor, PRInt32 aIf2Ancestor, nsIContent* aCommonAncestor = nsnull); - + + /** + * CompareTreePosition determines whether aFrame1 comes before or + * after aFrame2 in a preorder traversal of the frame tree, where out + * of flow frames are treated as children of their placeholders. This is + * basically the same ordering as DoCompareTreePosition(nsIContent*) except + * that it handles anonymous content properly and there are subtleties with + * continuations. + * + * @param aCommonAncestor either null, or a common ancestor of + * aContent1 and aContent2. Actually this is + * only a hint; if it's not an ancestor of + * aContent1 or aContent2, this function will + * still work, but it will be slower than + * normal. + * @return < 0 if aContent1 is before aContent2 + * > 0 if aContent1 is after aContent2, + * 0 otherwise (meaning they're the same, or they're in + * different frame trees) + */ + static PRInt32 CompareTreePosition(nsIFrame* aFrame1, + nsIFrame* aFrame2, + nsIFrame* aCommonAncestor = nsnull) + { + return DoCompareTreePosition(aFrame1, aFrame2, -1, 1, aCommonAncestor); + } + + /* + * More generic version of |CompareTreePosition|. |aIf1Ancestor| + * gives the value to return when 1 is an ancestor of 2, and likewise + * for |aIf2Ancestor|. Passing (-1, 1) gives preorder traversal + * order, and (1, -1) gives postorder traversal order. + */ + static PRInt32 DoCompareTreePosition(nsIFrame* aFrame1, + nsIFrame* aFrame2, + PRInt32 aIf1Ancestor, + PRInt32 aIf2Ancestor, + nsIFrame* aCommonAncestor = nsnull); + /** * GetLastSibling simply finds the last sibling of aFrame, or returns nsnull if * aFrame is null.