gecko/layout/generic/nsFloatManager.cpp
Xidorn Quan ef976642a0 Bug 1230034 part 5 - Convert all frame properties which use DeleteValue and ReleaseValue as destructor to be typesafe. r=dbaron
By changing signature of those two functions, we make compiler complain about
all their existing uses, so we can find all of them and convert them.

Some of the callsites of Get() with those properties are also converted, but not
all of them. It is fine because if there is any incorrect conversion, compilers
is able to find out now. So they are completely typesafe.
2016-01-28 14:23:59 +11:00

601 lines
19 KiB
C++

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