tor-browser

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

FixedTableLayoutStrategy.cpp (15291B)


      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 * Algorithms that determine column and table inline sizes used for
      9 * CSS2's 'table-layout: fixed'.
     10 */
     11 
     12 #include "FixedTableLayoutStrategy.h"
     13 
     14 #include <algorithm>
     15 
     16 #include "WritingModes.h"
     17 #include "nsLayoutUtils.h"
     18 #include "nsStyleConsts.h"
     19 #include "nsTableCellFrame.h"
     20 #include "nsTableColFrame.h"
     21 #include "nsTableFrame.h"
     22 
     23 using namespace mozilla;
     24 
     25 FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame* aTableFrame)
     26    : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed),
     27      mTableFrame(aTableFrame) {
     28  MarkIntrinsicISizesDirty();
     29 }
     30 
     31 /* virtual */
     32 FixedTableLayoutStrategy::~FixedTableLayoutStrategy() = default;
     33 
     34 /* virtual */
     35 nscoord FixedTableLayoutStrategy::GetMinISize(gfxContext* aRenderingContext) {
     36  if (mMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
     37    return mMinISize;
     38  }
     39 
     40  // It's theoretically possible to do something much better here that
     41  // depends only on the columns and the first row (where we look at
     42  // intrinsic inline sizes inside the first row and then reverse the
     43  // algorithm to find the narrowest inline size that would hold all of
     44  // those intrinsic inline sizes), but it wouldn't be compatible with
     45  // other browsers, or with the use of GetMinISize by
     46  // nsTableFrame::ComputeSize to determine the inline size of a fixed
     47  // layout table, since CSS2.1 says:
     48  //   The width of the table is then the greater of the value of the
     49  //   'width' property for the table element and the sum of the column
     50  //   widths (plus cell spacing or borders).
     51 
     52  // XXX Should we really ignore 'min-inline-size' and 'max-inline-size'?
     53  // XXX Should we really ignore inline sizes on column groups?
     54 
     55  nsTableCellMap* cellMap = mTableFrame->GetCellMap();
     56  int32_t colCount = cellMap->GetColCount();
     57 
     58  nscoord result = 0;
     59 
     60  if (colCount > 0) {
     61    result += mTableFrame->GetColSpacing(-1, colCount);
     62  }
     63 
     64  WritingMode wm = mTableFrame->GetWritingMode();
     65  for (int32_t col = 0; col < colCount; ++col) {
     66    nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
     67    if (!colFrame) {
     68      NS_ERROR("column frames out of sync with cell map");
     69      continue;
     70    }
     71    nscoord spacing = mTableFrame->GetColSpacing(col);
     72    auto styleISize = colFrame->StylePosition()->ISize(
     73        wm, AnchorPosResolutionParams::From(colFrame));
     74    if (styleISize->ConvertsToLength()) {
     75      result += styleISize->ToLength();
     76    } else if (styleISize->ConvertsToPercentage()) {
     77      // do nothing
     78    } else {
     79      // The 'table-layout: fixed' algorithm considers only cells in the
     80      // first row.
     81      bool originates;
     82      int32_t colSpan;
     83      nsTableCellFrame* cellFrame =
     84          cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
     85      if (cellFrame) {
     86        styleISize = cellFrame->StylePosition()->ISize(
     87            wm, AnchorPosResolutionParams::From(cellFrame));
     88        if (styleISize->ConvertsToLength() || styleISize->IsMinContent() ||
     89            styleISize->IsMaxContent()) {
     90          nscoord cellISize = nsLayoutUtils::IntrinsicForContainer(
     91              aRenderingContext, cellFrame, IntrinsicISizeType::MinISize);
     92          if (colSpan > 1) {
     93            // If a column-spanning cell is in the first row, split up
     94            // the space evenly.  (XXX This isn't quite right if some of
     95            // the columns it's in have specified inline sizes.  Should
     96            // we care?)
     97            cellISize = ((cellISize + spacing) / colSpan) - spacing;
     98          }
     99          result += cellISize;
    100        } else if (styleISize->ConvertsToPercentage()) {
    101          if (colSpan > 1) {
    102            // XXX Can this force columns to negative inline sizes?
    103            result -= spacing * (colSpan - 1);
    104          }
    105        }
    106        // else, for 'auto', '-moz-available', '-moz-fit-content',
    107        // and 'calc()' with both lengths and percentages, do nothing
    108      }
    109    }
    110  }
    111 
    112  return (mMinISize = result);
    113 }
    114 
    115 /* virtual */
    116 nscoord FixedTableLayoutStrategy::GetPrefISize(gfxContext* aRenderingContext,
    117                                               bool aComputingSize) {
    118  // It's theoretically possible to do something much better here that
    119  // depends only on the columns and the first row (where we look at
    120  // intrinsic inline sizes inside the first row and then reverse the
    121  // algorithm to find the narrowest inline size that would hold all of
    122  // those intrinsic inline sizes), but it wouldn't be compatible with
    123  // other browsers.
    124  return nscoord_MAX;
    125 }
    126 
    127 /* virtual */
    128 void FixedTableLayoutStrategy::MarkIntrinsicISizesDirty() {
    129  mMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
    130  mLastCalcISize = nscoord_MIN;
    131 }
    132 
    133 static inline nscoord AllocateUnassigned(nscoord aUnassignedSpace,
    134                                         float aShare) {
    135  if (aShare == 1.0f) {
    136    // This happens when the numbers we're dividing to get aShare are
    137    // equal.  We want to return unassignedSpace exactly, even if it
    138    // can't be precisely round-tripped through float.
    139    return aUnassignedSpace;
    140  }
    141  return NSToCoordRound(float(aUnassignedSpace) * aShare);
    142 }
    143 
    144 /* virtual */
    145 void FixedTableLayoutStrategy::ComputeColumnISizes(
    146    const ReflowInput& aReflowInput) {
    147  nscoord tableISize = aReflowInput.ComputedISize();
    148 
    149  if (mLastCalcISize == tableISize) {
    150    return;
    151  }
    152  mLastCalcISize = tableISize;
    153 
    154  nsTableCellMap* cellMap = mTableFrame->GetCellMap();
    155  int32_t colCount = cellMap->GetColCount();
    156 
    157  if (colCount == 0) {
    158    // No Columns - nothing to compute
    159    return;
    160  }
    161 
    162  // border-spacing isn't part of the basis for percentages.
    163  tableISize -= mTableFrame->GetColSpacing(-1, colCount);
    164 
    165  // store the old column inline sizes. We might call SetFinalISize
    166  // multiple times on the columns, due to this we can't compare at the
    167  // last call that the inline size has changed with respect to the last
    168  // call to ComputeColumnISizes. In order to overcome this we store the
    169  // old values in this array. A single call to SetFinalISize would make
    170  // it possible to call GetFinalISize before and to compare when
    171  // setting the final inline size.
    172  nsTArray<nscoord> oldColISizes;
    173 
    174  // XXX This ignores the 'min-width' and 'max-width' properties
    175  // throughout.  Then again, that's what the CSS spec says to do.
    176 
    177  // XXX Should we really ignore widths on column groups?
    178 
    179  uint32_t unassignedCount = 0;
    180  nscoord unassignedSpace = tableISize;
    181  const nscoord unassignedMarker = nscoord_MIN;
    182 
    183  // We use the PrefPercent on the columns to store the percentages
    184  // used to compute column inline sizes in case we need to shrink or
    185  // expand the columns.
    186  float pctTotal = 0.0f;
    187 
    188  // Accumulate the total specified (non-percent) on the columns for
    189  // distributing excess inline size to the columns.
    190  nscoord specTotal = 0;
    191 
    192  WritingMode wm = mTableFrame->GetWritingMode();
    193  for (int32_t col = 0; col < colCount; ++col) {
    194    nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
    195    if (!colFrame) {
    196      oldColISizes.AppendElement(0);
    197      NS_ERROR("column frames out of sync with cell map");
    198      continue;
    199    }
    200    oldColISizes.AppendElement(colFrame->GetFinalISize());
    201    colFrame->ResetPrefPercent();
    202    auto styleISize = colFrame->StylePosition()->ISize(
    203        wm, AnchorPosResolutionParams::From(colFrame));
    204    nscoord colISize;
    205    if (styleISize->ConvertsToLength()) {
    206      colISize = styleISize->ToLength();
    207      specTotal += colISize;
    208    } else if (styleISize->ConvertsToPercentage()) {
    209      float pct = styleISize->ToPercentage();
    210      colISize = NSToCoordFloor(pct * float(tableISize));
    211      colFrame->AddPrefPercent(pct);
    212      pctTotal += pct;
    213    } else {
    214      // The 'table-layout: fixed' algorithm considers only cells in the
    215      // first row.
    216      bool originates;
    217      int32_t colSpan;
    218      nsTableCellFrame* cellFrame =
    219          cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
    220      if (cellFrame) {
    221        const nsStylePosition* cellStylePos = cellFrame->StylePosition();
    222        styleISize =
    223            cellStylePos->ISize(wm, AnchorPosResolutionParams::From(cellFrame));
    224        if (styleISize->ConvertsToLength() || styleISize->IsMaxContent() ||
    225            styleISize->IsMinContent()) {
    226          // XXX This should use real percentage padding
    227          // Note that the difference between MinISize and PrefISize
    228          // shouldn't matter for any of these values of styleISize; use
    229          // MIN_ISIZE for symmetry with GetMinISize above, just in case
    230          // there is a difference.
    231          colISize = nsLayoutUtils::IntrinsicForContainer(
    232              aReflowInput.mRenderingContext, cellFrame,
    233              IntrinsicISizeType::MinISize);
    234        } else if (styleISize->ConvertsToPercentage()) {
    235          // XXX This should use real percentage padding
    236          float pct = styleISize->ToPercentage();
    237          colISize = NSToCoordFloor(pct * float(tableISize));
    238 
    239          if (cellStylePos->mBoxSizing == StyleBoxSizing::Content) {
    240            nsIFrame::IntrinsicSizeOffsetData offsets =
    241                cellFrame->IntrinsicISizeOffsets();
    242            colISize += offsets.padding + offsets.border;
    243          }
    244 
    245          pct /= float(colSpan);
    246          colFrame->AddPrefPercent(pct);
    247          pctTotal += pct;
    248        } else {
    249          // 'auto', '-moz-available', '-moz-fit-content', and 'calc()'
    250          // with percentages
    251          colISize = unassignedMarker;
    252        }
    253        if (colISize != unassignedMarker) {
    254          if (colSpan > 1) {
    255            // If a column-spanning cell is in the first row, split up
    256            // the space evenly.  (XXX This isn't quite right if some of
    257            // the columns it's in have specified iSizes.  Should we
    258            // care?)
    259            nscoord spacing = mTableFrame->GetColSpacing(col);
    260            colISize = ((colISize + spacing) / colSpan) - spacing;
    261            if (colISize < 0) {
    262              colISize = 0;
    263            }
    264          }
    265          if (!styleISize->ConvertsToPercentage()) {
    266            specTotal += colISize;
    267          }
    268        }
    269      } else {
    270        colISize = unassignedMarker;
    271      }
    272    }
    273 
    274    colFrame->SetFinalISize(colISize);
    275 
    276    if (colISize == unassignedMarker) {
    277      ++unassignedCount;
    278    } else {
    279      unassignedSpace -= colISize;
    280    }
    281  }
    282 
    283  if (unassignedSpace < 0) {
    284    if (pctTotal > 0) {
    285      // If the columns took up too much space, reduce those that had
    286      // percentage inline sizes.  The spec doesn't say to do this, but
    287      // we've always done it in the past, and so does WinIE6.
    288      nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableISize));
    289      nscoord reduce = std::min(pctUsed, -unassignedSpace);
    290      float reduceRatio = float(reduce) / pctTotal;
    291      for (int32_t col = 0; col < colCount; ++col) {
    292        nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
    293        if (!colFrame) {
    294          NS_ERROR("column frames out of sync with cell map");
    295          continue;
    296        }
    297        nscoord colISize = colFrame->GetFinalISize();
    298        colISize -= NSToCoordFloor(colFrame->GetPrefPercent() * reduceRatio);
    299        if (colISize < 0) {
    300          colISize = 0;
    301        }
    302        colFrame->SetFinalISize(colISize);
    303      }
    304    }
    305    unassignedSpace = 0;
    306  }
    307 
    308  if (unassignedCount > 0) {
    309    // The spec says to distribute the remaining space evenly among
    310    // the columns.
    311    nscoord toAssign = unassignedSpace / unassignedCount;
    312    for (int32_t col = 0; col < colCount; ++col) {
    313      nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
    314      if (!colFrame) {
    315        NS_ERROR("column frames out of sync with cell map");
    316        continue;
    317      }
    318      if (colFrame->GetFinalISize() == unassignedMarker) {
    319        colFrame->SetFinalISize(toAssign);
    320      }
    321    }
    322  } else if (unassignedSpace > 0) {
    323    // The spec doesn't say how to distribute the unassigned space.
    324    if (specTotal > 0) {
    325      // Distribute proportionally to non-percentage columns.
    326      nscoord specUndist = specTotal;
    327      for (int32_t col = 0; col < colCount; ++col) {
    328        nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
    329        if (!colFrame) {
    330          NS_ERROR("column frames out of sync with cell map");
    331          continue;
    332        }
    333        if (colFrame->GetPrefPercent() == 0.0f) {
    334          NS_ASSERTION(colFrame->GetFinalISize() <= specUndist,
    335                       "inline sizes don't add up");
    336          nscoord toAdd = AllocateUnassigned(
    337              unassignedSpace,
    338              float(colFrame->GetFinalISize()) / float(specUndist));
    339          specUndist -= colFrame->GetFinalISize();
    340          colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
    341          unassignedSpace -= toAdd;
    342          if (specUndist <= 0) {
    343            NS_ASSERTION(specUndist == 0, "math should be exact");
    344            break;
    345          }
    346        }
    347      }
    348      NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
    349    } else if (pctTotal > 0) {
    350      // Distribute proportionally to percentage columns.
    351      float pctUndist = pctTotal;
    352      for (int32_t col = 0; col < colCount; ++col) {
    353        nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
    354        if (!colFrame) {
    355          NS_ERROR("column frames out of sync with cell map");
    356          continue;
    357        }
    358        if (pctUndist < colFrame->GetPrefPercent()) {
    359          // This can happen with floating-point math.
    360          NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist < 0.0001,
    361                       "inline sizes don't add up");
    362          pctUndist = colFrame->GetPrefPercent();
    363        }
    364        nscoord toAdd = AllocateUnassigned(
    365            unassignedSpace, colFrame->GetPrefPercent() / pctUndist);
    366        colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
    367        unassignedSpace -= toAdd;
    368        pctUndist -= colFrame->GetPrefPercent();
    369        if (pctUndist <= 0.0f) {
    370          break;
    371        }
    372      }
    373      NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
    374    } else {
    375      // Distribute equally to the zero-iSize columns.
    376      int32_t colsRemaining = colCount;
    377      for (int32_t col = 0; col < colCount; ++col) {
    378        nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
    379        if (!colFrame) {
    380          NS_ERROR("column frames out of sync with cell map");
    381          continue;
    382        }
    383        NS_ASSERTION(colFrame->GetFinalISize() == 0, "yikes");
    384        nscoord toAdd =
    385            AllocateUnassigned(unassignedSpace, 1.0f / float(colsRemaining));
    386        colFrame->SetFinalISize(toAdd);
    387        unassignedSpace -= toAdd;
    388        --colsRemaining;
    389      }
    390      NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
    391    }
    392  }
    393  for (int32_t col = 0; col < colCount; ++col) {
    394    nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
    395    if (!colFrame) {
    396      NS_ERROR("column frames out of sync with cell map");
    397      continue;
    398    }
    399    if (oldColISizes.ElementAt(col) != colFrame->GetFinalISize()) {
    400      mTableFrame->DidResizeColumns();
    401      break;
    402    }
    403  }
    404 }