michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* class that manages rules for positioning floats */ michael@0: michael@0: #include "nsFloatManager.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsMemory.h" michael@0: #include "nsHTMLReflowState.h" michael@0: #include "nsBlockDebugFlags.h" michael@0: #include "nsError.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: int32_t nsFloatManager::sCachedFloatManagerCount = 0; michael@0: void* nsFloatManager::sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE]; michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // PresShell Arena allocate callback (for nsIntervalSet use below) michael@0: static void* michael@0: PSArenaAllocCB(size_t aSize, void* aClosure) michael@0: { michael@0: return static_cast(aClosure)->AllocateMisc(aSize); michael@0: } michael@0: michael@0: // PresShell Arena free callback (for nsIntervalSet use below) michael@0: static void michael@0: PSArenaFreeCB(size_t aSize, void* aPtr, void* aClosure) michael@0: { michael@0: static_cast(aClosure)->FreeMisc(aSize, aPtr); michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // nsFloatManager michael@0: michael@0: nsFloatManager::nsFloatManager(nsIPresShell* aPresShell) michael@0: : mX(0), mY(0), michael@0: mFloatDamage(PSArenaAllocCB, PSArenaFreeCB, aPresShell), michael@0: mPushedLeftFloatPastBreak(false), michael@0: mPushedRightFloatPastBreak(false), michael@0: mSplitLeftFloatAcrossBreak(false), michael@0: mSplitRightFloatAcrossBreak(false) michael@0: { michael@0: MOZ_COUNT_CTOR(nsFloatManager); michael@0: } michael@0: michael@0: nsFloatManager::~nsFloatManager() michael@0: { michael@0: MOZ_COUNT_DTOR(nsFloatManager); michael@0: } michael@0: michael@0: // static michael@0: void* nsFloatManager::operator new(size_t aSize) CPP_THROW_NEW michael@0: { michael@0: if (sCachedFloatManagerCount > 0) { michael@0: // We have cached unused instances of this class, return a cached michael@0: // instance in stead of always creating a new one. michael@0: return sCachedFloatManagers[--sCachedFloatManagerCount]; michael@0: } michael@0: michael@0: // The cache is empty, this means we haveto create a new instance using michael@0: // the global |operator new|. michael@0: return nsMemory::Alloc(aSize); michael@0: } michael@0: michael@0: void michael@0: nsFloatManager::operator delete(void* aPtr, size_t aSize) michael@0: { michael@0: if (!aPtr) michael@0: return; michael@0: // This float manager is no longer used, if there's still room in michael@0: // the cache we'll cache this float manager, unless the layout michael@0: // module was already shut down. michael@0: michael@0: if (sCachedFloatManagerCount < NS_FLOAT_MANAGER_CACHE_SIZE && michael@0: sCachedFloatManagerCount >= 0) { michael@0: // There's still space in the cache for more instances, put this michael@0: // instance in the cache in stead of deleting it. michael@0: michael@0: sCachedFloatManagers[sCachedFloatManagerCount++] = aPtr; michael@0: return; michael@0: } michael@0: michael@0: // The cache is full, or the layout module has been shut down, michael@0: // delete this float manager. michael@0: nsMemory::Free(aPtr); michael@0: } michael@0: michael@0: michael@0: /* static */ michael@0: void nsFloatManager::Shutdown() michael@0: { michael@0: // The layout module is being shut down, clean up the cache and michael@0: // disable further caching. michael@0: michael@0: int32_t i; michael@0: michael@0: for (i = 0; i < sCachedFloatManagerCount; i++) { michael@0: void* floatManager = sCachedFloatManagers[i]; michael@0: if (floatManager) michael@0: nsMemory::Free(floatManager); michael@0: } michael@0: michael@0: // Disable further caching. michael@0: sCachedFloatManagerCount = -1; michael@0: } michael@0: michael@0: nsFlowAreaRect michael@0: nsFloatManager::GetFlowArea(nscoord aYOffset, BandInfoType aInfoType, michael@0: nscoord aHeight, nsRect aContentArea, michael@0: SavedState* aState) const michael@0: { michael@0: NS_ASSERTION(aHeight >= 0, "unexpected max height"); michael@0: NS_ASSERTION(aContentArea.width >= 0, "unexpected content area width"); michael@0: michael@0: nscoord top = aYOffset + mY; michael@0: if (top < nscoord_MIN) { michael@0: NS_WARNING("bad value"); michael@0: top = nscoord_MIN; michael@0: } michael@0: michael@0: // Determine the last float that we should consider. michael@0: uint32_t floatCount; michael@0: if (aState) { michael@0: // Use the provided state. michael@0: floatCount = aState->mFloatInfoCount; michael@0: NS_ABORT_IF_FALSE(floatCount <= mFloats.Length(), "bad state"); michael@0: } else { michael@0: // Use our current state. michael@0: floatCount = mFloats.Length(); michael@0: } michael@0: michael@0: // If there are no floats at all, or we're below the last one, return michael@0: // quickly. michael@0: if (floatCount == 0 || michael@0: (mFloats[floatCount-1].mLeftYMost <= top && michael@0: mFloats[floatCount-1].mRightYMost <= top)) { michael@0: return nsFlowAreaRect(aContentArea.x, aYOffset, aContentArea.width, michael@0: aHeight, false); michael@0: } michael@0: michael@0: nscoord bottom; michael@0: if (aHeight == nscoord_MAX) { michael@0: // This warning (and the two below) are possible to hit on pages michael@0: // with really large objects. michael@0: NS_WARN_IF_FALSE(aInfoType == BAND_FROM_POINT, michael@0: "bad height"); michael@0: bottom = nscoord_MAX; michael@0: } else { michael@0: bottom = top + aHeight; michael@0: if (bottom < top || bottom > nscoord_MAX) { michael@0: NS_WARNING("bad value"); michael@0: bottom = nscoord_MAX; michael@0: } michael@0: } michael@0: nscoord left = mX + aContentArea.x; michael@0: nscoord right = mX + aContentArea.XMost(); michael@0: if (right < left) { michael@0: NS_WARNING("bad value"); michael@0: right = left; michael@0: } michael@0: michael@0: // Walk backwards through the floats until we either hit the front of michael@0: // the list or we're above |top|. michael@0: bool haveFloats = false; michael@0: for (uint32_t i = floatCount; i > 0; --i) { michael@0: const FloatInfo &fi = mFloats[i-1]; michael@0: if (fi.mLeftYMost <= top && fi.mRightYMost <= top) { michael@0: // There aren't any more floats that could intersect this band. michael@0: break; michael@0: } michael@0: if (fi.mRect.IsEmpty()) { michael@0: // For compatibility, ignore floats with empty rects, even though it michael@0: // disagrees with the spec. (We might want to fix this in the michael@0: // future, though.) michael@0: continue; michael@0: } michael@0: nscoord floatTop = fi.mRect.y, floatBottom = fi.mRect.YMost(); michael@0: if (top < floatTop && aInfoType == BAND_FROM_POINT) { michael@0: // This float is below our band. Shrink our band's height if needed. michael@0: if (floatTop < bottom) { michael@0: bottom = floatTop; michael@0: } michael@0: } michael@0: // If top == bottom (which happens only with WIDTH_WITHIN_HEIGHT), michael@0: // we include floats that begin at our 0-height vertical area. We michael@0: // need to to this to satisfy the invariant that a michael@0: // WIDTH_WITHIN_HEIGHT call is at least as narrow on both sides as a michael@0: // BAND_WITHIN_POINT call beginning at its top. michael@0: else if (top < floatBottom && michael@0: (floatTop < bottom || (floatTop == bottom && top == bottom))) { michael@0: // This float is in our band. michael@0: michael@0: // Shrink our band's height if needed. michael@0: if (floatBottom < bottom && aInfoType == BAND_FROM_POINT) { michael@0: bottom = floatBottom; michael@0: } michael@0: michael@0: // Shrink our band's width if needed. michael@0: if (fi.mFrame->StyleDisplay()->mFloats == NS_STYLE_FLOAT_LEFT) { michael@0: // A left float. michael@0: nscoord rightEdge = fi.mRect.XMost(); michael@0: if (rightEdge > left) { michael@0: left = rightEdge; michael@0: // Only set haveFloats to true if the float is inside our michael@0: // containing block. This matches the spec for what some michael@0: // callers want and disagrees for other callers, so we should michael@0: // probably provide better information at some point. michael@0: haveFloats = true; michael@0: } michael@0: } else { michael@0: // A right float. michael@0: nscoord leftEdge = fi.mRect.x; michael@0: if (leftEdge < right) { michael@0: right = leftEdge; michael@0: // See above. michael@0: haveFloats = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nscoord height = (bottom == nscoord_MAX) ? nscoord_MAX : (bottom - top); michael@0: return nsFlowAreaRect(left - mX, top - mY, right - left, height, haveFloats); michael@0: } michael@0: michael@0: nsresult michael@0: nsFloatManager::AddFloat(nsIFrame* aFloatFrame, const nsRect& aMarginRect) michael@0: { michael@0: NS_ASSERTION(aMarginRect.width >= 0, "negative width!"); michael@0: NS_ASSERTION(aMarginRect.height >= 0, "negative height!"); michael@0: michael@0: FloatInfo info(aFloatFrame, aMarginRect + nsPoint(mX, mY)); michael@0: michael@0: // Set mLeftYMost and mRightYMost. michael@0: if (HasAnyFloats()) { michael@0: FloatInfo &tail = mFloats[mFloats.Length() - 1]; michael@0: info.mLeftYMost = tail.mLeftYMost; michael@0: info.mRightYMost = tail.mRightYMost; michael@0: } else { michael@0: info.mLeftYMost = nscoord_MIN; michael@0: info.mRightYMost = nscoord_MIN; michael@0: } michael@0: uint8_t floatStyle = aFloatFrame->StyleDisplay()->mFloats; michael@0: NS_ASSERTION(floatStyle == NS_STYLE_FLOAT_LEFT || michael@0: floatStyle == NS_STYLE_FLOAT_RIGHT, "unexpected float"); michael@0: nscoord& sideYMost = (floatStyle == NS_STYLE_FLOAT_LEFT) ? info.mLeftYMost michael@0: : info.mRightYMost; michael@0: nscoord thisYMost = info.mRect.YMost(); michael@0: if (thisYMost > sideYMost) michael@0: sideYMost = thisYMost; michael@0: michael@0: if (!mFloats.AppendElement(info)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRect michael@0: nsFloatManager::CalculateRegionFor(nsIFrame* aFloat, michael@0: const nsMargin& aMargin) michael@0: { michael@0: // We consider relatively positioned frames at their original position. michael@0: nsRect region(aFloat->GetNormalPosition(), aFloat->GetSize()); michael@0: michael@0: // Float region includes its margin michael@0: region.Inflate(aMargin); michael@0: michael@0: // Don't store rectangles with negative margin-box width or height in michael@0: // the float manager; it can't deal with them. michael@0: if (region.width < 0) { michael@0: // Preserve the right margin-edge for left floats and the left michael@0: // margin-edge for right floats michael@0: const nsStyleDisplay* display = aFloat->StyleDisplay(); michael@0: if (NS_STYLE_FLOAT_LEFT == display->mFloats) { michael@0: region.x = region.XMost(); michael@0: } michael@0: region.width = 0; michael@0: } michael@0: if (region.height < 0) { michael@0: region.height = 0; michael@0: } michael@0: return region; michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(FloatRegionProperty, nsIFrame::DestroyMargin) michael@0: michael@0: nsRect michael@0: nsFloatManager::GetRegionFor(nsIFrame* aFloat) michael@0: { michael@0: nsRect region = aFloat->GetRect(); michael@0: void* storedRegion = aFloat->Properties().Get(FloatRegionProperty()); michael@0: if (storedRegion) { michael@0: nsMargin margin = *static_cast(storedRegion); michael@0: region.Inflate(margin); michael@0: } michael@0: return region; michael@0: } michael@0: michael@0: void michael@0: nsFloatManager::StoreRegionFor(nsIFrame* aFloat, michael@0: nsRect& aRegion) michael@0: { michael@0: nsRect rect = aFloat->GetRect(); michael@0: FrameProperties props = aFloat->Properties(); michael@0: if (aRegion.IsEqualEdges(rect)) { michael@0: props.Delete(FloatRegionProperty()); michael@0: } michael@0: else { michael@0: nsMargin* storedMargin = static_cast michael@0: (props.Get(FloatRegionProperty())); michael@0: if (!storedMargin) { michael@0: storedMargin = new nsMargin(); michael@0: props.Set(FloatRegionProperty(), storedMargin); michael@0: } michael@0: *storedMargin = aRegion - rect; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsFloatManager::RemoveTrailingRegions(nsIFrame* aFrameList) michael@0: { michael@0: if (!aFrameList) { michael@0: return NS_OK; michael@0: } michael@0: // This could be a good bit simpler if we could guarantee that the michael@0: // floats given were at the end of our list, so we could just search michael@0: // for the head of aFrameList. (But we can't; michael@0: // layout/reftests/bugs/421710-1.html crashes.) michael@0: nsTHashtable > frameSet(1); michael@0: michael@0: for (nsIFrame* f = aFrameList; f; f = f->GetNextSibling()) { michael@0: frameSet.PutEntry(f); michael@0: } michael@0: michael@0: uint32_t newLength = mFloats.Length(); michael@0: while (newLength > 0) { michael@0: if (!frameSet.Contains(mFloats[newLength - 1].mFrame)) { michael@0: break; michael@0: } michael@0: --newLength; michael@0: } michael@0: mFloats.TruncateLength(newLength); michael@0: michael@0: #ifdef DEBUG michael@0: for (uint32_t i = 0; i < mFloats.Length(); ++i) { michael@0: NS_ASSERTION(!frameSet.Contains(mFloats[i].mFrame), michael@0: "Frame region deletion was requested but we couldn't delete it"); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFloatManager::PushState(SavedState* aState) michael@0: { michael@0: NS_PRECONDITION(aState, "Need a place to save state"); michael@0: michael@0: // This is a cheap push implementation, which michael@0: // only saves the (x,y) and last frame in the mFrameInfoMap michael@0: // which is enough info to get us back to where we should be michael@0: // when pop is called. michael@0: // michael@0: // This push/pop mechanism is used to undo any michael@0: // floats that were added during the unconstrained reflow michael@0: // in nsBlockReflowContext::DoReflowBlock(). (See bug 96736) michael@0: // michael@0: // It should also be noted that the state for mFloatDamage is michael@0: // intentionally not saved or restored in PushState() and PopState(), michael@0: // since that could lead to bugs where damage is missed/dropped when michael@0: // we move from position A to B (during the intermediate incremental michael@0: // reflow mentioned above) and then from B to C during the subsequent michael@0: // reflow. In the typical case A and C will be the same, but not always. michael@0: // Allowing mFloatDamage to accumulate the damage incurred during both michael@0: // reflows ensures that nothing gets missed. michael@0: aState->mX = mX; michael@0: aState->mY = mY; michael@0: aState->mPushedLeftFloatPastBreak = mPushedLeftFloatPastBreak; michael@0: aState->mPushedRightFloatPastBreak = mPushedRightFloatPastBreak; michael@0: aState->mSplitLeftFloatAcrossBreak = mSplitLeftFloatAcrossBreak; michael@0: aState->mSplitRightFloatAcrossBreak = mSplitRightFloatAcrossBreak; michael@0: aState->mFloatInfoCount = mFloats.Length(); michael@0: } michael@0: michael@0: void michael@0: nsFloatManager::PopState(SavedState* aState) michael@0: { michael@0: NS_PRECONDITION(aState, "No state to restore?"); michael@0: michael@0: mX = aState->mX; michael@0: mY = aState->mY; michael@0: mPushedLeftFloatPastBreak = aState->mPushedLeftFloatPastBreak; michael@0: mPushedRightFloatPastBreak = aState->mPushedRightFloatPastBreak; michael@0: mSplitLeftFloatAcrossBreak = aState->mSplitLeftFloatAcrossBreak; michael@0: mSplitRightFloatAcrossBreak = aState->mSplitRightFloatAcrossBreak; michael@0: michael@0: NS_ASSERTION(aState->mFloatInfoCount <= mFloats.Length(), michael@0: "somebody misused PushState/PopState"); michael@0: mFloats.TruncateLength(aState->mFloatInfoCount); michael@0: } michael@0: michael@0: nscoord michael@0: nsFloatManager::GetLowestFloatTop() const michael@0: { michael@0: if (mPushedLeftFloatPastBreak || mPushedRightFloatPastBreak) { michael@0: return nscoord_MAX; michael@0: } michael@0: if (!HasAnyFloats()) { michael@0: return nscoord_MIN; michael@0: } michael@0: return mFloats[mFloats.Length() - 1].mRect.y - mY; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: void michael@0: DebugListFloatManager(const nsFloatManager *aFloatManager) michael@0: { michael@0: aFloatManager->List(stdout); michael@0: } michael@0: michael@0: nsresult michael@0: nsFloatManager::List(FILE* out) const michael@0: { michael@0: if (!HasAnyFloats()) michael@0: return NS_OK; michael@0: michael@0: for (uint32_t i = 0; i < mFloats.Length(); ++i) { michael@0: const FloatInfo &fi = mFloats[i]; michael@0: fprintf_stderr(out, "Float %u: frame=%p rect={%d,%d,%d,%d} ymost={l:%d, r:%d}\n", michael@0: i, static_cast(fi.mFrame), michael@0: fi.mRect.x, fi.mRect.y, fi.mRect.width, fi.mRect.height, michael@0: fi.mLeftYMost, fi.mRightYMost); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: nscoord michael@0: nsFloatManager::ClearFloats(nscoord aY, uint8_t aBreakType, michael@0: uint32_t aFlags) const michael@0: { michael@0: if (!(aFlags & DONT_CLEAR_PUSHED_FLOATS) && ClearContinues(aBreakType)) { michael@0: return nscoord_MAX; michael@0: } michael@0: if (!HasAnyFloats()) { michael@0: return aY; michael@0: } michael@0: michael@0: nscoord bottom = aY + mY; michael@0: michael@0: const FloatInfo &tail = mFloats[mFloats.Length() - 1]; michael@0: switch (aBreakType) { michael@0: case NS_STYLE_CLEAR_BOTH: michael@0: bottom = std::max(bottom, tail.mLeftYMost); michael@0: bottom = std::max(bottom, tail.mRightYMost); michael@0: break; michael@0: case NS_STYLE_CLEAR_LEFT: michael@0: bottom = std::max(bottom, tail.mLeftYMost); michael@0: break; michael@0: case NS_STYLE_CLEAR_RIGHT: michael@0: bottom = std::max(bottom, tail.mRightYMost); michael@0: break; michael@0: default: michael@0: // Do nothing michael@0: break; michael@0: } michael@0: michael@0: bottom -= mY; michael@0: michael@0: return bottom; michael@0: } michael@0: michael@0: bool michael@0: nsFloatManager::ClearContinues(uint8_t aBreakType) const michael@0: { michael@0: return ((mPushedLeftFloatPastBreak || mSplitLeftFloatAcrossBreak) && michael@0: (aBreakType == NS_STYLE_CLEAR_BOTH || michael@0: aBreakType == NS_STYLE_CLEAR_LEFT)) || michael@0: ((mPushedRightFloatPastBreak || mSplitRightFloatAcrossBreak) && michael@0: (aBreakType == NS_STYLE_CLEAR_BOTH || michael@0: aBreakType == NS_STYLE_CLEAR_RIGHT)); michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // FloatInfo michael@0: michael@0: nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame, const nsRect& aRect) michael@0: : mFrame(aFrame), mRect(aRect) michael@0: { michael@0: MOZ_COUNT_CTOR(nsFloatManager::FloatInfo); michael@0: } michael@0: michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: nsFloatManager::FloatInfo::FloatInfo(const FloatInfo& aOther) michael@0: : mFrame(aOther.mFrame), michael@0: mRect(aOther.mRect), michael@0: mLeftYMost(aOther.mLeftYMost), michael@0: mRightYMost(aOther.mRightYMost) michael@0: { michael@0: MOZ_COUNT_CTOR(nsFloatManager::FloatInfo); michael@0: } michael@0: michael@0: nsFloatManager::FloatInfo::~FloatInfo() michael@0: { michael@0: MOZ_COUNT_DTOR(nsFloatManager::FloatInfo); michael@0: } michael@0: #endif michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsAutoFloatManager::~nsAutoFloatManager() michael@0: { michael@0: // Restore the old float manager in the reflow state if necessary. michael@0: if (mNew) { michael@0: #ifdef NOISY_FLOATMANAGER michael@0: printf("restoring old float manager %p\n", mOld); michael@0: #endif michael@0: michael@0: mReflowState.mFloatManager = mOld; michael@0: michael@0: #ifdef NOISY_FLOATMANAGER michael@0: if (mOld) { michael@0: static_cast(mReflowState.frame)->ListTag(stdout); michael@0: printf(": space-manager %p after reflow\n", mOld); michael@0: mOld->List(stdout); michael@0: } michael@0: #endif michael@0: michael@0: delete mNew; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsAutoFloatManager::CreateFloatManager(nsPresContext *aPresContext) michael@0: { michael@0: // Create a new float manager and install it in the reflow michael@0: // state. `Remember' the old float manager so we can restore it michael@0: // later. michael@0: mNew = new nsFloatManager(aPresContext->PresShell()); michael@0: if (! mNew) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: #ifdef NOISY_FLOATMANAGER michael@0: printf("constructed new float manager %p (replacing %p)\n", michael@0: mNew, mReflowState.mFloatManager); michael@0: #endif michael@0: michael@0: // Set the float manager in the existing reflow state michael@0: mOld = mReflowState.mFloatManager; michael@0: mReflowState.mFloatManager = mNew; michael@0: return NS_OK; michael@0: }