Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / layout / tables / FixedTableLayoutStrategy.cpp
blob59a44fe4db906d303c0ece3577d022bc55b09103
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 // vim:cindent:ts=4:et:sw=4:
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla's table layout code.
18 * The Initial Developer of the Original Code is the Mozilla Foundation.
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * L. David Baron <dbaron@dbaron.org> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
40 * Algorithms that determine column and table widths used for CSS2's
41 * 'table-layout: fixed'.
44 #include "FixedTableLayoutStrategy.h"
45 #include "nsTableFrame.h"
46 #include "nsTableColFrame.h"
47 #include "nsTableCellFrame.h"
49 FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame)
50 : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed)
51 , mTableFrame(aTableFrame)
53 MarkIntrinsicWidthsDirty();
56 /* virtual */
57 FixedTableLayoutStrategy::~FixedTableLayoutStrategy()
61 /* virtual */ nscoord
62 FixedTableLayoutStrategy::GetMinWidth(nsIRenderingContext* aRenderingContext)
64 DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth);
65 if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN)
66 return mMinWidth;
68 // It's theoretically possible to do something much better here that
69 // depends only on the columns and the first row (where we look at
70 // intrinsic widths inside the first row and then reverse the
71 // algorithm to find the narrowest width that would hold all of
72 // those intrinsic widths), but it wouldn't be compatible with other
73 // browsers, or with the use of GetMinWidth by
74 // nsTableFrame::ComputeSize to determine the width of a fixed
75 // layout table, since CSS2.1 says:
76 // The width of the table is then the greater of the value of the
77 // 'width' property for the table element and the sum of the
78 // column widths (plus cell spacing or borders).
80 // XXX Should we really ignore 'min-width' and 'max-width'?
81 // XXX Should we really ignore widths on column groups?
83 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
84 PRInt32 colCount = cellMap->GetColCount();
85 nscoord spacing = mTableFrame->GetCellSpacingX();
87 nscoord result = 0;
89 if (colCount > 0) {
90 result += spacing * (colCount + 1);
93 for (PRInt32 col = 0; col < colCount; ++col) {
94 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
95 if (!colFrame) {
96 NS_ERROR("column frames out of sync with cell map");
97 continue;
99 const nsStyleCoord *styleWidth =
100 &colFrame->GetStylePosition()->mWidth;
101 if (styleWidth->GetUnit() == eStyleUnit_Coord) {
102 result += nsLayoutUtils::ComputeWidthValue(aRenderingContext,
103 colFrame, 0, 0, 0, *styleWidth);
104 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
105 // do nothing
106 } else {
107 NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
108 styleWidth->GetUnit() == eStyleUnit_Enumerated,
109 "bad width");
111 // The 'table-layout: fixed' algorithm considers only cells
112 // in the first row.
113 PRBool originates;
114 PRInt32 colSpan;
115 nsTableCellFrame *cellFrame =
116 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
117 if (cellFrame) {
118 styleWidth = &cellFrame->GetStylePosition()->mWidth;
119 if (styleWidth->GetUnit() == eStyleUnit_Coord ||
120 (styleWidth->GetUnit() == eStyleUnit_Enumerated &&
121 (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
122 styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
123 nscoord cellWidth = nsLayoutUtils::IntrinsicForContainer(
124 aRenderingContext, cellFrame, nsLayoutUtils::MIN_WIDTH);
125 if (colSpan > 1) {
126 // If a column-spanning cell is in the first
127 // row, split up the space evenly. (XXX This
128 // isn't quite right if some of the columns it's
129 // in have specified widths. Should we care?)
130 cellWidth = ((cellWidth + spacing) / colSpan) - spacing;
132 result += cellWidth;
133 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
134 if (colSpan > 1) {
135 // XXX Can this force columns to negative
136 // widths?
137 result -= spacing * (colSpan - 1);
140 // else, for 'auto', '-moz-available', and '-moz-fit-content'
141 // do nothing
146 return (mMinWidth = result);
149 /* virtual */ nscoord
150 FixedTableLayoutStrategy::GetPrefWidth(nsIRenderingContext* aRenderingContext,
151 PRBool aComputingSize)
153 // It's theoretically possible to do something much better here that
154 // depends only on the columns and the first row (where we look at
155 // intrinsic widths inside the first row and then reverse the
156 // algorithm to find the narrowest width that would hold all of
157 // those intrinsic widths), but it wouldn't be compatible with other
158 // browsers.
159 nscoord result = nscoord_MAX;
160 DISPLAY_PREF_WIDTH(mTableFrame, result);
161 return result;
164 /* virtual */ void
165 FixedTableLayoutStrategy::MarkIntrinsicWidthsDirty()
167 mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
168 mLastCalcWidth = nscoord_MIN;
171 static inline nscoord
172 AllocateUnassigned(nscoord aUnassignedSpace, float aShare)
174 if (aShare == 1.0f) {
175 // This happens when the numbers we're dividing to get aShare
176 // are equal. We want to return unassignedSpace exactly, even
177 // if it can't be precisely round-tripped through float.
178 return aUnassignedSpace;
180 return NSToCoordRound(float(aUnassignedSpace) * aShare);
183 /* virtual */ void
184 FixedTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState)
186 nscoord tableWidth = aReflowState.ComputedWidth();
188 if (mLastCalcWidth == tableWidth)
189 return;
190 mLastCalcWidth = tableWidth;
192 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
193 PRInt32 colCount = cellMap->GetColCount();
194 nscoord spacing = mTableFrame->GetCellSpacingX();
196 if (colCount == 0) {
197 // No Columns - nothing to compute
198 return;
201 // border-spacing isn't part of the basis for percentages.
202 tableWidth -= spacing * (colCount + 1);
204 // store the old column widths. We might call multiple times SetFinalWidth
205 // on the columns, due to this we can't compare at the last call that the
206 // width has changed with the respect to the last call to
207 // ComputeColumnWidths. In order to overcome this we store the old values
208 // in this array. A single call to SetFinalWidth would make it possible to
209 // call GetFinalWidth before and to compare when setting the final width.
210 nsTArray<nscoord> oldColWidths;
212 // XXX This ignores the 'min-width' and 'max-width' properties
213 // throughout. Then again, that's what the CSS spec says to do.
215 // XXX Should we really ignore widths on column groups?
217 PRUint32 unassignedCount = 0;
218 nscoord unassignedSpace = tableWidth;
219 const nscoord unassignedMarker = nscoord_MIN;
221 // We use the PrefPercent on the columns to store the percentages
222 // used to compute column widths in case we need to shrink or expand
223 // the columns.
224 float pctTotal = 0.0f;
226 // Accumulate the total specified (non-percent) on the columns for
227 // distributing excess width to the columns.
228 nscoord specTotal = 0;
230 for (PRInt32 col = 0; col < colCount; ++col) {
231 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
232 if (!colFrame) {
233 oldColWidths.AppendElement(0);
234 NS_ERROR("column frames out of sync with cell map");
235 continue;
237 oldColWidths.AppendElement(colFrame->GetFinalWidth());
238 colFrame->ResetPrefPercent();
239 const nsStyleCoord *styleWidth =
240 &colFrame->GetStylePosition()->mWidth;
241 nscoord colWidth;
242 if (styleWidth->GetUnit() == eStyleUnit_Coord) {
243 colWidth = nsLayoutUtils::ComputeWidthValue(
244 aReflowState.rendContext,
245 colFrame, 0, 0, 0, *styleWidth);
246 specTotal += colWidth;
247 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
248 float pct = styleWidth->GetPercentValue();
249 colWidth = NSToCoordFloor(pct * float(tableWidth));
250 colFrame->AddPrefPercent(pct);
251 pctTotal += pct;
252 } else {
253 NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
254 styleWidth->GetUnit() == eStyleUnit_Enumerated,
255 "bad width");
257 // The 'table-layout: fixed' algorithm considers only cells
258 // in the first row.
259 PRBool originates;
260 PRInt32 colSpan;
261 nsTableCellFrame *cellFrame =
262 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
263 if (cellFrame) {
264 styleWidth = &cellFrame->GetStylePosition()->mWidth;
265 if (styleWidth->GetUnit() == eStyleUnit_Coord ||
266 (styleWidth->GetUnit() == eStyleUnit_Enumerated &&
267 (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
268 styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
269 // XXX This should use real percentage padding
270 // Note that the difference between MIN_WIDTH and
271 // PREF_WIDTH shouldn't matter for any of these
272 // values of styleWidth; use MIN_WIDTH for symmetry
273 // with GetMinWidth above, just in case there is a
274 // difference.
275 colWidth = nsLayoutUtils::IntrinsicForContainer(
276 aReflowState.rendContext,
277 cellFrame, nsLayoutUtils::MIN_WIDTH);
278 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
279 // XXX This should use real percentage padding
280 nsIFrame::IntrinsicWidthOffsetData offsets =
281 cellFrame->IntrinsicWidthOffsets(aReflowState.rendContext);
282 float pct = styleWidth->GetPercentValue();
283 colWidth = NSToCoordFloor(pct * float(tableWidth)) +
284 offsets.hPadding + offsets.hBorder;
285 pct /= float(colSpan);
286 colFrame->AddPrefPercent(pct);
287 pctTotal += pct;
288 } else {
289 // 'auto', '-moz-available', and '-moz-fit-content'
290 colWidth = unassignedMarker;
292 if (colWidth != unassignedMarker) {
293 if (colSpan > 1) {
294 // If a column-spanning cell is in the first
295 // row, split up the space evenly. (XXX This
296 // isn't quite right if some of the columns it's
297 // in have specified widths. Should we care?)
298 colWidth = ((colWidth + spacing) / colSpan) - spacing;
299 if (colWidth < 0)
300 colWidth = 0;
302 if (styleWidth->GetUnit() != eStyleUnit_Percent) {
303 specTotal += colWidth;
306 } else {
307 colWidth = unassignedMarker;
311 colFrame->SetFinalWidth(colWidth);
313 if (colWidth == unassignedMarker) {
314 ++unassignedCount;
315 } else {
316 unassignedSpace -= colWidth;
320 if (unassignedSpace < 0) {
321 if (pctTotal > 0) {
322 // If the columns took up too much space, reduce those that
323 // had percentage widths. The spec doesn't say to do this,
324 // but we've always done it in the past, and so does WinIE6.
325 nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableWidth));
326 nscoord reduce = PR_MIN(pctUsed, -unassignedSpace);
327 float reduceRatio = float(reduce) / pctTotal;
328 for (PRInt32 col = 0; col < colCount; ++col) {
329 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
330 if (!colFrame) {
331 NS_ERROR("column frames out of sync with cell map");
332 continue;
334 nscoord colWidth = colFrame->GetFinalWidth();
335 colWidth -= NSToCoordFloor(colFrame->GetPrefPercent() *
336 reduceRatio);
337 if (colWidth < 0)
338 colWidth = 0;
339 colFrame->SetFinalWidth(colWidth);
342 unassignedSpace = 0;
345 if (unassignedCount > 0) {
346 // The spec says to distribute the remaining space evenly among
347 // the columns.
348 nscoord toAssign = unassignedSpace / unassignedCount;
349 for (PRInt32 col = 0; col < colCount; ++col) {
350 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
351 if (!colFrame) {
352 NS_ERROR("column frames out of sync with cell map");
353 continue;
355 if (colFrame->GetFinalWidth() == unassignedMarker)
356 colFrame->SetFinalWidth(toAssign);
358 } else if (unassignedSpace > 0) {
359 // The spec doesn't say how to distribute the unassigned space.
360 if (specTotal > 0) {
361 // Distribute proportionally to non-percentage columns.
362 nscoord specUndist = specTotal;
363 for (PRInt32 col = 0; col < colCount; ++col) {
364 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
365 if (!colFrame) {
366 NS_ERROR("column frames out of sync with cell map");
367 continue;
369 if (colFrame->GetPrefPercent() == 0.0f) {
370 NS_ASSERTION(colFrame->GetFinalWidth() <= specUndist,
371 "widths don't add up");
372 nscoord toAdd = AllocateUnassigned(unassignedSpace,
373 float(colFrame->GetFinalWidth()) / float(specUndist));
374 specUndist -= colFrame->GetFinalWidth();
375 colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd);
376 unassignedSpace -= toAdd;
377 if (specUndist <= 0) {
378 NS_ASSERTION(specUndist == 0,
379 "math should be exact");
380 break;
384 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
385 } else if (pctTotal > 0) {
386 // Distribute proportionally to percentage columns.
387 float pctUndist = pctTotal;
388 for (PRInt32 col = 0; col < colCount; ++col) {
389 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
390 if (!colFrame) {
391 NS_ERROR("column frames out of sync with cell map");
392 continue;
394 if (pctUndist < colFrame->GetPrefPercent()) {
395 // This can happen with floating-point math.
396 NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist
397 < 0.0001,
398 "widths don't add up");
399 pctUndist = colFrame->GetPrefPercent();
401 nscoord toAdd = AllocateUnassigned(unassignedSpace,
402 colFrame->GetPrefPercent() / pctUndist);
403 colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd);
404 unassignedSpace -= toAdd;
405 pctUndist -= colFrame->GetPrefPercent();
406 if (pctUndist <= 0.0f) {
407 break;
410 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
411 } else {
412 // Distribute equally to the zero-width columns.
413 PRInt32 colsLeft = colCount;
414 for (PRInt32 col = 0; col < colCount; ++col) {
415 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
416 if (!colFrame) {
417 NS_ERROR("column frames out of sync with cell map");
418 continue;
420 NS_ASSERTION(colFrame->GetFinalWidth() == 0, "yikes");
421 nscoord toAdd = AllocateUnassigned(unassignedSpace,
422 1.0f / float(colsLeft));
423 colFrame->SetFinalWidth(toAdd);
424 unassignedSpace -= toAdd;
425 --colsLeft;
427 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
430 for (PRInt32 col = 0; col < colCount; ++col) {
431 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
432 if (!colFrame) {
433 NS_ERROR("column frames out of sync with cell map");
434 continue;
436 if (oldColWidths.ElementAt(col) != colFrame->GetFinalWidth()) {
437 mTableFrame->DidResizeColumns();
439 break;