Bug 367673, Handle width-computation arithmetic with nscoord_MAX. r=roc sr=roc a1.9=roc
--- a/gfx/public/nsCoord.h
+++ b/gfx/public/nsCoord.h
@@ -117,16 +117,113 @@ inline nscoord NSCoordDivide(nscoord aCo
#ifdef NS_COORD_IS_FLOAT
return floorf(aCoord/aVal);
#else
return aCoord/aVal;
#endif
}
/**
+ * Returns a + b, capping the sum to nscoord_MAX.
+ *
+ * This function assumes that neither argument is nscoord_MIN.
+ *
+ * Note: If/when we start using floats for nscoords, this function won't be as
+ * necessary. Normal float addition correctly handles adding with infinity,
+ * assuming we aren't adding nscoord_MIN. (-infinity)
+ */
+inline nscoord
+NSCoordSaturatingAdd(nscoord a, nscoord b)
+{
+ VERIFY_COORD(a);
+ VERIFY_COORD(b);
+ NS_ASSERTION(a != nscoord_MIN && b != nscoord_MIN,
+ "NSCoordSaturatingAdd got nscoord_MIN as argument");
+
+#ifdef NS_COORD_IS_FLOAT
+ // Float math correctly handles a+b, given that neither is -infinity.
+ return a + b;
+#else
+ if (a == nscoord_MAX || b == nscoord_MAX) {
+ // infinity + anything = anything + infinity = infinity
+ return nscoord_MAX;
+ } else {
+ // a + b = a + b
+ NS_ASSERTION(a < nscoord_MAX && b < nscoord_MAX,
+ "Doing nscoord addition with values > nscoord_MAX");
+ NS_ASSERTION((PRInt64)a + (PRInt64)b < (PRInt64)nscoord_MAX,
+ "nscoord addition will reach or pass nscoord_MAX");
+ NS_ASSERTION((PRInt64)a + (PRInt64)b > (PRInt64)nscoord_MIN,
+ "nscoord addition will reach or pass nscoord_MIN");
+
+ // Cap the result, just in case we're dealing with numbers near nscoord_MAX
+ return PR_MIN(nscoord_MAX, a + b);
+ }
+#endif
+}
+
+/**
+ * Returns a - b, gracefully handling cases involving nscoord_MAX.
+ * This function assumes that neither argument is nscoord_MIN.
+ *
+ * The behavior is as follows:
+ *
+ * a) infinity - infinity -> infMinusInfResult
+ * b) N - infinity -> 0 (unexpected -- triggers NOTREACHED)
+ * c) infinity - N -> infinity
+ * d) N1 - N2 -> N1 - N2
+ *
+ * Note: For float nscoords, cases (c) and (d) are handled by normal float
+ * math. We still need to explicitly specify the behavior for cases (a)
+ * and (b), though. (Under normal float math, those cases would return NaN
+ * and -infinity, respectively.)
+ */
+inline nscoord
+NSCoordSaturatingSubtract(nscoord a, nscoord b,
+ nscoord infMinusInfResult)
+{
+ VERIFY_COORD(a);
+ VERIFY_COORD(b);
+ NS_ASSERTION(a != nscoord_MIN && b != nscoord_MIN,
+ "NSCoordSaturatingSubtract got nscoord_MIN as argument");
+
+ if (b == nscoord_MAX) {
+ if (a == nscoord_MAX) {
+ // case (a)
+ return infMinusInfResult;
+ } else {
+ // case (b)
+ NS_NOTREACHED("Attempted to subtract [n - nscoord_MAX]");
+ return 0;
+ }
+ } else {
+#ifdef NS_COORD_IS_FLOAT
+ // case (c) and (d) for floats. (float math handles both)
+ return a - b;
+#else
+ if (a == nscoord_MAX) {
+ // case (c) for integers
+ return nscoord_MAX;
+ } else {
+ // case (d) for integers
+ NS_ASSERTION(a < nscoord_MAX && b < nscoord_MAX,
+ "Doing nscoord subtraction with values > nscoord_MAX");
+ NS_ASSERTION((PRInt64)a - (PRInt64)b < (PRInt64)nscoord_MAX,
+ "nscoord subtraction will reach or pass nscoord_MAX");
+ NS_ASSERTION((PRInt64)a - (PRInt64)b > (PRInt64)nscoord_MIN,
+ "nscoord subtraction will reach or pass nscoord_MIN");
+
+ // Cap the result, in case we're dealing with numbers near nscoord_MAX
+ return PR_MIN(nscoord_MAX, a - b);
+ }
+ }
+#endif
+}
+
+/**
* Convert an nscoord to a PRInt32. This *does not* do rounding because
* coords are never fractional. They can be out of range, so this does
* clamp out of bounds coord values to PR_INT32_MIN and PR_INT32_MAX.
*/
inline PRInt32 NSCoordToInt(nscoord aCoord) {
VERIFY_COORD(aCoord);
#ifdef NS_COORD_IS_FLOAT
NS_ASSERTION(!NS_IEEEIsNan(aCoord), "NaN encountered in int conversion");
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1503,39 +1503,39 @@ nsLayoutUtils::IntrinsicForContainer(nsI
// linearly, they are handled monotonically.
nscoord coordOutsideWidth = offsets.hPadding;
float pctOutsideWidth = offsets.hPctPadding;
float pctTotal = 0.0f;
if (boxSizing == NS_STYLE_BOX_SIZING_PADDING) {
min += coordOutsideWidth;
- result += coordOutsideWidth;
+ result = NSCoordSaturatingAdd(result, coordOutsideWidth);
pctTotal += pctOutsideWidth;
coordOutsideWidth = 0;
pctOutsideWidth = 0.0f;
}
coordOutsideWidth += offsets.hBorder;
if (boxSizing == NS_STYLE_BOX_SIZING_BORDER) {
min += coordOutsideWidth;
- result += coordOutsideWidth;
+ result = NSCoordSaturatingAdd(result, coordOutsideWidth);
pctTotal += pctOutsideWidth;
coordOutsideWidth = 0;
pctOutsideWidth = 0.0f;
}
coordOutsideWidth += offsets.hMargin;
pctOutsideWidth += offsets.hPctMargin;
min += coordOutsideWidth;
- result += coordOutsideWidth;
+ result = NSCoordSaturatingAdd(result, coordOutsideWidth);
pctTotal += pctOutsideWidth;
nscoord w;
if (GetAbsoluteCoord(styleWidth, aRenderingContext, aFrame, w) ||
GetIntrinsicCoord(styleWidth, aRenderingContext, aFrame,
PROP_WIDTH, w)) {
result = AddPercents(aType, w + coordOutsideWidth, pctOutsideWidth);
}
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2785,18 +2785,19 @@ nsFrame::AddInlineMinWidth(nsIRenderingC
}
/* virtual */ void
nsFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext,
nsIFrame::InlinePrefWidthData *aData)
{
aData->trailingWhitespace = 0;
aData->skipWhitespace = PR_FALSE;
- aData->currentLine += nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
- this, nsLayoutUtils::PREF_WIDTH);
+ nscoord myPref = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ this, nsLayoutUtils::PREF_WIDTH);
+ aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, myPref);
}
void
nsIFrame::InlineMinWidthData::ForceBreak(nsIRenderingContext *aRenderingContext)
{
currentLine -= trailingWhitespace;
prevLines = PR_MAX(prevLines, currentLine);
currentLine = trailingWhitespace = 0;
--- a/layout/tables/BasicTableLayoutStrategy.cpp
+++ b/layout/tables/BasicTableLayoutStrategy.cpp
@@ -214,17 +214,17 @@ GetWidthInfo(nsIRenderingContext *aRende
// XXX Should col frame have border/padding considered?
if (aIsCell) {
nsIFrame::IntrinsicWidthOffsetData offsets =
aFrame->IntrinsicWidthOffsets(aRenderingContext);
// XXX Should we ignore percentage padding?
nscoord add = offsets.hPadding + offsets.hBorder;
minCoord += add;
- prefCoord += add;
+ prefCoord = NSCoordSaturatingAdd(prefCoord, add);
}
return CellWidthInfo(minCoord, prefCoord, prefPercent, hasSpecifiedWidth);
}
static inline CellWidthInfo
GetCellWidthInfo(nsIRenderingContext *aRenderingContext,
nsTableCellFrame *aCellFrame)
@@ -485,30 +485,34 @@ BasicTableLayoutStrategy::ComputeColumnI
}
// combine the two min-width distributions, and record
// min and pref
nscoord allocatedMinWithinPref =
NSToCoordRound(float(minWithinPref) * minRatio);
nscoord allocatedMinOutsidePref =
NSToCoordRound(float(minOutsidePref) * coordRatio);
- nscoord allocatedPref =
- NSToCoordRound(float(info.prefCoord) * coordRatio);
+ nscoord allocatedPref =
+ (info.prefCoord == nscoord_MAX ?
+ nscoord_MAX :
+ NSToCoordRound(float(info.prefCoord) * coordRatio));
nscoord spanMin = scolFrame->GetMinCoord() +
allocatedMinWithinPref + allocatedMinOutsidePref;
nscoord spanPref = scolFrame->GetPrefCoord() + allocatedPref;
scolFrame->AddSpanCoords(spanMin, spanPref,
info.hasSpecifiedWidth);
// To avoid accumulating rounding error from division,
// subtract everything to do with the column we've
// passed from the totals.
minWithinPref -= allocatedMinWithinPref;
minOutsidePref -= allocatedMinOutsidePref;
- info.prefCoord -= allocatedPref;
+ info.prefCoord = NSCoordSaturatingSubtract(info.prefCoord,
+ allocatedPref,
+ nscoord_MAX);
info.prefPercent -= allocatedPct;
totalSPref -= scolFrame->GetPrefCoord();
totalSMin -= scolFrame->GetMinCoord();
if (!scolFrame->GetHasSpecifiedCoord()) {
totalSAutoPref -= scolFrame->GetPrefCoord();
}
if (scolFrame->GetPrefPercent() == 0.0f) {
totalSNonPctPref -= scolFrame->GetPrefCoord();
@@ -516,17 +520,18 @@ BasicTableLayoutStrategy::ComputeColumnI
}
}
// Note that we only distribute the percentage if
// spanHasNonPct.
NS_ASSERTION(totalSPref == 0 && totalSMin == 0 &&
totalSNonPctPref == 0 && nonPctCount == 0 &&
minOutsidePref == 0 && minWithinPref == 0 &&
- info.prefCoord == 0 &&
+ (info.prefCoord == 0 ||
+ info.prefCoord == nscoord_MAX) &&
(info.prefPercent == 0.0f || !spanHasNonPct),
"didn't subtract all that we added");
} while ((item = item->next));
// Combine the results of the span analysis into the main results,
// for each increment of colspan.
for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
@@ -588,17 +593,17 @@ BasicTableLayoutStrategy::ComputeIntrins
if (!colFrame) {
NS_ERROR("column frames out of sync with cell map");
continue;
}
if (mTableFrame->GetNumCellsOriginatingInCol(col)) {
add += spacing;
}
min += colFrame->GetMinCoord();
- pref += colFrame->GetPrefCoord();
+ pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord());
// Percentages are of the table, so we have to reverse them for
// intrinsic widths.
float p = colFrame->GetPrefPercent();
if (p > 0.0f) {
nscoord new_small_pct_expand =
nscoord(float(colFrame->GetPrefCoord()) / p);
if (new_small_pct_expand > max_small_pct_pref) {
@@ -635,18 +640,18 @@ BasicTableLayoutStrategy::ComputeIntrins
(1.0f - pct_total));
if (large_pct_pref > pref_pct_expand)
pref_pct_expand = large_pct_pref;
}
// border-spacing isn't part of the basis for percentages
if (colCount > 0) {
min += add;
- pref += add;
- pref_pct_expand += add;
+ pref = NSCoordSaturatingAdd(pref, add);
+ pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add);
}
mMinWidth = min;
mPrefWidth = pref;
mPrefWidthPctExpand = pref_pct_expand;
}
/* virtual */ void
@@ -744,16 +749,17 @@ BasicTableLayoutStrategy::ComputeColumnW
nscoord guess_min = 0,
guess_min_pct = 0,
guess_min_spec = 0,
guess_pref = 0,
total_flex_pref = 0,
total_fixed_pref = 0;
float total_pct = 0.0f; // 0.0f to 1.0f
+ PRInt32 numInfiniteWidthCols = 0;
PRInt32 col;
for (col = 0; col < colCount; ++col) {
nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
if (!colFrame) {
NS_ERROR("column frames out of sync with cell map");
continue;
}
@@ -764,29 +770,36 @@ BasicTableLayoutStrategy::ComputeColumnW
total_pct += pct;
nscoord val = nscoord(float(width) * pct);
if (val < min_width)
val = min_width;
guess_min_pct += val;
guess_pref += val;
} else {
nscoord pref_width = colFrame->GetPrefCoord();
- guess_pref += pref_width;
+ if (pref_width == nscoord_MAX) {
+ numInfiniteWidthCols++;
+ }
+ guess_pref = NSCoordSaturatingAdd(guess_pref, pref_width);
guess_min_pct += min_width;
if (colFrame->GetHasSpecifiedCoord()) {
// we'll add on the rest of guess_min_spec outside the
// loop
- guess_min_spec += pref_width - min_width;
- total_fixed_pref += pref_width;
+ nscoord delta = NSCoordSaturatingSubtract(pref_width,
+ min_width, 0);
+ guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta);
+ total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref,
+ pref_width);
} else {
- total_flex_pref += pref_width;
+ total_flex_pref = NSCoordSaturatingAdd(total_flex_pref,
+ pref_width);
}
}
}
- guess_min_spec += guess_min_pct;
+ guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct);
// Determine what we're flexing:
enum Loop2Type {
FLEX_PCT_SMALL, // between (1) and (2) above
FLEX_FIXED_SMALL, // between (2) and (3) above
FLEX_FLEX_SMALL, // between (3) and (4) above
FLEX_FLEX_LARGE, // above (4) above, case (a)
FLEX_FIXED_LARGE, // above (4) above, case (b)
@@ -807,23 +820,28 @@ BasicTableLayoutStrategy::ComputeColumnW
NS_ASSERTION(width >= guess_min, "bad width");
if (width < guess_min_pct) {
l2t = FLEX_PCT_SMALL;
space = width - guess_min;
basis.c = guess_min_pct - guess_min;
} else if (width < guess_min_spec) {
l2t = FLEX_FIXED_SMALL;
space = width - guess_min_pct;
- basis.c = guess_min_spec - guess_min_pct;
+ basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct,
+ nscoord_MAX);
} else {
l2t = FLEX_FLEX_SMALL;
space = width - guess_min_spec;
- basis.c = guess_pref - guess_min_spec;
+ basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec,
+ nscoord_MAX);
}
} else {
+ // Note: Shouldn't have to check for nscoord_MAX in this case, because
+ // width should be much less than nscoord_MAX, and being here means
+ // guess_pref is no larger than width.
space = width - guess_pref;
if (total_flex_pref > 0) {
l2t = FLEX_FLEX_LARGE;
basis.c = total_flex_pref;
} else if (total_fixed_pref > 0) {
l2t = FLEX_FIXED_LARGE;
basis.c = total_fixed_pref;
} else if (total_pct > 0.0f) {
@@ -898,21 +916,40 @@ BasicTableLayoutStrategy::ComputeColumnW
}
break;
case FLEX_FLEX_SMALL:
if (pct == 0.0f &&
!colFrame->GetHasSpecifiedCoord()) {
NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
"wrong width assigned");
nscoord col_min = colFrame->GetMinCoord();
- nscoord pref_minus_min = col_width - col_min;
+ nscoord pref_minus_min =
+ NSCoordSaturatingSubtract(col_width, col_min, 0);
col_width = col_width_before_adjust = col_min;
if (pref_minus_min != 0) {
float c = float(space) / float(basis.c);
- basis.c -= pref_minus_min;
+ // If we have infinite-width cols, then the standard
+ // adjustment to col_width using 'c' won't work,
+ // because basis.c and pref_minus_min are both
+ // nscoord_MAX and will cancel each other out in the
+ // col_width adjustment (making us assign all the
+ // space to the first inf-width col). To correct for
+ // this, we'll also divide by numInfiniteWidthCols to
+ // spread the space equally among the inf-width cols.
+ if (numInfiniteWidthCols) {
+ if (colFrame->GetPrefCoord() == nscoord_MAX) {
+ c = c / float(numInfiniteWidthCols);
+ numInfiniteWidthCols--;
+ } else {
+ c = 0.0f;
+ }
+ }
+ basis.c = NSCoordSaturatingSubtract(basis.c,
+ pref_minus_min,
+ nscoord_MAX);
col_width += NSToCoordRound(
float(pref_minus_min) * c);
}
}
break;
case FLEX_FLEX_LARGE:
if (pct == 0.0f &&
!colFrame->GetHasSpecifiedCoord()) {
@@ -966,15 +1003,15 @@ BasicTableLayoutStrategy::ComputeColumnW
colFrame->SetFinalWidth(col_width);
if (old_final != col_width)
mTableFrame->DidResizeColumns();
}
NS_ASSERTION(space == 0 &&
((l2t == FLEX_PCT_LARGE)
? (-0.001f < basis.f && basis.f < 0.001f)
- : (basis.c == 0)),
+ : (basis.c == 0 || basis.c == nscoord_MAX)),
"didn't subtract all that we added");
#ifdef DEBUG_TABLE_STRATEGY
printf("ComputeColumnWidths final\n");
mTableFrame->Dump(PR_FALSE, PR_TRUE, PR_FALSE);
#endif
}