Bug 367673, Handle width-computation arithmetic with nscoord_MAX. r=roc sr=roc a1.9=roc
authordholbert@cs.stanford.edu
Mon, 24 Sep 2007 10:30:42 -0700
changeset 6254 7ba94b4dd00c28b7c56754ee6f63274c4726e768
parent 6253 1f9b0652bcf52204f637791e42b12fb53bb5965b
child 6255 31ab9783002ae5f241ce8245352b9d483551bda7
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, roc
bugs367673
milestone1.9a9pre
Bug 367673, Handle width-computation arithmetic with nscoord_MAX. r=roc sr=roc a1.9=roc
gfx/public/nsCoord.h
layout/base/nsLayoutUtils.cpp
layout/generic/nsFrame.cpp
layout/tables/BasicTableLayoutStrategy.cpp
--- 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
 }