gecko-dev/layout/base/nsLayoutUtils.cpp

330 lines
10 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* 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):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsLayoutUtils.h"
#include "nsIFrame.h"
#include "nsIPresContext.h"
#include "nsIContent.h"
#include "nsFrameList.h"
#include "nsLayoutAtoms.h"
#include "nsIAtom.h"
#include "nsCSSPseudoElements.h"
#include "nsIView.h"
/**
* A namespace class for static layout utilities.
*/
/**
* GetFirstChildFrame returns the first "real" child frame of a
* given frame. It will descend down into pseudo-frames (unless the
* pseudo-frame is the :before generated frame).
* @param aPresContext the prescontext
* @param aFrame the frame
* @param aFrame the frame's content node
*/
static nsIFrame*
GetFirstChildFrame(nsIPresContext* aPresContext,
nsIFrame* aFrame,
nsIContent* aContent)
{
NS_PRECONDITION(aFrame, "NULL frame pointer");
// Get the first child frame
nsIFrame* childFrame = aFrame->GetFirstChild(nsnull);
// If the child frame is a pseudo-frame, then return its first child.
// Note that the frame we create for the generated content is also a
// pseudo-frame and so don't drill down in that case
if (childFrame &&
childFrame->IsPseudoFrame(aContent) &&
!childFrame->IsGeneratedContentFrame()) {
return GetFirstChildFrame(aPresContext, childFrame, aContent);
}
return childFrame;
}
/**
* GetLastChildFrame returns the last "real" child frame of a
* given frame. It will descend down into pseudo-frames (unless the
* pseudo-frame is the :after generated frame).
* @param aPresContext the prescontext
* @param aFrame the frame
* @param aFrame the frame's content node
*/
static nsIFrame*
GetLastChildFrame(nsIPresContext* aPresContext,
nsIFrame* aFrame,
nsIContent* aContent)
{
NS_PRECONDITION(aFrame, "NULL frame pointer");
// Get the last in flow frame
nsIFrame* lastInFlow = aFrame->GetLastInFlow();
// Get the last child frame
nsIFrame* firstChildFrame = lastInFlow->GetFirstChild(nsnull);
if (firstChildFrame) {
nsFrameList frameList(firstChildFrame);
nsIFrame* lastChildFrame = frameList.LastChild();
NS_ASSERTION(lastChildFrame, "unexpected error");
// Get the frame's first-in-flow. This matters in case the frame has
// been continuted across multiple lines
lastChildFrame = lastChildFrame->GetFirstInFlow();
// If the last child frame is a pseudo-frame, then return its last child.
// Note that the frame we create for the generated content is also a
// pseudo-frame and so don't drill down in that case
if (lastChildFrame &&
lastChildFrame->IsPseudoFrame(aContent) &&
!lastChildFrame->IsGeneratedContentFrame()) {
return GetLastChildFrame(aPresContext, lastChildFrame, aContent);
}
return lastChildFrame;
}
return nsnull;
}
// static
nsIFrame*
nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame, nsIPresContext* aPresContext)
{
NS_PRECONDITION(aFrame, "NULL frame pointer");
#ifdef DEBUG
nsIFrame* prevInFlow = nsnull;
aFrame->GetPrevInFlow(&prevInFlow);
NS_ASSERTION(!prevInFlow, "aFrame must be first-in-flow");
#endif
nsIFrame* firstFrame = GetFirstChildFrame(aPresContext, aFrame, aFrame->GetContent());
if (firstFrame && IsGeneratedContentFor(nsnull, firstFrame,
nsCSSPseudoElements::before)) {
return firstFrame;
}
return nsnull;
}
// static
nsIFrame*
nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame, nsIPresContext* aPresContext)
{
NS_PRECONDITION(aFrame, "NULL frame pointer");
nsIFrame* lastFrame = GetLastChildFrame(aPresContext, aFrame, aFrame->GetContent());
if (lastFrame && IsGeneratedContentFor(nsnull, lastFrame,
nsCSSPseudoElements::after)) {
return lastFrame;
}
return nsnull;
}
// static
nsIFrame*
nsLayoutUtils::GetPageFrame(nsIFrame* aFrame)
{
for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
if (frame->GetType() == nsLayoutAtoms::pageFrame) {
return frame;
}
}
return nsnull;
}
// static
PRBool
nsLayoutUtils::IsGeneratedContentFor(nsIContent* aContent,
nsIFrame* aFrame,
nsIAtom* aPseudoElement)
{
NS_PRECONDITION(aFrame, "Must have a frame");
NS_PRECONDITION(aPseudoElement, "Must have a pseudo name");
if (!aFrame->IsGeneratedContentFrame()) {
return PR_FALSE;
}
if (aContent && aFrame->GetContent() != aContent) {
return PR_FALSE;
}
return aFrame->GetStyleContext()->GetPseudoType() == aPseudoElement;
}
// static
PRBool
nsLayoutUtils::IsProperAncestorFrame(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
nsIFrame* aCommonAncestor)
{
if (aFrame == aCommonAncestor) {
return PR_FALSE;
}
nsIFrame* parentFrame = aFrame->GetParent();
while (parentFrame != aCommonAncestor) {
if (parentFrame == aAncestorFrame) {
return PR_TRUE;
}
parentFrame = parentFrame->GetParent();
}
return PR_FALSE;
}
// static
PRInt32
nsLayoutUtils::CompareTreePosition(nsIContent* aContent1, nsIContent* aContent2,
nsIContent* aCommonAncestor) {
NS_PRECONDITION(aContent1, "aContent1 must not be null");
NS_PRECONDITION(aContent2, "aContent2 must not be null");
nsAutoVoidArray content1Ancestors;
nsIContent* c1;
for (c1 = aContent1; c1 && c1 != aCommonAncestor; c1 = c1->GetParent()) {
content1Ancestors.AppendElement(c1);
}
if (!c1 && aCommonAncestor) {
// So, it turns out aCommonAncestor was not an ancestor of c1. Oops.
// Never mind. We can continue as if aCommonAncestor was null.
aCommonAncestor = nsnull;
}
nsAutoVoidArray content2Ancestors;
nsIContent* c2;
for (c2 = aContent2; c2 && c2 != aCommonAncestor; c2 = c2->GetParent()) {
content2Ancestors.AppendElement(c2);
}
if (!c2 && aCommonAncestor) {
// So, it turns out aCommonAncestor was not an ancestor of c2.
// We need to retry with no common ancestor hint.
return CompareTreePosition(aContent1, aContent2, nsnull);
}
int last1 = content1Ancestors.Count() - 1;
int last2 = content2Ancestors.Count() - 1;
nsIContent* content1Ancestor = nsnull;
nsIContent* content2Ancestor = nsnull;
while (last1 >= 0 && last2 >= 0
&& ((content1Ancestor = NS_STATIC_CAST(nsIContent*, content1Ancestors.ElementAt(last1)))
== (content2Ancestor = NS_STATIC_CAST(nsIContent*, content2Ancestors.ElementAt(last2))))) {
last1--;
last2--;
}
if (last1 < 0) {
if (last2 < 0) {
NS_ASSERTION(aContent1 == aContent2, "internal error?");
return 0;
} else {
// aContent1 is an ancestor of aContent2
return -1;
}
} else {
if (last2 < 0) {
// aContent2 is an ancestor of aContent1
return 1;
} else {
// content1Ancestor != content2Ancestor, so they must be siblings with the same parent
nsIContent* parent = content1Ancestor->GetParent();
NS_ASSERTION(parent, "no common ancestor at all???");
if (!parent) { // different documents??
return 0;
}
PRInt32 index1 = parent->IndexOf(content1Ancestor);
PRInt32 index2 = parent->IndexOf(content2Ancestor);
if (index1 < 0 || index2 < 0) {
// one of them must be anonymous; we can't determine the order
return 0;
}
return index1 - index2;
}
}
}
// static
nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) {
if (!aFrame) {
return nsnull;
}
nsIFrame* next;
while ((next = aFrame->GetNextSibling()) != nsnull) {
aFrame = next;
}
return aFrame;
}
// static
nsIView*
nsLayoutUtils::FindSiblingViewFor(nsIView* aParentView, nsIFrame* aFrame) {
nsIFrame* parentViewFrame = NS_STATIC_CAST(nsIFrame*, aParentView->GetClientData());
nsIContent* parentViewContent = parentViewFrame ? parentViewFrame->GetContent() : nsnull;
for (nsIView* insertBefore = aParentView->GetFirstChild(); insertBefore;
insertBefore = insertBefore->GetNextSibling()) {
nsIFrame* f = NS_STATIC_CAST(nsIFrame*, insertBefore->GetClientData());
if (!f) {
// this view could be some anonymous view attached to a meaningful parent
for (nsIView* searchView = insertBefore->GetParent(); searchView;
searchView = searchView->GetParent()) {
f = NS_STATIC_CAST(nsIFrame*, searchView->GetClientData());
if (f) {
break;
}
}
NS_ASSERTION(f, "Can't find a frame anywhere!");
}
if (f && CompareTreePosition(aFrame->GetContent(),
f->GetContent(), parentViewContent) > 0) {
// aFrame's content is after f's content, so put our view before f's view
return insertBefore;
}
}
return nsnull;
}