tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

BasicTableLayoutStrategy.cpp (40637B)


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