layout/tables/BasicTableLayoutStrategy.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 // vim:cindent:ts=4:et:sw=4:
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /*
     8  * Web-compatible algorithms that determine column and table widths,
     9  * used for CSS2's 'table-layout: auto'.
    10  */
    12 #include "BasicTableLayoutStrategy.h"
    13 #include "nsTableFrame.h"
    14 #include "nsTableCellFrame.h"
    15 #include "nsLayoutUtils.h"
    16 #include "nsGkAtoms.h"
    17 #include "SpanningCellSorter.h"
    18 #include <algorithm>
    20 using namespace mozilla;
    21 using namespace mozilla::layout;
    23 namespace css = mozilla::css;
    25 #undef  DEBUG_TABLE_STRATEGY 
    27 BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame)
    28   : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto)
    29   , mTableFrame(aTableFrame)
    30 {
    31     MarkIntrinsicWidthsDirty();
    32 }
    34 /* virtual */
    35 BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
    36 {
    37 }
    39 /* virtual */ nscoord
    40 BasicTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext)
    41 {
    42     DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth);
    43     if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
    44         ComputeIntrinsicWidths(aRenderingContext);
    45     return mMinWidth;
    46 }
    48 /* virtual */ nscoord
    49 BasicTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext,
    50                                        bool aComputingSize)
    51 {
    52     DISPLAY_PREF_WIDTH(mTableFrame, mPrefWidth);
    53     NS_ASSERTION((mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN) ==
    54                  (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
    55                  "dirtyness out of sync");
    56     if (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
    57         ComputeIntrinsicWidths(aRenderingContext);
    58     return aComputingSize ? mPrefWidthPctExpand : mPrefWidth;
    59 }
    61 struct CellWidthInfo {
    62     CellWidthInfo(nscoord aMinCoord, nscoord aPrefCoord,
    63                   float aPrefPercent, bool aHasSpecifiedWidth)
    64         : hasSpecifiedWidth(aHasSpecifiedWidth)
    65         , minCoord(aMinCoord)
    66         , prefCoord(aPrefCoord)
    67         , prefPercent(aPrefPercent)
    68     {
    69     }
    71     bool hasSpecifiedWidth;
    72     nscoord minCoord;
    73     nscoord prefCoord;
    74     float prefPercent;
    75 };
    77 // Used for both column and cell calculations.  The parts needed only
    78 // for cells are skipped when aIsCell is false.
    79 static CellWidthInfo
    80 GetWidthInfo(nsRenderingContext *aRenderingContext,
    81              nsIFrame *aFrame, bool aIsCell)
    82 {
    83     nscoord minCoord, prefCoord;
    84     const nsStylePosition *stylePos = aFrame->StylePosition();
    85     bool isQuirks = aFrame->PresContext()->CompatibilityMode() ==
    86                     eCompatibility_NavQuirks;
    87     nscoord boxSizingToBorderEdge = 0;
    88     if (aIsCell) {
    89         // If aFrame is a container for font size inflation, then shrink
    90         // wrapping inside of it should not apply font size inflation.
    91         AutoMaybeDisableFontInflation an(aFrame);
    93         minCoord = aFrame->GetMinWidth(aRenderingContext);
    94         prefCoord = aFrame->GetPrefWidth(aRenderingContext);
    95         // Until almost the end of this function, minCoord and prefCoord
    96         // represent the box-sizing based width values (which mean they
    97         // should include horizontal padding and border width when
    98         // box-sizing is set to border-box).
    99         // Note that this function returns border-box width, we add the
   100         // outer edges near the end of this function.
   102         // XXX Should we ignore percentage padding?
   103         nsIFrame::IntrinsicWidthOffsetData offsets = aFrame->IntrinsicWidthOffsets(aRenderingContext);
   105         // In quirks mode, table cell width should be content-box,
   106         // but height should be border box.
   107         // Because of this historic anomaly, we do not use quirk.css.
   108         // (We can't specify one value of box-sizing for width and another
   109         // for height).
   110         // For this reason, we also do not use box-sizing for just one of
   111         // them, as this may be confusing.
   112         if (isQuirks) {
   113             boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder;
   114         }
   115         else {
   116             switch (stylePos->mBoxSizing) {
   117                 case NS_STYLE_BOX_SIZING_CONTENT:
   118                     boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder;
   119                     break;
   120                 case NS_STYLE_BOX_SIZING_PADDING:
   121                     minCoord += offsets.hPadding;
   122                     prefCoord += offsets.hPadding;
   123                     boxSizingToBorderEdge = offsets.hBorder;
   124                     break;
   125                 default:
   126                     // NS_STYLE_BOX_SIZING_BORDER
   127                     minCoord += offsets.hPadding + offsets.hBorder;
   128                     prefCoord += offsets.hPadding + offsets.hBorder;
   129                     break;
   130             }
   131         }
   132     } else {
   133         minCoord = 0;
   134         prefCoord = 0;
   135     }
   136     float prefPercent = 0.0f;
   137     bool hasSpecifiedWidth = false;
   139     const nsStyleCoord &width = stylePos->mWidth;
   140     nsStyleUnit unit = width.GetUnit();
   141     // NOTE: We're ignoring calc() units with percentages here, for lack of a
   142     // sensible idea for what to do with them.  This means calc() with
   143     // percentages is basically handled like 'auto' for table cells and
   144     // columns.
   145     if (width.ConvertsToLength()) {
   146         hasSpecifiedWidth = true;
   147         // Note: since ComputeWidthValue was designed to return content-box
   148         // width, it will (in some cases) subtract the box-sizing edges.
   149         // We prevent this unwanted behavior by calling it with
   150         // aContentEdgeToBoxSizing and aBoxSizingToMarginEdge set to 0.
   151         nscoord w = nsLayoutUtils::ComputeWidthValue(aRenderingContext,
   152                                                      aFrame, 0, 0, 0, width);
   153         // Quirk: A cell with "nowrap" set and a coord value for the
   154         // width which is bigger than the intrinsic minimum width uses
   155         // that coord value as the minimum width.
   156         // This is kept up-to-date with dynamic changes to nowrap by code in
   157         // nsTableCellFrame::AttributeChanged
   158         if (aIsCell && w > minCoord && isQuirks &&
   159             aFrame->GetContent()->HasAttr(kNameSpaceID_None,
   160                                           nsGkAtoms::nowrap)) {
   161             minCoord = w;
   162         }
   163         prefCoord = std::max(w, minCoord);
   164     } else if (unit == eStyleUnit_Percent) {
   165         prefPercent = width.GetPercentValue();
   166     } else if (unit == eStyleUnit_Enumerated && aIsCell) {
   167         switch (width.GetIntValue()) {
   168             case NS_STYLE_WIDTH_MAX_CONTENT:
   169                 // 'width' only affects pref width, not min
   170                 // width, so don't change anything
   171                 break;
   172             case NS_STYLE_WIDTH_MIN_CONTENT:
   173                 prefCoord = minCoord;
   174                 break;
   175             case NS_STYLE_WIDTH_FIT_CONTENT:
   176             case NS_STYLE_WIDTH_AVAILABLE:
   177                 // act just like 'width: auto'
   178                 break;
   179             default:
   180                 NS_NOTREACHED("unexpected enumerated value");
   181         }
   182     }
   184     nsStyleCoord maxWidth(stylePos->mMaxWidth);
   185     if (maxWidth.GetUnit() == eStyleUnit_Enumerated) {
   186         if (!aIsCell || maxWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE)
   187             maxWidth.SetNoneValue();
   188         else if (maxWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT)
   189             // for 'max-width', '-moz-fit-content' is like
   190             // '-moz-max-content'
   191             maxWidth.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT,
   192                                  eStyleUnit_Enumerated);
   193     }
   194     unit = maxWidth.GetUnit();
   195     // XXX To really implement 'max-width' well, we'd need to store
   196     // it separately on the columns.
   197     if (maxWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
   198         nscoord w =
   199             nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
   200                                              0, 0, 0, maxWidth);
   201         if (w < minCoord)
   202             minCoord = w;
   203         if (w < prefCoord)
   204             prefCoord = w;
   205     } else if (unit == eStyleUnit_Percent) {
   206         float p = stylePos->mMaxWidth.GetPercentValue();
   207         if (p < prefPercent)
   208             prefPercent = p;
   209     }
   210     // treat calc() with percentages on max-width just like 'none'.
   212     nsStyleCoord minWidth(stylePos->mMinWidth);
   213     if (minWidth.GetUnit() == eStyleUnit_Enumerated) {
   214         if (!aIsCell || minWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE)
   215             minWidth.SetCoordValue(0);
   216         else if (minWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT)
   217             // for 'min-width', '-moz-fit-content' is like
   218             // '-moz-min-content'
   219             minWidth.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT,
   220                                  eStyleUnit_Enumerated);
   221     }
   222     unit = minWidth.GetUnit();
   223     if (minWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
   224         nscoord w =
   225             nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
   226                                              0, 0, 0, minWidth);
   227         if (w > minCoord)
   228             minCoord = w;
   229         if (w > prefCoord)
   230             prefCoord = w;
   231     } else if (unit == eStyleUnit_Percent) {
   232         float p = stylePos->mMinWidth.GetPercentValue();
   233         if (p > prefPercent)
   234             prefPercent = p;
   235     }
   236     // treat calc() with percentages on min-width just like '0'.
   238     // XXX Should col frame have border/padding considered?
   239     if (aIsCell) {
   240         minCoord += boxSizingToBorderEdge;
   241         prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge);
   242     }
   244     return CellWidthInfo(minCoord, prefCoord, prefPercent, hasSpecifiedWidth);
   245 }
   247 static inline CellWidthInfo
   248 GetCellWidthInfo(nsRenderingContext *aRenderingContext,
   249                  nsTableCellFrame *aCellFrame)
   250 {
   251     return GetWidthInfo(aRenderingContext, aCellFrame, true);
   252 }
   254 static inline CellWidthInfo
   255 GetColWidthInfo(nsRenderingContext *aRenderingContext,
   256                 nsIFrame *aFrame)
   257 {
   258     return GetWidthInfo(aRenderingContext, aFrame, false);
   259 }
   262 /**
   263  * The algorithm in this function, in addition to meeting the
   264  * requirements of Web-compatibility, is also invariant under reordering
   265  * of the rows within a table (something that most, but not all, other
   266  * browsers are).
   267  */
   268 void
   269 BasicTableLayoutStrategy::ComputeColumnIntrinsicWidths(nsRenderingContext* aRenderingContext)
   270 {
   271     nsTableFrame *tableFrame = mTableFrame;
   272     nsTableCellMap *cellMap = tableFrame->GetCellMap();
   274     mozilla::AutoStackArena arena;
   275     SpanningCellSorter spanningCells;
   277     // Loop over the columns to consider the columns and cells *without*
   278     // a colspan.
   279     int32_t col, col_end;
   280     for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
   281         nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
   282         if (!colFrame) {
   283             NS_ERROR("column frames out of sync with cell map");
   284             continue;
   285         }
   286         colFrame->ResetIntrinsics();
   287         colFrame->ResetSpanIntrinsics();
   289         // Consider the widths on the column.
   290         CellWidthInfo colInfo = GetColWidthInfo(aRenderingContext, colFrame);
   291         colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
   292                             colInfo.hasSpecifiedWidth);
   293         colFrame->AddPrefPercent(colInfo.prefPercent);
   295         // Consider the widths on the column-group.  Note that we follow
   296         // what the HTML spec says here, and make the width apply to
   297         // each column in the group, not the group as a whole.
   299         // If column has width, column-group doesn't override width.
   300         if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 &&
   301             colInfo.prefPercent == 0.0f) {
   302             NS_ASSERTION(colFrame->GetParent()->GetType() ==
   303                              nsGkAtoms::tableColGroupFrame,
   304                          "expected a column-group");
   305             colInfo = GetColWidthInfo(aRenderingContext, colFrame->GetParent());
   306             colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
   307                                 colInfo.hasSpecifiedWidth);
   308             colFrame->AddPrefPercent(colInfo.prefPercent);
   309         }
   311         // Consider the contents of and the widths on the cells without
   312         // colspans.
   313         nsCellMapColumnIterator columnIter(cellMap, col);
   314         int32_t row, colSpan;
   315         nsTableCellFrame* cellFrame;
   316         while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) {
   317             if (colSpan > 1) {
   318                 spanningCells.AddCell(colSpan, row, col);
   319                 continue;
   320             }
   322             CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame);
   324             colFrame->AddCoords(info.minCoord, info.prefCoord,
   325                                 info.hasSpecifiedWidth);
   326             colFrame->AddPrefPercent(info.prefPercent);
   327         }
   328 #ifdef DEBUG_dbaron_off
   329         printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n",
   330                mTableFrame, col, colFrame->GetMinCoord(),
   331                colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
   332                colFrame->GetPrefPercent());
   333 #endif
   334     }
   335 #ifdef DEBUG_TABLE_STRATEGY
   336     printf("ComputeColumnIntrinsicWidths single\n");
   337     mTableFrame->Dump(false, true, false);
   338 #endif
   340     // Consider the cells with a colspan that we saved in the loop above
   341     // into the spanning cell sorter.  We consider these cells by seeing
   342     // if they require adding to the widths resulting only from cells
   343     // with a smaller colspan, and therefore we must process them sorted
   344     // in increasing order by colspan.  For each colspan group, we
   345     // accumulate new values to accumulate in the column frame's Span*
   346     // members.
   347     //
   348     // Considering things only relative to the widths resulting from
   349     // cells with smaller colspans (rather than incrementally including
   350     // the results from spanning cells, or doing spanning and
   351     // non-spanning cells in a single pass) means that layout remains
   352     // row-order-invariant and (except for percentage widths that add to
   353     // more than 100%) column-order invariant.
   354     //
   355     // Starting with smaller colspans makes it more likely that we
   356     // satisfy all the constraints given and don't distribute space to
   357     // columns where we don't need it.
   358     SpanningCellSorter::Item *item;
   359     int32_t colSpan;
   360     while ((item = spanningCells.GetNext(&colSpan))) {
   361         NS_ASSERTION(colSpan > 1,
   362                      "cell should not have been put in spanning cell sorter");
   363         do {
   364             int32_t row = item->row;
   365             col = item->col;
   366             CellData *cellData = cellMap->GetDataAt(row, col);
   367             NS_ASSERTION(cellData && cellData->IsOrig(),
   368                          "bogus result from spanning cell sorter");
   370             nsTableCellFrame *cellFrame = cellData->GetCellFrame();
   371             NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter");
   373             CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame);
   375             if (info.prefPercent > 0.0f) {
   376                 DistributePctWidthToColumns(info.prefPercent,
   377                                             col, colSpan);
   378             }
   379             DistributeWidthToColumns(info.minCoord, col, colSpan, 
   380                                      BTLS_MIN_WIDTH, info.hasSpecifiedWidth);
   381             DistributeWidthToColumns(info.prefCoord, col, colSpan, 
   382                                      BTLS_PREF_WIDTH, info.hasSpecifiedWidth);
   383         } while ((item = item->next));
   385         // Combine the results of the span analysis into the main results,
   386         // for each increment of colspan.
   388         for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
   389             nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
   390             if (!colFrame) {
   391                 NS_ERROR("column frames out of sync with cell map");
   392                 continue;
   393             }
   395             colFrame->AccumulateSpanIntrinsics();
   396             colFrame->ResetSpanIntrinsics();
   398 #ifdef DEBUG_dbaron_off
   399             printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n",
   400                    mTableFrame, col, colSpan, colFrame->GetMinCoord(),
   401                    colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
   402                    colFrame->GetPrefPercent());
   403 #endif
   404         }
   405     }
   407     // Prevent percentages from adding to more than 100% by (to be
   408     // compatible with other browsers) treating any percentages that would
   409     // increase the total percentage to more than 100% as the number that
   410     // would increase it to only 100% (which is 0% if we've already hit
   411     // 100%).  This means layout depends on the order of columns.
   412     float pct_used = 0.0f;
   413     for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
   414         nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
   415         if (!colFrame) {
   416             NS_ERROR("column frames out of sync with cell map");
   417             continue;
   418         }
   420         colFrame->AdjustPrefPercent(&pct_used);
   421     }
   423 #ifdef DEBUG_TABLE_STRATEGY
   424     printf("ComputeColumnIntrinsicWidths spanning\n");
   425     mTableFrame->Dump(false, true, false);
   426 #endif
   427 }
   429 void
   430 BasicTableLayoutStrategy::ComputeIntrinsicWidths(nsRenderingContext* aRenderingContext)
   431 {
   432     ComputeColumnIntrinsicWidths(aRenderingContext);
   434     nsTableCellMap *cellMap = mTableFrame->GetCellMap();
   435     nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0;
   436     float pct_total = 0.0f; // always from 0.0f - 1.0f
   437     int32_t colCount = cellMap->GetColCount();
   438     nscoord spacing = mTableFrame->GetCellSpacingX();
   439     nscoord add = spacing; // add (colcount + 1) * spacing for columns 
   440                            // where a cell originates
   442     for (int32_t col = 0; col < colCount; ++col) {
   443         nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
   444         if (!colFrame) {
   445             NS_ERROR("column frames out of sync with cell map");
   446             continue;
   447         }
   448         if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
   449             add += spacing;
   450         }
   451         min += colFrame->GetMinCoord();
   452         pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord());
   454         // Percentages are of the table, so we have to reverse them for
   455         // intrinsic widths.
   456         float p = colFrame->GetPrefPercent();
   457         if (p > 0.0f) {
   458             nscoord colPref = colFrame->GetPrefCoord();
   459             nscoord new_small_pct_expand = 
   460                 (colPref == nscoord_MAX ?
   461                  nscoord_MAX : nscoord(float(colPref) / p));
   462             if (new_small_pct_expand > max_small_pct_pref) {
   463                 max_small_pct_pref = new_small_pct_expand;
   464             }
   465             pct_total += p;
   466         } else {
   467             nonpct_pref_total = NSCoordSaturatingAdd(nonpct_pref_total, 
   468                                                      colFrame->GetPrefCoord());
   469         }
   470     }
   472     nscoord pref_pct_expand = pref;
   474     // Account for small percentages expanding the preferred width of
   475     // *other* columns.
   476     if (max_small_pct_pref > pref_pct_expand) {
   477         pref_pct_expand = max_small_pct_pref;
   478     }
   480     // Account for large percentages expanding the preferred width of
   481     // themselves.  There's no need to iterate over the columns multiple
   482     // times, since when there is such a need, the small percentage
   483     // effect is bigger anyway.  (I think!)
   484     NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f,
   485                  "column percentage widths not adjusted down to 100%");
   486     if (pct_total == 1.0f) {
   487         if (nonpct_pref_total > 0) {
   488             pref_pct_expand = nscoord_MAX;
   489             // XXX Or should I use some smaller value?  (Test this using
   490             // nested tables!)
   491         }
   492     } else {
   493         nscoord large_pct_pref =
   494             (nonpct_pref_total == nscoord_MAX ?
   495              nscoord_MAX :
   496              nscoord(float(nonpct_pref_total) / (1.0f - pct_total)));
   497         if (large_pct_pref > pref_pct_expand)
   498             pref_pct_expand = large_pct_pref;
   499     }
   501     // border-spacing isn't part of the basis for percentages
   502     if (colCount > 0) {
   503         min += add;
   504         pref = NSCoordSaturatingAdd(pref, add);
   505         pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add);
   506     }
   508     mMinWidth = min;
   509     mPrefWidth = pref;
   510     mPrefWidthPctExpand = pref_pct_expand;
   511 }
   513 /* virtual */ void
   514 BasicTableLayoutStrategy::MarkIntrinsicWidthsDirty()
   515 {
   516     mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
   517     mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
   518     mPrefWidthPctExpand = NS_INTRINSIC_WIDTH_UNKNOWN;
   519     mLastCalcWidth = nscoord_MIN;
   520 }
   522 /* virtual */ void
   523 BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState)
   524 {
   525     nscoord width = aReflowState.ComputedWidth();
   527     if (mLastCalcWidth == width)
   528         return;
   529     mLastCalcWidth = width;
   531     NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) ==
   532                  (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN),
   533                  "dirtyness out of sync");
   534     NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) ==
   535                  (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
   536                  "dirtyness out of sync");
   537     // XXX Is this needed?
   538     if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
   539         ComputeIntrinsicWidths(aReflowState.rendContext);
   541     nsTableCellMap *cellMap = mTableFrame->GetCellMap();
   542     int32_t colCount = cellMap->GetColCount();
   543     if (colCount <= 0)
   544         return; // nothing to do
   546     DistributeWidthToColumns(width, 0, colCount, BTLS_FINAL_WIDTH, false);
   548 #ifdef DEBUG_TABLE_STRATEGY
   549     printf("ComputeColumnWidths final\n");
   550     mTableFrame->Dump(false, true, false);
   551 #endif
   552 }
   554 void
   555 BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct,
   556                                                       int32_t aFirstCol,
   557                                                       int32_t aColCount)
   558 {
   559     // First loop to determine:
   560     int32_t nonPctColCount = 0; // number of spanned columns without % width
   561     nscoord nonPctTotalPrefWidth = 0; // total pref width of those columns
   562     // and to reduce aSpanPrefPct by columns that already have % width
   564     int32_t scol, scol_end;
   565     nsTableCellMap *cellMap = mTableFrame->GetCellMap();
   566     for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
   567          scol < scol_end; ++scol) {
   568         nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
   569         if (!scolFrame) {
   570             NS_ERROR("column frames out of sync with cell map");
   571             continue;
   572         }
   573         float scolPct = scolFrame->GetPrefPercent();
   574         if (scolPct == 0.0f) {
   575             nonPctTotalPrefWidth += scolFrame->GetPrefCoord();
   576             if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
   577                 ++nonPctColCount;
   578             }
   579         } else {
   580             aSpanPrefPct -= scolPct;
   581         }
   582     }
   584     if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) {
   585         // There's no %-width on the colspan left over to distribute,
   586         // or there are no columns to which we could distribute %-width
   587         return;
   588     }
   590     // Second loop, to distribute what remains of aSpanPrefPct
   591     // between the non-percent-width spanned columns
   592     const bool spanHasNonPctPref = nonPctTotalPrefWidth > 0; // Loop invariant
   593     for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
   594          scol < scol_end; ++scol) {
   595         nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
   596         if (!scolFrame) {
   597             NS_ERROR("column frames out of sync with cell map");
   598             continue;
   599         }
   601         if (scolFrame->GetPrefPercent() == 0.0f) {
   602             NS_ASSERTION((!spanHasNonPctPref ||
   603                           nonPctTotalPrefWidth != 0) &&
   604                          nonPctColCount != 0,
   605                          "should not be zero if we haven't allocated "
   606                          "all pref percent");
   608             float allocatedPct; // % width to be given to this column
   609             if (spanHasNonPctPref) {
   610                 // Group so we're multiplying by 1.0f when we need
   611                 // to use up aSpanPrefPct.
   612                 allocatedPct = aSpanPrefPct *
   613                     (float(scolFrame->GetPrefCoord()) /
   614                      float(nonPctTotalPrefWidth));
   615             } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
   616                 // distribute equally when all pref widths are 0
   617                 allocatedPct = aSpanPrefPct / float(nonPctColCount);
   618             } else {
   619                 allocatedPct = 0.0f;
   620             }
   621             // Allocate the percent
   622             scolFrame->AddSpanPrefPercent(allocatedPct);
   624             // To avoid accumulating rounding error from division,
   625             // subtract this column's values from the totals.
   626             aSpanPrefPct -= allocatedPct;
   627             nonPctTotalPrefWidth -= scolFrame->GetPrefCoord();
   628             if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
   629                 --nonPctColCount;
   630             }
   632             if (!aSpanPrefPct) {
   633                 // No more span-percent-width to distribute --> we're done.
   634                 NS_ASSERTION(spanHasNonPctPref ? 
   635                              nonPctTotalPrefWidth == 0 :
   636                              nonPctColCount == 0,
   637                              "No more pct width to distribute, but there are "
   638                              "still cols that need some.");
   639                 return;
   640             }
   641         }
   642     }
   643 }
   645 void
   646 BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth, 
   647                                                    int32_t aFirstCol, 
   648                                                    int32_t aColCount,
   649                                                    BtlsWidthType aWidthType,
   650                                                    bool aSpanHasSpecifiedWidth)
   651 {
   652     NS_ASSERTION(aWidthType != BTLS_FINAL_WIDTH || 
   653                  (aFirstCol == 0 && 
   654                   aColCount == mTableFrame->GetCellMap()->GetColCount()),
   655             "Computing final column widths, but didn't get full column range");
   657     // border-spacing isn't part of the basis for percentages.
   658     nscoord spacing = mTableFrame->GetCellSpacingX();
   659     nscoord subtract = 0;    
   660     // aWidth initially includes border-spacing for the boundaries in between
   661     // each of the columns. We start at aFirstCol + 1 because the first
   662     // in-between boundary would be at the left edge of column aFirstCol + 1
   663     for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) {
   664         if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
   665             subtract += spacing;
   666         }
   667     }
   668     if (aWidthType == BTLS_FINAL_WIDTH) {
   669         // If we're computing final col-width, then aWidth initially includes
   670         // border spacing on the table's far left + far right edge, too.  Need
   671         // to subtract those out, too.
   672         subtract += spacing * 2;
   673     }
   674     aWidth = NSCoordSaturatingSubtract(aWidth, subtract, nscoord_MAX);
   676     /*
   677      * The goal of this function is to distribute |aWidth| between the
   678      * columns by making an appropriate AddSpanCoords or SetFinalWidth
   679      * call for each column.  (We call AddSpanCoords if we're 
   680      * distributing a column-spanning cell's minimum or preferred width
   681      * to its spanned columns.  We call SetFinalWidth if we're 
   682      * distributing a table's final width to its columns.)
   683      *
   684      * The idea is to either assign one of the following sets of widths
   685      * or a weighted average of two adjacent sets of widths.  It is not
   686      * possible to assign values smaller than the smallest set of
   687      * widths.  However, see below for handling the case of assigning
   688      * values larger than the largest set of widths.  From smallest to
   689      * largest, these are:
   690      *
   691      * 1. [guess_min] Assign all columns their min width.
   692      *
   693      * 2. [guess_min_pct] Assign all columns with percentage widths
   694      * their percentage width, and all other columns their min width.
   695      *
   696      * 3. [guess_min_spec] Assign all columns with percentage widths
   697      * their percentage width, all columns with specified coordinate
   698      * widths their pref width (since it doesn't matter whether it's the
   699      * largest contributor to the pref width that was the specified
   700      * contributor), and all other columns their min width.
   701      *
   702      * 4. [guess_pref] Assign all columns with percentage widths their
   703      * specified width, and all other columns their pref width.
   704      *
   705      * If |aWidth| is *larger* than what we would assign in (4), then we
   706      * expand the columns:
   707      *
   708      *   a. if any columns without a specified coordinate width or
   709      *   percent width have nonzero pref width, in proportion to pref
   710      *   width [total_flex_pref]
   711      *
   712      *   b. otherwise, if any columns without a specified coordinate
   713      *   width or percent width, but with cells originating in them,
   714      *   have zero pref width, equally between these
   715      *   [numNonSpecZeroWidthCols]
   716      *
   717      *   c. otherwise, if any columns without percent width have nonzero
   718      *   pref width, in proportion to pref width [total_fixed_pref]
   719      *
   720      *   d. otherwise, if any columns have nonzero percentage widths, in
   721      *   proportion to the percentage widths [total_pct]
   722      *
   723      *   e. otherwise, equally.
   724      */
   726     // Loop #1 over the columns, to figure out the four values above so
   727     // we know which case we're dealing with.
   729     nscoord guess_min = 0,
   730             guess_min_pct = 0,
   731             guess_min_spec = 0,
   732             guess_pref = 0,
   733             total_flex_pref = 0,
   734             total_fixed_pref = 0;
   735     float total_pct = 0.0f; // 0.0f to 1.0f
   736     int32_t numInfiniteWidthCols = 0;
   737     int32_t numNonSpecZeroWidthCols = 0;
   739     int32_t col;
   740     nsTableCellMap *cellMap = mTableFrame->GetCellMap();
   741     for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
   742         nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
   743         if (!colFrame) {
   744             NS_ERROR("column frames out of sync with cell map");
   745             continue;
   746         }
   747         nscoord min_width = colFrame->GetMinCoord();
   748         guess_min += min_width;
   749         if (colFrame->GetPrefPercent() != 0.0f) {
   750             float pct = colFrame->GetPrefPercent();
   751             total_pct += pct;
   752             nscoord val = nscoord(float(aWidth) * pct);
   753             if (val < min_width)
   754                 val = min_width;
   755             guess_min_pct += val;
   756             guess_pref = NSCoordSaturatingAdd(guess_pref, val);
   757         } else {
   758             nscoord pref_width = colFrame->GetPrefCoord();
   759             if (pref_width == nscoord_MAX) {
   760                 ++numInfiniteWidthCols;
   761             }
   762             guess_pref = NSCoordSaturatingAdd(guess_pref, pref_width);
   763             guess_min_pct += min_width;
   764             if (colFrame->GetHasSpecifiedCoord()) {
   765                 // we'll add on the rest of guess_min_spec outside the
   766                 // loop
   767                 nscoord delta = NSCoordSaturatingSubtract(pref_width, 
   768                                                           min_width, 0);
   769                 guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta);
   770                 total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref, 
   771                                                         pref_width);
   772             } else if (pref_width == 0) {
   773                 if (cellMap->GetNumCellsOriginatingInCol(col) > 0) {
   774                     ++numNonSpecZeroWidthCols;
   775                 }
   776             } else {
   777                 total_flex_pref = NSCoordSaturatingAdd(total_flex_pref,
   778                                                        pref_width);
   779             }
   780         }
   781     }
   782     guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct);
   784     // Determine what we're flexing:
   785     enum Loop2Type {
   786         FLEX_PCT_SMALL, // between (1) and (2) above
   787         FLEX_FIXED_SMALL, // between (2) and (3) above
   788         FLEX_FLEX_SMALL, // between (3) and (4) above
   789         FLEX_FLEX_LARGE, // greater than (4) above, case (a)
   790         FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b)
   791         FLEX_FIXED_LARGE, // greater than (4) above, case (c)
   792         FLEX_PCT_LARGE, // greater than (4) above, case (d)
   793         FLEX_ALL_LARGE // greater than (4) above, case (e)
   794     };
   796     Loop2Type l2t;
   797     // These are constants (over columns) for each case's math.  We use
   798     // a pair of nscoords rather than a float so that we can subtract
   799     // each column's allocation so we avoid accumulating rounding error.
   800     nscoord space; // the amount of extra width to allocate
   801     union {
   802         nscoord c;
   803         float f;
   804     } basis; // the sum of the statistic over columns to divide it
   805     if (aWidth < guess_pref) {
   806         if (aWidthType != BTLS_FINAL_WIDTH && aWidth <= guess_min) {
   807             // Return early -- we don't have any extra space to distribute.
   808             return;
   809         }
   810         NS_ASSERTION(!(aWidthType == BTLS_FINAL_WIDTH && aWidth < guess_min),
   811                      "Table width is less than the "
   812                      "sum of its columns' min widths");
   813         if (aWidth < guess_min_pct) {
   814             l2t = FLEX_PCT_SMALL;
   815             space = aWidth - guess_min;
   816             basis.c = guess_min_pct - guess_min;
   817         } else if (aWidth < guess_min_spec) {
   818             l2t = FLEX_FIXED_SMALL;
   819             space = aWidth - guess_min_pct;
   820             basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct,
   821                                                 nscoord_MAX);
   822         } else {
   823             l2t = FLEX_FLEX_SMALL;
   824             space = aWidth - guess_min_spec;
   825             basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec,
   826                                                 nscoord_MAX);
   827         }
   828     } else {
   829         space = NSCoordSaturatingSubtract(aWidth, guess_pref, nscoord_MAX);
   830         if (total_flex_pref > 0) {
   831             l2t = FLEX_FLEX_LARGE;
   832             basis.c = total_flex_pref;
   833         } else if (numNonSpecZeroWidthCols > 0) {
   834             l2t = FLEX_FLEX_LARGE_ZERO;
   835             basis.c = numNonSpecZeroWidthCols;
   836         } else if (total_fixed_pref > 0) {
   837             l2t = FLEX_FIXED_LARGE;
   838             basis.c = total_fixed_pref;
   839         } else if (total_pct > 0.0f) {
   840             l2t = FLEX_PCT_LARGE;
   841             basis.f = total_pct;
   842         } else {
   843             l2t = FLEX_ALL_LARGE;
   844             basis.c = aColCount;
   845         }
   846     }
   848 #ifdef DEBUG_dbaron_off
   849     printf("ComputeColumnWidths: %d columns in width %d,\n"
   850            "  guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n"
   851            "  l2t=%d, space=%d, basis.c=%d\n",
   852            aColCount, aWidth,
   853            guess_min, guess_min_pct, guess_min_spec, guess_pref,
   854            total_flex_pref, total_fixed_pref, total_pct,
   855            l2t, space, basis.c);
   856 #endif
   858     for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
   859         nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
   860         if (!colFrame) {
   861             NS_ERROR("column frames out of sync with cell map");
   862             continue;
   863         }
   864         nscoord col_width;
   866         float pct = colFrame->GetPrefPercent();
   867         if (pct != 0.0f) {
   868             col_width = nscoord(float(aWidth) * pct);
   869             nscoord col_min = colFrame->GetMinCoord();
   870             if (col_width < col_min)
   871                 col_width = col_min;
   872         } else {
   873             col_width = colFrame->GetPrefCoord();
   874         }
   876         nscoord col_width_before_adjust = col_width;
   878         switch (l2t) {
   879             case FLEX_PCT_SMALL:
   880                 col_width = col_width_before_adjust = colFrame->GetMinCoord();
   881                 if (pct != 0.0f) {
   882                     nscoord pct_minus_min =
   883                         nscoord(float(aWidth) * pct) - col_width;
   884                     if (pct_minus_min > 0) {
   885                         float c = float(space) / float(basis.c);
   886                         basis.c -= pct_minus_min;
   887                         col_width += NSToCoordRound(float(pct_minus_min) * c);
   888                     }
   889                 }
   890                 break;
   891             case FLEX_FIXED_SMALL:
   892                 if (pct == 0.0f) {
   893                     NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
   894                                  "wrong width assigned");
   895                     if (colFrame->GetHasSpecifiedCoord()) {
   896                         nscoord col_min = colFrame->GetMinCoord();
   897                         nscoord pref_minus_min = col_width - col_min;
   898                         col_width = col_width_before_adjust = col_min;
   899                         if (pref_minus_min != 0) {
   900                             float c = float(space) / float(basis.c);
   901                             basis.c -= pref_minus_min;
   902                             col_width += NSToCoordRound(
   903                                 float(pref_minus_min) * c);
   904                         }
   905                     } else
   906                         col_width = col_width_before_adjust =
   907                             colFrame->GetMinCoord();
   908                 }
   909                 break;
   910             case FLEX_FLEX_SMALL:
   911                 if (pct == 0.0f &&
   912                     !colFrame->GetHasSpecifiedCoord()) {
   913                     NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
   914                                  "wrong width assigned");
   915                     nscoord col_min = colFrame->GetMinCoord();
   916                     nscoord pref_minus_min = 
   917                         NSCoordSaturatingSubtract(col_width, col_min, 0);
   918                     col_width = col_width_before_adjust = col_min;
   919                     if (pref_minus_min != 0) {
   920                         float c = float(space) / float(basis.c);
   921                         // If we have infinite-width cols, then the standard
   922                         // adjustment to col_width using 'c' won't work,
   923                         // because basis.c and pref_minus_min are both
   924                         // nscoord_MAX and will cancel each other out in the
   925                         // col_width adjustment (making us assign all the
   926                         // space to the first inf-width col).  To correct for
   927                         // this, we'll also divide by numInfiniteWidthCols to
   928                         // spread the space equally among the inf-width cols.
   929                         if (numInfiniteWidthCols) {
   930                             if (colFrame->GetPrefCoord() == nscoord_MAX) {
   931                                 c = c / float(numInfiniteWidthCols);
   932                                 --numInfiniteWidthCols;
   933                             } else {
   934                                 c = 0.0f;
   935                             }
   936                         }
   937                         basis.c = NSCoordSaturatingSubtract(basis.c, 
   938                                                             pref_minus_min,
   939                                                             nscoord_MAX);
   940                         col_width += NSToCoordRound(
   941                             float(pref_minus_min) * c);
   942                     }
   943                 }
   944                 break;
   945             case FLEX_FLEX_LARGE:
   946                 if (pct == 0.0f &&
   947                     !colFrame->GetHasSpecifiedCoord()) {
   948                     NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
   949                                  "wrong width assigned");
   950                     if (col_width != 0) {
   951                         if (space == nscoord_MAX) {
   952                             basis.c -= col_width;
   953                             col_width = nscoord_MAX;
   954                         } else {
   955                             float c = float(space) / float(basis.c);
   956                             basis.c -= col_width;
   957                             col_width += NSToCoordRound(float(col_width) * c);
   958                         }
   959                     }
   960                 }
   961                 break;
   962             case FLEX_FLEX_LARGE_ZERO:
   963                 if (pct == 0.0f &&
   964                     !colFrame->GetHasSpecifiedCoord() &&
   965                     cellMap->GetNumCellsOriginatingInCol(col) > 0) {
   967                     NS_ASSERTION(col_width == 0 &&
   968                                  colFrame->GetPrefCoord() == 0,
   969                                  "Since we're in FLEX_FLEX_LARGE_ZERO case, "
   970                                  "all auto-width cols should have zero pref "
   971                                  "width.");
   972                     float c = float(space) / float(basis.c);
   973                     col_width += NSToCoordRound(c);
   974                     --basis.c;
   975                 }
   976                 break;
   977             case FLEX_FIXED_LARGE:
   978                 if (pct == 0.0f) {
   979                     NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
   980                                  "wrong width assigned");
   981                     NS_ASSERTION(colFrame->GetHasSpecifiedCoord() ||
   982                                  colFrame->GetPrefCoord() == 0,
   983                                  "wrong case");
   984                     if (col_width != 0) {
   985                         float c = float(space) / float(basis.c);
   986                         basis.c -= col_width;
   987                         col_width += NSToCoordRound(float(col_width) * c);
   988                     }
   989                 }
   990                 break;
   991             case FLEX_PCT_LARGE:
   992                 NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0,
   993                              "wrong case");
   994                 if (pct != 0.0f) {
   995                     float c = float(space) / basis.f;
   996                     col_width += NSToCoordRound(pct * c);
   997                     basis.f -= pct;
   998                 }
   999                 break;
  1000             case FLEX_ALL_LARGE:
  1002                     float c = float(space) / float(basis.c);
  1003                     col_width += NSToCoordRound(c);
  1004                     --basis.c;
  1006                 break;
  1009         // Only subtract from space if it's a real number.
  1010         if (space != nscoord_MAX) {
  1011             NS_ASSERTION(col_width != nscoord_MAX,
  1012                  "How is col_width nscoord_MAX if space isn't?");
  1013             NS_ASSERTION(col_width_before_adjust != nscoord_MAX,
  1014                  "How is col_width_before_adjust nscoord_MAX if space isn't?");
  1015             space -= col_width - col_width_before_adjust;
  1018         NS_ASSERTION(col_width >= colFrame->GetMinCoord(),
  1019                      "assigned width smaller than min");
  1021         // Apply the new width
  1022         switch (aWidthType) {
  1023             case BTLS_MIN_WIDTH:
  1025                     // Note: AddSpanCoords requires both a min and pref width.
  1026                     // For the pref width, we'll just pass in our computed
  1027                     // min width, because the real pref width will be at least
  1028                     // as big
  1029                     colFrame->AddSpanCoords(col_width, col_width, 
  1030                                             aSpanHasSpecifiedWidth);
  1032                 break;
  1033             case BTLS_PREF_WIDTH:
  1035                     // Note: AddSpanCoords requires both a min and pref width.
  1036                     // For the min width, we'll just pass in 0, because
  1037                     // the real min width will be at least 0
  1038                     colFrame->AddSpanCoords(0, col_width, 
  1039                                             aSpanHasSpecifiedWidth);
  1041                 break;
  1042             case BTLS_FINAL_WIDTH:
  1044                     nscoord old_final = colFrame->GetFinalWidth();
  1045                     colFrame->SetFinalWidth(col_width);
  1047                     if (old_final != col_width)
  1048                         mTableFrame->DidResizeColumns();
  1050                 break;                
  1053     NS_ASSERTION((space == 0 || space == nscoord_MAX) &&
  1054                  ((l2t == FLEX_PCT_LARGE)
  1055                     ? (-0.001f < basis.f && basis.f < 0.001f)
  1056                     : (basis.c == 0 || basis.c == nscoord_MAX)),
  1057                  "didn't subtract all that we added");

mercurial