Coverage Report

Created: 2018-09-25 14:53

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