gecko-dev/layout/generic/nsInlineFrame.cpp
bzbarsky%mit.edu d44c8e2932 Change mCBReflowState to mean the containing block of _this_ reflow state
instead of the containing block of kids.  Fix the containing block calculations
for various cases of absolutely positioned descendants of relatively positioned
inlines (eg make "right" and "bottom" work in those situations).  Bug 135082,
r+sr=dbaron
2004-04-24 17:56:23 +00:00

1318 lines
44 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* 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 Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of 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 MPL, 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 MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsCOMPtr.h"
#include "nsInlineFrame.h"
#include "nsBlockFrame.h"
#include "nsHTMLAtoms.h"
#include "nsHTMLParts.h"
#include "nsStyleContext.h"
#include "nsIPresShell.h"
#include "nsIPresContext.h"
#include "nsIRenderingContext.h"
#include "nsIFontMetrics.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsLayoutAtoms.h"
#include "nsCSSAnonBoxes.h"
#include "nsReflowPath.h"
#include "nsAutoPtr.h"
#include "nsFrameManager.h"
#ifdef ACCESSIBILITY
#include "nsIServiceManager.h"
#include "nsIAccessibilityService.h"
#endif
#ifdef DEBUG
#undef NOISY_PUSHING
#endif
NS_DEFINE_IID(kInlineFrameCID, NS_INLINE_FRAME_CID);
//////////////////////////////////////////////////////////////////////
// Basic nsInlineFrame methods
nsresult
NS_NewInlineFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsInlineFrame* it = new (aPresShell) nsInlineFrame;
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aNewFrame = it;
return NS_OK;
}
nsInlineFrame::nsInlineFrame()
{
}
NS_IMETHODIMP
nsInlineFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if (nsnull == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if (aIID.Equals(kInlineFrameCID)) {
nsInlineFrame* tmp = this;
*aInstancePtr = (void*) tmp;
return NS_OK;
}
return nsInlineFrameSuper::QueryInterface(aIID, aInstancePtr);
}
#ifdef DEBUG
NS_IMETHODIMP
nsInlineFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("Inline"), aResult);
}
#endif
nsIAtom*
nsInlineFrame::GetType() const
{
return nsLayoutAtoms::inlineFrame;
}
inline PRBool
IsBorderZero(nsStyleUnit aUnit, nsStyleCoord &aCoord)
{
return ((aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0));
}
inline PRBool
IsPaddingZero(nsStyleUnit aUnit, nsStyleCoord &aCoord)
{
return (aUnit == eStyleUnit_Null ||
(aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
(aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
}
inline PRBool
IsMarginZero(nsStyleUnit aUnit, nsStyleCoord &aCoord)
{
return (aUnit == eStyleUnit_Null ||
aUnit == eStyleUnit_Auto ||
(aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
(aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
}
/* virtual */ PRBool
nsInlineFrame::IsEmpty()
{
#if 0
// I used to think inline frames worked this way, but it seems they
// don't. At least not in our codebase.
if (GetPresContext()->CompatibilityMode() == eCompatibility_FullStandards) {
return PR_FALSE;
}
#endif
const nsStyleMargin* margin = GetStyleMargin();
const nsStyleBorder* border = GetStyleBorder();
const nsStylePadding* padding = GetStylePadding();
nsStyleCoord coord;
// XXX Top and bottom removed, since they shouldn't affect things, but this
// doesn't really match with nsLineLayout.cpp's setting of
// ZeroEffectiveSpanBox, anymore, so what should this really be?
if ((border->IsBorderSideVisible(NS_SIDE_RIGHT) &&
!IsBorderZero(border->mBorder.GetRightUnit(),
border->mBorder.GetRight(coord))) ||
(border->IsBorderSideVisible(NS_SIDE_LEFT) &&
!IsBorderZero(border->mBorder.GetLeftUnit(),
border->mBorder.GetLeft(coord))) ||
!IsPaddingZero(padding->mPadding.GetRightUnit(),
padding->mPadding.GetRight(coord)) ||
!IsPaddingZero(padding->mPadding.GetLeftUnit(),
padding->mPadding.GetLeft(coord)) ||
!IsMarginZero(margin->mMargin.GetRightUnit(),
margin->mMargin.GetRight(coord)) ||
!IsMarginZero(margin->mMargin.GetLeftUnit(),
margin->mMargin.GetLeft(coord))) {
return PR_FALSE;
}
for (nsIFrame *kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
if (!kid->IsEmpty())
return PR_FALSE;
}
return PR_TRUE;
}
NS_IMETHODIMP
nsInlineFrame::AppendFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aFrameList)
{
if (nsnull != aListName) {
return NS_ERROR_INVALID_ARG;
}
if (aFrameList) {
mFrames.AppendFrames(this, aFrameList);
// Ask the parent frame to reflow me.
ReflowDirtyChild(&aPresShell, nsnull);
}
return NS_OK;
}
NS_IMETHODIMP
nsInlineFrame::InsertFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsIFrame* aFrameList)
{
if (nsnull != aListName) {
#ifdef IBMBIDI
if (aListName != nsLayoutAtoms::nextBidi)
#endif
return NS_ERROR_INVALID_ARG;
}
if (aFrameList) {
// Insert frames after aPrevFrame
mFrames.InsertFrames(this, aPrevFrame, aFrameList);
#ifdef IBMBIDI
if (nsnull == aListName)
#endif
// Ask the parent frame to reflow me.
ReflowDirtyChild(&aPresShell, nsnull);
}
return NS_OK;
}
NS_IMETHODIMP
nsInlineFrame::RemoveFrame(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame)
{
if (nsnull != aListName) {
#ifdef IBMBIDI
if (nsLayoutAtoms::nextBidi != aListName)
#endif
return NS_ERROR_INVALID_ARG;
}
if (aOldFrame) {
// Loop and destroy the frame and all of its continuations.
// If the frame we are removing is a brFrame, we need a reflow so
// the line the brFrame was on can attempt to pull up any frames
// that can fit from lines below it.
PRBool generateReflowCommand =
aOldFrame->GetType() == nsLayoutAtoms::brFrame;
nsInlineFrame* parent = NS_STATIC_CAST(nsInlineFrame*, aOldFrame->GetParent());
while (aOldFrame) {
#ifdef IBMBIDI
if (nsLayoutAtoms::nextBidi != aListName) {
#endif
// If the frame being removed has zero size then don't bother
// generating a reflow command, otherwise make sure we do.
nsRect bbox = aOldFrame->GetRect();
if (bbox.width || bbox.height) {
generateReflowCommand = PR_TRUE;
}
#ifdef IBMBIDI
}
#endif
// When the parent is an inline frame we have a simple task - just
// remove the frame from its parents list and generate a reflow
// command.
nsIFrame* oldFrameNextInFlow;
aOldFrame->GetNextInFlow(&oldFrameNextInFlow);
parent->mFrames.DestroyFrame(aPresContext, aOldFrame);
aOldFrame = oldFrameNextInFlow;
if (aOldFrame) {
parent = NS_STATIC_CAST(nsInlineFrame*, aOldFrame->GetParent());
}
}
if (generateReflowCommand) {
// Ask the parent frame to reflow me.
ReflowDirtyChild(&aPresShell, nsnull);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsInlineFrame::ReplaceFrame(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame,
nsIFrame* aNewFrame)
{
if (aListName) {
NS_ERROR("Don't have any special lists on inline frames!");
return NS_ERROR_INVALID_ARG;
}
if (!aOldFrame || !aNewFrame) {
NS_ERROR("Missing aOldFrame or aNewFrame");
return NS_ERROR_INVALID_ARG;
}
PRBool retval =
mFrames.ReplaceFrame(aPresContext, this, aOldFrame, aNewFrame, PR_TRUE);
// Ask the parent frame to reflow me.
ReflowDirtyChild(&aPresShell, nsnull);
return retval ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsInlineFrame::Paint(nsIPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer,
PRUint32 aFlags)
{
if (NS_FRAME_IS_UNFLOWABLE & mState) {
return NS_OK;
}
// Paint inline element backgrounds in the foreground layer (bug 36710).
if (aWhichLayer == NS_FRAME_PAINT_LAYER_FOREGROUND) {
PaintSelf(aPresContext, aRenderingContext, aDirtyRect);
}
// The sole purpose of this is to trigger display of the selection
// window for Named Anchors, which don't have any children and
// normally don't have any size, but in Editor we use CSS to display
// an image to represent this "hidden" element.
if (!mFrames.FirstChild()) {
nsFrame::Paint(aPresContext, aRenderingContext, aDirtyRect,
aWhichLayer, aFlags);
}
PaintDecorationsAndChildren(aPresContext, aRenderingContext,
aDirtyRect, aWhichLayer, PR_FALSE,
aFlags);
return NS_OK;
}
//////////////////////////////////////////////////////////////////////
// Reflow methods
NS_IMETHODIMP
nsInlineFrame::Reflow(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aMetrics,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
DO_GLOBAL_REFLOW_COUNT("nsInlineFrame", aReflowState.reason);
DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
if (nsnull == aReflowState.mLineLayout) {
return NS_ERROR_INVALID_ARG;
}
PRBool lazilySetParentPointer = PR_FALSE;
// Check for an overflow list with our prev-in-flow
nsInlineFrame* prevInFlow = (nsInlineFrame*)mPrevInFlow;
if (nsnull != prevInFlow) {
nsIFrame* prevOverflowFrames = prevInFlow->GetOverflowFrames(aPresContext, PR_TRUE);
if (prevOverflowFrames) {
// When pushing and pulling frames we need to check for whether any
// views need to be reparented.
nsHTMLContainerFrame::ReparentFrameViewList(aPresContext, prevOverflowFrames,
prevInFlow, this);
if (aReflowState.reason == eReflowReason_Initial) {
// If it's the initial reflow, then our child list must be empty, so
// just set the child list rather than calling InsertFrame(). This avoids
// having to get the last child frame in the list.
// Note that we don't set the parent pointer for the new frames. Instead wait
// to do this until we actually reflow the frame. If the overflow list contains
// thousands of frames this is a big performance issue (see bug #5588)
NS_ASSERTION(mFrames.IsEmpty(), "child list is not empty for initial reflow");
mFrames.SetFrames(prevOverflowFrames);
lazilySetParentPointer = PR_TRUE;
} else {
// Insert the new frames at the beginning of the child list
// and set their parent pointer
mFrames.InsertFrames(this, nsnull, prevOverflowFrames);
}
}
}
// It's also possible that we have an overflow list for ourselves
#ifdef DEBUG
if (aReflowState.reason == eReflowReason_Initial) {
// If it's our initial reflow, then we should not have an overflow list.
// However, add an assertion in case we get reflowed more than once with
// the initial reflow reason
nsIFrame* overflowFrames = GetOverflowFrames(aPresContext, PR_FALSE);
NS_ASSERTION(!overflowFrames, "overflow list is not empty for initial reflow");
}
#endif
if (aReflowState.reason != eReflowReason_Initial) {
nsIFrame* overflowFrames = GetOverflowFrames(aPresContext, PR_TRUE);
if (overflowFrames) {
NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
// Because we lazily set the parent pointer of child frames we get from
// our prev-in-flow's overflow list, it's possible that we have not set
// the parent pointer for these frames. Check the first frame to see, and
// if we haven't set the parent pointer then set it now
mFrames.AppendFrames(overflowFrames->GetParent() == this ? nsnull : this, overflowFrames);
}
}
if (IsFrameTreeTooDeep(aReflowState, aMetrics)) {
#ifdef DEBUG_kipp
{
extern char* nsPresShell_ReflowStackPointerTop;
char marker;
char* newsp = (char*) ▮
printf("XXX: frame tree is too deep; approx stack size = %d\n",
nsPresShell_ReflowStackPointerTop - newsp);
}
#endif
aStatus = NS_FRAME_COMPLETE;
return NS_OK;
}
// Set our own reflow state (additional state above and beyond
// aReflowState)
InlineReflowState irs;
irs.mPrevFrame = nsnull;
irs.mNextInFlow = (nsInlineFrame*) mNextInFlow;
irs.mSetParentPointer = lazilySetParentPointer;
nsresult rv;
if (mFrames.IsEmpty()) {
// Try to pull over one frame before starting so that we know
// whether we have an anonymous block or not.
PRBool complete;
(void) PullOneFrame(aPresContext, irs, &complete);
}
rv = ReflowFrames(aPresContext, aReflowState, irs, aMetrics, aStatus);
// Note: the line layout code will properly compute our
// NS_FRAME_OUTSIDE_CHILDREN state for us.
NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
return rv;
}
NS_IMETHODIMP
nsInlineFrame::CanContinueTextRun(PRBool& aContinueTextRun) const
{
// We can continue a text run through an inline frame
aContinueTextRun = PR_TRUE;
return NS_OK;
}
NS_IMETHODIMP
nsInlineFrame::ReflowDirtyChild(nsIPresShell* aPresShell, nsIFrame* aChild)
{
// The inline container frame does not handle the reflow
// request. It passes it up to its parent container.
// If you don't already have dirty children,
if (!(mState & NS_FRAME_HAS_DIRTY_CHILDREN)) {
if (mParent) {
// Record that you are dirty and have dirty children
mState |= NS_FRAME_IS_DIRTY;
mState |= NS_FRAME_HAS_DIRTY_CHILDREN;
// Pass the reflow request up to the parent
mParent->ReflowDirtyChild(aPresShell, this);
}
else {
NS_ERROR("No parent to pass the reflow request up to.");
}
}
return NS_OK;
}
nsresult
nsInlineFrame::ReflowFrames(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
InlineReflowState& irs,
nsHTMLReflowMetrics& aMetrics,
nsReflowStatus& aStatus)
{
nsresult rv = NS_OK;
aStatus = NS_FRAME_COMPLETE;
nsLineLayout* lineLayout = aReflowState.mLineLayout;
nscoord leftEdge = 0;
if (nsnull == mPrevInFlow) {
leftEdge = aReflowState.mComputedBorderPadding.left;
}
nscoord availableWidth = aReflowState.availableWidth;
if (NS_UNCONSTRAINEDSIZE != availableWidth) {
// Subtract off left and right border+padding from availableWidth
availableWidth -= leftEdge;
availableWidth -= aReflowState.mComputedBorderPadding.right;
availableWidth = PR_MAX(0, availableWidth);
}
lineLayout->BeginSpan(this, &aReflowState, leftEdge, leftEdge + availableWidth);
// First reflow our current children
nsIFrame* frame = mFrames.FirstChild();
PRBool done = PR_FALSE;
while (nsnull != frame) {
PRBool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK();
// Check if we should lazily set the child frame's parent pointer
if (irs.mSetParentPointer) {
frame->SetParent(this);
// We also need to check if frame has a next-in-flow. It it does, then set
// its parent frame pointer, too. Otherwise, if we reflow frame and it's
// complete we'll fail when deleting its next-in-flow which is no longer
// needed. This scenario doesn't happen often, but it can happen
nsIFrame* nextInFlow;
frame->GetNextInFlow(&nextInFlow);
while (nextInFlow) {
// Since we only do lazy setting of parent pointers for the frame's
// initial reflow, this frame can't have a next-in-flow. That means
// the continuing child frame must be in our child list as well. If
// not, then something is wrong
NS_ASSERTION(mFrames.ContainsFrame(nextInFlow), "unexpected flow");
nextInFlow->SetParent(this);
nextInFlow->GetNextInFlow(&nextInFlow);
}
}
rv = ReflowInlineFrame(aPresContext, aReflowState, irs, frame, aStatus);
if (NS_FAILED(rv)) {
done = PR_TRUE;
break;
}
if (NS_INLINE_IS_BREAK(aStatus) ||
(!reflowingFirstLetter && NS_FRAME_IS_NOT_COMPLETE(aStatus))) {
done = PR_TRUE;
break;
}
irs.mPrevFrame = frame;
frame = frame->GetNextSibling();
}
// Attempt to pull frames from our next-in-flow until we can't
if (!done && (nsnull != mNextInFlow)) {
while (!done) {
PRBool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK();
PRBool isComplete;
frame = PullOneFrame(aPresContext, irs, &isComplete);
#ifdef NOISY_PUSHING
printf("%p pulled up %p\n", this, frame);
#endif
if (nsnull == frame) {
if (!isComplete) {
aStatus = NS_FRAME_NOT_COMPLETE;
}
break;
}
rv = ReflowInlineFrame(aPresContext, aReflowState, irs, frame, aStatus);
if (NS_FAILED(rv)) {
done = PR_TRUE;
break;
}
if (NS_INLINE_IS_BREAK(aStatus) ||
(!reflowingFirstLetter && NS_FRAME_IS_NOT_COMPLETE(aStatus))) {
done = PR_TRUE;
break;
}
irs.mPrevFrame = frame;
}
}
#ifdef DEBUG
if (NS_FRAME_IS_COMPLETE(aStatus)) {
// We can't be complete AND have overflow frames!
nsIFrame* overflowFrames = GetOverflowFrames(aPresContext, PR_FALSE);
NS_ASSERTION(!overflowFrames, "whoops");
}
#endif
// If after reflowing our children they take up no area then make
// sure that we don't either.
//
// Note: CSS demands that empty inline elements still affect the
// line-height calculations. However, continuations of an inline
// that are empty we force to empty so that things like collapsed
// whitespace in an inline element don't affect the line-height.
nsSize size;
lineLayout->EndSpan(this, size,
aMetrics.mComputeMEW ? &aMetrics.mMaxElementWidth : nsnull);
if ((0 == size.height) && (0 == size.width) &&
((nsnull != mPrevInFlow) || (nsnull != mNextInFlow))) {
// This is a continuation of a previous inline. Therefore make
// sure we don't affect the line-height.
aMetrics.width = 0;
aMetrics.height = 0;
aMetrics.ascent = 0;
aMetrics.descent = 0;
if (aMetrics.mComputeMEW) {
aMetrics.mMaxElementWidth = 0;
}
}
else {
// Compute final width
aMetrics.width = size.width;
if (nsnull == mPrevInFlow) {
aMetrics.width += aReflowState.mComputedBorderPadding.left;
}
if (NS_FRAME_IS_COMPLETE(aStatus)) {
aMetrics.width += aReflowState.mComputedBorderPadding.right;
}
SetFontFromStyle(aReflowState.rendContext, mStyleContext);
nsCOMPtr<nsIFontMetrics> fm;
aReflowState.rendContext->GetFontMetrics(*getter_AddRefs(fm));
if (fm) {
// Compute final height of the frame.
//
// Do things the standard css2 way -- though it's hard to find it
// in the css2 spec! It's actually found in the css1 spec section
// 4.4 (you will have to read between the lines to really see
// it).
//
// The height of our box is the sum of our font size plus the top
// and bottom border and padding. The height of children do not
// affect our height.
fm->GetMaxAscent(aMetrics.ascent);
fm->GetMaxDescent(aMetrics.descent);
fm->GetHeight(aMetrics.height);
} else {
NS_WARNING("Cannot get font metrics - defaulting sizes to 0");
aMetrics.ascent = aMetrics.descent = aMetrics.height = 0;
}
aMetrics.ascent += aReflowState.mComputedBorderPadding.top;
aMetrics.descent += aReflowState.mComputedBorderPadding.bottom;
aMetrics.height += aReflowState.mComputedBorderPadding.top +
aReflowState.mComputedBorderPadding.bottom;
// Note: we normally use the actual font height for computing the
// line-height raw value from the style context. On systems where
// they disagree the actual font height is more appropriate. This
// little hack lets us override that behavior to allow for more
// precise layout in the face of imprecise fonts.
if (nsHTMLReflowState::UseComputedHeight()) {
const nsStyleFont* font = GetStyleFont();
aMetrics.height = font->mFont.size +
aReflowState.mComputedBorderPadding.top +
aReflowState.mComputedBorderPadding.bottom;
}
}
// For now our overflow area is zero. The real value will be
// computed during vertical alignment of the line we are on.
aMetrics.mOverflowArea.x = 0;
aMetrics.mOverflowArea.y = 0;
aMetrics.mOverflowArea.width = aMetrics.width;
aMetrics.mOverflowArea.height = aMetrics.height;
#ifdef NOISY_FINAL_SIZE
ListTag(stdout);
printf(": metrics=%d,%d ascent=%d descent=%d\n",
aMetrics.width, aMetrics.height, aMetrics.ascent, aMetrics.descent);
if (aMetrics.mComputeMEW) {
printf(" maxElementWidth %d\n", aMetrics.mMaxElementWidth);
}
#endif
return rv;
}
static
void SetContainsPercentAwareChild(nsIFrame *aFrame)
{
aFrame->AddStateBits(NS_INLINE_FRAME_CONTAINS_PERCENT_AWARE_CHILD);
}
static
void MarkPercentAwareFrame(nsIPresContext *aPresContext,
nsInlineFrame *aInline,
nsIFrame *aFrame)
{
if (aFrame->GetStateBits() & NS_FRAME_REPLACED_ELEMENT)
{ // aFrame is a replaced element, check it's style
if (nsLineLayout::IsPercentageAwareReplacedElement(aPresContext, aFrame)) {
SetContainsPercentAwareChild(aInline);
}
}
else
{
if (aFrame->GetFirstChild(nsnull))
{ // aFrame is an inline container frame, check my frame state
if (aFrame->GetStateBits() & NS_INLINE_FRAME_CONTAINS_PERCENT_AWARE_CHILD) {
SetContainsPercentAwareChild(aInline); // if a child container is effected, so am I
}
}
// else frame is a leaf that we don't care about
}
}
nsresult
nsInlineFrame::ReflowInlineFrame(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
InlineReflowState& irs,
nsIFrame* aFrame,
nsReflowStatus& aStatus)
{
nsLineLayout* lineLayout = aReflowState.mLineLayout;
PRBool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK();
PRBool pushedFrame;
nsresult rv = lineLayout->ReflowFrame(aFrame, aStatus, nsnull, pushedFrame);
/* This next block is for bug 28811
Test the child frame for %-awareness,
and mark this frame with a bit if it is %-aware.
Don't bother if this frame is already marked
*/
if (!(mState & NS_INLINE_FRAME_CONTAINS_PERCENT_AWARE_CHILD)) {
MarkPercentAwareFrame(aPresContext, this, aFrame);
}
if (NS_FAILED(rv)) {
return rv;
}
if (NS_INLINE_IS_BREAK(aStatus)) {
if (NS_INLINE_IS_BREAK_BEFORE(aStatus)) {
if (aFrame != mFrames.FirstChild()) {
// Change break-before status into break-after since we have
// already placed at least one child frame. This preserves the
// break-type so that it can be propagated upward.
aStatus = NS_FRAME_NOT_COMPLETE |
NS_INLINE_BREAK | NS_INLINE_BREAK_AFTER |
(aStatus & NS_INLINE_BREAK_TYPE_MASK);
PushFrames(aPresContext, aFrame, irs.mPrevFrame);
}
else {
// Preserve reflow status when breaking-before our first child
// and propagate it upward without modification.
// Note: if we're lazily setting the frame pointer for our child
// frames, then we need to set it now. Don't return and leave the
// remaining child frames in our child list with the wrong parent
// frame pointer...
if (irs.mSetParentPointer) {
for (nsIFrame* f = aFrame->GetNextSibling(); f; f = f->GetNextSibling()) {
f->SetParent(this);
}
}
}
}
else {
// Break-after
if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
nsIFrame* newFrame;
rv = CreateNextInFlow(aPresContext, this, aFrame, newFrame);
if (NS_FAILED(rv)) {
return rv;
}
}
nsIFrame* nextFrame = aFrame->GetNextSibling();
if (nextFrame) {
aStatus |= NS_FRAME_NOT_COMPLETE;
PushFrames(aPresContext, nextFrame, aFrame);
}
else if (nsnull != mNextInFlow) {
// We must return an incomplete status if there are more child
// frames remaining in a next-in-flow that follows this frame.
nsInlineFrame* nextInFlow = (nsInlineFrame*) mNextInFlow;
while (nsnull != nextInFlow) {
if (nextInFlow->mFrames.NotEmpty()) {
aStatus |= NS_FRAME_NOT_COMPLETE;
break;
}
nextInFlow = (nsInlineFrame*) nextInFlow->mNextInFlow;
}
}
}
}
else if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
if (nsLayoutAtoms::placeholderFrame == aFrame->GetType()) {
nsBlockReflowState* blockRS = lineLayout->mBlockRS;
blockRS->mBlock->SplitPlaceholder(*aPresContext, *aFrame);
}
else {
nsIFrame* newFrame;
rv = CreateNextInFlow(aPresContext, this, aFrame, newFrame);
if (NS_FAILED(rv)) {
return rv;
}
if (!reflowingFirstLetter) {
nsIFrame* nextFrame = aFrame->GetNextSibling();
if (nextFrame) {
PushFrames(aPresContext, nextFrame, aFrame);
}
}
}
}
return rv;
}
nsIFrame*
nsInlineFrame::PullOneFrame(nsIPresContext* aPresContext,
InlineReflowState& irs,
PRBool* aIsComplete)
{
PRBool isComplete = PR_TRUE;
nsIFrame* frame = nsnull;
nsInlineFrame* nextInFlow = irs.mNextInFlow;
while (nsnull != nextInFlow) {
frame = mFrames.PullFrame(this, irs.mPrevFrame, nextInFlow->mFrames);
if (nsnull != frame) {
isComplete = PR_FALSE;
nsHTMLContainerFrame::ReparentFrameView(aPresContext, frame, nextInFlow, this);
break;
}
nextInFlow = (nsInlineFrame*) nextInFlow->mNextInFlow;
irs.mNextInFlow = nextInFlow;
}
*aIsComplete = isComplete;
return frame;
}
void
nsInlineFrame::PushFrames(nsIPresContext* aPresContext,
nsIFrame* aFromChild,
nsIFrame* aPrevSibling)
{
NS_PRECONDITION(nsnull != aFromChild, "null pointer");
NS_PRECONDITION(nsnull != aPrevSibling, "pushing first child");
NS_PRECONDITION(aPrevSibling->GetNextSibling() == aFromChild, "bad prev sibling");
#ifdef NOISY_PUSHING
printf("%p pushing aFromChild %p, disconnecting from prev sib %p\n",
this, aFromChild, aPrevSibling);
#endif
// Disconnect aFromChild from its previous sibling
aPrevSibling->SetNextSibling(nsnull);
// Add the frames to our overflow list (let our next in flow drain
// our overflow list when it is ready)
SetOverflowFrames(aPresContext, aFromChild);
}
//////////////////////////////////////////////////////////////////////
PRIntn
nsInlineFrame::GetSkipSides() const
{
PRIntn skip = 0;
if (nsnull != mPrevInFlow) {
nsInlineFrame* prev = (nsInlineFrame*) mPrevInFlow;
if (prev->mRect.height || prev->mRect.width) {
// Prev-in-flow is not empty therefore we don't render our left
// border edge.
skip |= 1 << NS_SIDE_LEFT;
}
else {
// If the prev-in-flow is empty, then go ahead and let our left
// edge border render.
}
}
if (nsnull != mNextInFlow) {
nsInlineFrame* next = (nsInlineFrame*) mNextInFlow;
if (next->mRect.height || next->mRect.width) {
// Next-in-flow is not empty therefore we don't render our right
// border edge.
skip |= 1 << NS_SIDE_RIGHT;
}
else {
// If the next-in-flow is empty, then go ahead and let our right
// edge border render.
}
}
return skip;
}
#ifdef ACCESSIBILITY
NS_IMETHODIMP nsInlineFrame::GetAccessible(nsIAccessible** aAccessible)
{
// Broken image accessibles are created here, because layout
// replaces the image or image control frame with an inline frame
*aAccessible = nsnull;
nsIAtom *tagAtom = mContent->Tag();
if ((tagAtom == nsHTMLAtoms::img || tagAtom == nsHTMLAtoms::input ||
tagAtom == nsHTMLAtoms::label || tagAtom == nsHTMLAtoms::hr) &&
mContent->IsContentOfType(nsIContent::eHTML)) {
// Only get accessibility service if we're going to use it
nsCOMPtr<nsIAccessibilityService> accService(do_GetService("@mozilla.org/accessibilityService;1"));
if (!accService)
return NS_ERROR_FAILURE;
if (tagAtom == nsHTMLAtoms::input) // Broken <input type=image ... />
return accService->CreateHTML4ButtonAccessible(NS_STATIC_CAST(nsIFrame*, this), aAccessible);
else if (tagAtom == nsHTMLAtoms::img) // Create accessible for broken <img>
return accService->CreateHTMLImageAccessible(NS_STATIC_CAST(nsIFrame*, this), aAccessible);
else if (tagAtom == nsHTMLAtoms::label) // Creat accessible for <label>
return accService->CreateHTMLLabelAccessible(NS_STATIC_CAST(nsIFrame*, this), aAccessible);
// Create accessible for <hr>
return accService->CreateHTMLHRAccessible(NS_STATIC_CAST(nsIFrame*, this), aAccessible);
}
return NS_ERROR_FAILURE;
}
#endif
//////////////////////////////////////////////////////////////////////
// nsLineFrame implementation
static void
ReParentChildListStyle(nsIPresContext* aPresContext,
nsStyleContext* aParentStyleContext,
nsFrameList& aFrameList)
{
nsFrameManager *frameManager = aPresContext->FrameManager();
for (nsIFrame* kid = aFrameList.FirstChild(); kid;
kid = kid->GetNextSibling()) {
frameManager->ReParentStyleContext(kid, aParentStyleContext);
}
}
nsresult
NS_NewFirstLineFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
{
NS_PRECONDITION(nsnull != aNewFrame, "null ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsInlineFrame* it = new (aPresShell) nsFirstLineFrame;
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aNewFrame = it;
return NS_OK;
}
nsFirstLineFrame::nsFirstLineFrame()
{
}
#ifdef DEBUG
NS_IMETHODIMP
nsFirstLineFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("Line"), aResult);
}
#endif
nsIAtom*
nsFirstLineFrame::GetType() const
{
return nsLayoutAtoms::lineFrame;
}
void
nsFirstLineFrame::StealFramesFrom(nsIFrame* aFrame)
{
nsIFrame* prevFrame = mFrames.GetPrevSiblingFor(aFrame);
if (prevFrame) {
prevFrame->SetNextSibling(nsnull);
}
else {
mFrames.SetFrames(nsnull);
}
}
nsIFrame*
nsFirstLineFrame::PullOneFrame(nsIPresContext* aPresContext, InlineReflowState& irs, PRBool* aIsComplete)
{
nsIFrame* frame = nsInlineFrame::PullOneFrame(aPresContext, irs, aIsComplete);
if (frame && !mPrevInFlow) {
// We are a first-line frame. Fixup the child frames
// style-context that we just pulled.
aPresContext->FrameManager()->ReParentStyleContext(frame, mStyleContext);
}
return frame;
}
NS_IMETHODIMP
nsFirstLineFrame::Reflow(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aMetrics,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
if (nsnull == aReflowState.mLineLayout) {
return NS_ERROR_INVALID_ARG;
}
// Check for an overflow list with our prev-in-flow
nsFirstLineFrame* prevInFlow = (nsFirstLineFrame*)mPrevInFlow;
if (nsnull != prevInFlow) {
nsIFrame* prevOverflowFrames = prevInFlow->GetOverflowFrames(aPresContext, PR_TRUE);
if (prevOverflowFrames) {
nsFrameList frames(prevOverflowFrames);
mFrames.InsertFrames(this, nsnull, prevOverflowFrames);
ReParentChildListStyle(aPresContext, mStyleContext, frames);
}
}
// It's also possible that we have an overflow list for ourselves
nsIFrame* overflowFrames = GetOverflowFrames(aPresContext, PR_TRUE);
if (overflowFrames) {
NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
nsFrameList frames(overflowFrames);
mFrames.AppendFrames(nsnull, overflowFrames);
ReParentChildListStyle(aPresContext, mStyleContext, frames);
}
// Set our own reflow state (additional state above and beyond
// aReflowState)
InlineReflowState irs;
irs.mPrevFrame = nsnull;
irs.mNextInFlow = (nsInlineFrame*) mNextInFlow;
nsresult rv;
PRBool wasEmpty = mFrames.IsEmpty();
if (wasEmpty) {
// Try to pull over one frame before starting so that we know
// whether we have an anonymous block or not.
PRBool complete;
PullOneFrame(aPresContext, irs, &complete);
}
if (nsnull == mPrevInFlow) {
// XXX This is pretty sick, but what we do here is to pull-up, in
// advance, all of the next-in-flows children. We re-resolve their
// style while we are at at it so that when we reflow they have
// the right style.
//
// All of this is so that text-runs reflow properly.
irs.mPrevFrame = mFrames.LastChild();
for (;;) {
PRBool complete;
nsIFrame* frame = PullOneFrame(aPresContext, irs, &complete);
if (!frame) {
break;
}
irs.mPrevFrame = frame;
}
irs.mPrevFrame = nsnull;
}
else {
// XXX do this in the Init method instead
// For continuations, we need to check and see if our style
// context is right. If its the same as the first-in-flow, then
// we need to fix it up (that way :first-line style doesn't leak
// into this continuation since we aren't the first line).
nsFirstLineFrame* first = (nsFirstLineFrame*) GetFirstInFlow();
if (mStyleContext == first->mStyleContext) {
// Fixup our style context and our children. First get the
// proper parent context.
nsStyleContext* parentContext = first->GetParent()->GetStyleContext();
if (parentContext) {
// Create a new style context that is a child of the parent
// style context thus removing the :first-line style. This way
// we behave as if an anonymous (unstyled) span was the child
// of the parent frame.
nsRefPtr<nsStyleContext> newSC;
newSC = aPresContext->StyleSet()->
ResolvePseudoStyleFor(nsnull,
nsCSSAnonBoxes::mozLineFrame, parentContext);
if (newSC) {
// Switch to the new style context.
SetStyleContext(aPresContext, newSC);
// Re-resolve all children
ReParentChildListStyle(aPresContext, mStyleContext, mFrames);
}
}
}
}
rv = ReflowFrames(aPresContext, aReflowState, irs, aMetrics, aStatus);
// Note: the line layout code will properly compute our
// NS_FRAME_OUTSIDE_CHILDREN state for us.
return rv;
}
//////////////////////////////////////////////////////////////////////
nsresult
NS_NewPositionedInlineFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsPositionedInlineFrame* it = new (aPresShell) nsPositionedInlineFrame();
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aNewFrame = it;
return NS_OK;
}
NS_IMETHODIMP
nsPositionedInlineFrame::Destroy(nsIPresContext* aPresContext)
{
mAbsoluteContainer.DestroyFrames(this, aPresContext);
return nsInlineFrame::Destroy(aPresContext);
}
NS_IMETHODIMP
nsPositionedInlineFrame::SetInitialChildList(nsIPresContext* aPresContext,
nsIAtom* aListName,
nsIFrame* aChildList)
{
nsresult rv;
if (mAbsoluteContainer.GetChildListName() == aListName) {
rv = mAbsoluteContainer.SetInitialChildList(this, aPresContext, aListName, aChildList);
} else {
rv = nsInlineFrame::SetInitialChildList(aPresContext, aListName, aChildList);
}
return rv;
}
NS_IMETHODIMP
nsPositionedInlineFrame::AppendFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aFrameList)
{
nsresult rv;
if (mAbsoluteContainer.GetChildListName() == aListName) {
rv = mAbsoluteContainer.AppendFrames(this, aPresContext, aPresShell, aListName,
aFrameList);
} else {
rv = nsInlineFrame::AppendFrames(aPresContext, aPresShell, aListName,
aFrameList);
}
return rv;
}
NS_IMETHODIMP
nsPositionedInlineFrame::InsertFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsIFrame* aFrameList)
{
nsresult rv;
if (mAbsoluteContainer.GetChildListName() == aListName) {
rv = mAbsoluteContainer.InsertFrames(this, aPresContext, aPresShell, aListName,
aPrevFrame, aFrameList);
} else {
rv = nsInlineFrame::InsertFrames(aPresContext, aPresShell, aListName, aPrevFrame,
aFrameList);
}
return rv;
}
NS_IMETHODIMP
nsPositionedInlineFrame::RemoveFrame(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame)
{
nsresult rv;
if (mAbsoluteContainer.GetChildListName() == aListName) {
rv = mAbsoluteContainer.RemoveFrame(this, aPresContext, aPresShell, aListName, aOldFrame);
} else {
rv = nsInlineFrame::RemoveFrame(aPresContext, aPresShell, aListName, aOldFrame);
}
return rv;
}
NS_IMETHODIMP
nsPositionedInlineFrame::ReplaceFrame(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame,
nsIFrame* aNewFrame)
{
if (mAbsoluteContainer.GetChildListName() == aListName) {
return mAbsoluteContainer.ReplaceFrame(this, aPresContext, aPresShell,
aListName, aOldFrame, aNewFrame);
} else {
return nsInlineFrame::ReplaceFrame(aPresContext, aPresShell, aListName,
aOldFrame, aNewFrame);
}
}
nsIAtom*
nsPositionedInlineFrame::GetAdditionalChildListName(PRInt32 aIndex) const
{
if (0 == aIndex) {
return mAbsoluteContainer.GetChildListName();
}
return nsnull;
}
nsIFrame*
nsPositionedInlineFrame::GetFirstChild(nsIAtom* aListName) const
{
if (mAbsoluteContainer.GetChildListName() == aListName) {
nsIFrame* result = nsnull;
mAbsoluteContainer.FirstChild(this, aListName, &result);
return result;
}
return nsInlineFrame::GetFirstChild(aListName);
}
nsIAtom*
nsPositionedInlineFrame::GetType() const
{
return nsLayoutAtoms::positionedInlineFrame;
}
NS_IMETHODIMP
nsPositionedInlineFrame::Reflow(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
nsresult rv = NS_OK;
nsRect oldRect(mRect);
// See if it's an incremental reflow command
if (mAbsoluteContainer.HasAbsoluteFrames() &&
eReflowReason_Incremental == aReflowState.reason) {
// Give the absolute positioning code a chance to handle it
PRBool handled;
nscoord containingBlockWidth = -1;
nscoord containingBlockHeight = -1;
mAbsoluteContainer.IncrementalReflow(this, aPresContext, aReflowState,
containingBlockWidth, containingBlockHeight,
handled);
// If the incremental reflow command was handled by the absolute positioning
// code, then we're all done
if (handled) {
// Just return our current size as our desired size
// XXX I don't know how to compute that without a reflow, so for the
// time being pretend a resize reflow occured
nsHTMLReflowState reflowState(aReflowState);
reflowState.reason = eReflowReason_Resize;
reflowState.path = nsnull;
rv = nsInlineFrame::Reflow(aPresContext, aDesiredSize, reflowState, aStatus);
// Factor the absolutely positioned child bounds into the overflow area
nsRect childBounds;
mAbsoluteContainer.CalculateChildBounds(aPresContext, childBounds);
aDesiredSize.mOverflowArea.UnionRect(aDesiredSize.mOverflowArea, childBounds);
// Make sure the NS_FRAME_OUTSIDE_CHILDREN flag is set correctly
if ((aDesiredSize.mOverflowArea.x < 0) ||
(aDesiredSize.mOverflowArea.y < 0) ||
(aDesiredSize.mOverflowArea.XMost() > aDesiredSize.width) ||
(aDesiredSize.mOverflowArea.YMost() > aDesiredSize.height)) {
mState |= NS_FRAME_OUTSIDE_CHILDREN;
} else {
mState &= ~NS_FRAME_OUTSIDE_CHILDREN;
}
return rv;
}
}
// Let the inline frame do its reflow first
rv = nsInlineFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus);
// Let the absolutely positioned container reflow any absolutely positioned
// child frames that need to be reflowed
// We want to do this under either of two conditions:
// 1. If we didn't do the incremental reflow above.
// 2. If our size changed.
// Even though it's the padding edge that's the containing block, we
// can use our rect (the border edge) since if the border style
// changed, the reflow would have been targeted at us so we'd satisfy
// condition 1.
if (NS_SUCCEEDED(rv) &&
mAbsoluteContainer.HasAbsoluteFrames() &&
(eReflowReason_Incremental != aReflowState.reason ||
aReflowState.path->mReflowCommand ||
mRect != oldRect)) {
// The containing block for the abs pos kids is formed by our content edge.
nscoord containingBlockWidth = aDesiredSize.width -
(aReflowState.mComputedBorderPadding.left +
aReflowState.mComputedBorderPadding.right);
nscoord containingBlockHeight = aDesiredSize.height -
(aReflowState.mComputedBorderPadding.top +
aReflowState.mComputedBorderPadding.bottom);
nsRect childBounds;
rv = mAbsoluteContainer.Reflow(this, aPresContext, aReflowState,
containingBlockWidth, containingBlockHeight,
&childBounds);
// Factor the absolutely positioned child bounds into the overflow area
aDesiredSize.mOverflowArea.UnionRect(aDesiredSize.mOverflowArea, childBounds);
// Make sure the NS_FRAME_OUTSIDE_CHILDREN flag is set correctly
if ((aDesiredSize.mOverflowArea.x < 0) ||
(aDesiredSize.mOverflowArea.y < 0) ||
(aDesiredSize.mOverflowArea.XMost() > aDesiredSize.width) ||
(aDesiredSize.mOverflowArea.YMost() > aDesiredSize.height)) {
mState |= NS_FRAME_OUTSIDE_CHILDREN;
} else {
mState &= ~NS_FRAME_OUTSIDE_CHILDREN;
}
}
return rv;
}