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
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -360,6 +360,96 @@ nsLayoutUtils::DoCompareTreePosition(nsIContent* aContent1,
|
||||
return index1 - index2;
|
||||
}
|
||||
|
||||
static nsIFrame* FillAncestors(nsIFrame* aFrame,
|
||||
nsIFrame* aStopAtAncestor, nsFrameManager* aFrameManager,
|
||||
nsTArray<nsIFrame*>* 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<nsIFrame*,20> 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<nsIFrame*,20> 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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user